Merge pull request #371 from tomchristie/serializer-fixes

Serializer fixes
This commit is contained in:
Tom Christie 2012-11-05 08:35:54 -08:00
commit b87f01aa54
15 changed files with 154 additions and 100 deletions

View File

@ -57,9 +57,17 @@ To run the tests.
# Changelog # Changelog
## Master ## 2.1.0
* Minor field improvements (don't stringify dicts, more robust many-pk fields) **Date**: 5th Nov 2012
**Warning**: Please read [this thread][2.1.0-notes] regarding the `instance` and `data` keyword args before updating to 2.1.0.
* **Serializer `instance` and `data` keyword args have their position swapped.**
* `queryset` argument is now optional on writable model fields.
* Support Django's cache framework.
* Minor field improvements. (Don't stringify dicts, more robust many-pk fields.)
* Bugfixes (Support choice field in Browseable API)
## 2.0.2 ## 2.0.2
@ -113,6 +121,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/
[rest-framework-2-announcement]: topics/rest-framework-2-announcement.md [rest-framework-2-announcement]: topics/rest-framework-2-announcement.md
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[docs]: http://django-rest-framework.org/ [docs]: http://django-rest-framework.org/
[urlobject]: https://github.com/zacharyvoase/urlobject [urlobject]: https://github.com/zacharyvoase/urlobject

View File

@ -47,7 +47,7 @@ The first part of serializer class defines the fields that get serialized/deseri
We can now use `CommentSerializer` to serialize a comment, or list of comments. Again, using the `Serializer` class looks a lot like using a `Form` class. We can now use `CommentSerializer` to serialize a comment, or list of comments. Again, using the `Serializer` class looks a lot like using a `Form` class.
serializer = CommentSerializer(instance=comment) serializer = CommentSerializer(comment)
serializer.data serializer.data
# {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)} # {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
@ -65,20 +65,29 @@ Deserialization is similar. First we parse a stream into python native datatype
...then we restore those native datatypes into a fully populated object instance. ...then we restore those native datatypes into a fully populated object instance.
serializer = CommentSerializer(data) serializer = CommentSerializer(data=data)
serializer.is_valid() serializer.is_valid()
# True # True
serializer.object serializer.object
# <Comment object at 0x10633b2d0> # <Comment object at 0x10633b2d0>
>>> serializer.deserialize('json', stream) >>> serializer.deserialize('json', stream)
When deserializing data, we can either create a new instance, or update an existing instance.
serializer = CommentSerializer(data=data) # Create new instance
serializer = CommentSerializer(comment, data=data) # Update `instance`
## Validation ## 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. 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.
### Field-level validation ### Field-level validation
You can specify custom field-level validation by adding `validate_<fieldname>()` methods to your `Serializer` subclass. These are analagous to `clean_<fieldname>` methods on Django forms, but accept slightly different arguments. They take a dictionary of deserialized attributes as a first argument, and the field name in that dictionary as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided). Your `validate_<fieldname>` methods should either just return the attrs dictionary or raise a `ValidationError`. For example: You can specify custom field-level validation by adding `.validate_<fieldname>` methods to your `Serializer` subclass. These are analagous to `.clean_<fieldname>` methods on Django forms, but accept slightly different arguments.
They take a dictionary of deserialized attributes as a first argument, and the field name in that dictionary as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided).
Your `validate_<fieldname>` methods should either just return the `attrs` dictionary or raise a `ValidationError`. For example:
from rest_framework import serializers from rest_framework import serializers
@ -88,16 +97,22 @@ You can specify custom field-level validation by adding `validate_<fieldname>()`
def validate_title(self, attrs, source): def validate_title(self, attrs, source):
""" """
Check that the blog post is about Django Check that the blog post is about Django.
""" """
value = attrs[source] value = attrs[source]
if "Django" not in value: if "django" not in value.lower():
raise serializers.ValidationError("Blog post is not about Django") raise serializers.ValidationError("Blog post is not about Django")
return attrs return attrs
### Final cross-field validation ### Object-level validation
To do any other validation that requires access to multiple fields, add a method called `validate` to your `Serializer` subclass. This method takes a single argument, which is the `attrs` dictionary. It should raise a `ValidationError` if necessary, or just return `attrs`. To do any other validation that requires access to multiple fields, add a method called `.validate()` to your `Serializer` subclass. This method takes a single argument, which is the `attrs` dictionary. It should raise a `ValidationError` if necessary, or just return `attrs`.
## Saving object state
Serializers also include a `.save()` method that you can override if you want to provide a method of persisting the state of a deserialized object. The default behavior of the method is to simply call `.save()` on the deserialized object instance.
The generic views provided by REST framework call the `.save()` method when updating or creating entities.
## Dealing with nested objects ## Dealing with nested objects

View File

@ -4,11 +4,18 @@
> >
> &mdash; Eric S. Raymond, [The Cathedral and the Bazaar][cite]. > &mdash; Eric S. Raymond, [The Cathedral and the Bazaar][cite].
## Master ## 2.1.0
**Date**: 5th Nov 2012
**Warning**: Please read [this thread][2.1.0-notes] regarding the `instance` and `data` keyword args before updating to 2.1.0.
* **Serializer `instance` and `data` keyword args have their position swapped.**
* `queryset` argument is now optional on writable model fields.
* Support Django's cache framework. * Support Django's cache framework.
* Minor field improvements. (Don't stringify dicts, more robust many-pk fields.) * Minor field improvements. (Don't stringify dicts, more robust many-pk fields.)
* Bugfixes (Support choice field in Browseable API) * Bugfix: Support choice field in Browseable API.
* Bugfix: Related fields with `read_only=True` do not require a `queryset` argument.
## 2.0.2 ## 2.0.2
@ -29,7 +36,7 @@
**Date**: 30th Oct 2012 **Date**: 30th Oct 2012
* **Fix all of the things.** (Well, almost.) * **Fix all of the things.** (Well, almost.)
* For more information please see the [2.0 migration guide][migration]. * For more information please see the [2.0 announcement][announcement].
--- ---
@ -135,4 +142,5 @@
* Initial release. * Initial release.
[cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html [cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html
[migration]: migration.md [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[announcement]: rest-framework-2-announcement.md

View File

@ -162,7 +162,7 @@ Okay, once we've got a few imports out of the way, let's create a code snippet t
We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances. We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances.
serializer = SnippetSerializer(instance=snippet) serializer = SnippetSerializer(snippet)
serializer.data serializer.data
# {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'} # {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
@ -181,7 +181,7 @@ Deserialization is similar. First we parse a stream into python native datatype
...then we restore those native datatypes into to a fully populated object instance. ...then we restore those native datatypes into to a fully populated object instance.
serializer = SnippetSerializer(data) serializer = SnippetSerializer(data=data)
serializer.is_valid() serializer.is_valid()
# True # True
serializer.object serializer.object
@ -240,12 +240,12 @@ The root of our API is going to be a view that supports listing all the existing
""" """
if request.method == 'GET': if request.method == 'GET':
snippets = Snippet.objects.all() snippets = Snippet.objects.all()
serializer = SnippetSerializer(instance=snippets) serializer = SnippetSerializer(snippets)
return JSONResponse(serializer.data) return JSONResponse(serializer.data)
elif request.method == 'POST': elif request.method == 'POST':
data = JSONParser().parse(request) data = JSONParser().parse(request)
serializer = SnippetSerializer(data) serializer = SnippetSerializer(data=data)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return JSONResponse(serializer.data, status=201) return JSONResponse(serializer.data, status=201)
@ -267,12 +267,12 @@ We'll also need a view which corresponds to an individual snippet, and can be us
return HttpResponse(status=404) return HttpResponse(status=404)
if request.method == 'GET': if request.method == 'GET':
serializer = SnippetSerializer(instance=snippet) serializer = SnippetSerializer(snippet)
return JSONResponse(serializer.data) return JSONResponse(serializer.data)
elif request.method == 'PUT': elif request.method == 'PUT':
data = JSONParser().parse(request) data = JSONParser().parse(request)
serializer = SnippetSerializer(data, instance=snippet) serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return JSONResponse(serializer.data) return JSONResponse(serializer.data)

View File

@ -52,11 +52,11 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On
""" """
if request.method == 'GET': if request.method == 'GET':
snippets = Snippet.objects.all() snippets = Snippet.objects.all()
serializer = SnippetSerializer(instance=snippets) serializer = SnippetSerializer(snippets)
return Response(serializer.data) return Response(serializer.data)
elif request.method == 'POST': elif request.method == 'POST':
serializer = SnippetSerializer(request.DATA) serializer = SnippetSerializer(data=request.DATA)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
@ -77,11 +77,11 @@ Our instance view is an improvement over the previous example. It's a little mo
return Response(status=status.HTTP_404_NOT_FOUND) return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET': if request.method == 'GET':
serializer = SnippetSerializer(instance=snippet) serializer = SnippetSerializer(snippet)
return Response(serializer.data) return Response(serializer.data)
elif request.method == 'PUT': elif request.method == 'PUT':
serializer = SnippetSerializer(request.DATA, instance=snippet) serializer = SnippetSerializer(snippet, data=request.DATA)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return Response(serializer.data) return Response(serializer.data)

View File

@ -20,11 +20,11 @@ We'll start by rewriting the root view as a class based view. All this involves
""" """
def get(self, request, format=None): def get(self, request, format=None):
snippets = Snippet.objects.all() snippets = Snippet.objects.all()
serializer = SnippetSerializer(instance=snippets) serializer = SnippetSerializer(snippets)
return Response(serializer.data) return Response(serializer.data)
def post(self, request, format=None): def post(self, request, format=None):
serializer = SnippetSerializer(request.DATA) serializer = SnippetSerializer(data=request.DATA)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
@ -44,12 +44,12 @@ So far, so good. It looks pretty similar to the previous case, but we've got be
def get(self, request, pk, format=None): def get(self, request, pk, format=None):
snippet = self.get_object(pk) snippet = self.get_object(pk)
serializer = SnippetSerializer(instance=snippet) serializer = SnippetSerializer(snippet)
return Response(serializer.data) return Response(serializer.data)
def put(self, request, pk, format=None): def put(self, request, pk, format=None):
snippet = self.get_object(pk) snippet = self.get_object(pk)
serializer = SnippetSerializer(request.DATA, instance=snippet) serializer = SnippetSerializer(snippet, data=request.DATA)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return Response(serializer.data) return Response(serializer.data)

View File

@ -167,7 +167,7 @@ We've reached the end of our tutorial. If you want to get more involved in the
* Join the [REST framework discussion group][group], and help build the community. * Join the [REST framework discussion group][group], and help build the community.
* Follow the author [on Twitter][twitter] and say hi. * Follow the author [on Twitter][twitter] and say hi.
**Now go build some awesome things.** **Now go build awesome things.**
[repo]: https://github.com/tomchristie/rest-framework-tutorial [repo]: https://github.com/tomchristie/rest-framework-tutorial
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/

View File

@ -40,7 +40,7 @@ class Field(object):
self.source = source self.source = source
def initialize(self, parent): def initialize(self, parent, field_name):
""" """
Called to set up a field prior to field_to_native or field_from_native. Called to set up a field prior to field_to_native or field_from_native.
@ -248,7 +248,22 @@ class RelatedField(WritableField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.queryset = kwargs.pop('queryset', None) self.queryset = kwargs.pop('queryset', None)
super(RelatedField, self).__init__(*args, **kwargs) super(RelatedField, self).__init__(*args, **kwargs)
self.read_only = self.default_read_only self.read_only = kwargs.pop('read_only', self.default_read_only)
def initialize(self, parent, field_name):
super(RelatedField, self).initialize(parent, field_name)
if self.queryset is None and not self.read_only:
try:
manager = getattr(self.parent.opts.model, self.source or field_name)
if hasattr(manager, 'related'): # Forward
self.queryset = manager.related.model._default_manager.all()
else: # Reverse
self.queryset = manager.field.rel.to._default_manager.all()
except:
raise
msg = ('Serializer related fields must include a `queryset`' +
' argument or set `read_only=True')
raise Exception(msg)
### We need this stuff to make form choices work... ### We need this stuff to make form choices work...

View File

@ -48,7 +48,7 @@ class GenericAPIView(views.APIView):
# TODO: add support for seperate serializer/deserializer # TODO: add support for seperate serializer/deserializer
serializer_class = self.get_serializer_class() serializer_class = self.get_serializer_class()
context = self.get_serializer_context() context = self.get_serializer_context()
return serializer_class(data, instance=instance, context=context) return serializer_class(instance, data=data, context=context)
class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView): class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView):

View File

@ -6,6 +6,15 @@ from django.db import models
from django.forms import widgets from django.forms import widgets
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from rest_framework.compat import get_concrete_model from rest_framework.compat import get_concrete_model
# Note: We do the following so that users of the framework can use this style:
#
# example_field = serializers.CharField(...)
#
# This helps keep the seperation between model fields, form fields, and
# serializer fields more explicit.
from rest_framework.fields import * from rest_framework.fields import *
@ -82,10 +91,10 @@ class BaseSerializer(Field):
_options_class = SerializerOptions _options_class = SerializerOptions
_dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatability with unsorted implementations. _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatability with unsorted implementations.
def __init__(self, data=None, instance=None, context=None, **kwargs): def __init__(self, instance=None, data=None, context=None, **kwargs):
super(BaseSerializer, self).__init__(**kwargs) super(BaseSerializer, self).__init__(**kwargs)
self.fields = copy.deepcopy(self.base_fields)
self.opts = self._options_class(self.Meta) self.opts = self._options_class(self.Meta)
self.fields = copy.deepcopy(self.base_fields)
self.parent = None self.parent = None
self.root = None self.root = None
@ -100,13 +109,13 @@ class BaseSerializer(Field):
##### #####
# Methods to determine which fields to use when (de)serializing objects. # Methods to determine which fields to use when (de)serializing objects.
def default_fields(self, serialize, obj=None, data=None, nested=False): def default_fields(self, nested=False):
""" """
Return the complete set of default fields for the object, as a dict. Return the complete set of default fields for the object, as a dict.
""" """
return {} return {}
def get_fields(self, serialize, obj=None, data=None, nested=False): def get_fields(self, nested=False):
""" """
Returns the complete set of fields for the object as a dict. Returns the complete set of fields for the object as a dict.
@ -119,10 +128,10 @@ class BaseSerializer(Field):
for key, field in self.fields.items(): for key, field in self.fields.items():
ret[key] = field ret[key] = field
# Set up the field # Set up the field
field.initialize(parent=self) field.initialize(parent=self, field_name=key)
# Add in the default fields # Add in the default fields
fields = self.default_fields(serialize, obj, data, nested) fields = self.default_fields(nested)
for key, val in fields.items(): for key, val in fields.items():
if key not in ret: if key not in ret:
ret[key] = val ret[key] = val
@ -144,12 +153,12 @@ class BaseSerializer(Field):
##### #####
# Field methods - used when the serializer class is itself used as a field. # Field methods - used when the serializer class is itself used as a field.
def initialize(self, parent): def initialize(self, parent, field_name):
""" """
Same behaviour as usual Field, except that we need to keep track Same behaviour as usual Field, except that we need to keep track
of state so that we can deal with handling maximum depth. of state so that we can deal with handling maximum depth.
""" """
super(BaseSerializer, self).initialize(parent) super(BaseSerializer, self).initialize(parent, field_name)
if parent.opts.depth: if parent.opts.depth:
self.opts.depth = parent.opts.depth - 1 self.opts.depth = parent.opts.depth - 1
@ -170,7 +179,7 @@ class BaseSerializer(Field):
ret = self._dict_class() ret = self._dict_class()
ret.fields = {} ret.fields = {}
fields = self.get_fields(serialize=True, obj=obj, nested=bool(self.opts.depth)) fields = self.get_fields(nested=bool(self.opts.depth))
for field_name, field in fields.items(): for field_name, field in fields.items():
key = self.get_field_key(field_name) key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name) value = field.field_to_native(obj, field_name)
@ -183,7 +192,7 @@ class BaseSerializer(Field):
Core of deserialization, together with `restore_object`. Core of deserialization, together with `restore_object`.
Converts a dictionary of data into a dictionary of deserialized fields. Converts a dictionary of data into a dictionary of deserialized fields.
""" """
fields = self.get_fields(serialize=False, data=data, nested=bool(self.opts.depth)) fields = self.get_fields(nested=bool(self.opts.depth))
reverted_data = {} reverted_data = {}
for field_name, field in fields.items(): for field_name, field in fields.items():
try: try:
@ -198,7 +207,7 @@ class BaseSerializer(Field):
Run `validate_<fieldname>()` and `validate()` methods on the serializer Run `validate_<fieldname>()` and `validate()` methods on the serializer
""" """
# TODO: refactor this so we're not determining the fields again # TODO: refactor this so we're not determining the fields again
fields = self.get_fields(serialize=False, data=attrs, nested=bool(self.opts.depth)) fields = self.get_fields(nested=bool(self.opts.depth))
for field_name, field in fields.items(): for field_name, field in fields.items():
try: try:
@ -237,11 +246,8 @@ class BaseSerializer(Field):
""" """
Serialize objects -> primatives. Serialize objects -> primatives.
""" """
if isinstance(obj, dict): if hasattr(obj, '__iter__'):
return dict([(key, self.to_native(val)) return [self.convert_object(item) for item in obj]
for (key, val) in obj.items()])
elif hasattr(obj, '__iter__'):
return [self.to_native(item) for item in obj]
return self.convert_object(obj) return self.convert_object(obj)
def from_native(self, data): def from_native(self, data):
@ -323,7 +329,7 @@ class ModelSerializer(Serializer):
""" """
_options_class = ModelSerializerOptions _options_class = ModelSerializerOptions
def default_fields(self, serialize, obj=None, data=None, nested=False): def default_fields(self, nested=False):
""" """
Return all the fields that should be serialized for the model. Return all the fields that should be serialized for the model.
""" """
@ -360,7 +366,7 @@ class ModelSerializer(Serializer):
field = self.get_field(model_field) field = self.get_field(model_field)
if field: if field:
field.initialize(parent=self) field.initialize(parent=self, field_name=model_field.name)
ret[model_field.name] = field ret[model_field.name] = field
return ret return ret

View File

@ -25,7 +25,7 @@ class TestGenericRelations(TestCase):
model = Bookmark model = Bookmark
exclude = ('id',) exclude = ('id',)
serializer = BookmarkSerializer(instance=self.bookmark) serializer = BookmarkSerializer(self.bookmark)
expected = { expected = {
'tags': [u'django', u'python'], 'tags': [u'django', u'python'],
'url': u'https://www.djangoproject.com/' 'url': u'https://www.djangoproject.com/'

View File

@ -7,12 +7,13 @@ from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, Blo
factory = RequestFactory() factory = RequestFactory()
class BlogPostCommentSerializer(serializers.Serializer): class BlogPostCommentSerializer(serializers.ModelSerializer):
text = serializers.CharField() text = serializers.CharField()
blog_post_url = serializers.HyperlinkedRelatedField(source='blog_post', view_name='blogpost-detail', queryset=BlogPost.objects.all()) blog_post_url = serializers.HyperlinkedRelatedField(source='blog_post', view_name='blogpost-detail')
def restore_object(self, attrs, instance=None): class Meta:
return BlogPostComment(**attrs) model = BlogPostComment
fields = ('text', 'blog_post_url')
class BasicList(generics.ListCreateAPIView): class BasicList(generics.ListCreateAPIView):
@ -42,7 +43,7 @@ class ManyToManyDetail(generics.RetrieveAPIView):
class BlogPostCommentListCreate(generics.ListCreateAPIView): class BlogPostCommentListCreate(generics.ListCreateAPIView):
model = BlogPostComment model = BlogPostComment
model_serializer_class = BlogPostCommentSerializer serializer_class = BlogPostCommentSerializer
class BlogPostDetail(generics.RetrieveAPIView): class BlogPostDetail(generics.RetrieveAPIView):

View File

@ -74,13 +74,13 @@ class UnitTestPagination(TestCase):
self.last_page = paginator.page(3) self.last_page = paginator.page(3)
def test_native_pagination(self): def test_native_pagination(self):
serializer = pagination.PaginationSerializer(instance=self.first_page) serializer = pagination.PaginationSerializer(self.first_page)
self.assertEquals(serializer.data['count'], 26) self.assertEquals(serializer.data['count'], 26)
self.assertEquals(serializer.data['next'], '?page=2') self.assertEquals(serializer.data['next'], '?page=2')
self.assertEquals(serializer.data['previous'], None) self.assertEquals(serializer.data['previous'], None)
self.assertEquals(serializer.data['results'], self.objects[:10]) self.assertEquals(serializer.data['results'], self.objects[:10])
serializer = pagination.PaginationSerializer(instance=self.last_page) serializer = pagination.PaginationSerializer(self.last_page)
self.assertEquals(serializer.data['count'], 26) self.assertEquals(serializer.data['count'], 26)
self.assertEquals(serializer.data['next'], None) self.assertEquals(serializer.data['next'], None)
self.assertEquals(serializer.data['previous'], '?page=2') self.assertEquals(serializer.data['previous'], '?page=2')

View File

@ -15,7 +15,7 @@ class ManyToManySource(models.Model):
class ManyToManyTargetSerializer(serializers.ModelSerializer): class ManyToManyTargetSerializer(serializers.ModelSerializer):
sources = serializers.ManyPrimaryKeyRelatedField(queryset=ManyToManySource.objects.all()) sources = serializers.ManyPrimaryKeyRelatedField()
class Meta: class Meta:
model = ManyToManyTarget model = ManyToManyTarget
@ -63,7 +63,7 @@ class PrimaryKeyManyToManyTests(TestCase):
def test_many_to_many_retrieve(self): def test_many_to_many_retrieve(self):
queryset = ManyToManySource.objects.all() queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(instance=queryset) serializer = ManyToManySourceSerializer(queryset)
expected = [ expected = [
{'id': 1, 'name': u'source-1', 'targets': [1]}, {'id': 1, 'name': u'source-1', 'targets': [1]},
{'id': 2, 'name': u'source-2', 'targets': [1, 2]}, {'id': 2, 'name': u'source-2', 'targets': [1, 2]},
@ -73,7 +73,7 @@ class PrimaryKeyManyToManyTests(TestCase):
def test_reverse_many_to_many_retrieve(self): def test_reverse_many_to_many_retrieve(self):
queryset = ManyToManyTarget.objects.all() queryset = ManyToManyTarget.objects.all()
serializer = ManyToManyTargetSerializer(instance=queryset) serializer = ManyToManyTargetSerializer(queryset)
expected = [ expected = [
{'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]}, {'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]},
{'id': 2, 'name': u'target-2', 'sources': [2, 3]}, {'id': 2, 'name': u'target-2', 'sources': [2, 3]},
@ -84,14 +84,14 @@ class PrimaryKeyManyToManyTests(TestCase):
def test_many_to_many_update(self): def test_many_to_many_update(self):
data = {'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]} data = {'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]}
instance = ManyToManySource.objects.get(pk=1) instance = ManyToManySource.objects.get(pk=1)
serializer = ManyToManySourceSerializer(data, instance=instance) serializer = ManyToManySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid()) self.assertTrue(serializer.is_valid())
self.assertEquals(serializer.data, data) self.assertEquals(serializer.data, data)
serializer.save() 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 = ManyToManySource.objects.all() queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(instance=queryset) serializer = ManyToManySourceSerializer(queryset)
expected = [ expected = [
{'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]}, {'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]},
{'id': 2, 'name': u'source-2', 'targets': [1, 2]}, {'id': 2, 'name': u'source-2', 'targets': [1, 2]},
@ -102,14 +102,14 @@ class PrimaryKeyManyToManyTests(TestCase):
def test_reverse_many_to_many_update(self): def test_reverse_many_to_many_update(self):
data = {'id': 1, 'name': u'target-1', 'sources': [1]} data = {'id': 1, 'name': u'target-1', 'sources': [1]}
instance = ManyToManyTarget.objects.get(pk=1) instance = ManyToManyTarget.objects.get(pk=1)
serializer = ManyToManyTargetSerializer(data, instance=instance) serializer = ManyToManyTargetSerializer(instance, data=data)
self.assertTrue(serializer.is_valid()) self.assertTrue(serializer.is_valid())
self.assertEquals(serializer.data, data) self.assertEquals(serializer.data, data)
serializer.save() serializer.save()
# Ensure target 1 is updated, and everything else is as expected # Ensure target 1 is updated, and everything else is as expected
queryset = ManyToManyTarget.objects.all() queryset = ManyToManyTarget.objects.all()
serializer = ManyToManyTargetSerializer(instance=queryset) serializer = ManyToManyTargetSerializer(queryset)
expected = [ expected = [
{'id': 1, 'name': u'target-1', 'sources': [1]}, {'id': 1, 'name': u'target-1', 'sources': [1]},
{'id': 2, 'name': u'target-2', 'sources': [2, 3]}, {'id': 2, 'name': u'target-2', 'sources': [2, 3]},
@ -130,7 +130,7 @@ class PrimaryKeyForeignKeyTests(TestCase):
def test_foreign_key_retrieve(self): def test_foreign_key_retrieve(self):
queryset = ForeignKeySource.objects.all() queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(instance=queryset) serializer = ForeignKeySourceSerializer(queryset)
expected = [ expected = [
{'id': 1, 'name': u'source-1', 'target': 1}, {'id': 1, 'name': u'source-1', 'target': 1},
{'id': 2, 'name': u'source-2', 'target': 1}, {'id': 2, 'name': u'source-2', 'target': 1},
@ -140,7 +140,7 @@ class PrimaryKeyForeignKeyTests(TestCase):
def test_reverse_foreign_key_retrieve(self): def test_reverse_foreign_key_retrieve(self):
queryset = ForeignKeyTarget.objects.all() queryset = ForeignKeyTarget.objects.all()
serializer = ForeignKeyTargetSerializer(instance=queryset) serializer = ForeignKeyTargetSerializer(queryset)
expected = [ expected = [
{'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]}, {'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]},
{'id': 2, 'name': u'target-2', 'sources': []}, {'id': 2, 'name': u'target-2', 'sources': []},
@ -150,14 +150,14 @@ class PrimaryKeyForeignKeyTests(TestCase):
def test_foreign_key_update(self): def test_foreign_key_update(self):
data = {'id': 1, 'name': u'source-1', 'target': 2} data = {'id': 1, 'name': u'source-1', 'target': 2}
instance = ForeignKeySource.objects.get(pk=1) instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(data, instance=instance) serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid()) self.assertTrue(serializer.is_valid())
self.assertEquals(serializer.data, data) self.assertEquals(serializer.data, data)
serializer.save() 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() queryset = ForeignKeySource.objects.all()
serializer = ForeignKeySourceSerializer(instance=queryset) serializer = ForeignKeySourceSerializer(queryset)
expected = [ expected = [
{'id': 1, 'name': u'source-1', 'target': 2}, {'id': 1, 'name': u'source-1', 'target': 2},
{'id': 2, 'name': u'source-2', 'target': 1}, {'id': 2, 'name': u'source-2', 'target': 1},
@ -172,14 +172,14 @@ class PrimaryKeyForeignKeyTests(TestCase):
# def test_reverse_foreign_key_update(self): # def test_reverse_foreign_key_update(self):
# data = {'id': 1, 'name': u'target-1', 'sources': [1]} # data = {'id': 1, 'name': u'target-1', 'sources': [1]}
# instance = ForeignKeyTarget.objects.get(pk=1) # instance = ForeignKeyTarget.objects.get(pk=1)
# serializer = ForeignKeyTargetSerializer(data, instance=instance) # serializer = ForeignKeyTargetSerializer(instance, data=data)
# self.assertTrue(serializer.is_valid()) # self.assertTrue(serializer.is_valid())
# self.assertEquals(serializer.data, data) # self.assertEquals(serializer.data, data)
# serializer.save() # serializer.save()
# # Ensure target 1 is updated, and everything else is as expected # # Ensure target 1 is updated, and everything else is as expected
# queryset = ForeignKeyTarget.objects.all() # queryset = ForeignKeyTarget.objects.all()
# serializer = ForeignKeyTargetSerializer(instance=queryset) # serializer = ForeignKeyTargetSerializer(queryset)
# expected = [ # expected = [
# {'id': 1, 'name': u'target-1', 'sources': [1]}, # {'id': 1, 'name': u'target-1', 'sources': [1]},
# {'id': 2, 'name': u'target-2', 'sources': []}, # {'id': 2, 'name': u'target-2', 'sources': []},

View File

@ -87,11 +87,11 @@ class BasicTests(TestCase):
self.assertEquals(serializer.data, expected) self.assertEquals(serializer.data, expected)
def test_retrieve(self): def test_retrieve(self):
serializer = CommentSerializer(instance=self.comment) serializer = CommentSerializer(self.comment)
self.assertEquals(serializer.data, self.expected) self.assertEquals(serializer.data, self.expected)
def test_create(self): def test_create(self):
serializer = CommentSerializer(self.data) serializer = CommentSerializer(data=self.data)
expected = self.comment expected = self.comment
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
self.assertEquals(serializer.object, expected) self.assertEquals(serializer.object, expected)
@ -99,25 +99,25 @@ class BasicTests(TestCase):
self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!') self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
def test_update(self): def test_update(self):
serializer = CommentSerializer(self.data, instance=self.comment) serializer = CommentSerializer(self.comment, data=self.data)
expected = self.comment expected = self.comment
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
self.assertEquals(serializer.object, expected) self.assertEquals(serializer.object, expected)
self.assertTrue(serializer.object is expected) self.assertTrue(serializer.object is expected)
self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!') self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
def test_model_fields_as_expected(self): def test_model_fields_as_expected(self):
""" Make sure that the fields returned are the same as defined """ Make sure that the fields returned are the same as defined
in the Meta data in the Meta data
""" """
serializer = PersonSerializer(instance=self.person) serializer = PersonSerializer(self.person)
self.assertEquals(set(serializer.data.keys()), self.assertEquals(set(serializer.data.keys()),
set(['name', 'age', 'info'])) set(['name', 'age', 'info']))
def test_field_with_dictionary(self): def test_field_with_dictionary(self):
""" Make sure that dictionaries from fields are left intact """ Make sure that dictionaries from fields are left intact
""" """
serializer = PersonSerializer(instance=self.person) serializer = PersonSerializer(self.person)
expected = self.person_data expected = self.person_data
self.assertEquals(serializer.data['info'], expected) self.assertEquals(serializer.data['info'], expected)
@ -138,12 +138,12 @@ class ValidationTests(TestCase):
) )
def test_create(self): def test_create(self):
serializer = CommentSerializer(self.data) serializer = CommentSerializer(data=self.data)
self.assertEquals(serializer.is_valid(), False) self.assertEquals(serializer.is_valid(), False)
self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']}) self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']})
def test_update(self): def test_update(self):
serializer = CommentSerializer(self.data, instance=self.comment) serializer = CommentSerializer(self.comment, data=self.data)
self.assertEquals(serializer.is_valid(), False) self.assertEquals(serializer.is_valid(), False)
self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']}) self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']})
@ -152,7 +152,7 @@ class ValidationTests(TestCase):
'content': 'xxx', 'content': 'xxx',
'created': datetime.datetime(2012, 1, 1) 'created': datetime.datetime(2012, 1, 1)
} }
serializer = CommentSerializer(data, instance=self.comment) serializer = CommentSerializer(self.comment, data=data)
self.assertEquals(serializer.is_valid(), False) self.assertEquals(serializer.is_valid(), False)
self.assertEquals(serializer.errors, {'email': [u'This field is required.']}) self.assertEquals(serializer.errors, {'email': [u'This field is required.']})
@ -163,7 +163,7 @@ class ValidationTests(TestCase):
'title': 'Some action item', 'title': 'Some action item',
#No 'done' value. #No 'done' value.
} }
serializer = ActionItemSerializer(data, instance=self.actionitem) serializer = ActionItemSerializer(self.actionitem, data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
self.assertEquals(serializer.errors, {}) self.assertEquals(serializer.errors, {})
@ -183,12 +183,12 @@ class ValidationTests(TestCase):
'created': datetime.datetime(2012, 1, 1) 'created': datetime.datetime(2012, 1, 1)
} }
serializer = CommentSerializerWithFieldValidator(data) serializer = CommentSerializerWithFieldValidator(data=data)
self.assertTrue(serializer.is_valid()) self.assertTrue(serializer.is_valid())
data['content'] = 'This should not validate' data['content'] = 'This should not validate'
serializer = CommentSerializerWithFieldValidator(data) serializer = CommentSerializerWithFieldValidator(data=data)
self.assertFalse(serializer.is_valid()) self.assertFalse(serializer.is_valid())
self.assertEquals(serializer.errors, {'content': [u'Test not in value']}) self.assertEquals(serializer.errors, {'content': [u'Test not in value']})
@ -207,12 +207,12 @@ class ValidationTests(TestCase):
'created': datetime.datetime(2012, 1, 1) 'created': datetime.datetime(2012, 1, 1)
} }
serializer = CommentSerializerWithCrossFieldValidator(data) serializer = CommentSerializerWithCrossFieldValidator(data=data)
self.assertTrue(serializer.is_valid()) self.assertTrue(serializer.is_valid())
data['content'] = 'A comment from foo@bar.com' data['content'] = 'A comment from foo@bar.com'
serializer = CommentSerializerWithCrossFieldValidator(data) serializer = CommentSerializerWithCrossFieldValidator(data=data)
self.assertFalse(serializer.is_valid()) self.assertFalse(serializer.is_valid())
self.assertEquals(serializer.errors, {'non_field_errors': [u'Email address not in content']}) self.assertEquals(serializer.errors, {'non_field_errors': [u'Email address not in content']})
@ -220,7 +220,7 @@ class ValidationTests(TestCase):
""" """
Omitting a value for null-field should validate. Omitting a value for null-field should validate.
""" """
serializer = PersonSerializer({'name': 'marko'}) serializer = PersonSerializer(data={'name': 'marko'})
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
self.assertEquals(serializer.errors, {}) self.assertEquals(serializer.errors, {})
@ -270,7 +270,7 @@ class ManyToManyTests(TestCase):
Create an instance of a model with a ManyToMany relationship. Create an instance of a model with a ManyToMany relationship.
""" """
data = {'rel': [self.anchor.id]} data = {'rel': [self.anchor.id]}
serializer = self.serializer_class(data) serializer = self.serializer_class(data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(ManyToManyModel.objects.all()), 2) self.assertEquals(len(ManyToManyModel.objects.all()), 2)
@ -284,7 +284,7 @@ class ManyToManyTests(TestCase):
new_anchor = Anchor() new_anchor = Anchor()
new_anchor.save() new_anchor.save()
data = {'rel': [self.anchor.id, new_anchor.id]} data = {'rel': [self.anchor.id, new_anchor.id]}
serializer = self.serializer_class(data, instance=self.instance) serializer = self.serializer_class(self.instance, data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(ManyToManyModel.objects.all()), 1) self.assertEquals(len(ManyToManyModel.objects.all()), 1)
@ -297,7 +297,7 @@ class ManyToManyTests(TestCase):
containing no items. containing no items.
""" """
data = {'rel': []} data = {'rel': []}
serializer = self.serializer_class(data) serializer = self.serializer_class(data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(ManyToManyModel.objects.all()), 2) self.assertEquals(len(ManyToManyModel.objects.all()), 2)
@ -312,7 +312,7 @@ class ManyToManyTests(TestCase):
new_anchor = Anchor() new_anchor = Anchor()
new_anchor.save() new_anchor.save()
data = {'rel': []} data = {'rel': []}
serializer = self.serializer_class(data, instance=self.instance) serializer = self.serializer_class(self.instance, data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(ManyToManyModel.objects.all()), 1) self.assertEquals(len(ManyToManyModel.objects.all()), 1)
@ -326,7 +326,7 @@ class ManyToManyTests(TestCase):
lists (eg form data). lists (eg form data).
""" """
data = {'rel': ''} data = {'rel': ''}
serializer = self.serializer_class(data) serializer = self.serializer_class(data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(ManyToManyModel.objects.all()), 2) self.assertEquals(len(ManyToManyModel.objects.all()), 2)
@ -364,7 +364,7 @@ class ReadOnlyManyToManyTests(TestCase):
new_anchor = Anchor() new_anchor = Anchor()
new_anchor.save() new_anchor.save()
data = {'rel': [self.anchor.id, new_anchor.id]} data = {'rel': [self.anchor.id, new_anchor.id]}
serializer = self.serializer_class(data, instance=self.instance) serializer = self.serializer_class(self.instance, data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(ReadOnlyManyToManyModel.objects.all()), 1) self.assertEquals(len(ReadOnlyManyToManyModel.objects.all()), 1)
@ -380,7 +380,7 @@ class ReadOnlyManyToManyTests(TestCase):
new_anchor = Anchor() new_anchor = Anchor()
new_anchor.save() new_anchor.save()
data = {} data = {}
serializer = self.serializer_class(data, instance=self.instance) serializer = self.serializer_class(self.instance, data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(ReadOnlyManyToManyModel.objects.all()), 1) self.assertEquals(len(ReadOnlyManyToManyModel.objects.all()), 1)
@ -400,7 +400,7 @@ class DefaultValueTests(TestCase):
def test_create_using_default(self): def test_create_using_default(self):
data = {} data = {}
serializer = self.serializer_class(data) serializer = self.serializer_class(data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(self.objects.all()), 1) self.assertEquals(len(self.objects.all()), 1)
@ -409,7 +409,7 @@ class DefaultValueTests(TestCase):
def test_create_overriding_default(self): def test_create_overriding_default(self):
data = {'text': 'overridden'} data = {'text': 'overridden'}
serializer = self.serializer_class(data) serializer = self.serializer_class(data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(self.objects.all()), 1) self.assertEquals(len(self.objects.all()), 1)
@ -428,7 +428,7 @@ class CallableDefaultValueTests(TestCase):
def test_create_using_default(self): def test_create_using_default(self):
data = {} data = {}
serializer = self.serializer_class(data) serializer = self.serializer_class(data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(self.objects.all()), 1) self.assertEquals(len(self.objects.all()), 1)
@ -437,7 +437,7 @@ class CallableDefaultValueTests(TestCase):
def test_create_overriding_default(self): def test_create_overriding_default(self):
data = {'text': 'overridden'} data = {'text': 'overridden'}
serializer = self.serializer_class(data) serializer = self.serializer_class(data=data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
instance = serializer.save() instance = serializer.save()
self.assertEquals(len(self.objects.all()), 1) self.assertEquals(len(self.objects.all()), 1)
@ -499,11 +499,11 @@ class BlankFieldTests(TestCase):
self.data = {'title': ''} self.data = {'title': ''}
def test_create_blank_field(self): def test_create_blank_field(self):
serializer = self.serializer_class(self.data) serializer = self.serializer_class(data=self.data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
def test_create_model_blank_field(self): def test_create_model_blank_field(self):
serializer = self.model_serializer_class(self.data) serializer = self.model_serializer_class(data=self.data)
self.assertEquals(serializer.is_valid(), True) self.assertEquals(serializer.is_valid(), True)
def test_create_not_blank_field(self): def test_create_not_blank_field(self):
@ -511,7 +511,7 @@ class BlankFieldTests(TestCase):
Test to ensure blank data in a field not marked as blank=True Test to ensure blank data in a field not marked as blank=True
is considered invalid in a non-model serializer is considered invalid in a non-model serializer
""" """
serializer = self.not_blank_serializer_class(self.data) serializer = self.not_blank_serializer_class(data=self.data)
self.assertEquals(serializer.is_valid(), False) self.assertEquals(serializer.is_valid(), False)
def test_create_model_not_blank_field(self): def test_create_model_not_blank_field(self):
@ -519,5 +519,5 @@ class BlankFieldTests(TestCase):
Test to ensure blank data in a field not marked as blank=True Test to ensure blank data in a field not marked as blank=True
is considered invalid in a model serializer is considered invalid in a model serializer
""" """
serializer = self.not_blank_model_serializer_class(self.data) serializer = self.not_blank_model_serializer_class(data=self.data)
self.assertEquals(serializer.is_valid(), False) self.assertEquals(serializer.is_valid(), False)