From b4b860b45b90769833a598d01d7ccf8950a2753b Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 14:54:01 +0100 Subject: moved field_mapping to be BrowsableAPIRenderer attr from local serializer_to_form_fields var to BrowsableAPIRenderer class attr in order to - allow customization when subclassing --- rest_framework/renderers.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 22fd6e74..2446f5b5 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -258,6 +258,22 @@ class BrowsableAPIRenderer(BaseRenderer): media_type = 'text/html' format = 'api' template = 'rest_framework/api.html' + field_mapping = { + serializers.FloatField: forms.FloatField, + serializers.IntegerField: forms.IntegerField, + serializers.DateTimeField: forms.DateTimeField, + serializers.DateField: forms.DateField, + serializers.EmailField: forms.EmailField, + serializers.CharField: forms.CharField, + serializers.ChoiceField: forms.ChoiceField, + serializers.BooleanField: forms.BooleanField, + serializers.PrimaryKeyRelatedField: forms.ChoiceField, + serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField, + serializers.SlugRelatedField: forms.ChoiceField, + serializers.ManySlugRelatedField: forms.MultipleChoiceField, + serializers.HyperlinkedRelatedField: forms.ChoiceField, + serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField + } def get_default_renderer(self, view): """ @@ -306,22 +322,6 @@ class BrowsableAPIRenderer(BaseRenderer): return True def serializer_to_form_fields(self, serializer): - field_mapping = { - serializers.FloatField: forms.FloatField, - serializers.IntegerField: forms.IntegerField, - serializers.DateTimeField: forms.DateTimeField, - serializers.DateField: forms.DateField, - serializers.EmailField: forms.EmailField, - serializers.CharField: forms.CharField, - serializers.ChoiceField: forms.ChoiceField, - serializers.BooleanField: forms.BooleanField, - serializers.PrimaryKeyRelatedField: forms.ChoiceField, - serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField, - serializers.SlugRelatedField: forms.ChoiceField, - serializers.ManySlugRelatedField: forms.MultipleChoiceField, - serializers.HyperlinkedRelatedField: forms.ChoiceField, - serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField - } fields = {} for k, v in serializer.get_fields(True).items(): @@ -347,7 +347,7 @@ class BrowsableAPIRenderer(BaseRenderer): kwargs['label'] = k try: - fields[k] = field_mapping[v.__class__](**kwargs) + fields[k] = self.field_mapping[v.__class__](**kwargs) except KeyError: if getattr(v, 'choices', None) is not None: fields[k] = forms.ChoiceField(**kwargs) -- cgit v1.2.3 From 08fef1ac81cdf3fb76b6cdf2bdd0896eca513c09 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 14:58:53 +0100 Subject: Allowing custom Serializer Fields to have different BrowsableApiRendered Form Fields than CharField moved field_mapping from local serializer_to_form_fields var to BrowsableAPIRenderer class attr --- rest_framework/renderers.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 2446f5b5..748c1512 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -259,21 +259,21 @@ class BrowsableAPIRenderer(BaseRenderer): format = 'api' template = 'rest_framework/api.html' field_mapping = { - serializers.FloatField: forms.FloatField, - serializers.IntegerField: forms.IntegerField, - serializers.DateTimeField: forms.DateTimeField, - serializers.DateField: forms.DateField, - serializers.EmailField: forms.EmailField, - serializers.CharField: forms.CharField, - serializers.ChoiceField: forms.ChoiceField, - serializers.BooleanField: forms.BooleanField, - serializers.PrimaryKeyRelatedField: forms.ChoiceField, - serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField, - serializers.SlugRelatedField: forms.ChoiceField, - serializers.ManySlugRelatedField: forms.MultipleChoiceField, - serializers.HyperlinkedRelatedField: forms.ChoiceField, - serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField - } + serializers.FloatField: forms.FloatField, + serializers.IntegerField: forms.IntegerField, + serializers.DateTimeField: forms.DateTimeField, + serializers.DateField: forms.DateField, + serializers.EmailField: forms.EmailField, + serializers.CharField: forms.CharField, + serializers.ChoiceField: forms.ChoiceField, + serializers.BooleanField: forms.BooleanField, + serializers.PrimaryKeyRelatedField: forms.ChoiceField, + serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField, + serializers.SlugRelatedField: forms.ChoiceField, + serializers.ManySlugRelatedField: forms.MultipleChoiceField, + serializers.HyperlinkedRelatedField: forms.ChoiceField, + serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField + } def get_default_renderer(self, view): """ -- cgit v1.2.3 From e9dfebc9c6c44bebc317611b84696582f502d1ac Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 15:27:32 +0100 Subject: clean support for view namespaces in as serializer attribute view name is prepended with namespace if existend --- rest_framework/serializers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4f68ada6..522878e4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -480,6 +480,7 @@ class HyperlinkedModelSerializer(ModelSerializer): """ _options_class = HyperlinkedModelSerializerOptions _default_view_name = '%(model_name)s-detail' + _default_view_namespace = None # default: no namespace is prepend to view_name url = HyperlinkedIdentityField() @@ -497,7 +498,14 @@ class HyperlinkedModelSerializer(ModelSerializer): 'app_label': model_meta.app_label, 'model_name': model_meta.object_name.lower() } - return self._default_view_name % format_kwargs + view_name = self._default_view_name % format_kwargs + if self._default_view_namespace: + return "%(namespace)s:%(view)s" % { + 'view': view_name, + 'namespace': self._default_view_namespace + } + else: + return view_name def get_pk_field(self, model_field): return None -- cgit v1.2.3 From 607cf823313d4a3bdb0da4caddf19739a5f133b2 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 15:42:09 +0100 Subject: revert merge --- rest_framework/serializers.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 522878e4..4f68ada6 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -480,7 +480,6 @@ class HyperlinkedModelSerializer(ModelSerializer): """ _options_class = HyperlinkedModelSerializerOptions _default_view_name = '%(model_name)s-detail' - _default_view_namespace = None # default: no namespace is prepend to view_name url = HyperlinkedIdentityField() @@ -498,14 +497,7 @@ class HyperlinkedModelSerializer(ModelSerializer): 'app_label': model_meta.app_label, 'model_name': model_meta.object_name.lower() } - view_name = self._default_view_name % format_kwargs - if self._default_view_namespace: - return "%(namespace)s:%(view)s" % { - 'view': view_name, - 'namespace': self._default_view_namespace - } - else: - return view_name + return self._default_view_name % format_kwargs def get_pk_field(self, model_field): return None -- cgit v1.2.3 From 5cd64cc551c8c519bc9797ebf4336bd8c6128250 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 16:02:03 +0100 Subject: Fields specify what FormFieldClass should be used by BrowsableApiRenderer added SerializerField Attribute "form_field_class" and defaults for existing Fields --- rest_framework/fields.py | 18 ++++++++++++++++-- rest_framework/renderers.py | 24 +----------------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index a4e29a30..b8e1e2ad 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -7,6 +7,7 @@ from django.core import validators from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.urlresolvers import resolve, get_script_prefix from django.conf import settings +from django import forms from django.forms import widgets from django.forms.models import ModelChoiceIterator from django.utils.encoding import is_protected_type, smart_unicode @@ -31,6 +32,7 @@ class Field(object): creation_counter = 0 empty = '' type_name = None + form_field_class = forms.CharField def __init__(self, source=None): self.parent = None @@ -374,6 +376,7 @@ class PrimaryKeyRelatedField(RelatedField): Represents a to-one relationship as a pk value. """ default_read_only = False + form_field_class = forms.ChoiceField # TODO: Remove these field hacks... def prepare_value(self, obj): @@ -420,6 +423,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): Represents a to-many relationship as a pk value. """ default_read_only = False + form_field_class = forms.MultipleChoiceField def prepare_value(self, obj): return self.to_native(obj.pk) @@ -463,6 +467,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): class SlugRelatedField(RelatedField): default_read_only = False + form_field_class = forms.ChoiceField def __init__(self, *args, **kwargs): self.slug_field = kwargs.pop('slug_field', None) @@ -484,7 +489,7 @@ class SlugRelatedField(RelatedField): class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField): - pass + form_field_class = forms.MultipleChoiceField ### Hyperlinked relationships @@ -497,6 +502,7 @@ class HyperlinkedRelatedField(RelatedField): slug_field = 'slug' slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden default_read_only = False + form_field_class = forms.ChoiceField def __init__(self, *args, **kwargs): try: @@ -593,7 +599,7 @@ class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField): """ Represents a to-many relationship, using hyperlinking. """ - pass + form_field_class = forms.MultipleChoiceField class HyperlinkedIdentityField(Field): @@ -651,6 +657,7 @@ class HyperlinkedIdentityField(Field): class BooleanField(WritableField): type_name = 'BooleanField' + form_field_class = forms.BooleanField widget = widgets.CheckboxInput default_error_messages = { 'invalid': _(u"'%s' value must be either True or False."), @@ -672,6 +679,7 @@ class BooleanField(WritableField): class CharField(WritableField): type_name = 'CharField' + form_field_class = forms.CharField def __init__(self, max_length=None, min_length=None, *args, **kwargs): self.max_length, self.min_length = max_length, min_length @@ -699,6 +707,7 @@ class CharField(WritableField): class ChoiceField(WritableField): type_name = 'ChoiceField' + form_field_class = forms.ChoiceField widget = widgets.Select default_error_messages = { 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), @@ -745,6 +754,7 @@ class ChoiceField(WritableField): class EmailField(CharField): type_name = 'EmailField' + form_field_class = forms.EmailField default_error_messages = { 'invalid': _('Enter a valid e-mail address.'), @@ -767,6 +777,7 @@ class EmailField(CharField): class DateField(WritableField): type_name = 'DateField' + form_field_class = forms.DateField default_error_messages = { 'invalid': _(u"'%s' value has an invalid date format. It must be " @@ -804,6 +815,7 @@ class DateField(WritableField): class DateTimeField(WritableField): type_name = 'DateTimeField' + form_field_class = forms.DateTimeField default_error_messages = { 'invalid': _(u"'%s' value has an invalid format. It must be in " @@ -858,6 +870,7 @@ class DateTimeField(WritableField): class IntegerField(WritableField): type_name = 'IntegerField' + form_field_class = forms.IntegerField default_error_messages = { 'invalid': _('Enter a whole number.'), @@ -887,6 +900,7 @@ class IntegerField(WritableField): class FloatField(WritableField): type_name = 'FloatField' + form_field_class = forms.FloatField default_error_messages = { 'invalid': _("'%s' value must be a float."), diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 748c1512..4f3aa02c 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -258,22 +258,6 @@ class BrowsableAPIRenderer(BaseRenderer): media_type = 'text/html' format = 'api' template = 'rest_framework/api.html' - field_mapping = { - serializers.FloatField: forms.FloatField, - serializers.IntegerField: forms.IntegerField, - serializers.DateTimeField: forms.DateTimeField, - serializers.DateField: forms.DateField, - serializers.EmailField: forms.EmailField, - serializers.CharField: forms.CharField, - serializers.ChoiceField: forms.ChoiceField, - serializers.BooleanField: forms.BooleanField, - serializers.PrimaryKeyRelatedField: forms.ChoiceField, - serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField, - serializers.SlugRelatedField: forms.ChoiceField, - serializers.ManySlugRelatedField: forms.MultipleChoiceField, - serializers.HyperlinkedRelatedField: forms.ChoiceField, - serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField - } def get_default_renderer(self, view): """ @@ -346,13 +330,7 @@ class BrowsableAPIRenderer(BaseRenderer): kwargs['label'] = k - try: - fields[k] = self.field_mapping[v.__class__](**kwargs) - except KeyError: - if getattr(v, 'choices', None) is not None: - fields[k] = forms.ChoiceField(**kwargs) - else: - fields[k] = forms.CharField(**kwargs) + fields[k] = v.form_field_class(**kwargs) return fields def get_form(self, view, method, request): -- cgit v1.2.3 From 8b0561c57e4684ac440d36b39069a6c7f6168a02 Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Tue, 20 Nov 2012 23:09:47 -0800 Subject: Cache all fields on serializer init, not just default fields. --- rest_framework/serializers.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index f7918c4c..efd564d4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -103,7 +103,7 @@ class BaseSerializer(Field): self.init_data = data self.init_files = files self.object = instance - self.default_fields = self.get_default_fields() + self.serialize_fields = self.get_fields() self._data = None self._files = None @@ -134,7 +134,7 @@ class BaseSerializer(Field): field.initialize(parent=self, field_name=key) # Add in the default fields - for key, val in self.default_fields.items(): + for key, val in self.get_default_fields().items(): if key not in ret: ret[key] = val @@ -181,8 +181,7 @@ class BaseSerializer(Field): ret = self._dict_class() ret.fields = {} - fields = self.get_fields() - for field_name, field in fields.items(): + for field_name, field in self.serialize_fields.items(): key = self.get_field_key(field_name) value = field.field_to_native(obj, field_name) ret[key] = value @@ -194,9 +193,8 @@ class BaseSerializer(Field): Core of deserialization, together with `restore_object`. Converts a dictionary of data into a dictionary of deserialized fields. """ - fields = self.get_fields() reverted_data = {} - for field_name, field in fields.items(): + for field_name, field in self.serialize_fields.items(): try: field.field_from_native(data, files, field_name, reverted_data) except ValidationError as err: @@ -208,10 +206,7 @@ class BaseSerializer(Field): """ Run `validate_()` and `validate()` methods on the serializer """ - # TODO: refactor this so we're not determining the fields again - fields = self.get_fields() - - for field_name, field in fields.items(): + for field_name, field in self.serialize_fields.items(): try: validate_method = getattr(self, 'validate_%s' % field_name, None) if validate_method: -- cgit v1.2.3 From e03bb9c2fe53591c40707569d091b8793ea1000e Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Tue, 20 Nov 2012 23:17:30 -0800 Subject: Change pagination to update Serializer.serialize_fields --- rest_framework/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index d241ade7..fdffec35 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -62,7 +62,7 @@ class BasePaginationSerializer(serializers.Serializer): super(BasePaginationSerializer, self).__init__(*args, **kwargs) results_field = self.results_field object_serializer = self.opts.object_serializer_class - self.fields[results_field] = object_serializer(source='object_list') + self.serialize_fields[results_field] = object_serializer(source='object_list') def to_native(self, obj): """ -- cgit v1.2.3 From e9944f82d1efd7c6bf89ca02fb9e41b9b8973129 Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Thu, 22 Nov 2012 10:50:29 -0800 Subject: Keep Serializer.fields API consistent while caching values. --- rest_framework/pagination.py | 2 +- rest_framework/serializers.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index fdffec35..d241ade7 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -62,7 +62,7 @@ class BasePaginationSerializer(serializers.Serializer): super(BasePaginationSerializer, self).__init__(*args, **kwargs) results_field = self.results_field object_serializer = self.opts.object_serializer_class - self.serialize_fields[results_field] = object_serializer(source='object_list') + self.fields[results_field] = object_serializer(source='object_list') def to_native(self, obj): """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index efd564d4..380c67e4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -60,7 +60,7 @@ def _get_declared_fields(bases, attrs): # If this class is subclassing another Serializer, add that Serializer's # fields. Note that we loop over the bases in *reverse*. This is necessary - # in order to the correct order of fields. + # in order to maintain the correct order of fields. for base in bases[::-1]: if hasattr(base, 'base_fields'): fields = base.base_fields.items() + fields @@ -94,7 +94,6 @@ class BaseSerializer(Field): def __init__(self, instance=None, data=None, files=None, context=None, **kwargs): super(BaseSerializer, self).__init__(**kwargs) self.opts = self._options_class(self.Meta) - self.fields = copy.deepcopy(self.base_fields) self.parent = None self.root = None @@ -103,7 +102,7 @@ class BaseSerializer(Field): self.init_data = data self.init_files = files self.object = instance - self.serialize_fields = self.get_fields() + self.fields = self.get_fields() self._data = None self._files = None @@ -128,7 +127,8 @@ class BaseSerializer(Field): ret = SortedDict() # Get the explicitly declared fields - for key, field in self.fields.items(): + base_fields = copy.deepcopy(self.base_fields) + for key, field in base_fields.items(): ret[key] = field # Set up the field field.initialize(parent=self, field_name=key) @@ -181,7 +181,7 @@ class BaseSerializer(Field): ret = self._dict_class() ret.fields = {} - for field_name, field in self.serialize_fields.items(): + for field_name, field in self.fields.items(): key = self.get_field_key(field_name) value = field.field_to_native(obj, field_name) ret[key] = value @@ -194,7 +194,7 @@ class BaseSerializer(Field): Converts a dictionary of data into a dictionary of deserialized fields. """ reverted_data = {} - for field_name, field in self.serialize_fields.items(): + for field_name, field in self.fields.items(): try: field.field_from_native(data, files, field_name, reverted_data) except ValidationError as err: @@ -206,7 +206,7 @@ class BaseSerializer(Field): """ Run `validate_()` and `validate()` methods on the serializer """ - for field_name, field in self.serialize_fields.items(): + for field_name, field in self.fields.items(): try: validate_method = getattr(self, 'validate_%s' % field_name, None) if validate_method: -- cgit v1.2.3 From 08e7818530757fe4b5fb75ba9c6b1db2bf54ee5d Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Thu, 22 Nov 2012 11:27:55 -0800 Subject: More consistent iteration over default_fields, per feedback. --- rest_framework/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 380c67e4..24fbcb79 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -134,7 +134,8 @@ class BaseSerializer(Field): field.initialize(parent=self, field_name=key) # Add in the default fields - for key, val in self.get_default_fields().items(): + default_fields = self.get_default_fields() + for key, val in self.default_fields.items(): if key not in ret: ret[key] = val -- cgit v1.2.3 From 2e36e0c9106ad8de49ce8c169083ec1d09448458 Mon Sep 17 00:00:00 2001 From: jedavis83@gmail.com Date: Thu, 22 Nov 2012 12:22:30 -0800 Subject: Remove unneeded and incorrect self reference --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 24fbcb79..546abc06 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -135,7 +135,7 @@ class BaseSerializer(Field): # Add in the default fields default_fields = self.get_default_fields() - for key, val in self.default_fields.items(): + for key, val in default_fields.items(): if key not in ret: ret[key] = val -- cgit v1.2.3 From 412f737ab2ea10bfb41e1737e338996f9f458320 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:08:57 +0000 Subject: Typo. Fixes #437. --- rest_framework/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 655b78a3..258eb9ad 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -109,6 +109,6 @@ class DjangoModelPermissions(BasePermission): if (request.user and request.user.is_authenticated() and - request.user.has_perms(perms, obj)): + request.user.has_perm(perms, obj)): return True return False -- cgit v1.2.3 From 95aa99d8dfc4b073bf549f5acba8ef2387512c86 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:09:09 +0000 Subject: Version 2.1.5 --- README.md | 6 ++++++ docs/topics/release-notes.md | 6 ++++++ rest_framework/__init__.py | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2743dd3e..cef95cb8 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ To run the tests. # Changelog +## 2.1.5 + +**Date**: 23rd Nov 2012 + +* Bugfix: Fix DjangoModelPermissions. + ## 2.1.4 **Date**: 22nd Nov 2012 diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index fe5466be..118e764b 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -4,6 +4,12 @@ > > — Eric S. Raymond, [The Cathedral and the Bazaar][cite]. +## 2.1.5 + +**Date**: 23rd Nov 2012 + +* Bugfix: Fix DjangoModelPermissions. + ## 2.1.4 **Date**: 22nd Nov 2012 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index a2233f3d..c1452d52 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.4' +__version__ = '2.1.5' VERSION = __version__ # synonym -- cgit v1.2.3 From fd89bca35f065cd3917fffc79b59927460a88a3a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:21:18 +0000 Subject: Version 2.1.6. AKA: I am a doofus. --- README.md | 6 ++++++ docs/topics/release-notes.md | 6 ++++++ rest_framework/__init__.py | 2 +- rest_framework/permissions.py | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cef95cb8..f646f957 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ To run the tests. # Changelog +## 2.1.6 + +**Date**: 23rd Nov 2012 + +* Bugfix: Unfix DjangoModelPermissions. (I am a doofus.) + ## 2.1.5 **Date**: 23rd Nov 2012 diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 118e764b..867b138b 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -4,6 +4,12 @@ > > — Eric S. Raymond, [The Cathedral and the Bazaar][cite]. +## 2.1.6 + +**Date**: 23rd Nov 2012 + +* Bugfix: Unfix DjangoModelPermissions. (I am a doofus.) + ## 2.1.5 **Date**: 23rd Nov 2012 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index c1452d52..48cebbc5 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.5' +__version__ = '2.1.6' VERSION = __version__ # synonym diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 258eb9ad..655b78a3 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -109,6 +109,6 @@ class DjangoModelPermissions(BasePermission): if (request.user and request.user.is_authenticated() and - request.user.has_perm(perms, obj)): + request.user.has_perms(perms, obj)): return True return False -- cgit v1.2.3 From 85a921c7efcea50a5d594082f0e4ddeefd95402f Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Sat, 24 Nov 2012 17:18:32 +0000 Subject: Added setter to user property --- rest_framework/request.py | 9 +++++++++ rest_framework/tests/request.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/rest_framework/request.py b/rest_framework/request.py index a1827ba4..39c64321 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -169,6 +169,15 @@ class Request(object): self._user, self._auth = self._authenticate() return self._user + @user.setter + def user(self, value): + """ + Sets the user on the current request. This is necessary to maintain + compatilbility with django.contrib.auth where the user proprety is + set in the login and logout functions. + """ + self._user = value + @property def auth(self): """ diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py index ff48f3fa..2850992d 100644 --- a/rest_framework/tests/request.py +++ b/rest_framework/tests/request.py @@ -3,6 +3,8 @@ Tests for content parsing, and form-overloaded content parsing. """ from django.conf.urls.defaults import patterns from django.contrib.auth.models import User +from django.contrib.auth import authenticate, login, logout +from django.contrib.sessions.middleware import SessionMiddleware from django.test import TestCase, Client from django.utils import simplejson as json @@ -276,3 +278,29 @@ class TestContentParsingWithAuthentication(TestCase): # response = self.csrf_client.post('/', content) # self.assertEqual(status.OK, response.status_code, "POST data is malformed") + + +class TestUserSetter(TestCase): + + def setUp(self): + # Pass request object through session middleware so session is + # available to login and logout functions + self.request = Request(factory.get('/')) + SessionMiddleware().process_request(self.request) + + User.objects.create_user('ringo', 'starr@thebeatles.com', 'yellow') + self.user = authenticate(username='ringo', password='yellow') + + def test_user_can_be_set(self): + self.request.user = self.user + self.assertEqual(self.request.user, self.user) + + def test_user_can_login(self): + login(self.request, self.user) + self.assertEqual(self.request.user, self.user) + + def test_user_can_logout(self): + self.request.user = self.user + self.assertFalse(self.request.user.is_anonymous()) + logout(self.request) + self.assertTrue(self.request.user.is_anonymous()) -- cgit v1.2.3 From 731443b71eaa94a624952bb4ea5b142ac884cccb Mon Sep 17 00:00:00 2001 From: Fabian Büchler Date: Tue, 27 Nov 2012 10:13:15 +0100 Subject: Renderer negotiation: media_type specificty evaluation weak The `DefaultContentNegotiation` handler uses For example: Google Chrome sends an Accept-header of `Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`, when I request a *.png URL. After matching the media-types with the available renderers (in my case only a custom `PNGRenderer` with a `media_type='image/png'`), only `*/*;q=0.8` is left, which happens to have the same length as the "image/png" media-type defined by the renderer (9 characters). The specificity of the renderer's media-type over the Accept-header's one is only determined by length. Using your `_MediaType.precedence` would be preferable in my eyes. Regards, Fabian --- rest_framework/negotiation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py index dae38477..ee2800a6 100644 --- a/rest_framework/negotiation.py +++ b/rest_framework/negotiation.py @@ -2,6 +2,7 @@ from django.http import Http404 from rest_framework import exceptions from rest_framework.settings import api_settings from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches +from rest_framework.utils.mediatypes import _MediaType class BaseContentNegotiation(object): @@ -48,7 +49,8 @@ class DefaultContentNegotiation(BaseContentNegotiation): for media_type in media_type_set: if media_type_matches(renderer.media_type, media_type): # Return the most specific media type as accepted. - if len(renderer.media_type) > len(media_type): + if (_MediaType(renderer.media_type).precedence > + _MediaType(media_type).precedence): # Eg client requests '*/*' # Accepted media type is 'application/json' return renderer, renderer.media_type -- cgit v1.2.3 From e8a41322fbf96866c70ceb6b188894e02e7718f4 Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Tue, 27 Nov 2012 11:23:40 +0200 Subject: api-guide/views.md - add imports to code example * It wasn't clear where `Response` should be imported from. --- docs/api-guide/views.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 5b072827..6cef1cd0 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -19,6 +19,9 @@ Using the `APIView` class is pretty much the same as using a regular `View` clas For example: + from rest_framework.views import APIView, Response + from rest_framework import authentication, permissions + class ListUsers(APIView): """ View to list all users in the system. -- cgit v1.2.3 From af8beb90c256586d9016d9e6411ecd9ac01f4f22 Mon Sep 17 00:00:00 2001 From: Olivier Aubert Date: Tue, 27 Nov 2012 15:19:49 +0100 Subject: Tutorial: fix module name in section 2 snippet -> snippets (to match section 1).--- docs/tutorial/2-requests-and-responses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index b29daf05..789c2ee7 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -113,7 +113,7 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns - urlpatterns = patterns('snippet.views', + urlpatterns = patterns('snippets.views', url(r'^snippets/$', 'snippet_list'), url(r'^snippets/(?P[0-9]+)$', 'snippet_detail') ) -- cgit v1.2.3 From 71129dc747fd59bfcd3b3c1ffd250988a979d30a Mon Sep 17 00:00:00 2001 From: Olivier Aubert Date: Tue, 27 Nov 2012 15:30:14 +0100 Subject: Tutorial: fix module name in section 3 Again snippet -> snippets, but then it could be simpler (and possibly intended) to rename snippets to snippet in the first section of the tutorial.--- docs/tutorial/3-class-based-views.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index eddf6311..d87d2046 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -6,8 +6,8 @@ We can also write our API views using class based views, rather than function ba We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring. - from snippet.models import Snippet - from snippet.serializers import SnippetSerializer + from snippets.models import Snippet + from snippets.serializers import SnippetSerializer from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response @@ -66,7 +66,7 @@ We'll also need to refactor our URLconf slightly now we're using class based vie from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns - from snippetpost import views + from snippets import views urlpatterns = patterns('', url(r'^snippets/$', views.SnippetList.as_view()), @@ -85,8 +85,8 @@ The create/retrieve/update/delete operations that we've been using so far are go Let's take a look at how we can compose our views by using the mixin classes. - from snippet.models import Snippet - from snippet.serializers import SnippetSerializer + from snippets.models import Snippet + from snippets.serializers import SnippetSerializer from rest_framework import mixins from rest_framework import generics @@ -128,8 +128,8 @@ Pretty similar. This time we're using the `SingleObjectBaseView` class to provi Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use. - from snippet.models import Snippet - from snippet.serializers import SnippetSerializer + from snippets.models import Snippet + from snippets.serializers import SnippetSerializer from rest_framework import generics -- cgit v1.2.3 From 24baf6425b82316ddedf39d44d38e22f28850fd0 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Tue, 27 Nov 2012 18:33:56 +0100 Subject: Added @oaubert Thanks! --- docs/topics/credits.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 5323e9c0..f0b51507 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -66,6 +66,7 @@ The following people have helped make REST framework great. * Justin Davis - [irrelative] * Dustin Bachrach - [dbachrach] * Mark Shirley - [maspwr] +* Olivier Aubert - [oaubert] Many thanks to everyone who's contributed to the project. @@ -166,4 +167,5 @@ To contact the author directly: [jonlil]: https://github.com/jonlil [irrelative]: https://github.com/irrelative [dbachrach]: https://github.com/dbachrach -[maspwr]: https://github.com/maspwr \ No newline at end of file +[maspwr]: https://github.com/maspwr +[oaubert]: https://github.com/oaubert \ No newline at end of file -- cgit v1.2.3 From 80be571b2e1deebff247ce5dfc4a325f2e2df9ae Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Tue, 27 Nov 2012 19:42:37 +0200 Subject: Import from correct place --- docs/api-guide/views.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 6cef1cd0..d1e42ec1 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -19,7 +19,8 @@ Using the `APIView` class is pretty much the same as using a regular `View` clas For example: - from rest_framework.views import APIView, Response + from rest_framework.views import APIView + from rest_framework.response import Response from rest_framework import authentication, permissions class ListUsers(APIView): -- cgit v1.2.3 From 11ef60b127d38341874989a40500fe3c61771d28 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Wed, 28 Nov 2012 07:32:12 +0100 Subject: Added @yprez Thanks! --- docs/topics/credits.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index f0b51507..b53771a7 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -67,6 +67,7 @@ The following people have helped make REST framework great. * Dustin Bachrach - [dbachrach] * Mark Shirley - [maspwr] * Olivier Aubert - [oaubert] +* Yuri Prezument - [yprez] Many thanks to everyone who's contributed to the project. @@ -168,4 +169,5 @@ To contact the author directly: [irrelative]: https://github.com/irrelative [dbachrach]: https://github.com/dbachrach [maspwr]: https://github.com/maspwr -[oaubert]: https://github.com/oaubert \ No newline at end of file +[oaubert]: https://github.com/oaubert +[yprez]: https://github.com/yprez \ No newline at end of file -- cgit v1.2.3 From fd383b2b5e752962242066b3587deec5820eaa2e Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Wed, 28 Nov 2012 11:58:34 +0200 Subject: Fix location of obtain_auth_token view --- docs/api-guide/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 8ed6ef31..43fc15d2 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -116,7 +116,7 @@ When using `TokenAuthentication`, you may want to provide a mechanism for client REST framework provides a built-in view to provide this behavior. To use it, add the `obtain_auth_token` view to your URLconf: urlpatterns += patterns('', - url(r'^api-token-auth/', 'rest_framework.authtoken.obtain_auth_token') + url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token') ) Note that the URL part of the pattern can be whatever you want to use. -- cgit v1.2.3 From 7eec582d406b9b366f9d364b53d1fc509831d9b4 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Wed, 28 Nov 2012 17:04:36 +0200 Subject: Better to return 401 when failing to authenticate --- rest_framework/authtoken/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py index 3ac674e2..cfaacbe9 100644 --- a/rest_framework/authtoken/views.py +++ b/rest_framework/authtoken/views.py @@ -18,7 +18,7 @@ class ObtainAuthToken(APIView): if serializer.is_valid(): token, created = Token.objects.get_or_create(user=serializer.object['user']) return Response({'token': token.key}) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED) obtain_auth_token = ObtainAuthToken.as_view() -- cgit v1.2.3 From 19f67bd578ecd15ab057c6aff8a9cc788a459d10 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Wed, 28 Nov 2012 23:05:33 +0200 Subject: also update test with response code 401 --- rest_framework/tests/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index 96ca9f52..d12e6e2a 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -167,7 +167,7 @@ class TokenAuthTests(TestCase): client = Client(enforce_csrf_checks=True) response = client.post('/auth-token/login/', json.dumps({'username': self.username, 'password': "badpass"}), 'application/json') - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 401) def test_token_login_json_missing_fields(self): """Ensure token login view using JSON POST fails if missing fields.""" -- cgit v1.2.3 From 2f6cecc9a4a465daf3bc5b28dae260cae155ac19 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Wed, 28 Nov 2012 23:18:28 +0100 Subject: Update django-filter to released version. --- optionals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optionals.txt b/optionals.txt index 320cf216..1d2358c6 100644 --- a/optionals.txt +++ b/optionals.txt @@ -1,3 +1,3 @@ markdown>=2.1.0 PyYAML>=3.10 --e git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter +django-filter>=0.5.4 -- cgit v1.2.3 From 1b9d0eefba07cb3567ad7dbeb72c4c3b3c1f0de3 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Thu, 29 Nov 2012 09:35:22 +0200 Subject: fix forgotten 400 test --- rest_framework/tests/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index d12e6e2a..802bc6c1 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -174,7 +174,7 @@ class TokenAuthTests(TestCase): client = Client(enforce_csrf_checks=True) response = client.post('/auth-token/login/', json.dumps({'username': self.username}), 'application/json') - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 401) def test_token_login_form(self): """Ensure token login view using form POST works.""" -- cgit v1.2.3 From 8d485da483c2a5cc0713a65ef30606966c082327 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Nov 2012 19:05:34 -0400 Subject: Added @fabianbuechler. Thanks! --- docs/topics/credits.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index b53771a7..e0c589b2 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -68,6 +68,7 @@ The following people have helped make REST framework great. * Mark Shirley - [maspwr] * Olivier Aubert - [oaubert] * Yuri Prezument - [yprez] +* Fabian Buechler - [fabianbuechler] Many thanks to everyone who's contributed to the project. @@ -170,4 +171,5 @@ To contact the author directly: [dbachrach]: https://github.com/dbachrach [maspwr]: https://github.com/maspwr [oaubert]: https://github.com/oaubert -[yprez]: https://github.com/yprez \ No newline at end of file +[yprez]: https://github.com/yprez +[fabianbuechler]: https://github.com/fabianbuechler -- cgit v1.2.3 From e311b763e193b41c6a679ddbcf813702691145a0 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Fri, 30 Nov 2012 01:34:46 +0200 Subject: add traverse_related feature + tests (fixes issue#461) --- rest_framework/serializers.py | 14 ++++++++++--- rest_framework/tests/models.py | 25 ++++++++++++----------- rest_framework/tests/serializer.py | 41 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4519ab05..e63f4783 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -272,10 +272,18 @@ class BaseSerializer(Field): Override default so that we can apply ModelSerializer as a nested field to relationships. """ - obj = getattr(obj, self.source or field_name) - if is_simple_callable(obj): - obj = obj() + if self.source: + value = obj + for component in self.source.split('.'): + value = getattr(value, component) + if is_simple_callable(value): + value = value() + obj = value + else: + value = getattr(obj, field_name) + if is_simple_callable(value): + obj = value() # If the object has an "all" method, assume it's a relationship if is_simple_callable(getattr(obj, 'all', None)): diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index c35861c6..76435df8 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -124,8 +124,21 @@ class ActionItem(RESTFrameworkModel): # Models for reverse relations +class Person(RESTFrameworkModel): + name = models.CharField(max_length=10) + age = models.IntegerField(null=True, blank=True) + + @property + def info(self): + return { + 'name': self.name, + 'age': self.age, + } + + class BlogPost(RESTFrameworkModel): title = models.CharField(max_length=100) + writer = models.ForeignKey(Person, null=True, blank=True) def get_first_comment(self): return self.blogpostcomment_set.all()[0] @@ -145,18 +158,6 @@ class Photo(RESTFrameworkModel): album = models.ForeignKey(Album) -class Person(RESTFrameworkModel): - name = models.CharField(max_length=10) - age = models.IntegerField(null=True, blank=True) - - @property - def info(self): - return { - 'name': self.name, - 'age': self.age, - } - - # Model for issue #324 class BlankFieldModel(RESTFrameworkModel): title = models.CharField(max_length=100, blank=True) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 61a05da1..b16f2772 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -560,6 +560,47 @@ class ManyRelatedTests(TestCase): self.assertEqual(serializer.data, expected) +class RelatedTraversalTest(TestCase): + def test_nested_traversal(self): + user = Person.objects.create(name="django") + post = BlogPost.objects.create(title="Test blog post", writer=user) + post.blogpostcomment_set.create(text="I love this blog post") + + from rest_framework.tests.models import BlogPostComment + + class PersonSerializer(serializers.ModelSerializer): + class Meta: + model = Person + fields = ("name", "age") + + class BlogPostCommentSerializer(serializers.ModelSerializer): + class Meta: + model = BlogPostComment + fields = ("text", "post_owner") + + text = serializers.CharField() + post_owner = PersonSerializer(source='blog_post.writer') + + class BlogPostSerializer(serializers.Serializer): + title = serializers.CharField() + comments = BlogPostCommentSerializer(source='blogpostcomment_set') + + serializer = BlogPostSerializer(instance=post) + + expected = { + 'title': 'Test blog post', + 'comments': [{ + 'text': 'I hate this blog post', + 'post_owner': { + "name": "django", + "age": None + } + }] + } + + self.assertEqual(serializer.data, expected) + + class SerializerMethodFieldTests(TestCase): def setUp(self): -- cgit v1.2.3 From 1c1bd3fc5d7e65ae8c16e9946be87956c96a1723 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Fri, 30 Nov 2012 01:37:21 +0200 Subject: fix test response --- rest_framework/tests/serializer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index b16f2772..26a7d6bf 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -588,11 +588,11 @@ class RelatedTraversalTest(TestCase): serializer = BlogPostSerializer(instance=post) expected = { - 'title': 'Test blog post', + 'title': u'Test blog post', 'comments': [{ - 'text': 'I hate this blog post', + 'text': u'I love this blog post', 'post_owner': { - "name": "django", + "name": u"django", "age": None } }] -- cgit v1.2.3 From 3e8336af5000a2ae9554d3e1a220ad7510c16f42 Mon Sep 17 00:00:00 2001 From: mvdwaeter Date: Fri, 30 Nov 2012 21:50:51 +0100 Subject: Fixed typo in import statement of tutorial --- docs/tutorial/2-requests-and-responses.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 789c2ee7..187effb9 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -41,8 +41,8 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response - from snippet.models import Snippet - from snippet.serializers import SnippetSerializer + from snippets.models import Snippet + from snippets.serializers import SnippetSerializer @api_view(['GET', 'POST']) -- cgit v1.2.3 From 45d28f49e03a394bfcfc6348558ec5bd4bac2b6c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 2 Dec 2012 11:04:34 -0400 Subject: Added @mhsparks. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index e0c589b2..75a2d596 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -69,6 +69,7 @@ The following people have helped make REST framework great. * Olivier Aubert - [oaubert] * Yuri Prezument - [yprez] * Fabian Buechler - [fabianbuechler] +* Mark Hughes - [mhsparks] Many thanks to everyone who's contributed to the project. @@ -173,3 +174,4 @@ To contact the author directly: [oaubert]: https://github.com/oaubert [yprez]: https://github.com/yprez [fabianbuechler]: https://github.com/fabianbuechler +[mhsparks]: https://github.com/mhsparks -- cgit v1.2.3 From 3e3ede71d2f4826fa1d07523705dd53ab2cba29a Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Mon, 3 Dec 2012 12:47:12 +0100 Subject: Added @mvdwaeter. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 75a2d596..f2f09c0e 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -70,6 +70,7 @@ The following people have helped make REST framework great. * Yuri Prezument - [yprez] * Fabian Buechler - [fabianbuechler] * Mark Hughes - [mhsparks] +* Michael van de Waeter - [mvdwaeter] Many thanks to everyone who's contributed to the project. @@ -175,3 +176,4 @@ To contact the author directly: [yprez]: https://github.com/yprez [fabianbuechler]: https://github.com/fabianbuechler [mhsparks]: https://github.com/mhsparks +[mvdwaeter]: https://github.com/mvdwaeter -- cgit v1.2.3 From 3867d9deb18d132ec5e0325370c77c2cf9aa0215 Mon Sep 17 00:00:00 2001 From: Michael Richards Date: Tue, 4 Dec 2012 11:07:31 -0800 Subject: Added support for 'true'/'false' as valid boolean data --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 482a3d48..ff39fac4 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -693,9 +693,9 @@ class BooleanField(WritableField): default = False def from_native(self, value): - if value in ('t', 'True', '1'): + if value in ('true', 't', 'True', '1'): return True - if value in ('f', 'False', '0'): + if value in ('false', 'f', 'False', '0'): return False return bool(value) -- cgit v1.2.3 From fc6dbb45e023a5e5e6c92bd434b93350c4fbb8d3 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 12:20:03 +0100 Subject: Fixed wording. --- docs/tutorial/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 9a36a2b0..74084541 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -137,7 +137,7 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a 'PAGINATE_BY': 10 } -Okay, that's us done. +Okay, we're done. --- -- cgit v1.2.3 From 3417c4631d0680aee14f7b06435d00c25ce5b464 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 12:31:38 +0100 Subject: Fixed typos and fixed wording. Some singular/plural fixes. Fixed some 'serialise->serialize' kind of UK/US differences. The 'z' seems more common in the rest of the docs, so that's what I used. Removed a half-finished-sentence left dangling somewhere.--- docs/tutorial/1-serialization.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index ba64f2aa..e61fb946 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -14,7 +14,7 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o ## Setting up a new environment -Before we do anything else we'll create a new virtual environment, using [virtualenv]. This will make sure our package configuration is keep nicely isolated from any other projects we're working on. +Before we do anything else we'll create a new virtual environment, using [virtualenv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on. :::bash mkdir ~/env @@ -39,7 +39,6 @@ To get started, let's create a new project to work with. cd tutorial Once that's done we can create an app that we'll use to create a simple Web API. -We're going to create a project that python manage.py startapp snippets @@ -64,7 +63,7 @@ We'll also need to add our new `snippets` app and the `rest_framework` app to `I 'snippets' ) -We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet views. +We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs. urlpatterns = patterns('', url(r'^', include('snippets.urls')), @@ -105,7 +104,7 @@ Don't forget to sync the database for the first time. ## Creating a Serializer class -The first thing we need to get started on our Web API is provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similarly to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following. +The first thing we need to get started on our Web API is provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similar to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following. from django.forms import widgets from rest_framework import serializers @@ -146,7 +145,7 @@ We can actually also save ourselves some time by using the `ModelSerializer` cla ## Working with Serializers -Before we go any further we'll familiarise ourselves with using our new Serializer class. Let's drop into the Django shell. +Before we go any further we'll familiarize ourselves with using our new Serializer class. Let's drop into the Django shell. python manage.py shell @@ -166,7 +165,7 @@ We've now got a few snippet instances to play with. Let's take a look at serial serializer.data # {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'} -At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`. +At this point we've translated the model instance into python native datatypes. To finalize the serialization process we render the data into `json`. content = JSONRenderer().render(serializer.data) content @@ -292,7 +291,7 @@ Finally we need to wire these views up. Create the `snippets/urls.py` file: url(r'^snippets/(?P[0-9]+)/$', 'snippet_detail') ) -It's worth noting that there's a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now. +It's worth noting that there are a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now. ## Testing our first attempt at a Web API @@ -304,7 +303,7 @@ It's worth noting that there's a couple of edge cases we're not dealing with pro We're doing okay so far, we've got a serialization API that feels pretty similar to Django's Forms API, and some regular Django views. -Our API views don't do anything particularly special at the moment, beyond serve `json` responses, and there's some error handling edge cases we'd still like to clean up, but it's a functioning Web API. +Our API views don't do anything particularly special at the moment, beyond serving `json` responses, and there are some error handling edge cases we'd still like to clean up, but it's a functioning Web API. We'll see how we can start to improve things in [part 2 of the tutorial][tut-2]. -- cgit v1.2.3 From 3868241f6acda96fbd08cc81211b09ffbc2f38b3 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 5 Dec 2012 15:09:06 +0100 Subject: Update docs/api-guide/permissions.md @permission_classes takes a tuple or list.--- docs/api-guide/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 1a746fb6..fce68f6d 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -53,7 +53,7 @@ You can also set the authentication policy on a per-view basis, using the `APIVi Or, if you're using the `@api_view` decorator with function based views. @api_view('GET') - @permission_classes(IsAuthenticated) + @permission_classes((IsAuthenticated, )) def example_view(request, format=None): content = { 'status': 'request was permitted' -- cgit v1.2.3 From cb4e85721717517c9afd86c5e5e027ba19885b27 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 16:04:52 +0100 Subject: Textual fixes. Added a sentence introducing the second view. Fix one or two additional sentences.--- docs/tutorial/2-requests-and-responses.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 187effb9..08cf91cd 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -66,6 +66,8 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious. +Here is the view for an individual snippet. + @api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ @@ -92,7 +94,7 @@ Our instance view is an improvement over the previous example. It's a little mo snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) -This should all feel very familiar - there's not a lot different to working with regular Django views. +This should all feel very familiar - it is not a lot different from working with regular Django views. Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.DATA` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. @@ -128,7 +130,7 @@ Go ahead and test the API from the command line, as we did in [tutorial part 1][ **TODO: Describe using accept headers, content-type headers, and format suffixed URLs** -Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver]." +Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver]. ### Browsability -- cgit v1.2.3 From ee184b86292d347ba747ee4a438f17e4fc613947 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 16:08:13 +0100 Subject: Small textual fixes. --- docs/tutorial/3-class-based-views.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index d87d2046..a3a18060 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -102,7 +102,7 @@ Let's take a look at how we can compose our views by using the mixin classes. def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) -We'll take a moment to examine exactly what's happening here - We're building our view using `MultipleObjectAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`. +We'll take a moment to examine exactly what's happening here. We're building our view using `MultipleObjectAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`. The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explicitly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far. @@ -142,7 +142,7 @@ Using the mixin classes we've rewritten the views to use slightly less code than model = Snippet serializer_class = SnippetSerializer -Wow, that's pretty concise. We've got a huge amount for free, and our code looks like good, clean, idiomatic Django. +Wow, that's pretty concise. We've gotten a huge amount for free, and our code looks like good, clean, idiomatic Django. Next we'll move onto [part 4 of the tutorial][tut-4], where we'll take a look at how we can deal with authentication and permissions for our API. -- cgit v1.2.3 From 3f39828788d856bb7923bfb3acf801e571597e55 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 16:16:46 +0100 Subject: Small textual fixes. --- docs/tutorial/4-authentication-and-permissions.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index f85250be..9576a7f0 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -61,7 +61,7 @@ Now that we've got some users to work with, we'd better add representations of t model = User fields = ('id', 'username', 'snippets') -Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we've needed to add an explicit field for it. +Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we needed to add an explicit field for it. We'll also add a couple of views. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views. @@ -92,9 +92,7 @@ On **both** the `SnippetList` and `SnippetDetail` view classes, add the followin ## Updating our serializer -Now that snippets are associated with the user that created them, let's update our SnippetSerializer to reflect that. - -Add the following field to the serializer definition: +Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition: owner = serializers.Field(source='owner.username') @@ -108,7 +106,7 @@ The field we've added is the untyped `Field` class, in contrast to the other typ ## Adding required permissions to views -Now that code snippets are associated with users we want to make sure that only authenticated users are able to create, update and delete code snippets. +Now that code snippets are associated with users, we want to make sure that only authenticated users are able to create, update and delete code snippets. REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we're looking for is `IsAuthenticatedOrReadOnly`, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access. -- cgit v1.2.3 From 7a110a3006b47e61c12dd5ec9e62b278d1b17298 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 16:24:41 +0100 Subject: Two typo fixes. Plural/singular fix. Typo fixed.--- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 98c45b82..b5d37875 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -25,7 +25,7 @@ Notice that we're using REST framework's `reverse` function in order to return f The other obvious thing that's still missing from our pastebin API is the code highlighting endpoints. -Unlike all our other API endpoints, we don't want to use JSON, but instead just present an HTML representation. There are two style of HTML renderer provided by REST framework, one for dealing with HTML rendered using templates, the other for dealing with pre-rendered HTML. The second renderer is the one we'd like to use for this endpoint. +Unlike all our other API endpoints, we don't want to use JSON, but instead just present an HTML representation. There are two styles of HTML renderer provided by REST framework, one for dealing with HTML rendered using templates, the other for dealing with pre-rendered HTML. The second renderer is the one we'd like to use for this endpoint. The other thing we need to consider when creating the code highlight view is that there's no existing concrete generic view that we can use. We're not returning an object instance, but instead a property of an object instance. @@ -151,7 +151,7 @@ We could also customize the pagination style if we needed too, but in this case If we open a browser and navigate to the browseable API, you'll find that you can now work your way around the API simply by following links. -You'll also be able to see the 'highlight' links on the snippet instances, that will take you to the hightlighted code HTML representations. +You'll also be able to see the 'highlight' links on the snippet instances, that will take you to the highlighted code HTML representations. We've now got a complete pastebin Web API, which is fully web browseable, and comes complete with authentication, per-object permissions, and multiple renderer formats. -- cgit v1.2.3 From 2938bc13b12ec73084c21e629bdde4a20a1de0cb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 6 Dec 2012 16:30:22 -0400 Subject: Added @reinout for the copy fixes. Thanks! --- docs/topics/credits.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index f2f09c0e..a2e0a23f 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -71,6 +71,7 @@ The following people have helped make REST framework great. * Fabian Buechler - [fabianbuechler] * Mark Hughes - [mhsparks] * Michael van de Waeter - [mvdwaeter] +* Reinout van Rees - [reinout] Many thanks to everyone who's contributed to the project. @@ -177,3 +178,5 @@ To contact the author directly: [fabianbuechler]: https://github.com/fabianbuechler [mhsparks]: https://github.com/mhsparks [mvdwaeter]: https://github.com/mvdwaeter +[reinout]: https://github.com/reinout + -- cgit v1.2.3 From 6a5f4f2a90ab19a8586a9d762c9b2618e8db5c30 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 6 Dec 2012 22:38:20 +0000 Subject: Added @justanotherbody. Thanks! --- docs/topics/credits.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index a2e0a23f..9e59d678 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -72,6 +72,7 @@ The following people have helped make REST framework great. * Mark Hughes - [mhsparks] * Michael van de Waeter - [mvdwaeter] * Reinout van Rees - [reinout] +* Michael Richards - [justanotherbody] Many thanks to everyone who's contributed to the project. @@ -179,4 +180,4 @@ To contact the author directly: [mhsparks]: https://github.com/mhsparks [mvdwaeter]: https://github.com/mvdwaeter [reinout]: https://github.com/reinout - +[justanotherbody]: https://github.com/justanotherbody -- cgit v1.2.3