django-rest-framework-tutorial-4

drf框架教程4(djangorestframework)

写在前面

在外包的这段日子,不知如何描述滋味,虽然没有网络可以说限制了工作时间逛网页这种,但是自我感觉收获满足不了自己。是时候重新出发了。

工作当中有用到drf框架,在csdn中找到了还不错的中文翻译文档,受益匪浅。想着自己也做一部分翻译的工作。如有错误之处,请多多指教。

额外说明

目前版本为 3.6.3

马太胖老师的前三篇翻译

框架github地址

文档地址

认证和权限

目前为止,我们的API对谁能够编辑或者删除snippet(代码片段)还没有任何限制。我们将增加一些扩展功能来确保以下:

  • snippets总关联一个创建者
  • 只有认证后的用户才能创建一个snippets
  • 只有创建者才能更新或者删除snippet
  • 非认证的请求只拥有只读的权限

对model做一些修改,增加一个表示创建者,另外增加一个用来存储代码中的HTML高亮。

1
2
3
4
# snippets/models.py
owner = models.ForeignKey('auth.User',
related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()

另外 我们需要确保模型保存的时候,我们能够生成高亮的字段,这里使用pygments代码高亮库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight


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

为用户模型增加endpoints

现在我们有一些用户了,我们最好也把用户增加到API上,创建一个新的序列化脚本serializers.py

1
2
3
4
5
6
7
8
9
# snippets/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和user是反向关联,所以在使用ModelSerializers时不会缺省加入,因此需要显示加入。

我们还需要创建一些对用户呈现而言的views,最好使用只读的view,所以使用ListAPIView和RetrieveAPIView泛型类Views。

1
2
3
4
5
6
7
8
9
10
11
12
# snippets/views.py
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

确保导入了UserSerializers类

1
from snippets.serializers import UserSerializer

修改url

1
2
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),

snippets与users关联

现在,如果我们创建一个code snippet,还没有方法指定其创建者。User并没有作为序列化内容的一部分发送,而是作为request的一个属性。

这里的处理方法是重载snippet view中的.pre_save()方法,它可以让我们处理request中隐式的信息。

在SnippetList和SnippetDetail的view类中,都需要添加如下的方法:

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

更新序列器

现在snippets已经和创建者关联起来了,我们接下来还需要更新SnippetSerializer,在其定义中增加一个新的字段:

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

Note: 确定你在嵌入类Meta的字段列表中也加入了’owner’。

这个字段所做的十分有趣。source参数表明增加一个新字段,可以指定序列化实例任何属性。它可以采用如上的点式表示(dotted notation),这时他可以直接遍历到指定的属性。在Django’s template中使用时,也可以采用类似的方式。

我们增加字段是一个无类型Field类,而我们之前的字段都是有类型的,例如CharField,BooleanFieldetc… 无类型字段总是只读的,它们只用在序列化表示中,而在反序列化时(修改model)不被使用。

给views增加必需的权限

现在代码片段 snippets 已经关联了用户,我们需要确保只有认证用户才能增、删、改snippets.

REST framework 包括许多权限类可用于view的控制。这里我们使用IsAuthenticatedOrReadOnly, 它可确保认证的request获取read-write权限,而非认证的request只有read-only 权限.

现需要在views模块中增加 import。

from rest_framework import permissions

然后需要在SnippetList和SnippetDetailview类中都增加如下属性:

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

可视化API增加登陆

如果你打开浏览器,访问可浏览API,你会发现只有登录后才能创建新的snippet了。

我们可以编辑URLconf来增加一个登录view。首先增加新的import:

from django.conf.urls import include

然后,在文件末尾增加一个pattern来为browsable API增加 login 和 logout views.

urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls',
                           namespace='rest_framework')),

]

具体的,r’^api-auth/‘部分可以用任何你想用的URL来替代。这里唯一的限制就是 urls 必须使用’rest_framework’命名空间。

现在如果你打开浏览器,刷新页面会看到页面右上方的 ‘Login’ 链接。如果你用之前的用户登录后,你就又可以创建 snippets了。

一旦你创建了一些snippets,当导航至’/users/‘时,你会看到在每个user的snippets字段都包含了一系列snippet的pk。

对象级权限

我们希望任何人都可以浏览snippets,但只有创建snippet的用户才能编辑或删除它。

为了实现这个需求,我们需要创建定制的权限(custom permission)。

在 snippets 应用中,创建一个新文件:permissions.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

现在我们可以为snippet实例增加定制权限了,需要编辑SnippetDetail 类的permission_classes属性:

permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                  IsOwnerOrReadOnly,)

别忘了import 这个IsOwnerOrReadOnly类。

from snippets.permissions import IsOwnerOrReadOnly

现在打开浏览器,你可以看见 ‘DELETE’ 和 ‘PUT’ 动作只会出现在那些你的登录用户创建的snippet页面上了.

通过API验证

我们已经有了一系列的权限,如果我们需要编辑任何snippet,我们需要认证我们的request。因为我们还没有建立任何authentication classes, 所以目前是默认的SessionAuthentication和BasicAuthentication在起作用。

当我们通过Web浏览器与API互动时,我们登录后、然后浏览器session可以为所有的request提供所需的验证。

如果我们使用程序访问这些API,我们则需要显式的为每个request提供认证凭证(authentication credentials)。

如果我们试图未认证就创建一个snippet,将得到错误如下:

1
2
3
4
5
http POST http://127.0.0.1:8000/snippets/ code="print 123"

{
"detail": "Authentication credentials were not provided."
}

如果我们带着用户名和密码来请求时则可以成功创建:

1
2
3
4
5
6
7
8
9
10
11
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"

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

我们已经为我们的Web API创建了相当细粒度的权限控制和相应的系统用户。

在教程第5部分part 5,我们将把所有的内容串联起来,为我们的高亮代码片段创建HTML节点,并利用系统内的超链接关联来提升API的一致性表现。