从 Django REST 框架教程实现自定义权限

Implementing the custom permissions from the Django REST framework tutorial

我正在关注 Django REST framework tutorial (part 4),但我观察到 API 的行为与教程中的预期行为之间存在差异。这是我的项目级别 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'))
]

这是随附的 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)

我创建了一个具有自定义权限的 permissions.py 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

并且我已将此 class 添加到 SnippetDetail 中的 permission_classes views.py 中的 class:

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,)

但是,当我发出 POST 请求时仅指定我的凭据(使用 Httpie),我收到 400 错误请求:

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."
    ]
}

显然,我需要指定 owner="1" 才能让它工作:

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": ""
}

但是,据我了解IsOwnerOrReadOnly权限class的has_object_permission方法的逻辑,所有者应该是从request.user推断出来的。这也不是本教程中给出示例的方式。谁能看出这里出了什么问题?

这与您的序列化程序的方式有关defined/used,这不是权限问题。字段 'owner' 需要在序列化程序中设置为只读或不需要。

POST 请求应该转到您的 SnippetList 视图,而不是 SnippetDetail,因此不使用自定义权限 class。

虽然我不知道你为什么会收到这个错误。 According to the tutorial,您应该将 owner 设置为 SnippetSerializer 中的只读字段。

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