Django REST framework: method PUT not allowed in ViewSet with def update()

PUT needs id in URL by default

Sometimes there is the difference between POST and PUT, because PUT needs id in URL That's why you get the error: "PUT is not Allowed".

Example:

  • POST: /api/users/
  • PUT: /api/users/1/

Hope it'll save a lot of time for somebody


Had a similar "Method PUT not allowed" issue with this code, because 'id' was missing in the request:

class ProfileStep2Serializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ('middle_initial', 'mobile_phone', 'address', 'apt_unit_num', 'city', 'state', 'zip')

class Step2ViewSet(viewsets.ModelViewSet):
    serializer_class = ProfileStep2Serializer

    def get_queryset(self):
        return Profile.objects.filter(pk=self.request.user.profile.id)

Turned out that i have missed 'id' in the serializer fields, so PUT request was NOT able to provide an id for the record. The fixed version of the serializer is below:

class ProfileStep2Serializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ('id', 'middle_initial', 'mobile_phone', 'address', 'apt_unit_num', 'city', 'state', 'zip')

This is because the APIView has no handler defined for .put() method so the incoming request could not be mapped to a handler method on the view, thereby raising an exception.

(Note: viewsets.ViewSet inherit from ViewSetMixin and APIView)

The dispatch() method in the APIView checks if a method handler is defined for the request method.If the dispatch() method finds a handler for the request method, it returns the appropriate response. Otherwise, it raises an exception MethodNotAllowed.

As per the source code of dispatch() method in the APIView class:

def dispatch(self, request, *args, **kwargs):       
        ...
        ...    
        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                 # here handler is fetched for the request method
                 # `http_method_not_allowed` handler is assigned if no handler was found
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed 

            response = handler(request, *args, **kwargs) # handler is called here

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

Since .put() method handler is not defined in your view, DRF calls the fallback handler .http_method_not_allowed. This raises an MethodNotAllowed exception.

The source code for .http_method_not_allowed() is:

def http_method_not_allowed(self, request, *args, **kwargs):
    """
    If `request.method` does not correspond to a handler method,
    determine what kind of exception to raise.
    """
    raise exceptions.MethodNotAllowed(request.method) # raise an exception 

Why it worked when you defined .put() in your view?

When you defined def put(self, request): in your view, DRF could map the incoming request method to a handler method on the view. This led to appropriate response being returned without an exception being raised.