Say, I'm building a REST API based on Django/DRF and PostgreSQL. I want all GET
, PUT
, DELETE
endpoints to follow the best practices and be idempotent. How can I ensure/verify that this is true?

- 14,248
- 20
- 86
- 160
-
How Django/DRF is involved in it? Take a look at https://tools.ietf.org/html/rfc7231#section-8.1.3 – Tigran May 06 '20 at 13:55
-
I mentioned django/drf in case there's any functionality there that deals with idempotency. – planetp May 07 '20 at 14:10
-
Not in core. You can implement X-Idempotency-Key header and Middleware for it. – Tigran May 07 '20 at 14:14
-
There is a library for this, but not sure why it's incompatible with Django 3.x.x. I've asked the author to provide information so I can do a PR. https://github.com/yoyowallet/django-idempotency-key – Kevin Parker Mar 26 '21 at 17:57
2 Answers
I know it is too late to answer this question, but I would like to answer it for those others who might be interested.
By default, GET
and DELETE
are idempotent since the only possible response states are either 404
or sending data, but not PUT
or if we needed to make any POST request idempotent. for PUT
the most common way is the validation of input data. Make sure that all the fields of the model(including id
) are passed in unless the client receives 400 bad request
this way since even id is passed in no one can add unwanted records to the database. another way to be 100% sure everything is idempotent, is we may use cache to store the request body and user's id as hash into cache server for enough amount of time. something like this:
# in views.py
# for DRF
import hashlib
from django.core.cache import cache
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class IdempotentView(APIView):
def put(self, req, pk, format=None):
key = hashlib.blake2b(
f"{req.user.username}, {req.body},{pk}".encode("utf-8")
).hexdigest()
is_cached = cache.get(key)
if is_cached :
return Response (req.body, status=status.HTTP_201_CREATED)
# preform the view commands then:
expiary = 60*60*3 # 3 hours
cache.set(key, 'True' , exp_date)
return Response (data, status=status.HTTP_201_CREATED)

- 464
- 5
- 11
-
1I would suggest using something like `hashlib.blake2b` rather than relying on `hash`, for the reduced chance of collisions. – suayip uzulmez Jan 12 '23 at 11:26
I believe the following post can shed a light on the topic: Create or Update (with PUT) in Django Rest Framework
Quote:
class ResourceViewSet(viewsets.ModelViewSet):
"""
This endpoint provides `create`, `retrieve`, `update` and `destroy` actions.
"""
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
def get_object(self):
if self.request.method == 'PUT':
resource = Resource.objects.filter(id=self.kwargs.get('pk')).first()
if resource:
return resource
else:
return Resource(id=self.kwargs.get('pk'))
else:
return super(ResourceViewSet, self).get_object()
but more elegant solution which also checks for permissions - is https://gist.github.com/tomchristie/a2ace4577eff2c603b1b

- 13
- 6