Serhat Teker
Posted on November 23, 2020
Background
There are 2 HTTP Request Methods
to update the data:
PUT
The PUT method requests that the enclosed entity be stored under the supplied
URI. If the URI refers to an already existing resource, it is modified; if the
URI does not point to an existing resource, then the server can create the
resource with that URI.PATCH
The PATCH method applies partial modifications to a resource.
As we can see PUT
updates the every resource of entire data, whereas PATCH
updates the partial of data.
In other words: We can say PUT
replace where PATCH
modify.
So in this article we are going to look for PATCH
method.
If you want more information about HTTP you can look at: Hypertext Transfer Protocol - WIKI.
Entrée
Let's assume you have a simple ModelViewSet
for your Django Rest Framework, and you want to allow users to update partial data field(s) of this model. How can you achieve that?
# views.py
from rest_framework import permissions, viewsets
class TodoViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
After some lookup for the doc and core of DRF I found that ModelViewSet
inherits some Mixins
parent classes and you have tooverride default partial kwargs
in UpdateModelMixin
.
# viewsets.py
# https://github.com/encode/django-rest-framework/blob/91916a4db1/rest_framework/viewsets.py
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
You can see it below on line 9.
# https://github.com/encode/django-rest-framework/blob/91916a4db14cd6a06aca13fb9a46fc667f6c0682/rest_framework/mixins.py#L64
class UpdateModelMixin:
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
Solution
So you simply override update
function like below:
# views.py
from rest_framework import permissions, viewsets
class TodoViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
def update(self, request, *args, **kwargs):
kwargs['partial'] = True
return super().update(request, *args, **kwargs)
Clean Solution
In order to be clean side of programming it would be better to separate it and use it as Mixin
for maintenance and testing:
# utils.py
class EnablePartialUpdateMixin:
"""Enable partial updates
Override partial kwargs in UpdateModelMixin class
https://github.com/encode/django-rest-framework/blob/91916a4db14cd6a06aca13fb9a46fc667f6c0682/rest_framework/mixins.py#L64
"""
def update(self, request, *args, **kwargs):
kwargs['partial'] = True
return super().update(request, *args, **kwargs)
Use it like below:
# views.py
from rest_framework import permissions, viewsets
from .utils import EnablePartialUpdateMixin
class TodoViewSet(EnablePartialUpdateMixin, viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
Appendix
You can find model
and serializer
class of the TodoViewSet below.
Also you can look up for full code detail in this repo: YADRTA
# models.py
class Task(BaseModelMixin):
STATES = (
("todo", "Todo"),
("wip", "Work in Progress"),
("suspended", "Suspended"),
("waiting", "Waiting"),
("done", "Done"),
)
title = models.CharField(max_length=255, blank=False, unique=True)
description = models.TextField()
status = models.CharField(max_length=15, choices=STATES, default="todo")
tag = models.ForeignKey(to=Tag, on_delete=models.DO_NOTHING)
category = models.ForeignKey(to=Category, on_delete=models.DO_NOTHING)
class Meta:
ordering = ("title",)
def __str__(self):
return f"{self.created_by}:{self.title}"
# serializers.py
from rest_framework import serializers
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = base_model_mixin_fields + [
"title",
"description",
"status",
"tag",
"category",
]
All done!
Changelog
- 2020-10-26 : Added Background subsection
Posted on November 23, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.