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?
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?
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
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 = hash(f"{req.user.username}, {req.body},{pk}")
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)
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