rammanoj
rammanoj

Reputation: 497

django rest framework list update api view

I am trying to add update options to the list items. So that if anyone perform 'PATCH' request to it I will get the details and update them. This is my code for the implementation

class SwitchListView(UpdateModelMixin, ListAPIView):
  serializer_class = serializers.SwitchSerializer
  lookup_field = 'home_id'

  def get_queryset(self):
    home_id = self.kwargs.get('home_id', None)
    if home_id is None or int(home_id) < 0 or \
            self.request.user.pk != models.Home.objects.filter(pk=home_id)[0].user.pk:
        return models.Switch.objects.none()
    query = models.Switch.objects.filter(home=models.Home.objects.filter(pk=home_id))
    return query

  def get(self, request, *args, **kwargs):
    return super(SwitchListView, self).get(request, *args, **kwargs)

  def partial_update(self, request, *args, **kwargs):
    print("Came here")
    data = request.data['data']
    for i in data:
        query = self.get_queryset().filter(i['pk'])
        if query.exists():
            query.switch_status = i['switch_status']
            query.save()

    return Response({'message': 'successfully updated switch!'})

But here the request to the api is only accepting GET, HEAD and OPTIONS. I even tried adding http_method_names = ('get', 'patch') but even this is not working!!

Is there any way to put the patch request to the view ?

Thanks

Upvotes: 4

Views: 7073

Answers (3)

frnhr
frnhr

Reputation: 12903

Here is my implementation, FWIW:

from rest_framework import viewsets
from rest_framework.response import Response

class MyListView(viewsets.mixins.ListModelMixin, viewsets.GenericViewSet):
    serializer_class = ...
    queryset = MyModel.objects.all()

    def list_update(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        instances = list(queryset)
        count = len(instances)
        # TODO perhaps validate for max allowed count, before doing list(queryset) !
        data = [request.data] * count
        serializer = self.get_serializer(
            instances, data, many=True, partial=True)
        serializer.is_valid(raise_exception=True)
        self.perform_list_update(serializer)
        return Response(status=204)

    def perform_list_update(self, serializer):
        for instance, data in zip(
                serializer.instance, serializer.validated_data):
            for attr, value in data.items():
                setattr(instance, attr, value)
            instance.save()
        # alternatively use a queryset.update, but be aware that it will not
        # fire pre_save and post_save signals

If you're not using DRF routers (because when doing anything "custom" like this, using only naked views is usually much more pain-free), edit urls.py like so:

urlpatterns = [
    ...
    re_path(
        r'^path/to/mymodels',
        MyListView.as_view({
            'get': 'list', 
            'patch': 'list_update',  # <--
        }),
    ),
]

If using routers, this little hack works and is simple, but not terribly nice:

router = routers.DefaultRouter(trailing_slash=False)
router.routes[0].mapping['patch'] = 'list_update'  # <--
...

It might also makes sense to override get_serializer_class on the viewset to have a different serializer for list_update action.

Upvotes: 4

notanumber
notanumber

Reputation: 6529

I based my version on frnhr's but didn't want to use GenericViewSet. I also tried to make it conform a bit more to the existing generic views, using put and patch so I wouldn't need to mess with routers.

class FooBulkUpdateAPIView(ListAPIView):
    queryset = Foo.objects.all()
    serializer_class = FooBulkUpdateSerializer

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instances = self.filter_queryset(self.get_queryset())
        data = [request.data] * instances.count()
        serializer = self.get_serializer(instances, data, many=True, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        return Response(status=204)

    def perform_update(self, serializer):
        for instance, data in zip(serializer.instance, serializer.validated_data):
            for attr, value in data.items():
                setattr(instance, attr, value)
            instance.save()

Upvotes: 0

NerV
NerV

Reputation: 51

from rest_framework.decorators import detail_route

...

@detail_route(methods=['put', 'patch'])
def partial_update(self, request, *args, **kwargs):
    ...

try it?

Upvotes: -2

Related Questions