James Lin
James Lin

Reputation: 26538

Django Rest Framework method in action decorator needs be specified in class http_method_names

DRF version is 3.11.2

I was working on some existing code to add an extra "delete" action to a viewset, but I got 405 Method not allowed, consider the following made up example

class UserViewSet(ModelViewSet):
    ....
    queryset = User.objects.all()
    http_method_names = ['get', 'post']

    @action(detail=True, method=['delete'])
    def delete_profile(self, request, pk=None)
        Profile.objects.get(user=pk).delete()
        ....

Like I said, when I make a delete method call to /user/123/delete_profile, I get 405 Method not allowed, however, if I add delete to http_method_names as below, it works fine.

class UserViewSet(ViewSet):
    ....
    queryset = User.objects.all()
    http_method_names = ['get', 'post', 'delete']

    @action(detail=True, method=['delete'])
    def delete_profile(self, request, pk=None)
        Profile.objects.get(user=pk).delete()
        ....

But this is not ideal as I don't want to allow "delete" operation on the User, whilst I can overwrite the delete/destroy method on the viewset to avoid user being deleted, this is not very elegant.

So the question is, is this a bug or by design? By design I mean the extra actions decorated by @action suppose to be manipulating the same model defined in the ModelViewSet (in this case User and not Profile)?

Upvotes: 2

Views: 3024

Answers (1)

LiquidDeath
LiquidDeath

Reputation: 1828

The issue is with setting the http_method_names attribute to the ModelViewSet. since you have set it to only get and post , other methods ( put, patch, delete) will not work on the class. either you have to add the delete method to http_method_names attribute or remove the whole attribute to fix this as shown below.

class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    queryset = User.objects.all()

    @action(detail=True, methods=['delete'])
    def delete_profile(self, request, pk=None):
        Profile.objects.get(user=pk).delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Also if you dont want other methods than get,post,delete to work, why not use mixins ?

Update : if you dont want the delete method to work on the main viewset, just override it as shown.

 class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    queryset = User.objects.all() 

    @action(detail=True, methods=['delete'])
    def delete_profile(self, request, pk=None):
         ....

    def destroy(self, request, *args, **kwargs):
        return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

Upvotes: 2

Related Questions