From b4b860b45b90769833a598d01d7ccf8950a2753b Mon Sep 17 00:00:00 2001 From: Ludwig Kraatz Date: Thu, 8 Nov 2012 14:54:01 +0100 Subject: [PATCH 01/37] 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/37] 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/37] 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/37] 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/37] 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 7bf03bbd66008b58d023de312378be17c0a7931d Mon Sep 17 00:00:00 2001 From: Dustin Bachrach Date: Tue, 20 Nov 2012 08:27:52 -0800 Subject: [PATCH 06/37] Add widgets for DateField and DateTimeField. --- rest_framework/fields.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 01cf5ae3d..1be575a25 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -804,6 +804,7 @@ class EmailField(CharField): class DateField(WritableField): type_name = 'DateField' + widget = widgets.DateInput default_error_messages = { 'invalid': _(u"'%s' value has an invalid date format. It must be " @@ -841,6 +842,7 @@ class DateField(WritableField): class DateTimeField(WritableField): type_name = 'DateTimeField' + widget = widgets.DateTimeInput default_error_messages = { 'invalid': _(u"'%s' value has an invalid format. It must be in " From c3644234cda5c457d72baf1fbf145f12f49a1fa4 Mon Sep 17 00:00:00 2001 From: Mark Aaron Shirley Date: Tue, 20 Nov 2012 11:01:21 -0800 Subject: [PATCH 07/37] Add support for partial serializer updates --- rest_framework/fields.py | 4 ++-- rest_framework/serializers.py | 3 ++- rest_framework/tests/serializer.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 01cf5ae3d..d35e918cd 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -148,7 +148,7 @@ class WritableField(Field): self.widget = widget def validate(self, value): - if value in validators.EMPTY_VALUES and self.required: + if value in validators.EMPTY_VALUES and self.required and not self.root.partial: raise ValidationError(self.error_messages['required']) def run_validators(self, value): @@ -186,7 +186,7 @@ class WritableField(Field): if self.default is not None: native = self.default else: - if self.required: + if self.required and not self.root.partial: raise ValidationError(self.error_messages['required']) return diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2e7e2cf5d..229c1b2cd 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -91,12 +91,13 @@ class BaseSerializer(Field): _options_class = SerializerOptions _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations. - def __init__(self, instance=None, data=None, files=None, context=None, **kwargs): + def __init__(self, instance=None, data=None, files=None, context=None, partial=False, **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 + self.partial = partial self.context = context or {} diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index d522ef972..882f769c0 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -108,6 +108,18 @@ class BasicTests(TestCase): self.assertTrue(serializer.object is expected) self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!') + def test_partial_update(self): + msg = 'Merry New Year!' + partial_data = {'content': msg} + serializer = CommentSerializer(self.comment, data=partial_data) + self.assertEquals(serializer.is_valid(), False) + serializer = CommentSerializer(self.comment, data=partial_data, partial=True) + expected = self.comment + self.assertEqual(serializer.is_valid(), True) + self.assertEquals(serializer.object, expected) + self.assertTrue(serializer.object is expected) + self.assertEquals(serializer.data['content'], msg) + def test_model_fields_as_expected(self): """ Make sure that the fields returned are the same as defined From 3b43d41e918b70e5ce83f7da2caabcae2e1bcd72 Mon Sep 17 00:00:00 2001 From: Mark Aaron Shirley Date: Tue, 20 Nov 2012 15:57:54 -0800 Subject: [PATCH 08/37] Documentation changes for partial serializer updates --- docs/api-guide/serializers.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index a95891449..624c41595 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -77,6 +77,10 @@ When deserializing data, we can either create a new instance, or update an exist serializer = CommentSerializer(data=data) # Create new instance serializer = CommentSerializer(comment, data=data) # Update `instance` +By default, serializers must be passed values for all required fields or they will throw validation errors. You can use the `partial` argument in order to allow partial updates. + + serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True) # Update `instance` with partial data + ## Validation When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages. From 8b0561c57e4684ac440d36b39069a6c7f6168a02 Mon Sep 17 00:00:00 2001 From: "jedavis83@gmail.com" Date: Tue, 20 Nov 2012 23:09:47 -0800 Subject: [PATCH 09/37] 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 10/37] 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 b0bad35ef0972ec26ff808d81b1f43f16683898d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 21 Nov 2012 17:32:20 +0000 Subject: [PATCH 11/37] Tweak to work with serializer performance improvement --- rest_framework/renderers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 27340745a..550963cb2 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -327,7 +327,7 @@ class BrowsableAPIRenderer(BaseRenderer): } fields = {} - for k, v in serializer.get_fields(True).items(): + for k, v in serializer.get_fields().items(): if getattr(v, 'read_only', True): continue From 1adfc41dc7c7e50edbf72f87ebf62bae33eb212c Mon Sep 17 00:00:00 2001 From: Mark Aaron Shirley Date: Wed, 21 Nov 2012 09:36:37 -0800 Subject: [PATCH 12/37] partial argument should override required --- rest_framework/fields.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d35e918cd..b61dcb42a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -53,6 +53,8 @@ class Field(object): self.parent = parent self.root = parent.root or parent self.context = self.root.context + if self.root.partial: + self.required = False def field_from_native(self, data, files, field_name, into): """ @@ -148,7 +150,7 @@ class WritableField(Field): self.widget = widget def validate(self, value): - if value in validators.EMPTY_VALUES and self.required and not self.root.partial: + if value in validators.EMPTY_VALUES and self.required: raise ValidationError(self.error_messages['required']) def run_validators(self, value): @@ -186,7 +188,7 @@ class WritableField(Field): if self.default is not None: native = self.default else: - if self.required and not self.root.partial: + if self.required: raise ValidationError(self.error_messages['required']) return From d031ccce6e693df088faa1b85dc50c0a5e636acf Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 22 Nov 2012 10:07:42 +0000 Subject: [PATCH 13/37] Updated release notes. --- docs/topics/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index c641a1b39..99cc9b6d8 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -16,6 +16,7 @@ **Date**: 16th Nov 2012 +* Support for partial updates with serializers. * Added `FileField` and `ImageField`. For use with `MultiPartParser`. * Added `URLField` and `SlugField`. * Support for `read_only_fields` on `ModelSerializer` classes. From df545f7a2560928fdfd8677648aa402e4f2e642b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 22 Nov 2012 10:08:14 +0000 Subject: [PATCH 14/37] Updated release notes. --- docs/topics/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 99cc9b6d8..9235dadab 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -6,6 +6,7 @@ ## Master +* Support for partial updates with serializers. * Added `RegexField`. * Added `SerializerMethodField`. * Serializer performance improvements. From 4eaac26427b8574026705cdffbdf9601d620e45b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 22 Nov 2012 10:12:22 +0000 Subject: [PATCH 15/37] Added @dbachrach. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 955870d25..01aff6e06 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -64,6 +64,7 @@ The following people have helped make REST framework great. * Eugene Mechanism - [mechanism] * Jonas Liljestrand - [jonlil] * Justin Davis - [irrelative] +* Dustin Bachrach - [dbachrach] Many thanks to everyone who's contributed to the project. @@ -163,3 +164,4 @@ To contact the author directly: [mechanism]: https://github.com/mechanism [jonlil]: https://github.com/jonlil [irrelative]: https://github.com/irrelative +[dbachrach]: https://github.com/dbachrach \ No newline at end of file From db3dc79288f7129ceaf2d8500699920db073cbc3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 22 Nov 2012 10:16:47 +0000 Subject: [PATCH 16/37] Added @maspwr for the partial updates work. Ta! --- 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 01aff6e06..5323e9c06 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -65,6 +65,7 @@ The following people have helped make REST framework great. * Jonas Liljestrand - [jonlil] * Justin Davis - [irrelative] * Dustin Bachrach - [dbachrach] +* Mark Shirley - [maspwr] Many thanks to everyone who's contributed to the project. @@ -164,4 +165,5 @@ To contact the author directly: [mechanism]: https://github.com/mechanism [jonlil]: https://github.com/jonlil [irrelative]: https://github.com/irrelative -[dbachrach]: https://github.com/dbachrach \ No newline at end of file +[dbachrach]: https://github.com/dbachrach +[maspwr]: https://github.com/maspwr \ No newline at end of file From ac84c2ed2e3571b918f6c995a8f753e86c8126f1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 22 Nov 2012 17:49:53 +0000 Subject: [PATCH 17/37] Version 2.1.4 --- README.md | 11 +++++++++++ docs/topics/release-notes.md | 5 +++-- rest_framework/__init__.py | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9a12d5358..2743dd3e9 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,17 @@ To run the tests. # Changelog +## 2.1.4 + +**Date**: 22nd Nov 2012 + +* Support for partial updates with serializers. +* Added `RegexField`. +* Added `SerializerMethodField`. +* Serializer performance improvements. +* Added `obtain_token_view` to get tokens when using `TokenAuthentication`. +* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`. + ## 2.1.3 **Date**: 16th Nov 2012 diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 9235dadab..fe5466be5 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -4,7 +4,9 @@ > > — Eric S. Raymond, [The Cathedral and the Bazaar][cite]. -## Master +## 2.1.4 + +**Date**: 22nd Nov 2012 * Support for partial updates with serializers. * Added `RegexField`. @@ -17,7 +19,6 @@ **Date**: 16th Nov 2012 -* Support for partial updates with serializers. * Added `FileField` and `ImageField`. For use with `MultiPartParser`. * Added `URLField` and `SlugField`. * Support for `read_only_fields` on `ModelSerializer` classes. diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 88108a8d2..a2233f3df 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.3' +__version__ = '2.1.4' VERSION = __version__ # synonym From e9944f82d1efd7c6bf89ca02fb9e41b9b8973129 Mon Sep 17 00:00:00 2001 From: "jedavis83@gmail.com" Date: Thu, 22 Nov 2012 10:50:29 -0800 Subject: [PATCH 18/37] 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 19/37] 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 20/37] 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 21/37] 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 22/37] 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 23/37] 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 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 24/37] 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 25/37] 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 26/37] 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 27/37] 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 28/37] 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 29/37] 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 30/37] 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 31/37] 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 32/37] 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 33/37] 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 34/37] 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 35/37] 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 36/37] 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 3e8336af5000a2ae9554d3e1a220ad7510c16f42 Mon Sep 17 00:00:00 2001 From: mvdwaeter Date: Fri, 30 Nov 2012 21:50:51 +0100 Subject: [PATCH 37/37] 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'])