Skip to content

Instantly share code, notes, and snippets.

@tomchristie
Created November 3, 2014 11:55
  • Star 34 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save tomchristie/a2ace4577eff2c603b1b to your computer and use it in GitHub Desktop.
PUT-as-create mixin class for Django REST framework.
class AllowPUTAsCreateMixin(object):
"""
The following mixin class may be used in order to support PUT-as-create
behavior for incoming requests.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object_or_none()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
if instance is None:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup_value = self.kwargs[lookup_url_kwarg]
extra_kwargs = {self.lookup_field: lookup_value}
serializer.save(**extra_kwargs)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.save()
return Response(serializer.data)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def get_object_or_none(self):
try:
return self.get_object()
except Http404:
if self.request.method == 'PUT':
# For PUT-as-create operation, we need to ensure that we have
# relevant permissions, as if this was a POST request. This
# will either raise a PermissionDenied exception, or simply
# return None.
self.check_permissions(clone_request(self.request, 'POST'))
else:
# PATCH requests where the object does not exist should still
# return a 404 response.
raise
@libcthorne
Copy link

Are there any plans to have something like this included in DRF by default?

@tomchristie
Copy link
Author

Nope.

@SydneyUni-Jim
Copy link

This seems to work for me with Django 4.2 and Python 3.11. Although I haven't tested it with permissions.

from django.http
import rest_framework.mixins

class AllowPUTAsCreateMixin(rest_framework.mixins.CreateModelMixin, rest_framework.mixins.UpdateModelMixin):

    def put(self, request, *args, **kwargs):
        try:
            return self.update(request, *args, **kwargs)
        except Http404:
            pass
        return self.create(request, *args, **kwargs)

With this I can create a view that allows retrieving via GET and upsert via PUT.

import rest_framework.generics
import rest_framework.serializers

class Serializer(rest_framework.serializers.ModelSerializer):
    ...  # TODO: As normal

class RetrieveCreatePatch(AllowPUTAsCreateMixin, rest_framework.generics.RetrieveAPIView):
    queryset = MyModel.objects.all()
    serializer_class = Serializer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment