django-rest-framework学习之路-5-认证和权限
1、模型添加字段
我们想要实现以下功能:
用户可以看到所有人的代码,但是只能修改和删除自己的代码
只有登录的用户可以添加代码
未登录的用户只有读的权限
代码应该有一个创建者的字段,表示代码是属于用户的,就是作者了
因此,我们进行如下操作
Snippet模型中增加字段
修改snippets/models.py的Snippet模型,添加下面两个字段
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE, null=True, default=None)
highlighted = models.TextField(null=True, blank=True, default=None)
owner表示创建代码的用户
highlighted用于保存高亮的代码
设置自动生成高亮代码
重构一下model.Model父类的save()方法,用于每次保存代码时,生成高亮的代码
因此snippets/models.py变成了如下代码
from django.db import models
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_all_lexers, get_lexer_by_name
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE, null=True, default=None)
highlighted = models.TextField(null=True, blank=True, default=None)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
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)
迁移数据库
打开一个cmd,cd到此项目路径下(有manage.py的目录),正如以前一样,执行一下命令,
python .\manage.py makemigrations
python .\manage.py migrate
数据库
我们既然创建了外键owner,关联到auth.User下,那么我们来创建几个用户吧
为用户模特添加序列化视图
我们在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/serializers.py代码就变成了这样
from django.contrib.auth.models import User
from rest_framework import serializers
from snippets.models import Snippet
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
我们通过snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())来显示该用户对应的Snippet对象集合,因为在Snippet模型中,有个外键是User表,而User想要显示它,就只能是显示的添加这个字段
我们再添加一下序列化User的视图,当然也是在snippets/views.py文件中
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
最后snippets/views.py文件变成了这样
from django.contrib.auth.models import User
from rest_framework import generics
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
接下来注册一下User的路由到snippets/urls.py中
re_path(r'^users/$', views.UserList.as_view()),
re_path(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view()),
snippets/urls.py文件变成了这样
from django.urls import re_path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
re_path(r'^snippets_list/$', views.SnippetList.as_view()),
re_path(r'^snippets_detail/(?P[0-9]+)/$', views.SnippetDetail.as_view()),
re_path(r'^users/$', views.UserList.as_view()),
re_path(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
浏览器查看信息
http://127.0.0.1:8000/snippets/users/
2、设置一个登录接口
因为我们要用户登录后才可以进行添加,修改,删除代码,所以我们需要一个登录接口
我们只需要在tutorial/urls.py中添加以下代码即可
re_path(r'^api-auth/', include('rest_framework.urls')),
这样用的就是rest framework的登录接口了,而用户信息就是我们刚才创建的用户
浏览器查看
我们先浏览器查看我们的根目录 http://127.0.0.1:8000/
可以看到我们有三个子路由,其中一个是django自带的admin路由,还有一个snippets是我们的代码片段模型的路由,还有一个就是刚才添加的api-auth登录路由
我们访问这个登录路由
http://127.0.0.1:8000/api-auth/
又出来了两级子路由,可以看到是登录和注销,我们先看看登录吧
http://127.0.0.1:8000/api-auth/login/
登录就出来啦~~~
试着登录一下,用之前创建的用户和密码
输入用户名密码后点击登录
发现自动跳转到了http://127.0.0.1:8000/accounts/profile/接口
先不管他,这个应该是rest framework自己约定的登录后跳转的默认路由。
我们访问一下http://127.0.0.1:8000/snippets/snippets_list/接口,可以看到右上角有登录的标识了,刚才我用的是admin用户登录的
我们点击右上角就会发现,这里可以登录和注销,真是好用哇!
我们在看看详情页
http://127.0.0.1:8000/snippets/snippets_detail/2/
以上我们看了列表页和详情页,发现都非常好用,但是有一个缺陷,就是没登录也可以添加修改删除代码,我们接下来要验证一下权限,当用户登陆了,就可以增加修改删除代码了,没登录的话只能查看一下代码,只有只读的权限。
3、添加权限认证
我们在之前添加了作者以及自动生成高亮代码,接下来我们应该把作者添加到序列化中
如果我们按照以前的方式直接添加ower到fields字段中,如下
页面就变成了这样子
那么我们就变成了每次创建代码,需要连同作者一起添加,而这样不是我们想要的,我们想要的是用户登录,然后添加自己的代码,我们根据登录的用户,把他与他的代码绑定,因此我们设置owner为只读字段
设置owner为只读字段
添加以下代码到SnippetSerializer类中,同时添加pwner到fields中
owner = serializers.ReadOnlyField(source='owner.username')
因此snippets/serializers.py变成了
from django.contrib.auth.models import User
from rest_framework import serializers
from snippets.models import Snippet
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Snippet
fields = ('id', 'owner', 'title', 'code', 'linenos', 'language', 'style')
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
将Snippet和用户关联
我们需要把登录的用户关联到他的代码,因此我们会进行登录,登录后就有了用户的信息。我们把用户与代码关联上
我们处理的方式是在我们的代码片段视图中重写一个.perform_create()方法,这样我们可以修改实例保存的方法,并处理传入请求或请求URL中隐含的任何信息。
在SnippetList视图类中,添加以下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
这个snippets/views.py就变成了
from django.contrib.auth.models import User
from rest_framework import generics
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
接下来就是设置只读权限了,用户不登录只读,登录后可以增加修改删除代码
设置只读权限
在SnippetList和SnippetDetail两个视图中,添加一句代码,限制一下必须登录才可以更新,删除,修改代码
from rest_framework import permissions
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
所以snippets/views.py又变成了
from django.contrib.auth.models import User
from rest_framework import generics, permissions
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, 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, ]
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
这时候,我们添加了登录接口,添加了只读权限,添加了代码和作者关联,我们来看一下浏览器
http://127.0.0.1:8000/snippets/snippets_detail/2/
可以看到,没有登录就没有删除,创建,修改接口
列表页也是一样的
如果登录了,就会出现对应的接口了
这里是浏览器的显示,如果你用ApiPost工具调用接口创建信息或者更新信息就会收到这样的提示
{ "detail": "Authentication credentials were not provided." }
自定义权限
我们想代码可以被所有人看见,因此如果是GET请求,我们应该不校验权限
在snippets app中,创建一个新文件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
把IsOwnerOrReadOnly权限添加到SnippetDetail视图中
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
所以snippets/views.py又变成了
from django.contrib.auth.models import User
from rest_framework import generics, permissions
from snippets.models import Snippet
from snippets.permissions import IsOwnerOrReadOnly
from snippets.serializers import SnippetSerializer, 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]
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
4、通过requests库提交请求带用户名密码通过认证
如果需要通过requests发送POST请求,来达到登录的效果,即登录了就可以添加修改和删除代码了。
需要使用HTTPBasicAuth认证,带上用户名和密码,这样就可以访问了
import requests
from requests.auth import HTTPBasicAuth
url = 'http://127.0.0.1:8000/snippets/snippets_list/'
headers = {}
payload = {
"title": "2",
"code": "3",
"linenos": False,
"language": 'abap',
"style": 'abap',
}
user = 'admin'
password = 'admin'
re = requests.post(url=url, json=payload, headers=headers, auth=HTTPBasicAuth(user, password))
print(re.headers)
print(re.text)