-
-
Save tomchristie/a2ace4577eff2c603b1b to your computer and use it in GitHub Desktop.
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 | |
from django.http import Http404
from rest_framework.response import Response
from rest_framework.request import clone_request
from rest_framework import status
If you want to support overridable hooks perform_create and/or perform_update similar to what are available in CreateModelMixin and UpdateModelMixin ( documented in http://www.django-rest-framework.org/api-guide/generic-views/ ) you could modify the update function above as follows:
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}
self.perform_create(serializer, **extra_kwargs)
return Response(serializer.data, status=status.HTTP_201_CREATED)
self.perform_udpate(serializer)
return Response(serializer.data)
def perform_create(self, serializer, **kwargs):
serializer.save(**kwargs)
def perform_udpate(self, serializer):
serializer.save()
The mixin works fine, but how can i make optional the pk field of my object? is it possible?
I have problem with this fixture - I am getting duplicate errors if the request is called more than once quickly. Does anyone had the same problem? I tried @transaction.atomic
, but without success.
with support for perform_create and perform_update hooks (without changing signature)
and multiple-field mixins.
class PutAsCreateMixin(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:
self.perform_create(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
self.perform_update(serializer)
return Response(serializer.data)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def perform_create(self, serializer):
if not hasattr(self, 'lookup_fields'):
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}
else:
# set kwargs for additional fields
extra_kwargs = {
field: self.kwargs[field]
for field in self.lookup_fields if self.kwargs[field]
}
serializer.save(**extra_kwargs)
def perform_update(self, serializer):
serializer.save()
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
this way, additional attributes can also be added during creation.
Are there any plans to have something like this included in DRF by default?
Nope.
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
PATCH
is also allowed to create. From http://tools.ietf.org/html/rfc5789: