From b4b860b45b90769833a598d01d7ccf8950a2753b Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 14:54:01 +0100 Subject: [PATCH 01/42] 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 22fd6e740..2446f5b59 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) From 08fef1ac81cdf3fb76b6cdf2bdd0896eca513c09 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 14:58:53 +0100 Subject: [PATCH 02/42] 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 2446f5b59..748c1512b 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): """ From e9dfebc9c6c44bebc317611b84696582f502d1ac Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 15:27:32 +0100 Subject: [PATCH 03/42] 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 4f68ada68..522878e47 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 From 607cf823313d4a3bdb0da4caddf19739a5f133b2 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 15:42:09 +0100 Subject: [PATCH 04/42] 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 522878e47..4f68ada68 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 From 5cd64cc551c8c519bc9797ebf4336bd8c6128250 Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 16:02:03 +0100 Subject: [PATCH 05/42] 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 a4e29a30a..b8e1e2ade 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 748c1512b..4f3aa02c2 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): From 8b0561c57e4684ac440d36b39069a6c7f6168a02 Mon Sep 17 00:00:00 2001 From: "jedavis83@gmail.com" Date: Tue, 20 Nov 2012 23:09:47 -0800 Subject: [PATCH 06/42] 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 f7918c4c3..efd564d41 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: From e03bb9c2fe53591c40707569d091b8793ea1000e Mon Sep 17 00:00:00 2001 From: "jedavis83@gmail.com" Date: Tue, 20 Nov 2012 23:17:30 -0800 Subject: [PATCH 07/42] 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 d241ade7c..fdffec351 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): """ From e9944f82d1efd7c6bf89ca02fb9e41b9b8973129 Mon Sep 17 00:00:00 2001 From: "jedavis83@gmail.com" Date: Thu, 22 Nov 2012 10:50:29 -0800 Subject: [PATCH 08/42] 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 fdffec351..d241ade7c 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 efd564d41..380c67e46 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: From 08e7818530757fe4b5fb75ba9c6b1db2bf54ee5d Mon Sep 17 00:00:00 2001 From: "jedavis83@gmail.com" Date: Thu, 22 Nov 2012 11:27:55 -0800 Subject: [PATCH 09/42] 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 380c67e46..24fbcb79a 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 From 2e36e0c9106ad8de49ce8c169083ec1d09448458 Mon Sep 17 00:00:00 2001 From: "jedavis83@gmail.com" Date: Thu, 22 Nov 2012 12:22:30 -0800 Subject: [PATCH 10/42] 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 24fbcb79a..546abc061 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 From 412f737ab2ea10bfb41e1737e338996f9f458320 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:08:57 +0000 Subject: [PATCH 11/42] 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 655b78a34..258eb9adb 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 From 95aa99d8dfc4b073bf549f5acba8ef2387512c86 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:09:09 +0000 Subject: [PATCH 12/42] 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 2743dd3e9..cef95cb89 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 fe5466be5..118e764bd 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 a2233f3df..c1452d52d 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 From fd89bca35f065cd3917fffc79b59927460a88a3a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Nov 2012 13:21:18 +0000 Subject: [PATCH 13/42] 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 cef95cb89..f646f957f 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 118e764bd..867b138bf 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 c1452d52d..48cebbc5e 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 258eb9adb..655b78a34 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 From 85a921c7efcea50a5d594082f0e4ddeefd95402f Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Sat, 24 Nov 2012 17:18:32 +0000 Subject: [PATCH 14/42] 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 a1827ba48..39c643219 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 ff48f3fa3..2850992d2 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()) From 731443b71eaa94a624952bb4ea5b142ac884cccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=BCchler?= Date: Tue, 27 Nov 2012 10:13:15 +0100 Subject: [PATCH 15/42] 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 dae384772..ee2800a6e 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 From e8a41322fbf96866c70ceb6b188894e02e7718f4 Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Tue, 27 Nov 2012 11:23:40 +0200 Subject: [PATCH 16/42] 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 5b072827e..6cef1cd00 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. From af8beb90c256586d9016d9e6411ecd9ac01f4f22 Mon Sep 17 00:00:00 2001 From: Olivier Aubert Date: Tue, 27 Nov 2012 15:19:49 +0100 Subject: [PATCH 17/42] 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 b29daf05a..789c2ee7e 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') ) From 71129dc747fd59bfcd3b3c1ffd250988a979d30a Mon Sep 17 00:00:00 2001 From: Olivier Aubert Date: Tue, 27 Nov 2012 15:30:14 +0100 Subject: [PATCH 18/42] 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 eddf63110..d87d20464 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 From 24baf6425b82316ddedf39d44d38e22f28850fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Tue, 27 Nov 2012 18:33:56 +0100 Subject: [PATCH 19/42] 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 5323e9c06..f0b515071 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 From 80be571b2e1deebff247ce5dfc4a325f2e2df9ae Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Tue, 27 Nov 2012 19:42:37 +0200 Subject: [PATCH 20/42] 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 6cef1cd00..d1e42ec1c 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): From 11ef60b127d38341874989a40500fe3c61771d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Wed, 28 Nov 2012 07:32:12 +0100 Subject: [PATCH 21/42] 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 f0b515071..b53771a7a 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 From fd383b2b5e752962242066b3587deec5820eaa2e Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Wed, 28 Nov 2012 11:58:34 +0200 Subject: [PATCH 22/42] 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 8ed6ef318..43fc15d2d 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. From 7eec582d406b9b366f9d364b53d1fc509831d9b4 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Wed, 28 Nov 2012 17:04:36 +0200 Subject: [PATCH 23/42] 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 3ac674e28..cfaacbe9a 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() From 19f67bd578ecd15ab057c6aff8a9cc788a459d10 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Wed, 28 Nov 2012 23:05:33 +0200 Subject: [PATCH 24/42] 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 96ca9f52c..d12e6e2ae 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.""" From 2f6cecc9a4a465daf3bc5b28dae260cae155ac19 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Wed, 28 Nov 2012 23:18:28 +0100 Subject: [PATCH 25/42] 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 320cf2163..1d2358c6e 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 From 1b9d0eefba07cb3567ad7dbeb72c4c3b3c1f0de3 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Thu, 29 Nov 2012 09:35:22 +0200 Subject: [PATCH 26/42] 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 d12e6e2ae..802bc6c1f 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.""" From 8d485da483c2a5cc0713a65ef30606966c082327 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Nov 2012 19:05:34 -0400 Subject: [PATCH 27/42] 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 b53771a7a..e0c589b23 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 From e311b763e193b41c6a679ddbcf813702691145a0 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Fri, 30 Nov 2012 01:34:46 +0200 Subject: [PATCH 28/42] 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 4519ab053..e63f47835 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 c35861c6c..76435df84 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 61a05da18..b16f2772f 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): From 1c1bd3fc5d7e65ae8c16e9946be87956c96a1723 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Fri, 30 Nov 2012 01:37:21 +0200 Subject: [PATCH 29/42] 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 b16f2772f..26a7d6bfd 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 } }] From 3e8336af5000a2ae9554d3e1a220ad7510c16f42 Mon Sep 17 00:00:00 2001 From: mvdwaeter Date: Fri, 30 Nov 2012 21:50:51 +0100 Subject: [PATCH 30/42] 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 789c2ee7e..187effb9d 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']) From 45d28f49e03a394bfcfc6348558ec5bd4bac2b6c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 2 Dec 2012 11:04:34 -0400 Subject: [PATCH 31/42] 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 e0c589b23..75a2d596c 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 From 3e3ede71d2f4826fa1d07523705dd53ab2cba29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Mon, 3 Dec 2012 12:47:12 +0100 Subject: [PATCH 32/42] 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 75a2d596c..f2f09c0e8 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 From 3867d9deb18d132ec5e0325370c77c2cf9aa0215 Mon Sep 17 00:00:00 2001 From: Michael Richards Date: Tue, 4 Dec 2012 11:07:31 -0800 Subject: [PATCH 33/42] 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 482a3d485..ff39fac48 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) From fc6dbb45e023a5e5e6c92bd434b93350c4fbb8d3 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 12:20:03 +0100 Subject: [PATCH 34/42] 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 9a36a2b0d..74084541d 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. --- From 3417c4631d0680aee14f7b06435d00c25ce5b464 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 12:31:38 +0100 Subject: [PATCH 35/42] 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 ba64f2aa7..e61fb9469 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]. From 3868241f6acda96fbd08cc81211b09ffbc2f38b3 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Wed, 5 Dec 2012 15:09:06 +0100 Subject: [PATCH 36/42] 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 1a746fb64..fce68f6db 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' From cb4e85721717517c9afd86c5e5e027ba19885b27 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 16:04:52 +0100 Subject: [PATCH 37/42] 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 187effb9d..08cf91cdb 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 From ee184b86292d347ba747ee4a438f17e4fc613947 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 16:08:13 +0100 Subject: [PATCH 38/42] 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 d87d20464..a3a18060f 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. From 3f39828788d856bb7923bfb3acf801e571597e55 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 16:16:46 +0100 Subject: [PATCH 39/42] 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 f85250bea..9576a7f0b 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. From 7a110a3006b47e61c12dd5ec9e62b278d1b17298 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 5 Dec 2012 16:24:41 +0100 Subject: [PATCH 40/42] 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 98c45b821..b5d378751 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. From 2938bc13b12ec73084c21e629bdde4a20a1de0cb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 6 Dec 2012 16:30:22 -0400 Subject: [PATCH 41/42] 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 f2f09c0e8..a2e0a23fc 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 + From 6a5f4f2a90ab19a8586a9d762c9b2618e8db5c30 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 6 Dec 2012 22:38:20 +0000 Subject: [PATCH 42/42] 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 a2e0a23fc..9e59d678f 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