Matt
Matt

Reputation: 2350

How do I get an Django ViewSet to return a 403 error on anonymous post

I'm trying to get my app to return a 403 error when an anonymous user attempts to POST to it. Right now it returns a 201 code, but doesn't save the post to the database.

The problem is that my unit test fails because it's checking for a 403 code.

Here is my views

from post.models import Post
from post.serializers import PostSerializer
from post.permissions import IsOwnerOrReadOnly, IsOwnerOrAdmin
from rest_framework import viewsets, status
from rest_framework.response import Response

class PostViewSet(viewsets.ModelViewSet):
    """
    This viewset automatically provides `list`, `create`, `retrieve`,
    `update` and `destroy` actions.
    """
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    # The default will be that anyone can read a post, but only owners can change it
    permission_classes = (IsOwnerOrReadOnly,)

    def get_permissions(self):
        # Both owners and admins can destroy a post, so if we're destroying we change permissions
        if self.action in ('destroy',):
            self.permission_classes = [IsOwnerOrAdmin, ]
        return super(self.__class__, self).get_permissions()

    def perform_create(self, serializer):
        if self.request.user.is_authenticated:
            serializer.save(author=self.request.user)
        else:
            return Response('Cannot post anonymously', status=status.HTTP_403_FORBIDDEN)

You can see I'm checking if the user is authenticated and if not, return a Response with a 403 code, but for some reason a 201 code is being returned.

How do I get it to return a 403 code?

Upvotes: 3

Views: 2696

Answers (2)

user15364819
user15364819

Reputation: 61

Instead of using return, use raise.

raise PermissionDenied

Upvotes: 1

xyres
xyres

Reputation: 21779

You are trying to send a response from perform_create, but this can't be done. You see, DRF (Django REST Framework) doesn't call the perform_create method in a straight-forward manner. What happens is that DRF first calls CreateModelMixin's create method. Which then calls the perform_create method and then returns the response.

In, short, the response is returned by the create method, not perform_create method. And the create method, by default, returns the 201 status code (or sometimes 400).

So, you will need to override the create method instead. First, take a look at the source code for this method. Now, override:

from rest_framework.exceptions import PermissionDenied

def create(self, request, *args, **kwargs):
    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)

    if request.user.is_authenticated:
        self.perform_create(serializer)
    else:
        raise PermissionDenied('Cannot post anonymously')

    headers = self.get_success_headers(serializer.data)
    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Upvotes: 5

Related Questions