Kurt Peek
Kurt Peek

Reputation: 57601

Implementing the custom permissions from the Django REST framework tutorial

I'm following the Django REST framework tutorial (part 4) but I'm observing a discrepancy between the behavior of my API and what is expected according to the tutorial. Here is my project-level urls.py:

from django.contrib import admin
from django.urls import path, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('users/', views.UserList.as_view()),
    path('users/<int:pk>/', views.UserDetail.as_view()),
    path('', include('snippets.urls'))
]

urlpatterns += [
    path('api-auth/', include('rest_framework.urls'))
]

and here is the included snippets.urls:

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    path('snippets/', views.SnippetList.as_view()),
    path('snippets/<int:pk>/', views.SnippetDetail.as_view())
]

urlpatterns = format_suffix_patterns(urlpatterns)

I've created a permissions.py with a custom permission class IsOwnerOrReadOnly:

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user

and I've added this class to permission_classes in the SnippetDetail class in views.py:

from django.contrib.auth.models import User
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
from rest_framework import generics, permissions
from snippets.permissions import IsOwnerOrReadOnly


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)

However, when I make a POST request specifying only my credentials (using Httpie), I get a 400 Bad Request:

Kurts-MacBook-Pro:~ kurtpeek$ http -a kurtpeek:mypassword POST http://localhost:8000/snippets/ code="print 789"
HTTP/1.1 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 37
Content-Type: application/json
Date: Wed, 24 Jan 2018 18:13:47 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "owner": [
        "This field is required."
    ]
}

Apparently, I need to specify owner="1" to get this to work:

Kurts-MacBook-Pro:~ kurtpeek$ http -a kurtpeek:mypassword POST http://localhost:8000/snippets/ code="print 789" owner="1"
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 103
Content-Type: application/json
Date: Wed, 24 Jan 2018 18:13:35 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "print 789",
    "id": 3,
    "language": "python",
    "linenos": false,
    "owner": 1,
    "style": "friendly",
    "title": ""
}

However, as I understand from the logic of the has_object_permission method of the IsOwnerOrReadOnly permissions class, the owner should be inferred from the request.user. This is also not how the example is given in the tutorial. Can anyone spot what's wrong here?

Upvotes: 0

Views: 1991

Answers (2)

H&#229;ken Lid
H&#229;ken Lid

Reputation: 23064

The POST request should go to your SnippetList view, not to SnippetDetail, so the custom permission class isn't used.

I don't know why you get this error, though. According to the tutorial, you should set owner to be a read-only field in SnippetSerializer.

owner = serializers.ReadOnlyField(source='owner.username')

Upvotes: 1

kevswanberg
kevswanberg

Reputation: 2119

This has to do with how your serializer is defined/used, it isn't a permission issue. The field 'owner' needs to be set as read only or not required in the serializer.

Upvotes: 1

Related Questions