Drf官网教程(四) - 认证与许可
目录
- 修改model
- 增加User相关的代码
- 将user与snippet联系起来
- 增加认证的许可
- 自定义对象级的许可
- 关于Auth
- 总结
0. 概述
到目前为止,我们的Snippet
提供了五种ACTION(LIST
, RETRIEVE
, DESTORY
, UPDATE
, CREATE
)。但是我们还未提供用户这个概念,也没用为ACTION增加限制。因此规定下面四个规则:
- 每个snippet都关联一个用户(creator)
- 经过认证的用户可以使用
LIST
,RETRIEVE
,CREATE
- 每个
snippet
只能被creator进行DESTORY
,UPDATE
- 未经过认证的用户只能使用
LIST
,RETRIEVE
1. 修改model
-
增加字段
- owner字段中外键对应的是django的内置用户表,并为用户表提供了
related_name
来访问相应用户所有的snippets
。 - 增加高亮代码的字段(以后章节使用)
- owner字段中外键对应的是django的内置用户表,并为用户表提供了
...
class Snippet(models.Model):
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
...
-
增加代码高亮字段的逻辑
这部分只需要知道,我们可以overridemodels.Model
的save
方法,来完成一些字段的修改或创建,比如这个例子中的字段highlighted
就不是直接由用户传入的字段,而是通过用户传入的其他字段计算出来的。(举个其他的例子,在用户信息中常用到的字段:生日和年龄,我们只需要用户填入生日,然后在这个save
方法中计算出年龄即可)最后调用父类的save
方法
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
class Snippet(models.Model):
...
def save(self, *args, **kwargs):
"""
在save前先保存代码高亮字段
"""
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)
-
重置数据库
由于前几章存储的record没有使用这两个字段,为了后面的调试我们采取的策略是:删除之前数据库相关的内容。
- 删除数据库(下面是linux的命令,win可以手动删除)
- 将
makemigrations
生成的snippets/migrations
删除 - 重新生成数据库表
# win中需要手动删除
rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
-
创建一个用户
python manage.py createsuperuser
2. 增加User相关的代码
-
完成一个app的顺序
这里Model使用的是Django内置的用户表。所以不需要第一步。
-
修改
serializers.py
- django内置的用户表在
django.contrib.auth.models.User
- 通过model中定义的
related_name
来反向获取snippets时,需要指定model集合,还因为是一对多的关系,所以还需要增加参数many=True
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')
-
修改
views.py
user
只提供LIST
和RETRIEVE
两种action。CREATE
action目前只能通过python manage.py createsuperuser
命令。
from django.contrib.auth.models import User
from snippets.serializers import UserSerializer
...
...
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
-
修改
urls.py
在前几章中由于snippet这个应用中只是用了一张表。所以我们将url的前缀snippet
定义在drf_tutorial/urls.py
。
而现在user表和snippet表都在snippet应用中,所以先要修改一下drf_tutorial/urls.py
...
urlpatterns = [
...
url(r'^', include('snippets.urls'))
]
在snippets/urls
中修改之前snippets的url,再增加user的url。
...
urlpatterns = format_suffix_patterns([
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
url(r'^users/$', views.UserList.as_view()),
url(r'^usres/(?P<pk>[0-9]+)/$', views.UserDetail.as_view())
])
3. 将user与snippet联系起来
-
先看下mixins中create方法的源码
源码
在源码中可以看到,当调用验证完字段的方法(is_valid
)后,调用了perform_create
方法,这是Drf为我们提供的一个方便override的方法,让我们可以在验证后,对字段进行一定的处理。因此我们只需要重写这个
perform_create
方法即可。 -
重写
perform_create
在views.SnippetList
类中加入下面的方法。
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
在save方法中,可以传递一些命名参数来调整或增加字段的值(这里我们为snippet实例增加一个creator)。
在这里我们要增加creator,而当前请求的user是保存在self.request.user
中的。
- 修改
serializers.py
为snippet的序列化类增加owner字段。
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
...
class Meta:
...
fields = (..., 'owner')
- 别忘了owner是外键!
- source参数代表了最终这个字段返回的值(这段代码中owner字段会返回用户名)
-
ReadOnlyField
在本例中等于CharField(read_only=True)
总结:如果某个model中使到用户作为外键,需要在对应CBV的view
中重写perform_create
方法,手动将外键指定为当前请求中的用户,然后再序列化中指定参数source
来选择如何序列化这个外键。
4. 增加认证的许可
-
views.py
增加许可类
在SnippetList
& SnippetDetail
加入下面的代码。
from rest_framework import permissions
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
-
为Drf的BrowserAPI提供登录功能
在drf_tutorial/urls.py
urlpatterns = [
...
url(r'^drf-auth/', include('rest_framework.urls'))
]
drf-auth
可以随意命名。(官网给的默认值是api-auth
)
-
测试
打开urlhttp://127.0.0.1:8000/snippets/
,网页右上角会看到一个登录功能,登陆后在网页最下面可提交表单。
5. 自定义对象级的许可
-
创建
snippet/permissions.py
完成每个snippet只能被creator进行DESTORY, UPDATE的功能。
from rest_framework import permissions
class IsSnippetOwnerOrReadOnly(permissions.BasePermission):
"""只有snipper的onwer才有权限资格进行删改更新"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
# 当snipper的owner和当前认证的用户相同时才返回True
return obj.owner == request.user
-
在
views.py/SnippetDetail
类中增加自定义的许可类
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
...
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsSnippetOwnerOrReadOnly)
6. 关于Auth
在教程中没有指定authentication classes
,所以使用的是默认的SessionAuthentication
& BasicAuthentication
。
7. 总结
- Auth和Permission的区别
Auth模块负责确定用哪种方式进行认证(例如本教程中使用的是Django默认的认证方式)。
Permission负责管理方法的访问权。例如是否认证,是否满足实例的属性(对象级许可)。 - Permission类
-
看下Permission类的结构
-
看下最基础的类:BasePermission
- 提供了两种方法,在上文中其实我们是分别重写的这两种方法。
-
IsAuthenticatedOrReadOnly
这个类是我们先放入view中的,它完成了view级别的permission验证,也就是如果是SAFE_MEHOTDS
(下面代码中显示了都包含哪些Method)就直接返回True,否则则判断当前的请求中是否存在用户,并且用户是认证后的才返回True。
-
自定义的IsSnippetOwnerOrReadOnly
我们自定的许可类则覆盖了object级别的permission,也就是可以获取到model的实例中的字段,根据这些字段的值判断是否返回True。