Reputation: 57601
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
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
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