ExTexan
ExTexan

Reputation: 453

Is there a get_model equivalent for serializers?

I've read the docs on serializers and Googled (a lot), but haven't found the exact answer I'm looking for.

I'm trying to write a generic "archive" process (class) to handle changing a status field to "archived" for any model instance based on what's passed in the URL.

For example:

example.com/archive/clients.client/123

That would change the status to "archived" for ID 123 of the Client model.

Here's my URLconf:

path('archive/<model>/<int:pk>/', Archive.as_view()),

And here's my view class. I had first got this working for a specific model, so I've copied that code and am trying to adapt it to be more generic. Also, I know this needs more bulletproofing, but I'm trying to show the simplest version of my code.

class Archive(RetrieveAPIView):

    def retrieve(self, request, model=None, pk=None, *args, **kwargs):
        app, model_name = model.split('.')

        get_model = apps.get_model
        model = get_model(app, model_name)
        self.queryset = model.objects.all()

        instance = self.get_object(pk=pk)

        if instance.status == 'archived':
            return APIMessage('That item has already been archived.', message_code='already_archived')

        setattr(instance, 'status', 'archived')
        instance.save()

        serializer = self.get_serializer(instance)
        return Response(serializer.data)

When I try to run this as is, I get:

'Archive' should either include a `serializer_class` attribute, or override the `get_serializer_class()` method.

And that was pretty much what I expected, but I don't have anything I could actually set serializer_class to from the outset, and if I set it to None, I get the same error. Of course, I already knew about get_serializer_class, but that's where I would need something like get_model, except that it would return the serializer class based on the model I've determined from the URL.

Upvotes: 2

Views: 479

Answers (1)

wencakisa
wencakisa

Reputation: 5958

Since you are updating your resource, you should use a view that supports PUT/PATCH requests, which are exactly for updating properties by design (in HTTP).

RetrieveAPIView is:

Used for read-only endpoints to represent a single model instance

In your specific case, you should use the most generic APIView from DRF, since it does not require defining serializer_class and other properties that you wouldn't use.

from rest_framework.views import APIView


class Archive(APIView):
    def put(self, request, model=None, pk=None, *args, **kwargs):
        app, model_name = model.split('.')

        get_model = apps.get_model
        model = get_model(app, model_name)
        self.queryset = model.objects.all()

        instance = self.get_object(pk=pk)

        if instance.status == 'archived':
            return APIMessage('That item has already been archived.', message_code='already_archived')

        setattr(instance, 'status', 'archived')
        instance.save()

        return Response({'success': True})

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

This should do the work. I've also mapped the PATCH method to perform the PUT logic, which means you can use both of these HTTP methods to update your model instance.

Now your view is as generic as possible with the help of APIView.

Upvotes: 3

Related Questions