目前,我们的API对谁可以编辑或删除代码段没有任何限制。我们希望有更高级的行为,以确保:
代码片段始终与创建者相关联。
只有通过身份验证的用户可以创建片段。
只有代码片段的创建者可以更新或删除它。
未经身份验证的请求应具有完全只读访问权限。
在我们的模型(model)中添加信息
Snippet
models.pySnippet
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) highlighted = models.TextField()
pygments
我们需要导入额外的模块:
from pygments.lexers import get_lexer_by_name from pygments.formatters.html import HtmlFormatter from pygments import highlight
.save()
def save(self, *args, **kwargs): """ 使用`pygments`库创建一个高亮显示的HTML表示代码段。 """ lexer = get_lexer_by_name(self.language) linenos = self.linenos and 'table' or False options = self.title and {'title': self.title} or {} formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options) self.highlighted = highlight(self.code, lexer, formatter) super(Snippet, self).save(*args, **kwargs)
完成这些工作后,我们需要更新我们的数据库表。 通常这种情况我们会创建一个数据库迁移(migration)来实现这一点,但现在我们只是个教程示例,所以我们选择直接删除数据库并重新开始。
rm -f tmp.db db.sqlite3 rm -r snippets/migrations python manage.py makemigrations snippets python manage.py migrate
createsuperuser
python manage.py createsuperuser
为我们的用户模型添加路径
serializers.py
from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all()) class Meta: model = User fields = ('id', 'username', 'snippets')
'snippets'ModelSerializer
views.pyListAPIViewRetrieveAPIView
from django.contrib.auth.models import User class UserList(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer class UserDetail(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer
UserSerializer
from snippets.serializers import UserSerializer
urls.py
url(r'^users/$', views.UserList.as_view()), url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
将Snippet和用户关联
现在,如果我们创建了一个代码片段,并不能将创建该代码片段的用户与代码段实例相关联。用户不是作为序列化表示的一部分发送的,而是作为传入请求的属性。(译者注:user不在传过来的数据中,而是通过request.user获得)
.perform_create()
SnippetList
def perform_create(self, serializer): serializer.save(owner=self.request.user)
create()'owner'
更新我们的序列化器
SnippetSerializerserializers.pyserializers.py
owner = serializers.ReadOnlyField(source='owner.username')
'owner',Meta
source
ReadOnlyFieldCharFieldBooleanFieldReadOnlyFieldCharField(read_only=True)
添加视图所需的权限
现在,代码片段与用户是相关联的,我们希望确保只有经过身份验证的用户才能创建,更新和删除代码片段。
IsAuthenticatedOrReadOnly
首先要在视图模块中导入以下内容
from rest_framework import permissions
SnippetListSnippetDetail
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
给Browsable API添加登陆
如果你打开浏览器并浏览我们的API,那么你会发现不能创建新的代码片段。只有登陆用户才能创建新的代码片段。
urls.py
在文件顶部添加以下导入:
from django.conf.urls import include
而且,在文件末尾添加一个模式(pattern)以包括可浏览的API的登录和注销视图。
urlpatterns += [ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), ]
r'^api-auth/''rest_framework'
现在,如果你再次打开浏览器并刷新页面,你将在页面右上角看到一个“登录”链接。如果你用早期创建的用户登录,就可以再次创建代码片段。
一旦你创建了一些代码片段后,在'/users/'路径下你会注意到每个用户的'snippets'字段都包含与每个用户相关联的代码片段的列表。
对象级别的权限
我们希望所有的代码片段都可以被任何人看到,但也要确保只有创建代码片段的用户才能更新或删除它。
为此,我们将需要创建一个自定义权限。
permissions.py
from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ 自定义权限只允许对象的所有者编辑它。 """ def has_object_permission(self, request, view, obj): # 读取权限允许任何请求, # 所以我们总是允许GET,HEAD或OPTIONS请求。 if request.method in permissions.SAFE_METHODS: return True # 只有该snippet的所有者才允许写权限。 return obj.owner == request.user
SnippetDetailpermission_classes
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
IsOwnerOrReadOnly
from snippets.permissions import IsOwnerOrReadOnly
现在,如果再次打开浏览器,你会发现如果你以代码片段创建者的身份登录的话,“DELETE”和“PUT”操作才会显示在代码片段实例路径上。
使用API进行身份验证
SessionAuthenticationBasicAuthentication
当我们通过Web浏览器与API进行交互时,我们可以登录,然后浏览器会话将为请求提供所需的身份验证。
如果我们在代码中与API交互,我们需要在每次请求上显式提供身份验证凭据。
如果我们通过没有验证就尝试创建一个代码片段,我们会像下面展示的那样收到报错:
http POST http://127.0.0.1:8000/snippets/ code="print 123" { "detail": "Authentication credentials were not provided." }
我们可以通过加上我们之前创建的一个用户的用户名和密码来成功创建:
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789" { "id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python", "style": "friendly" }
总结
我们现在已经在我们的Web API上获得了相当精细的一组权限控制,并为系统的用户和他们创建的代码片段提供了API路径。