From ca5b99486d15e7392754178ab0948de2a60763a3 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 22 Nov 2012 22:36:37 +0100 Subject: Added _post_clean() behaviour by adding a .perform_model_validation() method. Fixed some tests that were failing due to extra strict validation. --- rest_framework/serializers.py | 57 +++++++++++++++++++++++++++++++++++--- rest_framework/tests/models.py | 4 +-- rest_framework/tests/serializer.py | 2 +- 3 files changed, 56 insertions(+), 7 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 53dcec16..6d5b4cb5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -428,10 +428,6 @@ class ModelSerializer(Serializer): kwargs['choices'] = model_field.flatchoices return ChoiceField(**kwargs) - max_length = getattr(model_field, 'max_length', None) - if max_length: - kwargs['max_length'] = max_length - field_mapping = { models.FloatField: FloatField, models.IntegerField: IntegerField, @@ -455,6 +451,59 @@ class ModelSerializer(Serializer): except KeyError: return ModelField(model_field=model_field, **kwargs) + def from_native(self, data, files): + restored_object = super(ModelSerializer, self).from_native(data, files) + + if restored_object is None: + return + + self.perform_model_validation(restored_object) + return restored_object + + def perform_model_validation(self, restored_object): + + # if hasattr(restored_object, '__iter__'): # Iterables are not model instances + # return restored_object + #self._errors[field_name] = list(err.messages) + +# opts = self._meta + # Update the model instance with self.cleaned_data. +# instance = construct_instance(self, self.instance, opts.fields, opts.exclude) + +# exclude = self._get_validation_exclusions() + + # Foreign Keys being used to represent inline relationships + # are excluded from basic field value validation. This is for two + # reasons: firstly, the value may not be supplied (#12507; the + # case of providing new values to the admin); secondly the + # object being referred to may not yet fully exist (#12749). + # However, these fields *must* be included in uniqueness checks, + # so this can't be part of _get_validation_exclusions(). +# for f_name, field in self.fields.items(): +# if isinstance(field, InlineForeignKeyField): +# exclude.append(f_name) + + # Clean the model instance's fields. + try: + restored_object.clean_fields() # exclude=exclude) + except ValidationError as e: + for field_name, error_messages in e.message_dict.items(): + self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + + # Call the model instance's clean method. + try: + restored_object.clean() + except ValidationError as e: + for field_name, error_messages in e.message_dict.items(): + self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + + # Validate uniqueness if needed. + # exclude = self._get_validation_exclusions() +# try: +# restored_object.validate_unique() # exclude=exclude) +# except ValidationError as e: +# model_errors.append(e.message_dict) + def restore_object(self, attrs, instance=None): """ Restore the model instance. diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index c35861c6..9a59e841 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -61,7 +61,7 @@ class BasicModel(RESTFrameworkModel): class SlugBasedModel(RESTFrameworkModel): text = models.CharField(max_length=100) - slug = models.SlugField(max_length=32) + slug = models.SlugField(max_length=32, blank=True) class DefaultValueModel(RESTFrameworkModel): @@ -159,7 +159,7 @@ class Person(RESTFrameworkModel): # Model for issue #324 class BlankFieldModel(RESTFrameworkModel): - title = models.CharField(max_length=100, blank=True) + title = models.CharField(max_length=100, blank=True, null=True) # Model for issue #380 diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 61a05da1..5751e894 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -169,7 +169,7 @@ class ValidationTests(TestCase): 'content': 'x' * 1001, 'created': datetime.datetime(2012, 1, 1) } - self.actionitem = ActionItem('Some to do item', + self.actionitem = ActionItem(title='Some to do item', ) def test_create(self): -- cgit v1.2.3 From bd8c742df2cc72896fa975196fdf56961e89cd94 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 22 Nov 2012 23:39:16 +0100 Subject: Cleanup. --- rest_framework/serializers.py | 40 ++-------------------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 6d5b4cb5..bb15faa8 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -461,49 +461,13 @@ class ModelSerializer(Serializer): return restored_object def perform_model_validation(self, restored_object): - - # if hasattr(restored_object, '__iter__'): # Iterables are not model instances - # return restored_object - #self._errors[field_name] = list(err.messages) - -# opts = self._meta - # Update the model instance with self.cleaned_data. -# instance = construct_instance(self, self.instance, opts.fields, opts.exclude) - -# exclude = self._get_validation_exclusions() - - # Foreign Keys being used to represent inline relationships - # are excluded from basic field value validation. This is for two - # reasons: firstly, the value may not be supplied (#12507; the - # case of providing new values to the admin); secondly the - # object being referred to may not yet fully exist (#12749). - # However, these fields *must* be included in uniqueness checks, - # so this can't be part of _get_validation_exclusions(). -# for f_name, field in self.fields.items(): -# if isinstance(field, InlineForeignKeyField): -# exclude.append(f_name) - - # Clean the model instance's fields. try: - restored_object.clean_fields() # exclude=exclude) + # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() + restored_object.full_clean(exclude=list(self.opts.exclude)) except ValidationError as e: for field_name, error_messages in e.message_dict.items(): self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) - # Call the model instance's clean method. - try: - restored_object.clean() - except ValidationError as e: - for field_name, error_messages in e.message_dict.items(): - self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) - - # Validate uniqueness if needed. - # exclude = self._get_validation_exclusions() -# try: -# restored_object.validate_unique() # exclude=exclude) -# except ValidationError as e: -# model_errors.append(e.message_dict) - def restore_object(self, attrs, instance=None): """ Restore the model instance. -- cgit v1.2.3 From 3f47f6cea9d178a57855e3b90208601b7e28a80f Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Thu, 22 Nov 2012 23:50:42 +0100 Subject: Added a validate_unique test. --- rest_framework/tests/serializer.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 5751e894..0baf0e89 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -1,7 +1,7 @@ import datetime from django.test import TestCase from rest_framework import serializers -from rest_framework.tests.models import (ActionItem, Anchor, BasicModel, +from rest_framework.tests.models import (Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, ManyToManyModel, Person, ReadOnlyManyToManyModel) @@ -48,7 +48,7 @@ class BookSerializer(serializers.ModelSerializer): class ActionItemSerializer(serializers.ModelSerializer): - + class Meta: model = ActionItem @@ -62,6 +62,12 @@ class PersonSerializer(serializers.ModelSerializer): read_only_fields = ('age',) +class AlbumsSerializer(serializers.ModelSerializer): + + class Meta: + model = Album + + class BasicTests(TestCase): def setUp(self): self.comment = Comment( @@ -276,6 +282,16 @@ class ValidationTests(TestCase): self.assertEquals(serializer.is_valid(), False) self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']}) + def test_validate_unique(self): + """ + Just check if serializers.ModelSerializer.perform_model_validation() handles unique checks via .full_clean() + """ + serializer = AlbumsSerializer(data={'title': 'a'}) + serializer.is_valid() + serializer.save() + second_serializer = AlbumsSerializer(data={'title': 'a'}) + self.assertFalse(second_serializer.is_valid()) + class RegexValidationTest(TestCase): def test_create_failed(self): -- cgit v1.2.3 From e7666014a85d65e204b40e1f54911e654f974932 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 26 Nov 2012 23:39:49 +0100 Subject: Added an assertion to the tests that checks the '.errors' value for the unique-test --- rest_framework/tests/serializer.py | 1 + 1 file changed, 1 insertion(+) (limited to 'rest_framework') diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 0baf0e89..bdf72a91 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -291,6 +291,7 @@ class ValidationTests(TestCase): serializer.save() second_serializer = AlbumsSerializer(data={'title': 'a'}) self.assertFalse(second_serializer.is_valid()) + self.assertEqual(second_serializer.errors, {'title': [u'Album with this Title already exists.']}) class RegexValidationTest(TestCase): -- cgit v1.2.3 From f104f7434052bedf6dd970806ff54b73489b339b Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Tue, 27 Nov 2012 23:21:12 +0100 Subject: Moved model validation from .perform_validation() to .validate() --- rest_framework/serializers.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index bb15faa8..5046c7b1 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -221,10 +221,17 @@ class BaseSerializer(Field): except ValidationError as err: self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) - try: - attrs = self.validate(attrs) - except ValidationError as err: - self._errors['non_field_errors'] = err.messages + # We don't run .validate() because field-validation failed and thus `attrs` may not be complete. + # which in turn can cause inconsistent validation errors. + if not self._errors: + try: + attrs = self.validate(attrs) + except ValidationError as err: + if hasattr(err, 'message_dict'): + for field_name, error_messages in err.message_dict.items(): + self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + elif hasattr(err, 'messages'): + self._errors['non_field_errors'] = err.messages return attrs @@ -451,22 +458,15 @@ class ModelSerializer(Serializer): except KeyError: return ModelField(model_field=model_field, **kwargs) - def from_native(self, data, files): - restored_object = super(ModelSerializer, self).from_native(data, files) - - if restored_object is None: - return - + def validate(self, attrs): + copied_attrs = copy.deepcopy(attrs) + restored_object = self.restore_object(copied_attrs, instance=getattr(self, 'object', None)) self.perform_model_validation(restored_object) - return restored_object + return attrs def perform_model_validation(self, restored_object): - try: - # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() - restored_object.full_clean(exclude=list(self.opts.exclude)) - except ValidationError as e: - for field_name, error_messages in e.message_dict.items(): - self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() + restored_object.full_clean(exclude=list(self.opts.exclude)) def restore_object(self, attrs, instance=None): """ -- cgit v1.2.3 From 899f96ae9186e68009dba5d54246232d34457354 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Tue, 27 Nov 2012 23:49:27 +0100 Subject: Added a get_excluded_fieldnames() method. Model validation now excludes fields not listed in Meta fields (if set). --- rest_framework/serializers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5046c7b1..775a8a1e 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -119,6 +119,17 @@ class BaseSerializer(Field): """ return {} + def get_excluded_fieldnames(self): + """ + Returns the fieldnames that should not be validated. + """ + excluded_fields = list(self.opts.exclude) + for field in self.fields.keys() + self.default_fields.keys(): + if self.opts.fields: + if field not in self.opts.fields + self.opts.exclude: + excluded_fields.append(field) + return excluded_fields + def get_fields(self): """ Returns the complete set of fields for the object as a dict. @@ -466,7 +477,7 @@ class ModelSerializer(Serializer): def perform_model_validation(self, restored_object): # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() - restored_object.full_clean(exclude=list(self.opts.exclude)) + restored_object.full_clean(exclude=list(self.get_excluded_fieldnames())) def restore_object(self, attrs, instance=None): """ -- cgit v1.2.3 From e311b763e193b41c6a679ddbcf813702691145a0 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Fri, 30 Nov 2012 01:34:46 +0200 Subject: add traverse_related feature + tests (fixes issue#461) --- rest_framework/serializers.py | 14 ++++++++++--- rest_framework/tests/models.py | 25 ++++++++++++----------- rest_framework/tests/serializer.py | 41 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 15 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4519ab05..e63f4783 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -272,10 +272,18 @@ class BaseSerializer(Field): Override default so that we can apply ModelSerializer as a nested field to relationships. """ - obj = getattr(obj, self.source or field_name) - if is_simple_callable(obj): - obj = obj() + if self.source: + value = obj + for component in self.source.split('.'): + value = getattr(value, component) + if is_simple_callable(value): + value = value() + obj = value + else: + value = getattr(obj, field_name) + if is_simple_callable(value): + obj = value() # If the object has an "all" method, assume it's a relationship if is_simple_callable(getattr(obj, 'all', None)): diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index c35861c6..76435df8 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -124,8 +124,21 @@ class ActionItem(RESTFrameworkModel): # Models for reverse relations +class Person(RESTFrameworkModel): + name = models.CharField(max_length=10) + age = models.IntegerField(null=True, blank=True) + + @property + def info(self): + return { + 'name': self.name, + 'age': self.age, + } + + class BlogPost(RESTFrameworkModel): title = models.CharField(max_length=100) + writer = models.ForeignKey(Person, null=True, blank=True) def get_first_comment(self): return self.blogpostcomment_set.all()[0] @@ -145,18 +158,6 @@ class Photo(RESTFrameworkModel): album = models.ForeignKey(Album) -class Person(RESTFrameworkModel): - name = models.CharField(max_length=10) - age = models.IntegerField(null=True, blank=True) - - @property - def info(self): - return { - 'name': self.name, - 'age': self.age, - } - - # Model for issue #324 class BlankFieldModel(RESTFrameworkModel): title = models.CharField(max_length=100, blank=True) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 61a05da1..b16f2772 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -560,6 +560,47 @@ class ManyRelatedTests(TestCase): self.assertEqual(serializer.data, expected) +class RelatedTraversalTest(TestCase): + def test_nested_traversal(self): + user = Person.objects.create(name="django") + post = BlogPost.objects.create(title="Test blog post", writer=user) + post.blogpostcomment_set.create(text="I love this blog post") + + from rest_framework.tests.models import BlogPostComment + + class PersonSerializer(serializers.ModelSerializer): + class Meta: + model = Person + fields = ("name", "age") + + class BlogPostCommentSerializer(serializers.ModelSerializer): + class Meta: + model = BlogPostComment + fields = ("text", "post_owner") + + text = serializers.CharField() + post_owner = PersonSerializer(source='blog_post.writer') + + class BlogPostSerializer(serializers.Serializer): + title = serializers.CharField() + comments = BlogPostCommentSerializer(source='blogpostcomment_set') + + serializer = BlogPostSerializer(instance=post) + + expected = { + 'title': 'Test blog post', + 'comments': [{ + 'text': 'I hate this blog post', + 'post_owner': { + "name": "django", + "age": None + } + }] + } + + self.assertEqual(serializer.data, expected) + + class SerializerMethodFieldTests(TestCase): def setUp(self): -- cgit v1.2.3 From 1c1bd3fc5d7e65ae8c16e9946be87956c96a1723 Mon Sep 17 00:00:00 2001 From: Pavel Savchenko Date: Fri, 30 Nov 2012 01:37:21 +0200 Subject: fix test response --- rest_framework/tests/serializer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index b16f2772..26a7d6bf 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -588,11 +588,11 @@ class RelatedTraversalTest(TestCase): serializer = BlogPostSerializer(instance=post) expected = { - 'title': 'Test blog post', + 'title': u'Test blog post', 'comments': [{ - 'text': 'I hate this blog post', + 'text': u'I love this blog post', 'post_owner': { - "name": "django", + "name": u"django", "age": None } }] -- cgit v1.2.3 From e044fa089b5ccdcc3557a65c106fad0f44f1b7b8 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Tue, 4 Dec 2012 09:40:23 +0100 Subject: fixed #469 - RegexField <--> BrowsableAPI Bug --- rest_framework/fields.py | 1 + rest_framework/renderers.py | 3 +++ 2 files changed, 4 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 482a3d48..ea0667f5 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -817,6 +817,7 @@ class EmailField(CharField): class RegexField(CharField): type_name = 'RegexField' + form_field_class = forms.RegexField def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs): super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 25a32baa..1220bca1 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -320,6 +320,9 @@ class BrowsableAPIRenderer(BaseRenderer): if getattr(v, 'choices', None) is not None: kwargs['choices'] = v.choices + if getattr(v, 'regex', None) is not None: + kwargs['regex'] = v.regex + if getattr(v, 'widget', None): widget = copy.deepcopy(v.widget) kwargs['widget'] = widget -- cgit v1.2.3 From 3867d9deb18d132ec5e0325370c77c2cf9aa0215 Mon Sep 17 00:00:00 2001 From: Michael Richards Date: Tue, 4 Dec 2012 11:07:31 -0800 Subject: Added support for 'true'/'false' as valid boolean data --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 482a3d48..ff39fac4 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -693,9 +693,9 @@ class BooleanField(WritableField): default = False def from_native(self, value): - if value in ('t', 'True', '1'): + if value in ('true', 't', 'True', '1'): return True - if value in ('f', 'False', '0'): + if value in ('false', 'f', 'False', '0'): return False return bool(value) -- cgit v1.2.3 From 705c7ad09db65c6ea6fb69bbd417cb7a45f6e3b9 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Wed, 5 Dec 2012 17:43:47 -0700 Subject: added tests and fix for unpickleable metadata in SortedDictWithMetadata --- rest_framework/serializers.py | 12 +++++++++- rest_framework/tests/serializer.py | 47 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4519ab05..fcc0744a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -22,7 +22,16 @@ class DictWithMetadata(dict): """ A dict-like object, that can have additional properties attached. """ - pass + + def __getstate__(self): + """ Used by pickle (e.g., caching). + Overriden to remove metadata from the dict, since it shouldn't be pickled + and may in some instances be unpickleable. + """ + # return an instance of the first dict in MRO that isn't a DictWithMetadata + for base in self.__class__.__mro__: + if not isinstance(base, DictWithMetadata) and isinstance(base, dict): + return base(self) class SortedDictWithMetadata(SortedDict, DictWithMetadata): @@ -32,6 +41,7 @@ class SortedDictWithMetadata(SortedDict, DictWithMetadata): pass + def _is_protected_type(obj): """ True if the object is a native datatype that does not need to diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 61a05da1..af182917 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -1,9 +1,9 @@ -import datetime +import datetime, pickle from django.test import TestCase from rest_framework import serializers from rest_framework.tests.models import (ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel) + ManyToManyModel, Person, ReadOnlyManyToManyModel, BlogPostComment) class SubComment(object): @@ -641,3 +641,46 @@ class BlankFieldTests(TestCase): """ serializer = self.not_blank_model_serializer_class(data=self.data) self.assertEquals(serializer.is_valid(), False) + + +#class PersonGroup(object): +# name = "group" +# persons = [Person(name="joe"), Person(name="job")] +# +#class PersonGroupSerializer(serializers.Serializer): +# name = serializers.CharField() +# persons = PersonSerializer() +# +#class BlogPostSerializer(serializers.ModelSerializer): +# class Meta: +# model = BlogPost +# +# +#class BlogPostCommentSerializer(serializers.ModelSerializer): +# class Meta: +# model = BlogPostComment +# fields = ('text', 'blog_post') +# +# blog_post = BlogPostSerializer() +# + +#test for issue #460 +class SerializerPickleTests(TestCase): + """ Test pickleability of the output of Serializers + """ + def test_pickle_simple_model_serializer_data(self): + """ Test simple serializer + """ + pickle.dumps(PersonSerializer(Person(name="Methusela", age=969)).data) + + + def test_pickle_inner_serializer(self): + """ Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will + have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle. + See DictWithMetadata.__getstate__ + """ + class InnerPersonSerializer(serializers.ModelSerializer): + class Meta: + model = Person + fields = ('name', 'age') + pickle.dumps(InnerPersonSerializer(Person(name="Noah", age=950)).data) -- cgit v1.2.3 From 7f28a784146b9ba6ab303e79597f85a0f8b1e76e Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Wed, 5 Dec 2012 17:54:21 -0700 Subject: cleaned up last commit --- rest_framework/tests/serializer.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index af182917..9cedb54b 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -3,7 +3,7 @@ from django.test import TestCase from rest_framework import serializers from rest_framework.tests.models import (ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel, BlogPostComment) + ManyToManyModel, Person, ReadOnlyManyToManyModel) class SubComment(object): @@ -643,27 +643,6 @@ class BlankFieldTests(TestCase): self.assertEquals(serializer.is_valid(), False) -#class PersonGroup(object): -# name = "group" -# persons = [Person(name="joe"), Person(name="job")] -# -#class PersonGroupSerializer(serializers.Serializer): -# name = serializers.CharField() -# persons = PersonSerializer() -# -#class BlogPostSerializer(serializers.ModelSerializer): -# class Meta: -# model = BlogPost -# -# -#class BlogPostCommentSerializer(serializers.ModelSerializer): -# class Meta: -# model = BlogPostComment -# fields = ('text', 'blog_post') -# -# blog_post = BlogPostSerializer() -# - #test for issue #460 class SerializerPickleTests(TestCase): """ Test pickleability of the output of Serializers -- cgit v1.2.3 From cb7d9ea5c9843ffa99db4400670a11c3651520cc Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 6 Dec 2012 12:45:50 -0700 Subject: cleaned up white space & docstring styling --- rest_framework/serializers.py | 9 ++++----- rest_framework/tests/serializer.py | 14 ++++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index fcc0744a..51e0b664 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -22,11 +22,11 @@ class DictWithMetadata(dict): """ A dict-like object, that can have additional properties attached. """ - def __getstate__(self): - """ Used by pickle (e.g., caching). - Overriden to remove metadata from the dict, since it shouldn't be pickled - and may in some instances be unpickleable. + """ + Used by pickle (e.g., caching). + Overriden to remove metadata from the dict, since it shouldn't be pickled + and may in some instances be unpickleable. """ # return an instance of the first dict in MRO that isn't a DictWithMetadata for base in self.__class__.__mro__: @@ -41,7 +41,6 @@ class SortedDictWithMetadata(SortedDict, DictWithMetadata): pass - def _is_protected_type(obj): """ True if the object is a native datatype that does not need to diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 9cedb54b..31dd3699 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -645,18 +645,20 @@ class BlankFieldTests(TestCase): #test for issue #460 class SerializerPickleTests(TestCase): - """ Test pickleability of the output of Serializers + """ + Test pickleability of the output of Serializers """ def test_pickle_simple_model_serializer_data(self): - """ Test simple serializer + """ + Test simple serializer """ pickle.dumps(PersonSerializer(Person(name="Methusela", age=969)).data) - def test_pickle_inner_serializer(self): - """ Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will - have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle. - See DictWithMetadata.__getstate__ + """ + Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will + have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle. + See DictWithMetadata.__getstate__ """ class InnerPersonSerializer(serializers.ModelSerializer): class Meta: -- cgit v1.2.3 From 919aff329ee1bd214831095e4d96af71795ed572 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Fri, 7 Dec 2012 00:08:27 +0100 Subject: Fix AttributeError caused by accessing a non-existing default_keys attribute. --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 984f3ac5..43bfda83 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -123,7 +123,7 @@ class BaseSerializer(Field): Returns the fieldnames that should not be validated. """ excluded_fields = list(self.opts.exclude) - for field in self.fields.keys() + self.default_fields.keys(): + for field in self.fields.keys() + self.get_default_fields().keys(): if self.opts.fields: if field not in self.opts.fields + self.opts.exclude: excluded_fields.append(field) -- cgit v1.2.3 From 303bc7cf95033d2560668bf6f4d97f05f1268967 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 7 Dec 2012 21:32:39 +0000 Subject: Support nullable FKs, with blank=True --- rest_framework/fields.py | 8 +++- rest_framework/serializers.py | 14 ++++--- rest_framework/tests/hyperlinkedserializers.py | 25 +++++++++--- rest_framework/tests/pk_relations.py | 53 ++++++++++++++++++++++++-- 4 files changed, 85 insertions(+), 15 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c28a9695..bffc0fb0 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -350,7 +350,13 @@ class RelatedField(WritableField): return value = data.get(field_name) - into[(self.source or field_name)] = self.from_native(value) + + if value is None and not self.blank: + raise ValidationError('Value may not be null') + elif value is None and self.blank: + into[(self.source or field_name)] = None + else: + into[(self.source or field_name)] = self.from_native(value) class ManyRelatedMixin(object): diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5edd46f5..13c41a4b 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -431,10 +431,14 @@ class ModelSerializer(Serializer): """ # TODO: filter queryset using: # .using(db).complex_filter(self.rel.limit_choices_to) - queryset = model_field.rel.to._default_manager + kwargs = { + 'blank': model_field.blank, + 'queryset': model_field.rel.to._default_manager + } + if to_many: - return ManyPrimaryKeyRelatedField(queryset=queryset) - return PrimaryKeyRelatedField(queryset=queryset) + return ManyPrimaryKeyRelatedField(**kwargs) + return PrimaryKeyRelatedField(**kwargs) def get_field(self, model_field): """ @@ -572,9 +576,9 @@ class HyperlinkedModelSerializer(ModelSerializer): # TODO: filter queryset using: # .using(db).complex_filter(self.rel.limit_choices_to) rel = model_field.rel.to - queryset = rel._default_manager kwargs = { - 'queryset': queryset, + 'blank': model_field.blank, + 'queryset': rel._default_manager, 'view_name': self._get_default_view_name(rel) } if to_many: diff --git a/rest_framework/tests/hyperlinkedserializers.py b/rest_framework/tests/hyperlinkedserializers.py index d7effce7..24bf61bf 100644 --- a/rest_framework/tests/hyperlinkedserializers.py +++ b/rest_framework/tests/hyperlinkedserializers.py @@ -1,6 +1,7 @@ from django.conf.urls.defaults import patterns, url from django.test import TestCase from django.test.client import RequestFactory +from django.utils import simplejson as json from rest_framework import generics, status, serializers from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel @@ -54,10 +55,12 @@ class BlogPostCommentListCreate(generics.ListCreateAPIView): model = BlogPostComment serializer_class = BlogPostCommentSerializer + class BlogPostCommentDetail(generics.RetrieveAPIView): model = BlogPostComment serializer_class = BlogPostCommentSerializer + class BlogPostDetail(generics.RetrieveAPIView): model = BlogPost @@ -71,7 +74,7 @@ class AlbumDetail(generics.RetrieveAPIView): model = Album -class OptionalRelationDetail(generics.RetrieveAPIView): +class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView): model = OptionalRelationModel model_serializer_class = serializers.HyperlinkedModelSerializer @@ -162,7 +165,7 @@ class TestManyToManyHyperlinkedView(TestCase): GET requests to ListCreateAPIView should return list of objects. """ request = factory.get('/manytomany/') - response = self.list_view(request).render() + response = self.list_view(request) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(response.data, self.data) @@ -171,7 +174,7 @@ class TestManyToManyHyperlinkedView(TestCase): GET requests to ListCreateAPIView should return list of objects. """ request = factory.get('/manytomany/1/') - response = self.detail_view(request, pk=1).render() + response = self.detail_view(request, pk=1) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(response.data, self.data[0]) @@ -194,7 +197,7 @@ class TestCreateWithForeignKeys(TestCase): } request = factory.post('/comments/', data=data) - response = self.create_view(request).render() + response = self.create_view(request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response['Location'], 'http://testserver/comments/1/') self.assertEqual(self.post.blogpostcomment_set.count(), 1) @@ -219,7 +222,7 @@ class TestCreateWithForeignKeysAndCustomSlug(TestCase): } request = factory.post('/photos/', data=data) - response = self.list_create_view(request).render() + response = self.list_create_view(request) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertNotIn('Location', response, msg='Location should only be included if there is a "url" field on the serializer') self.assertEqual(self.post.photo_set.count(), 1) @@ -244,6 +247,16 @@ class TestOptionalRelationHyperlinkedView(TestCase): for non existing relations. """ request = factory.get('/optionalrelationmodel-detail/1') - response = self.detail_view(request, pk=1).render() + response = self.detail_view(request, pk=1) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(response.data, self.data) + + def test_put_detail_view(self): + """ + PUT requests to RetrieveUpdateDestroyAPIView with optional relations + should accept None for non existing relations. + """ + response = self.client.put('/optionalrelation/1/', + data=json.dumps(self.data), + content_type='application/json') + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py index 3dcc76f9..53245d94 100644 --- a/rest_framework/tests/pk_relations.py +++ b/rest_framework/tests/pk_relations.py @@ -49,9 +49,22 @@ class ForeignKeySourceSerializer(serializers.ModelSerializer): model = ForeignKeySource +# Nullable ForeignKey + +class NullableForeignKeySource(models.Model): + name = models.CharField(max_length=100) + target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True, + related_name='nullable_sources') + + +class NullableForeignKeySourceSerializer(serializers.ModelSerializer): + class Meta: + model = NullableForeignKeySource + + # TODO: Add test that .data cannot be accessed prior to .is_valid -class PrimaryKeyManyToManyTests(TestCase): +class PKManyToManyTests(TestCase): def setUp(self): for idx in range(1, 4): target = ManyToManyTarget(name='target-%d' % idx) @@ -137,7 +150,7 @@ class PrimaryKeyManyToManyTests(TestCase): self.assertEquals(serializer.data, expected) -class PrimaryKeyForeignKeyTests(TestCase): +class PKForeignKeyTests(TestCase): def setUp(self): target = ForeignKeyTarget(name='target-1') target.save() @@ -174,7 +187,7 @@ class PrimaryKeyForeignKeyTests(TestCase): self.assertEquals(serializer.data, data) serializer.save() - # # Ensure source 1 is updated, and everything else is as expected + # Ensure source 1 is updated, and everything else is as expected queryset = ForeignKeySource.objects.all() serializer = ForeignKeySourceSerializer(queryset) expected = [ @@ -184,6 +197,40 @@ class PrimaryKeyForeignKeyTests(TestCase): ] self.assertEquals(serializer.data, expected) + def test_foreign_key_update_with_invalid_null(self): + data = {'id': 1, 'name': u'source-1', 'target': None} + instance = ForeignKeySource.objects.get(pk=1) + serializer = ForeignKeySourceSerializer(instance, data=data) + self.assertFalse(serializer.is_valid()) + self.assertEquals(serializer.errors, {'target': [u'Value may not be null']}) + + +class PKNullableForeignKeyTests(TestCase): + def setUp(self): + target = ForeignKeyTarget(name='target-1') + target.save() + for idx in range(1, 4): + source = NullableForeignKeySource(name='source-%d' % idx, target=target) + source.save() + + def test_foreign_key_update_with_valid_null(self): + data = {'id': 1, 'name': u'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) + serializer = NullableForeignKeySourceSerializer(instance, data=data) + self.assertTrue(serializer.is_valid()) + self.assertEquals(serializer.data, data) + serializer.save() + + # Ensure source 1 is updated, and everything else is as expected + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset) + expected = [ + {'id': 1, 'name': u'source-1', 'target': None}, + {'id': 2, 'name': u'source-2', 'target': 1}, + {'id': 3, 'name': u'source-3', 'target': 1} + ] + self.assertEquals(serializer.data, expected) + # reverse foreign keys MUST be read_only # In the general case they do not provide .remove() or .clear() # and cannot be arbitrarily set. -- cgit v1.2.3 From c911d54ae3769243fe6c74c29b5d16c7ac6efa10 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 7 Dec 2012 22:25:16 +0000 Subject: Reverted #458 When incorrect parameters are supplied to the obtain auth token view 400 *is* the correct response. --- rest_framework/authtoken/serializers.py | 2 +- rest_framework/authtoken/views.py | 5 +++-- rest_framework/tests/authentication.py | 16 ++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index a5ed6e6d..60a3740e 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -1,6 +1,7 @@ from django.contrib.auth import authenticate from rest_framework import serializers + class AuthTokenSerializer(serializers.Serializer): username = serializers.CharField() password = serializers.CharField() @@ -21,4 +22,3 @@ class AuthTokenSerializer(serializers.Serializer): raise serializers.ValidationError('Unable to login with provided credentials.') else: raise serializers.ValidationError('Must include "username" and "password"') - diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py index cfaacbe9..d318c723 100644 --- a/rest_framework/authtoken/views.py +++ b/rest_framework/authtoken/views.py @@ -6,11 +6,12 @@ from rest_framework.response import Response from rest_framework.authtoken.models import Token from rest_framework.authtoken.serializers import AuthTokenSerializer + class ObtainAuthToken(APIView): throttle_classes = () permission_classes = () parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) - renderer_classes = (renderers.JSONRenderer,) + renderer_classes = (renderers.JSONRenderer,) model = Token def post(self, request): @@ -18,7 +19,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_401_UNAUTHORIZED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) obtain_auth_token = ObtainAuthToken.as_view() diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index 802bc6c1..d498ae3e 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -1,4 +1,4 @@ -from django.conf.urls.defaults import patterns, include +from django.conf.urls.defaults import patterns from django.contrib.auth.models import User from django.test import Client, TestCase @@ -27,7 +27,7 @@ MockView.authentication_classes += (TokenAuthentication,) urlpatterns = patterns('', (r'^$', MockView.as_view()), - (r'^auth-token/', 'rest_framework.authtoken.views.obtain_auth_token'), + (r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'), ) @@ -157,7 +157,7 @@ class TokenAuthTests(TestCase): def test_token_login_json(self): """Ensure token login view using JSON POST works.""" client = Client(enforce_csrf_checks=True) - response = client.post('/auth-token/login/', + response = client.post('/auth-token/', json.dumps({'username': self.username, 'password': self.password}), 'application/json') self.assertEqual(response.status_code, 200) self.assertEqual(json.loads(response.content)['token'], self.key) @@ -165,21 +165,21 @@ class TokenAuthTests(TestCase): def test_token_login_json_bad_creds(self): """Ensure token login view using JSON POST fails if bad credentials are used.""" client = Client(enforce_csrf_checks=True) - response = client.post('/auth-token/login/', + response = client.post('/auth-token/', json.dumps({'username': self.username, 'password': "badpass"}), 'application/json') - self.assertEqual(response.status_code, 401) + self.assertEqual(response.status_code, 400) def test_token_login_json_missing_fields(self): """Ensure token login view using JSON POST fails if missing fields.""" client = Client(enforce_csrf_checks=True) - response = client.post('/auth-token/login/', + response = client.post('/auth-token/', json.dumps({'username': self.username}), 'application/json') - self.assertEqual(response.status_code, 401) + self.assertEqual(response.status_code, 400) def test_token_login_form(self): """Ensure token login view using form POST works.""" client = Client(enforce_csrf_checks=True) - response = client.post('/auth-token/login/', + response = client.post('/auth-token/', {'username': self.username, 'password': self.password}) self.assertEqual(response.status_code, 200) self.assertEqual(json.loads(response.content)['token'], self.key) -- cgit v1.2.3 From b170973993cb269e2a061ab592d272ec9b67c86f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 7 Dec 2012 22:36:30 +0000 Subject: Version 2.1.7 --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 48cebbc5..da2c5d5c 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.6' +__version__ = '2.1.7' VERSION = __version__ # synonym -- cgit v1.2.3 From f72be7b8faf4489c904bb6df97d51274872315bb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 8 Dec 2012 12:29:35 +0000 Subject: Add test for m2m create --- rest_framework/tests/pk_relations.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py index 53245d94..3fb3a07b 100644 --- a/rest_framework/tests/pk_relations.py +++ b/rest_framework/tests/pk_relations.py @@ -130,6 +130,25 @@ class PKManyToManyTests(TestCase): ] self.assertEquals(serializer.data, expected) + def test_many_to_many_create(self): + data = {'id': 4, 'name': u'source-4', 'targets': [1, 3]} + serializer = ManyToManySourceSerializer(data=data) + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + self.assertEquals(serializer.data, data) + self.assertEqual(obj.name, u'source-4') + + # Ensure source 4 is added, and everything else is as expected + queryset = ManyToManySource.objects.all() + serializer = ManyToManySourceSerializer(queryset) + expected = [ + {'id': 1, 'name': u'source-1', 'targets': [1]}, + {'id': 2, 'name': u'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': u'source-3', 'targets': [1, 2, 3]}, + {'id': 4, 'name': u'source-4', 'targets': [1, 3]}, + ] + self.assertEquals(serializer.data, expected) + def test_reverse_many_to_many_create(self): data = {'id': 4, 'name': u'target-4', 'sources': [1, 3]} serializer = ManyToManyTargetSerializer(data=data) -- cgit v1.2.3 From 936fdfb78e6987ce18812fbe3e17e2af8822704e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 8 Dec 2012 12:48:27 +0000 Subject: More tests for nullable FKs --- rest_framework/tests/pk_relations.py | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py index 3fb3a07b..e3360939 100644 --- a/rest_framework/tests/pk_relations.py +++ b/rest_framework/tests/pk_relations.py @@ -232,6 +232,49 @@ class PKNullableForeignKeyTests(TestCase): source = NullableForeignKeySource(name='source-%d' % idx, target=target) source.save() + def test_foreign_key_create_with_valid_null(self): + data = {'id': 4, 'name': u'source-4', 'target': None} + serializer = NullableForeignKeySourceSerializer(data=data) + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + self.assertEquals(serializer.data, data) + self.assertEqual(obj.name, u'source-4') + + # Ensure source 4 is created, and everything else is as expected + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset) + expected = [ + {'id': 1, 'name': u'source-1', 'target': 1}, + {'id': 2, 'name': u'source-2', 'target': 1}, + {'id': 3, 'name': u'source-3', 'target': 1}, + {'id': 4, 'name': u'source-4', 'target': None} + ] + self.assertEquals(serializer.data, expected) + + def test_foreign_key_create_with_valid_emptystring(self): + """ + The emptystring should be interpreted as null in the context + of relationships. + """ + data = {'id': 4, 'name': u'source-4', 'target': ''} + expected_data = {'id': 4, 'name': u'source-4', 'target': None} + serializer = NullableForeignKeySourceSerializer(data=data) + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + self.assertEquals(serializer.data, expected_data) + self.assertEqual(obj.name, u'source-4') + + # Ensure source 4 is created, and everything else is as expected + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset) + expected = [ + {'id': 1, 'name': u'source-1', 'target': 1}, + {'id': 2, 'name': u'source-2', 'target': 1}, + {'id': 3, 'name': u'source-3', 'target': 1}, + {'id': 4, 'name': u'source-4', 'target': None} + ] + self.assertEquals(serializer.data, expected) + def test_foreign_key_update_with_valid_null(self): data = {'id': 1, 'name': u'source-1', 'target': None} instance = NullableForeignKeySource.objects.get(pk=1) @@ -250,6 +293,29 @@ class PKNullableForeignKeyTests(TestCase): ] self.assertEquals(serializer.data, expected) + def test_foreign_key_update_with_valid_emptystring(self): + """ + The emptystring should be interpreted as null in the context + of relationships. + """ + data = {'id': 1, 'name': u'source-1', 'target': ''} + expected_data = {'id': 1, 'name': u'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) + serializer = NullableForeignKeySourceSerializer(instance, data=data) + self.assertTrue(serializer.is_valid()) + self.assertEquals(serializer.data, expected_data) + serializer.save() + + # Ensure source 1 is updated, and everything else is as expected + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset) + expected = [ + {'id': 1, 'name': u'source-1', 'target': None}, + {'id': 2, 'name': u'source-2', 'target': 1}, + {'id': 3, 'name': u'source-3', 'target': 1} + ] + self.assertEquals(serializer.data, expected) + # reverse foreign keys MUST be read_only # In the general case they do not provide .remove() or .clear() # and cannot be arbitrarily set. -- cgit v1.2.3 From 733f03fba35cb13ad53723b0b15d439e40da32ad Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 8 Dec 2012 12:48:38 +0000 Subject: Fix for emptystring as nullable FK --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index bffc0fb0..c5726ff0 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -351,9 +351,9 @@ class RelatedField(WritableField): value = data.get(field_name) - if value is None and not self.blank: + if value in (None, '') and not self.blank: raise ValidationError('Value may not be null') - elif value is None and self.blank: + elif value in (None, '') and self.blank: into[(self.source or field_name)] = None else: into[(self.source or field_name)] = self.from_native(value) -- cgit v1.2.3 From ac2720afcba7be5e937b5f0360a470701861baaa Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 8 Dec 2012 13:00:49 +0000 Subject: Add null related field option --- rest_framework/fields.py | 5 +++-- rest_framework/serializers.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c5726ff0..285ec9be 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -269,6 +269,7 @@ class RelatedField(WritableField): def __init__(self, *args, **kwargs): self.queryset = kwargs.pop('queryset', None) + self.null = kwargs.pop('null', False) super(RelatedField, self).__init__(*args, **kwargs) self.read_only = kwargs.pop('read_only', self.default_read_only) @@ -351,9 +352,9 @@ class RelatedField(WritableField): value = data.get(field_name) - if value in (None, '') and not self.blank: + if value in (None, '') and not self.null: raise ValidationError('Value may not be null') - elif value in (None, '') and self.blank: + elif value in (None, '') and self.null: into[(self.source or field_name)] = None else: into[(self.source or field_name)] = self.from_native(value) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 13c41a4b..7eab9860 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -432,7 +432,7 @@ class ModelSerializer(Serializer): # TODO: filter queryset using: # .using(db).complex_filter(self.rel.limit_choices_to) kwargs = { - 'blank': model_field.blank, + 'null': model_field.null, 'queryset': model_field.rel.to._default_manager } @@ -577,7 +577,7 @@ class HyperlinkedModelSerializer(ModelSerializer): # .using(db).complex_filter(self.rel.limit_choices_to) rel = model_field.rel.to kwargs = { - 'blank': model_field.blank, + 'null': model_field.null, 'queryset': rel._default_manager, 'view_name': self._get_default_view_name(rel) } -- cgit v1.2.3 From ff01ae3571298b9da67f9b9583f0cb264676ed2b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 8 Dec 2012 13:01:03 +0000 Subject: Version 2.1.8 --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index da2c5d5c..02a60675 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.7' +__version__ = '2.1.8' VERSION = __version__ # synonym -- cgit v1.2.3 From d0935d1fbb87711b0ffda8655c44ede29ee4208a Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Mon, 10 Dec 2012 23:10:04 +0100 Subject: get_excluded_fieldnames() should respect Meta options' ability to be either a tuple or list. Fixes #490. Refactored `if self.opt.fields` out of the for loop. Updated and cleaned up the validation-tests. --- rest_framework/serializers.py | 6 +++--- rest_framework/tests/serializer.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 7eab9860..c3f260c7 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -132,9 +132,9 @@ class BaseSerializer(Field): Returns the fieldnames that should not be validated. """ excluded_fields = list(self.opts.exclude) - for field in self.fields.keys() + self.get_default_fields().keys(): - if self.opts.fields: - if field not in self.opts.fields + self.opts.exclude: + if self.opts.fields: + for field in self.fields.keys() + self.get_default_fields().keys(): + if field not in list(self.opts.fields) + excluded_fields: excluded_fields.append(field) return excluded_fields diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 455fa270..a16f6abd 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -66,6 +66,7 @@ class AlbumsSerializer(serializers.ModelSerializer): class Meta: model = Album + fields = ['title'] # lists are also valid options class BasicTests(TestCase): @@ -282,9 +283,11 @@ class ValidationTests(TestCase): self.assertEquals(serializer.is_valid(), False) self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']}) + +class ModelValidationTests(TestCase): def test_validate_unique(self): """ - Just check if serializers.ModelSerializer.perform_model_validation() handles unique checks via .full_clean() + Just check if serializers.ModelSerializer handles unique checks via .full_clean() """ serializer = AlbumsSerializer(data={'title': 'a'}) serializer.is_valid() -- cgit v1.2.3 From 1815cdd24732e8102ccdf7d28cb5f0cc400c7eaf Mon Sep 17 00:00:00 2001 From: Venkat Date: Mon, 10 Dec 2012 17:46:21 -0800 Subject: Making sure the assert does not fail when required=False, read_only=True --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 285ec9be..82745973 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -133,7 +133,7 @@ class WritableField(Field): if required is None: self.required = not(read_only) else: - assert not read_only, "Cannot set required=True and read_only=True" + assert not (read_only and required), "Cannot set required=True and read_only=True" self.required = required messages = {} -- cgit v1.2.3 From 80f15c598afe138df4170ceb2198484889511d0c Mon Sep 17 00:00:00 2001 From: George Kappel Date: Tue, 11 Dec 2012 09:14:52 -0600 Subject: Added depth test --- rest_framework/runtests/runtests.py | 5 +++++ rest_framework/tests/serializer.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) (limited to 'rest_framework') diff --git a/rest_framework/runtests/runtests.py b/rest_framework/runtests/runtests.py index 1bd0a5fc..729ef26a 100755 --- a/rest_framework/runtests/runtests.py +++ b/rest_framework/runtests/runtests.py @@ -5,6 +5,11 @@ # http://code.djangoproject.com/svn/django/trunk/tests/runtests.py import os import sys +""" +Need to fix sys path so following works without specifically messing with PYTHONPATH +python ./rest_framework/runtests/runtests.py +""" +sys.path.append(os.path.join(os.path.dirname(__file__), "../..")) os.environ['DJANGO_SETTINGS_MODULE'] = 'rest_framework.runtests.settings' from django.conf import settings diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index a16f6abd..f80762f0 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -726,3 +726,24 @@ class SerializerPickleTests(TestCase): model = Person fields = ('name', 'age') pickle.dumps(InnerPersonSerializer(Person(name="Noah", age=950)).data) + +class DepthTest(TestCase): + def test_depth(self): + user = Person.objects.create(name="django",age=1) + post = BlogPost.objects.create(title="Test blog post", writer=user) + + class PersonSerializer(serializers.ModelSerializer): + class Meta: + model = Person + fields = ("name", "age") + + class BlogPostSerializer(serializers.ModelSerializer): + class Meta: + model = BlogPost + depth = 1 + + serializer = BlogPostSerializer(instance=post) + expected = {'id': 1, 'title': u'Test blog post', + 'writer': {'id': 1, 'name': u'django', 'age':1}} + + self.assertEqual(serializer.data, expected) -- cgit v1.2.3 From 405822330958c5432dde56b07a61b223c03ca4c7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 11 Dec 2012 21:07:25 +0000 Subject: Fix broken nested fields --- rest_framework/compat.py | 10 ++++++++++ rest_framework/fields.py | 7 ++----- rest_framework/serializers.py | 38 +++++++++++++++++++------------------- rest_framework/tests/serializer.py | 31 ++++++++++++++++++++++++------- 4 files changed, 55 insertions(+), 31 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 09b76368..d4901437 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -19,6 +19,16 @@ except ImportError: import StringIO +# Try to import PIL in either of the two ways it can end up installed. +try: + from PIL import Image +except ImportError: + try: + import Image + except ImportError: + Image = None + + def get_concrete_model(model_cls): try: return model_cls._meta.concrete_model diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 82745973..75ce1b9f 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1056,11 +1056,8 @@ class ImageField(FileField): if f is None: return None - # Try to import PIL in either of the two ways it can end up installed. - try: - from PIL import Image - except ImportError: - import Image + from compat import Image + assert Image is not None, 'PIL must be installed for ImageField support' # We need to get a file object for PIL. We might have a path or we might # have to read the data into memory. diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c3f260c7..ebeb43e8 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -237,7 +237,8 @@ class BaseSerializer(Field): except ValidationError as err: self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) - # We don't run .validate() because field-validation failed and thus `attrs` may not be complete. + # If there are already errors, we don't run .validate() because + # field-validation failed and thus `attrs` may not be complete. # which in turn can cause inconsistent validation errors. if not self._errors: try: @@ -299,17 +300,14 @@ class BaseSerializer(Field): Override default so that we can apply ModelSerializer as a nested field to relationships. """ - if self.source: - value = obj for component in self.source.split('.'): - value = getattr(value, component) - if is_simple_callable(value): - value = value() - obj = value + obj = getattr(obj, component) + if is_simple_callable(obj): + obj = obj() else: - value = getattr(obj, field_name) - if is_simple_callable(value): + obj = getattr(obj, field_name) + if is_simple_callable(obj): obj = value() # If the object has an "all" method, assume it's a relationship @@ -486,15 +484,10 @@ class ModelSerializer(Serializer): except KeyError: return ModelField(model_field=model_field, **kwargs) - def validate(self, attrs): - copied_attrs = copy.deepcopy(attrs) - restored_object = self.restore_object(copied_attrs, instance=getattr(self, 'object', None)) - self.perform_model_validation(restored_object) - return attrs - - def perform_model_validation(self, restored_object): - # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique() - restored_object.full_clean(exclude=list(self.get_excluded_fieldnames())) + # def validate(self, attrs): + # restored_object = self.restore_object(attrs, instance=getattr(self, 'object', None)) + # restored_object.full_clean(exclude=list(self.get_excluded_fieldnames())) + # return attrs def restore_object(self, attrs, instance=None): """ @@ -517,7 +510,14 @@ class ModelSerializer(Serializer): for field in self.opts.model._meta.many_to_many: if field.name in attrs: self.m2m_data[field.name] = attrs.pop(field.name) - return self.opts.model(**attrs) + + instance = self.opts.model(**attrs) + try: + instance.full_clean(exclude=list(self.get_excluded_fieldnames())) + except ValidationError, err: + self._errors = err.message_dict + return None + return instance def save(self, save_m2m=True): """ diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index f80762f0..50a5f5a4 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -1,4 +1,5 @@ -import datetime, pickle +import datetime +import pickle from django.test import TestCase from rest_framework import serializers from rest_framework.tests.models import (Album, ActionItem, Anchor, BasicModel, @@ -727,23 +728,39 @@ class SerializerPickleTests(TestCase): fields = ('name', 'age') pickle.dumps(InnerPersonSerializer(Person(name="Noah", age=950)).data) + class DepthTest(TestCase): - def test_depth(self): - user = Person.objects.create(name="django",age=1) - post = BlogPost.objects.create(title="Test blog post", writer=user) + def test_implicit_nesting(self): + writer = Person.objects.create(name="django", age=1) + post = BlogPost.objects.create(title="Test blog post", writer=writer) + + class BlogPostSerializer(serializers.ModelSerializer): + class Meta: + model = BlogPost + depth = 1 + + serializer = BlogPostSerializer(instance=post) + expected = {'id': 1, 'title': u'Test blog post', + 'writer': {'id': 1, 'name': u'django', 'age': 1}} + + self.assertEqual(serializer.data, expected) + + def test_explicit_nesting(self): + writer = Person.objects.create(name="django", age=1) + post = BlogPost.objects.create(title="Test blog post", writer=writer) class PersonSerializer(serializers.ModelSerializer): class Meta: model = Person - fields = ("name", "age") class BlogPostSerializer(serializers.ModelSerializer): + writer = PersonSerializer() + class Meta: model = BlogPost - depth = 1 serializer = BlogPostSerializer(instance=post) expected = {'id': 1, 'title': u'Test blog post', - 'writer': {'id': 1, 'name': u'django', 'age':1}} + 'writer': {'id': 1, 'name': u'django', 'age': 1}} self.assertEqual(serializer.data, expected) -- cgit v1.2.3 From 0824761f471ee5130af980acc9fdbb2758a3a92a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 11 Dec 2012 21:07:48 +0000 Subject: Version 2.1.9 --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 02a60675..83a6f302 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.8' +__version__ = '2.1.9' VERSION = __version__ # synonym -- cgit v1.2.3 From 85bf4164ddef2ad6d2f58457d6621cb807ab4d29 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 11 Dec 2012 22:09:04 +0000 Subject: Drop left over code --- rest_framework/serializers.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ebeb43e8..5465d7b7 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -484,11 +484,6 @@ class ModelSerializer(Serializer): except KeyError: return ModelField(model_field=model_field, **kwargs) - # def validate(self, attrs): - # restored_object = self.restore_object(attrs, instance=getattr(self, 'object', None)) - # restored_object.full_clean(exclude=list(self.get_excluded_fieldnames())) - # return attrs - def restore_object(self, attrs, instance=None): """ Restore the model instance. -- cgit v1.2.3 From 497da7fc699b9e88c966e37bc48739865336683d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 12 Dec 2012 20:44:55 +0000 Subject: Clean up field initialization. Fixes #497 --- rest_framework/serializers.py | 16 ++++++++--- rest_framework/tests/serializer.py | 54 +++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5465d7b7..caa7c980 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -100,7 +100,8 @@ 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, partial=False, **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.parent = None @@ -151,8 +152,6 @@ class BaseSerializer(Field): 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) # Add in the default fields default_fields = self.get_default_fields() @@ -172,6 +171,10 @@ class BaseSerializer(Field): for key in self.opts.exclude: ret.pop(key, None) + # Initialize the fields + for key, field in ret.items(): + field.initialize(parent=self, field_name=key) + return ret ##### @@ -186,6 +189,13 @@ class BaseSerializer(Field): if parent.opts.depth: self.opts.depth = parent.opts.depth - 1 + # We need to call initialize here to ensure any nested + # serializers that will have already called initialize on their + # descendants get updated with *their* parent. + # We could be a bit more smart about this, but it'll do for now. + for key, field in self.fields.items(): + field.initialize(parent=self, field_name=key) + ##### # Methods to convert or revert from objects <--> primitive representations. diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 50a5f5a4..780177aa 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -4,7 +4,7 @@ from django.test import TestCase from rest_framework import serializers from rest_framework.tests.models import (Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel) + ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo) class SubComment(object): @@ -764,3 +764,55 @@ class DepthTest(TestCase): 'writer': {'id': 1, 'name': u'django', 'age': 1}} self.assertEqual(serializer.data, expected) + + +class NestedSerializerContextTests(TestCase): + + def test_nested_serializer_context(self): + """ + Regression for #497 + + https://github.com/tomchristie/django-rest-framework/issues/497 + """ + class PhotoSerializer(serializers.ModelSerializer): + class Meta: + model = Photo + fields = ("description", "callable") + + callable = serializers.SerializerMethodField('_callable') + + def _callable(self, instance): + if not 'context_item' in self.context: + raise RuntimeError("context isn't getting passed into 2nd level nested serializer") + return "success" + + class AlbumSerializer(serializers.ModelSerializer): + class Meta: + model = Album + fields = ("photo_set", "callable") + + photo_set = PhotoSerializer(source="photo_set") + callable = serializers.SerializerMethodField("_callable") + + def _callable(self, instance): + if not 'context_item' in self.context: + raise RuntimeError("context isn't getting passed into 1st level nested serializer") + return "success" + + class AlbumCollection(object): + albums = None + + class AlbumCollectionSerializer(serializers.Serializer): + albums = AlbumSerializer(source="albums") + + album1 = Album.objects.create(title="album 1") + album2 = Album.objects.create(title="album 2") + Photo.objects.create(description="Bigfoot", album=album1) + Photo.objects.create(description="Unicorn", album=album1) + Photo.objects.create(description="Yeti", album=album2) + Photo.objects.create(description="Sasquatch", album=album2) + album_collection = AlbumCollection() + album_collection.albums = [album1, album2] + + # This will raise RuntimeError if context doesn't get passed correctly to the nested Serializers + AlbumCollectionSerializer(album_collection, context={'context_item': 'album context'}).data -- cgit v1.2.3 From 5f08ec70e24ee3bdf74eed62def2fd9108af0eb0 Mon Sep 17 00:00:00 2001 From: Szymon Teżewski Date: Thu, 13 Dec 2012 12:07:56 +0100 Subject: context to custom field in pagination --- rest_framework/pagination.py | 2 +- rest_framework/tests/pagination.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index d241ade7..7d7bb647 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.fields[results_field] = object_serializer(source='object_list', **kwargs) def to_native(self, obj): """ diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 3062007d..9c34001d 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -4,7 +4,7 @@ from django.core.paginator import Paginator from django.test import TestCase from django.test.client import RequestFactory from django.utils import unittest -from rest_framework import generics, status, pagination, filters +from rest_framework import generics, status, pagination, filters, serializers from rest_framework.compat import django_filters from rest_framework.tests.models import BasicModel, FilterableItem @@ -236,3 +236,32 @@ class TestCustomPaginateByParam(TestCase): response = self.view(request).render() self.assertEquals(response.data['count'], 13) self.assertEquals(response.data['results'], self.data[:5]) + + +class CustomField(serializers.Field): + def to_native(self, value): + if not 'view' in self.context: + raise RuntimeError("context isn't getting passed into custom field") + return "value" + + +class BasicModelSerializer(serializers.Serializer): + text = CustomField() + + +class TestContextPassedToCustomField(TestCase): + def setUp(self): + BasicModel.objects.create(text='ala ma kota') + + def test_with_pagination(self): + class ListView(generics.ListCreateAPIView): + model = BasicModel + serializer_class = BasicModelSerializer + paginate_by = 1 + + self.view = ListView.as_view() + request = factory.get('/') + response = self.view(request).render() + + self.assertEquals(response.status_code, status.HTTP_200_OK) + -- cgit v1.2.3 From 54d9cd4dba6b207fc8debaba1eda4d330c22e693 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Thu, 13 Dec 2012 16:44:45 +0100 Subject: fixed validationerror usage --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 75ce1b9f..da588082 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -581,7 +581,7 @@ class HyperlinkedRelatedField(RelatedField): except: pass - raise ValidationError('Could not resolve URL for field using view name "%s"', view_name) + raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name) def from_native(self, value): # Convert URL -> model instance pk @@ -680,7 +680,7 @@ class HyperlinkedIdentityField(Field): except: pass - raise ValidationError('Could not resolve URL for field using view name "%s"', view_name) + raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name) ##### Typed Fields ##### -- cgit v1.2.3 From 9eaf8e4330e0c6a4485dba650481a2578a3979b4 Mon Sep 17 00:00:00 2001 From: Simon Pantzare Date: Thu, 13 Dec 2012 17:57:27 +0100 Subject: Test to verify that context is passed on The paginator and its object serializer should share the same context. --- rest_framework/tests/pagination.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 3062007d..7bc23f1d 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -4,7 +4,7 @@ from django.core.paginator import Paginator from django.test import TestCase from django.test.client import RequestFactory from django.utils import unittest -from rest_framework import generics, status, pagination, filters +from rest_framework import generics, status, pagination, filters, serializers from rest_framework.compat import django_filters from rest_framework.tests.models import BasicModel, FilterableItem @@ -148,6 +148,12 @@ class IntegrationTestPaginationAndFiltering(TestCase): self.assertEquals(response.data['previous'], None) +class PassOnContextPaginationSerializer(pagination.PaginationSerializer): + + class Meta: + object_serializer_class = serializers.Serializer + + class UnitTestPagination(TestCase): """ Unit tests for pagination of primitive objects. @@ -172,6 +178,12 @@ class UnitTestPagination(TestCase): self.assertEquals(serializer.data['previous'], '?page=2') self.assertEquals(serializer.data['results'], self.objects[20:]) + def test_context_available_in_result(self): + serializer = PassOnContextPaginationSerializer(self.first_page) + results = serializer.fields[serializer.results_field] + # assertIs is available in Python 2.7 + self.assertTrue(serializer.context is results.context) + class TestUnpaginated(TestCase): """ -- cgit v1.2.3 From 39b01d6802310a90487f217c3de5428144c93429 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 14 Dec 2012 19:59:21 +0000 Subject: Ensure context is passed to dynamically added fields. Fixes #476. --- rest_framework/serializers.py | 7 ++----- rest_framework/tests/pagination.py | 6 ++++-- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index caa7c980..8026205e 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -171,10 +171,6 @@ class BaseSerializer(Field): for key in self.opts.exclude: ret.pop(key, None) - # Initialize the fields - for key, field in ret.items(): - field.initialize(parent=self, field_name=key) - return ret ##### @@ -214,6 +210,7 @@ class BaseSerializer(Field): ret.fields = {} for field_name, field in self.fields.items(): + field.initialize(parent=self, field_name=field_name) key = self.get_field_key(field_name) value = field.field_to_native(obj, field_name) ret[key] = value @@ -227,6 +224,7 @@ class BaseSerializer(Field): """ reverted_data = {} for field_name, field in self.fields.items(): + field.initialize(parent=self, field_name=field_name) try: field.field_from_native(data, files, field_name, reverted_data) except ValidationError as err: @@ -407,7 +405,6 @@ class ModelSerializer(Serializer): field = self.get_field(model_field) if field: - field.initialize(parent=self, field_name=model_field.name) ret[model_field.name] = field for field_name in self.opts.read_only_fields: diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 7bc23f1d..3c65e9db 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -149,7 +149,6 @@ class IntegrationTestPaginationAndFiltering(TestCase): class PassOnContextPaginationSerializer(pagination.PaginationSerializer): - class Meta: object_serializer_class = serializers.Serializer @@ -179,9 +178,12 @@ class UnitTestPagination(TestCase): self.assertEquals(serializer.data['results'], self.objects[20:]) def test_context_available_in_result(self): + """ + Ensure context gets passed through to the object serializer. + """ serializer = PassOnContextPaginationSerializer(self.first_page) + serializer.data results = serializer.fields[serializer.results_field] - # assertIs is available in Python 2.7 self.assertTrue(serializer.context is results.context) -- cgit v1.2.3 From 65f7aa021450f86eaa7d9ac1a061138a71d20737 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 14 Dec 2012 20:12:50 +0000 Subject: Drop unneeded passing through of kwargs now context issue is resolved. --- rest_framework/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 7d7bb647..d241ade7 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -62,7 +62,7 @@ class BasePaginationSerializer(serializers.Serializer): super(BasePaginationSerializer, self).__init__(*args, **kwargs) results_field = self.results_field object_serializer = self.opts.object_serializer_class - self.fields[results_field] = object_serializer(source='object_list', **kwargs) + self.fields[results_field] = object_serializer(source='object_list') def to_native(self, obj): """ -- cgit v1.2.3 From 35f72cecb199e1790a42fadd6037da43cdd5a05a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 15 Dec 2012 20:40:41 +0000 Subject: Fix model validation exclusions. Fixes #500. Fixes #506. --- rest_framework/fields.py | 1 + rest_framework/serializers.py | 25 +++++++++++++------------ rest_framework/tests/models.py | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index da588082..d3ef8f77 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -32,6 +32,7 @@ def is_simple_callable(obj): class Field(object): + read_only = True creation_counter = 0 empty = '' type_name = None diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8026205e..1d93f777 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -128,17 +128,6 @@ class BaseSerializer(Field): """ return {} - def get_excluded_fieldnames(self): - """ - Returns the fieldnames that should not be validated. - """ - excluded_fields = list(self.opts.exclude) - if self.opts.fields: - for field in self.fields.keys() + self.get_default_fields().keys(): - if field not in list(self.opts.fields) + excluded_fields: - excluded_fields.append(field) - return excluded_fields - def get_fields(self): """ Returns the complete set of fields for the object as a dict. @@ -491,6 +480,18 @@ class ModelSerializer(Serializer): except KeyError: return ModelField(model_field=model_field, **kwargs) + def get_validation_exclusions(self): + """ + Return a list of field names to exclude from model validation. + """ + cls = self.opts.model + opts = get_concrete_model(cls)._meta + exclusions = [field.name for field in opts.fields + opts.many_to_many] + for field_name, field in self.fields.items(): + if field_name in exclusions and not field.read_only: + exclusions.remove(field_name) + return exclusions + def restore_object(self, attrs, instance=None): """ Restore the model instance. @@ -515,7 +516,7 @@ class ModelSerializer(Serializer): instance = self.opts.model(**attrs) try: - instance.full_clean(exclude=list(self.get_excluded_fieldnames())) + instance.full_clean(exclude=self.get_validation_exclusions()) except ValidationError, err: self._errors = err.message_dict return None diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index 428bf130..e2b287d0 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -61,7 +61,7 @@ class BasicModel(RESTFrameworkModel): class SlugBasedModel(RESTFrameworkModel): text = models.CharField(max_length=100) - slug = models.SlugField(max_length=32, blank=True) + slug = models.SlugField(max_length=32) class DefaultValueModel(RESTFrameworkModel): -- cgit v1.2.3 From 70714c234630cd205ed88686ece3b594f387a48f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 17 Dec 2012 09:08:28 +0000 Subject: Version 2.1.10 --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 83a6f302..d61632bc 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,3 +1,3 @@ -__version__ = '2.1.9' +__version__ = '2.1.10' VERSION = __version__ # synonym -- cgit v1.2.3