manabreak
manabreak

Reputation: 5597

How to update a single column on a single row?

I'm trying to write a simple update view for my Django REST application, but I cannot figure out how to do it. I've gotten a range of errors from 403 to 500.

Basically, I have a table 'Foo' and I want to set the 'active' field from 'True' to 'False'. Here's the view I have currently:

class DisableFoo(generics.UpdateAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = FooSerializer
    queryset = Foo.objects.all()

    def perform_update(self, serializer):
        queryset = Foo.objects.filter(pk = self.request.data['id'])
        queryset.update(active = False)

However, this results in AssertionError:

Expected view DisableFoo to be called with a URL keyword argument named "pk".
Fix your URL conf, or set the '.lookup_field' attribute on the view correctly.

Upvotes: 3

Views: 1266

Answers (1)

Todor
Todor

Reputation: 16010

You should not send the id of the row you want to update with request.data but within the url.

So if you are hitting right now something like:

/api/foo/

try with

/api/foo/<id>/

of course this won't be enough. You should also consider a few more things.

You are hooking on perform_update method. But that's not likely to be correct. perform_update is called in order to update the whole object with serializer.save() and therefore it get called only when serializer.is_valid().

This means that you will have to send a valid Foo object. But that's not what you want. You just need to update a single field of the Foo object. So the right approach here is to use partial_update. partial_update will be used when you make a PATCH request to /api/foo/<id>/

So if you send a PATCH request to /api/foo/<id>/ with active=0 inside request.data DRF will update the object automatically without any further code changes. Just using

class DisableFoo(generics.UpdateAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = FooSerializer
    queryset = Foo.objects.all()

But this will eventually expose all your model fields to update. So you can override the partial_update method like so:

def partial_update(self, request, *args, **kwargs):
    instance = self.get_object()
    instance.active = False
    instance.save(update_fields=['active'])
    return Response()

Another approach

DRF support creation of extra extra-actions via @detail_route and @list_route decorators.

You can use the @detail_route to create a custom disable action. Consider the following peace of code:

class FooViewSet(viewsets.GenericViewSet):
    queryset = Foo.objects.all()
    serializer_class = FooSerializer

    @detail_route(methods=['post'])
    def disable(self, request, pk=None):
        instance = self.get_object()
        instance.active = False
        instance.save(update_fields=['active'])
        return Response('done')

Making a POST request to /api/foo/<id>/disable will call the disable method we just wrote and disable the foo instance under <id>.

This way you can escape the requirement to use PATCH request method.

Upvotes: 1

Related Questions