-
Notifications
You must be signed in to change notification settings - Fork 1
Django DRF Cheatsheet
Several more complicated topics described as per-section below.
How does DRF handle update request like PATCH/PUT when the parent object has relational, nested fields? This also implies that the parent object's serializer contains other object's serializer, which is also what we mean by "nested serializer".
By looking at several examples, like on SO, or this post, there seems no way DRF is doing update for us. The latter post even states:
Nested serializers by default don't support create and update
So we can probably assume that nested serializer supports GET, where it will return the full parent object with all children, relational field object embedded. But, when it's a PATCH/PUT that involves update, the parent serializer does not update them out of the box (not sure, however).
The dominant way people is doing is, pop out the validated client data, use it, and update the nested fields manually. An important part like this post mentioned is to pop out the nested field data, so that DRF does not process on them while we still want to reuse DRF for the rest of the updates (like other non-relational fields on parent object)
Refer & credit to this SO answer for clarifying the control flow.
The viewset calls dispatch
, which in turn checks if there's corresponding method on the viewset class. It all works in lower case. e.g., GET method request will find a def get()
, which by default is already there. Same for POST.
However for other methods, like PATCH
, etc, you'll have to add the method def patch()
yourself on the viewset class. Otherwise you'll get 405 method not allowed.
- HTTP POST method -> calls
create()
- HTTP PUT method -> calls
update()
- HTTP PATCH method -> you can call
partial_update()
-> in turns callupdate(partial=True)
DRF does not implement update behavior for ListSerializer
, so below only applies to not many
serializer case. However, for each steps below, we include what you should do if you want to implement and customize update()
behavior for ListSerializer
. You'll overwrite def update()
on your serializer class, and perhaps checking if request.data
is a list, then run your custom update logic.
- Gets instance (
QuerySet
) in db, it simply callsself.get_object()
.- For
ListSerializer
, you can useself.get_queryset().filter(uuid__in=your_uuid_list)
to get the instances.
- For
- Create serializer instance -
serializer(instance=instance, data=request.data, partial=partial)
. Partial is only used when serializer is validating. Django's ORM is already doing partial update if attribute not passed over as kwargs ininstance.update()
.- For
ListSerializer
, doserializer(instance=instances, data=request.data, partial=partial, many=True)
- For
- Run validation - call
serializer.is_valid(raise_exception=True)
, then jump to Step 3 below.- For
ListSerializer
, no change needed
- For
- Ready to commit to database - run
self.perform_update(serializer)
, jump to Step 4 below.- For
ListSerializer
, no change needed
- For
- Response data back to client -
return Response(serializer.data)
- For
ListSerializer
, no change needed
- For
Calling serializer.is_valid()
will call the methods below on the serializer class. To customize, add these methods and customize them on the serializer class.
def validate(self, parent_object) -> parent_object
To setup validation error, simply raise the exception serializers.ValidationError
.
def validate_${field_name}(self, attrs) -> attrs
Only do this if you want to reuse validation logic.
If you come from update operations (PUT/PATCH), will call perform_update()
on viewset class.
If from POST request, will call perform_create()
on viewset class instead.
- (
many=False
) It simply callsserializer.save()
; you can overwrite attributes by passing kwargs e.g.,.save(user=enforced_user_object)
. This in turn calls serializer'screate()
orupdate()
. Then go to Step 5 below.- For
ListSerializer
, kwargs supplied to.save(...)
enforcing attribute overwriting, will be applied to all instances.
- For
If in step 2, we create serializer by instance=...
, then it'll automatically call update()
; otherwise in step 2 if you pass instance=None
or don't pass it, it'll automatically call create()
here.
- Loops through
validated_data
items, assign data ontoinstance
attributes, callinstance.save()
, and returninstance
.- For
ListSerializer
, the 1st arg will be a list of instances, so we better use pluralinstances
for arg name. We'll need to loop over each instance and update their attributes. Simply do so by callingupdated_instance = self.child.update(instance, validated_data)
, and returnupdated_instance
at the end. No need to callinstance.save()
on each instance, becauseself.child
is the singular (many=False
) Serializer, and by defaultdef update()
on singular Serializer is already callinginstance.save()
. - If you want to use
bulk_update(updated_instance, writable_fields)
which requiresDjango>2.2
, you want to avoid callinginstance.save()
on each instance. Hence, either don't callself.child.update(...)
and set attributes to instances yourself, or callself.child.update(...)
but overwritedef update()
on singular serializer class such that it does not callinstance.save()
when data is a list. Then you can pass overupdated_instance
tobulk_update()
. Take a look at this Medium post as an example
- For