Skip to content

Commit 276a2a5

Browse files
committed
Docs on nested creates/updates and suchlike
1 parent d4b8e35 commit 276a2a5

File tree

1 file changed

+100
-66
lines changed

1 file changed

+100
-66
lines changed

docs/api-guide/serializers.md

Lines changed: 100 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -271,97 +271,127 @@ Similarly if a nested representation should be a list of items, you should pass
271271
content = serializers.CharField(max_length=200)
272272
created = serializers.DateTimeField()
273273

274-
Validation of nested objects will work the same as before. Errors with nested objects will be nested under the field name of the nested object.
274+
## Writable nested representations
275+
276+
When dealing with nested representations that support deserializing the data, an errors with nested objects will be nested under the field name of the nested object.
275277

276278
serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
277279
serializer.is_valid()
278280
# False
279281
serializer.errors
280282
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
281283

282-
**TODO** Document create and update for nested serializers
284+
Similarly, the `.validated_data` property will include nested data structures.
283285

284-
## Dealing with multiple objects
286+
#### Writing `.create()` methods for nested representations
285287

286-
The `Serializer` class can also handle serializing or deserializing lists of objects.
288+
If you're supporting writable nested representations you'll need to write `.create()` or `.update()` methods that handle saving multiple objects.
287289

288-
#### Serializing multiple objects
290+
The following example demonstrates how you might handle creating a user with a nested profile object.
289291

290-
To serialize a queryset or list of objects instead of a single object instance, you should pass the `many=True` flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.
292+
class UserSerializer(serializers.ModelSerializer):
293+
profile = ProfileSerializer()
291294

292-
queryset = Book.objects.all()
293-
serializer = BookSerializer(queryset, many=True)
294-
serializer.data
295-
# [
296-
# {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
297-
# {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
298-
# {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
299-
# ]
295+
class Meta:
296+
model = User
297+
fields = ('username', 'email', 'profile')
300298

301-
#### Deserializing multiple objects for creation
299+
def create(self, validated_data):
300+
profile_data = validated_data.pop('profile')
301+
user = User.objects.create(**validated_data)
302+
Profile.objects.create(user=user, **profile_data)
303+
return user
302304

303-
**TODO**
305+
#### Writing `.update()` methods for nested representations
304306

305-
To deserialize a list of object data, and create multiple object instances in a single pass, you should also set the `many=True` flag, and pass a list of data to be deserialized.
307+
For updates you'll want to think carefully about how to handle updates to relationships. For example if the data for the relationship is `None`, or not provided, which of the following should occur?
306308

307-
This allows you to write views that create multiple items when a `POST` request is made.
309+
* Set the relationship to `NULL` in the database.
310+
* Delete the associated instance.
311+
* Ignore the data and leave the instance as it is.
312+
* Raise a validation error.
308313

309-
For example:
314+
Here's an example for an `update()` method on our previous `UserSerializer` class.
310315

311-
data = [
312-
{'title': 'The bell jar', 'author': 'Sylvia Plath'},
313-
{'title': 'For whom the bell tolls', 'author': 'Ernest Hemingway'}
314-
]
315-
serializer = BookSerializer(data=data, many=True)
316-
serializer.is_valid()
317-
# True
318-
serializer.save() # `.save()` will be called on each deserialized instance
316+
def update(self, instance, validated_data):
317+
profile_data = validated_data.pop('profile')
318+
# Unless the application properly enforces that this field is
319+
# always set, the follow could raise a `DoesNotExist`, which
320+
# would need to be handled.
321+
profile = instance.profile
322+
323+
user.username = validated_data.get('username', instance.username)
324+
user.email = validated_data.get('email', instance.email)
325+
user.save()
319326

320-
The default implementation for multiple object creation is to simply call `.create()` for each item in the list. If you want to customize this behavior, you'll need to customize the `.create()` method on `ListSerializer` class that is used when `many=True` is passed.
327+
profile.is_premium_member = profile_data.get(
328+
'is_premium_member',
329+
profile.is_premium_member
330+
)
331+
profile.has_support_contract = profile_data.get(
332+
'has_support_contract',
333+
profile.has_support_contract
334+
)
335+
profile.save()
321336

322-
For example:
337+
return user
323338

324-
class BookListSerializer(serializers.ListSerializer):
325-
def create(self, validated_data):
326-
books = [Book(**item) for item in validated_data]
327-
return Book.objects.bulk_create(books)
339+
Because the behavior of nested creates and updates can be ambiguous, and may require complex dependancies between related models, REST framework 3 requires you to always write these methods explicitly. The default `ModelSerializer` `.create()` and `.update()` methods do not include support for writable nested representations.
328340

329-
class BookSerializer(serializers.Serializer):
330-
331-
class Meta:
332-
list_serializer_class = BookListSerializer
341+
It is possible that a third party package, providing automatic support some kinds of automatic writable nested representations may be released alongside the 3.1 release.
333342

334-
#### Deserializing multiple objects for update
343+
#### Handling saving related instances in model manager classes
335344

336-
**TODO**
345+
An alternative to saving multiple related instances in the serializer is to write custom model manager classes handle creating the correct instances.
337346

338-
You can also deserialize a list of objects as part of a bulk update of multiple existing items.
339-
In this case you need to supply both an existing list or queryset of items, as well as a list of data to update those items with.
347+
For example, suppose we wanted to ensure that `User` instances and `Profile` instances are always created together as a pair. We might write a custom manager class that looks something like this:
340348

341-
This allows you to write views that update or create multiple items when a `PUT` request is made.
349+
class UserManager(models.Manager):
350+
...
342351

343-
# Capitalizing the titles of the books
344-
queryset = Book.objects.all()
345-
data = [
346-
{'id': 3, 'title': 'The Bell Jar', 'author': 'Sylvia Plath'},
347-
{'id': 4, 'title': 'For Whom the Bell Tolls', 'author': 'Ernest Hemingway'}
348-
]
349-
serializer = BookSerializer(queryset, data=data, many=True)
350-
serializer.is_valid()
351-
# True
352-
serializer.save() # `.save()` will be called on each updated or newly created instance.
352+
def create(self, username, email, is_premium_member=False, has_support_contract=False):
353+
user = User(username=username, email=email)
354+
user.save()
355+
profile = Profile(
356+
user=user,
357+
is_premium_member=is_premium_member,
358+
has_support_contract=has_support_contract
359+
)
360+
profile.save()
361+
return user
353362

354-
By default bulk updates will be limited to updating instances that already exist in the provided queryset.
363+
This manager class now more nicely encapsulates that user instances and profile instances are always created at the same time. Our `.create()` method on the serializer class can now be re-written to use the new manager method.
355364

356-
When performing a bulk update you may want to allow new items to be created, and missing items to be deleted. To do so, pass `allow_add_remove=True` to the serializer.
365+
def create(self, validated_data):
366+
return User.objects.create(
367+
username=validated_data['username'],
368+
email=validated_data['email']
369+
is_premium_member=validated_data['profile']['is_premium_member']
370+
has_support_contract=validated_data['profile']['has_support_contract']
371+
)
357372

358-
serializer = BookSerializer(queryset, data=data, many=True, allow_add_remove=True)
359-
serializer.is_valid()
360-
# True
361-
serializer.save() # `.save()` will be called on updated or newly created instances.
362-
# `.delete()` will be called on any other items in the `queryset`.
373+
For more details on this approach see the Django documentation on [model managers](model-managers), and [this blogpost on using model and manger classes](encapsulation-blogpost).
363374

364-
Passing `allow_add_remove=True` ensures that any update operations will completely overwrite the existing queryset, rather than simply updating existing objects.
375+
## Dealing with multiple objects
376+
377+
The `Serializer` class can also handle serializing or deserializing lists of objects.
378+
379+
#### Serializing multiple objects
380+
381+
To serialize a queryset or list of objects instead of a single object instance, you should pass the `many=True` flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.
382+
383+
queryset = Book.objects.all()
384+
serializer = BookSerializer(queryset, many=True)
385+
serializer.data
386+
# [
387+
# {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
388+
# {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
389+
# {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
390+
# ]
391+
392+
#### Deserializing multiple objects
393+
394+
The default behavior for deserializing multiple objects is to support multiple object creation, but not support multiple object updates. For more information on how to support or customize either of these cases, see the [ListSerializer](#ListSerializer) documentation below.
365395

366396
## Including extra context
367397

@@ -399,7 +429,7 @@ By default, all the model fields on the class will be mapped to a corresponding
399429

400430
Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Reverse relationships are not included by default unless explicitly included as described below.
401431

402-
#### Inspecting the generated `ModelSerializer` class.
432+
#### Inspecting a `ModelSerializer`
403433

404434
Serializer classes generate helpful verbose representation strings, that allow you to fully inspect the state of their fields. This is particularly useful when working with `ModelSerializers` where you want to determine what set of fields and validators are being automatically created for you.
405435

@@ -607,7 +637,7 @@ For example:
607637
class Meta:
608638
list_serializer_class = CustomListSerializer
609639

610-
#### Customizing `.create()` for multiple objects.
640+
#### Customizing multiple create
611641

612642
The default implementation for multiple object creation is to simply call `.create()` for each item in the list. If you want to customize this behavior, you'll need to customize the `.create()` method on `ListSerializer` class that is used when `many=True` is passed.
613643

@@ -623,7 +653,7 @@ For example:
623653
class Meta:
624654
list_serializer_class = BookListSerializer
625655

626-
#### Customizing `.update()` for multiple objects.
656+
#### Customizing multiple update
627657

628658
By default the `ListSerializer` class does not support multiple updates. This is because the behavior that should be expected for insertions and deletions is ambiguous.
629659

@@ -663,6 +693,8 @@ Here's an example of how you might choose to implement multiple updates:
663693
class Meta:
664694
list_serializer_class = BookListSerializer
665695

696+
It is possible that a third party package may be included alongside the 3.1 release that provides some automatic support for multiple update operations, similar to the `allow_add_remove` behavior that was present in REST framework 2.
697+
666698
---
667699

668700
# BaseSerializer
@@ -687,7 +719,7 @@ Because this class provides the same interface as the `Serializer` class, you ca
687719

688720
The only difference you'll notice when doing so is the `BaseSerializer` classes will not generate HTML forms in the browsable API. This is because the data they return does not include all the field information that would allow each field to be rendered into a suitable HTML input.
689721

690-
##### Read-only `BaseSerializer` classes.
722+
##### Read-only `BaseSerializer` classes
691723

692724
To implement a read-only serializer using the `BaseSerializer` class, we just need to override the `.to_representation()` method. Let's take a look at an example using a simple Django model:
693725

@@ -721,7 +753,7 @@ Or use it to serialize multiple instances:
721753
serializer = HighScoreSerializer(queryset, many=True)
722754
return Response(serializer.data)
723755

724-
##### Read-write `BaseSerializer` classes.
756+
##### Read-write `BaseSerializer` classes
725757

726758
To create a read-write serializer we first need to implement a `.to_internal_value()` method. This method returns the validated values that will be used to construct the object instance, and may raise a `ValidationError` if the supplied data is in an incorrect format.
727759

@@ -766,7 +798,7 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd
766798
def create(self, validated_data):
767799
return HighScore.objects.create(**validated_data)
768800

769-
#### Creating new generic serializers with `BaseSerializer`.
801+
#### Creating new base classes
770802

771803
The `BaseSerializer` class is also useful if you want to implement new generic serializer classes for dealing with particular serialization styles, or for integrating with alternative storage backends.
772804

@@ -905,6 +937,8 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide
905937

906938
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
907939
[relations]: relations.md
940+
[model-managers]: https://docs.djangoproject.com/en/dev/topics/db/managers/
941+
[encapsulation-blogpost]: http://www.dabapps.com/blog/django-models-and-encapsulation/
908942
[mongoengine]: https://github.com/umutbozkurt/django-rest-framework-mongoengine
909943
[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis
910944
[django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore

0 commit comments

Comments
 (0)