发布日期 2019/May/10
最后更新日期:? 2019/May/10
当前,我们的 API 在“谁”可以编辑或删除 snippets 上没有任何限制。我们希望有一些更高级的行为来确保:
- Code snippets are always associated with a creator.
- 仅有验证过的用户可以创建 snippets。
- 仅有该 snippet 的创建者可以更新或者删除它。
- 未认证过的/匿名(用户)请求应当有完全的 read-only 访问权限。
Overivew
添加必要信息段到我们的模型中
Adding information to our model
我们将会在 Snippet
模型上做几处修改。
首先,我们添加一对 fields。其中一处的 fields 将会用于表示创建了 code snippet
的用户。另外的 field 将会被用户储存 the highlighted HTML representation of the code.
在 models.py
中添加以下两处 fields 到 Snippet 模型
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
我们还需要确保在保存模型时,使用pygments代码突出显示库填充突出显示的字段。
原:We’d also need to make sure that when the model is saved, that we populate the highlighted field, using the pygments code highlighting library.
我们需要导入一些其它的包:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
现在我们可以重载 .save()
方法在 model class 中:
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
完成所有操作后,我们需要更新数据库表。通常我们会创建一个数据库迁移来执行此操作,但是出于本教程的目的,我们只需删除数据库并重新开始。
原: When that’s all done we’ll need to update our database tables. Normally we’d create a database migration in order to do that, but for the purposes of this tutorial, let’s just delete the database and start again.
$ rm -f db.sqlite3
$ rm -r snippets/migrations
$ python manage.py makemigrations snippets
$ python manage.py migrate
您可能还想创建一些不同的用户,以用于测试API。最快的方法是使用createsuperuser
命令。
原:You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with the
createsuperuser
command.
$ python manage.py createsuperuser
为我们的 User 模型添加 endpoints
Adding endpoints for our User models
既然我们已经有一些用户可以使用,我们最好将这些用户的表示添加到我们的API中。创建新的序列化器很简单。在serializers.py
中添加:
原: Now that we’ve got some users to work with, we’d better add representations of those users to our API. Creating a new serializer is easy. In
serializers.py
add:
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类时,默认情况下不会包含它,因此我们需要为它添加一个显式字段。
原:Because ‘snippets’ is a reverse relationship on the User model, it will not be included by default when using the ModelSerializer class, so we needed to add an explicit field for it.
我们还将向views.py添加一些视图。我们只想对用户表示使用只读视图,所以我们将使用ListapiView和RetrieveapiView基于类的通用视图。
原:We’ll also add a couple of views to views.py. We’d like to just use read-only views for the user representations, so we’ll use the ListAPIView and RetrieveAPIView generic class-based views.
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
最后,我们需要将这些视图添加到API中,方法是从URL conf中引用它们。将以下内容添加到 snippets/urls.py
中的模式。
原:Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in
snippets/urls.py
.
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
将 Snippets 关联到 Users
Associating Snippets with Users
现在,如果我们创建了代码段,则无法将创建代码段的用户与代码段实例相关联。用户不是作为序列化表示的一部分发送的,而是传入请求的属性。
原:Right now, if we created a code snippet, there’d be no way of associating the user that created the snippet, with the snippet instance. The user isn’t sent as part of the serialized representation, but is instead a property of the incoming request.
我们处理的方法是在我们的代码段视图上覆盖.perform_create()方法,这允许我们修改实例保存的管理方式,并处理传入请求或请求的URL中隐含的任何信息。
原: The way we deal with that is by overriding a .perform_create() method on our snippet views, that allows us to modify how the instance save is managed, and handle any information that is implicit in the incoming request or requested URL.
在 SnippetList
view class,添加如下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
现在,我们的序列化程序的 create()
方法将传递一个额外的“所有者”字段,以及来自请求的验证数据。
原:The create() method of our serializer will now be passed an additional ‘owner’ field, along with the validated data from the request.
更新我们的序列器(serializer)
Updating our serializer
既然片段与创建它们的用户相关联,那么让我们更新我们的SnippetSerializer以反映它。将以下字段添加到serializers.py中的序列化程序定义:
原: Now that snippets are associated with the user that created them, let’s update our SnippetSerializer to reflect that. Add the following field to the serializer definition in serializers.py:
owner = serializers.ReadOnlyField(source='owner.username')
Note: Make sure you also add ‘owner’, to the list of fields in the inner Meta
class.
This field is doing something quite interesting. The source argument controls which attribute is used to populate a field, and can point at any attribute on the serialized instance. It can also take the dotted notation shown above, in which case it will traverse the given attributes, in a similar way as it is used with Django’s template language.
The field we’ve added is the untyped ReadOnlyField class, in contrast to the other typed fields, such as CharField, BooleanField etc… The untyped ReadOnlyField is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized. We could have also used CharField(read_only=True) here.
添加权限请求到 views 中
Adding required permissions to views
Now that code snippets are associated with users, we want to make sure that only authenticated users are able to create, update and delete code snippets.
REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we’re looking for is IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access.
First add the following import in the views module
from rest_framework import permissions
Then, add the following property to both the SnippetList and SnippetDetail view classes.
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
添加 login 功能到 Browsable API
Adding login to the Browsable API
If you open a browser and navigate to the browsable API at the moment, you’ll find that you’re no longer able to create new code snippets. In order to do so we’d need to be able to login as a user.
We can add a login view for use with the browsable API, by editing the URLconf in our project-level urls.py file.
Add the following import at the top of the file:
from django.conf.urls import include
And, at the end of the file, add a pattern to include the login and logout views for the browsable API.
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]
The ‘api-auth/’ part of pattern can actually be whatever URL you want to use.
Now if you open up the browser again and refresh the page you’ll see a ‘Login’ link in the top right of the page. If you log in as one of the users you created earlier, you’ll be able to create code snippets again.
Once you’ve created a few code snippets, navigate to the ‘/users/’ endpoint, and notice that the representation includes a list of the snippet ids that are associated with each user, in each user’s ‘snippets’ field.
Object 级别的权限
Object level permissions
Really we’d like all code snippets to be visible to anyone, but also make sure that only the user that created a code snippet is able to update or delete it.
To do that we’re going to need to create a custom permission.
In the snippets app, create a new file, permissions.py
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
现在,我们可以通过编辑SnippetDetail视图类上的permission_classes属性,将该自定义权限添加到我们的代码段实例端点:
原:Now we can add that custom permission to our snippet instance endpoint, by editing the permission_classes property on the SnippetDetail view class:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
Make sure to also import the IsOwnerOrReadOnly class.
from snippets.permissions import IsOwnerOrReadOnly
现在,如果再次打开浏览器,如果您以创建代码段的同一用户身份登录,则会发现“DELETE”和“PUT”操作仅显示在代码段实例端点上。
原: Now, if you open a browser again, you find that the ‘DELETE’ and ‘PUT’ actions only appear on a snippet instance endpoint if you’re logged in as the same user that created the code snippet.
使用 API 通过认证
Authenticating with the API
Because we now have a set of permissions on the API, we need to authenticate our requests to it if we want to edit any snippets. We haven’t set up any authentication classes, so the defaults are currently applied, which are SessionAuthentication and BasicAuthentication.
当我们通过Web浏览器与API交互时,我们可以登录,然后浏览器会话将为请求提供所需的身份验证。
原: When we interact with the API through the web browser, we can login, and the browser session will then provide the required authentication for the requests.
如果我们以编程方式与API交互,我们需要在每个请求上明确提供身份验证凭据。
原:If we’re interacting with the API programmatically we need to explicitly provide the authentication credentials on each request.
如果我们尝试在不进行身份验证的情况下创建代码段,则会收到错误消息:
原:If we try to create a snippet without authenticating, we’ll get an error:
http POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"detail": "Authentication credentials were not provided."
}
我们可以通过包含我们之前创建的用户之一的用户名和密码来成功提出请求。
原:We can make a successful request by including the username and password of one of the users we created earlier.
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print(789)",
"linenos": false,
"language": "python",
"style": "friendly"
}
Summary
We’ve now got a fairly fine-grained set of permissions on our Web API, and end points for users of the system and for the code snippets that they have created.
In part 5 of the tutorial we’ll look at how we can tie everything together by creating an HTML endpoint for our highlighted snippets, and improve the cohesion of our API by using hyperlinking for the relationships within the system.