DRF总结
【一】drf入门规范
【1】web应用模式
前后端不分离
# 模板渲染在后端完成
前后端分离(主流)
# 后端就只负责写接口,前端来调用,通信使用json格式
# 多端(web、app...)都可以使用同一个接口
【2】API接口
前端可以通过访问得到数据的url被称为API接口
# 四大特点
# 1.url
长得像返回数据的url链接
# 2.请求方式
get、post、put、patch、delete
# 3.请求参数
json或xml(老项目)格式的key-value类型数据
# 4.响应结果
json或xml格式的数据
【3】接口则是工具
- Postman
- APIfox等
【4】RESTful API规范(3星)
# 1 使用https协议进行传输数据(保证数据安全)
# 2 url中带关键字api
https://api.baidu.com
https://www.baidu.com/api
# 3 url中带版本信息
https://api.baidu.com/v1
https://api.baidu.com/v2
# 4 数据即资源,均使用名词(重要)
https://api.baidu.com/users
https://api.baidu.com/books
https://api.baidu.com/book
# 5 资源操作由请求方式决定(重要)
'查询操作' : get
'新增操作' : post
'修改操作' : put
'删除操作' : delete
https://api.baidu.com/books - get请求:获取所有书
https://api.baidu.com/books/1 - get请求:获取主键为1的书
https://api.baidu.com/books - post请求:新增一本书书
https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
https://api.baidu.com/books/1 - patch请求:局部修改主键为1的书
https://api.baidu.com/books/1 - delete请求:删除主键为1的书
# 6 请求url中带搜索筛选条件(重要)
https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置、
https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件
# 7 响应中要带状态码(自己定义,http响应状态码)
{
status:200
}
# 8 响应返回错误信息
{
status:200
msg: "无权限操作"
}
# 9 返回结果,遵循如下规范(大概率都没有遵循)
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
# 10 返回数据中带有链接地址
{
"status": 0,
"msg": "ok",
"results":[
{
"name":"肯德基(罗餐厅)",
"img": "https://image.baidu.com/kfc/001.png"
}
]
}
【5】drf介绍与安装
# Django-Rest-Framework: 是django的一个app,可以借助它快速在django框架开发出符合restful规范的接口
# Django-Rest-Framework提供了: 认证、权限、限流、过滤、分页、接口文档等功能支持
# 官方文档: https://www.django-rest-framework.org/
# python Django版本的支持情况
Python(3.5、3.6、3.7、3.8、3.9)
Django(2.2、3.0、3.1) 2.x用的多,1.x(老项目)
-----------------------------------
drf(详细)
https://blog.51cto.com/u_15127561/3375773
【6】CBV源码分析
【二】序列化组件
【1】介绍
1 序列化,序列化器(类)会把模型对象(Book对象,Queryset对象)转换成字典,经过response以后变成json字符串
2 反序列化,把客户端发送过来的数据,经过request以后变成字典(request.data),序列化器(类)可以把字典转成模型
3 反序列化,完成数据校验功能
【2】序列化器之Serializer类的使用(跟表模型没有必然联系)(5星)
视图类
from django.shortcuts import render
from rest_framework.views import APIView
from .models import Student
from .serializers import Student_serializers
from rest_framework.response import Response
# Create your views here.
class Student_serializers_APIView(APIView):
# 查询所有学生信息
def get(self, request, *args, **kwargs):
student_list = Student.objects.all()
ser = Student_serializers(instance=student_list, many=True)
return Response(ser.data)
# 新增学生信息
def post(self, request, *args, **kwargs):
ser = Student_serializers(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
class Student_Detial_serializers_APIView(APIView):
# 查询单个学生信息
def get(self, request, pk):
student = Student.objects.filter(pk=pk).first()
ser = Student_serializers(instance=student)
return Response(ser.data)
# 修改单个学生信息
def put(self, request, pk):
student = Student.objects.filter(pk=pk).first()
ser = Student_serializers(instance=student, data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
# 删除单个学生信息
def delete(self, request, pk):
Student.objects.filter(pk=pk).delete()
return Response()
序列化类
from rest_framework import serializers
from .models import Student
class Student_serializers(serializers.Serializer):
name = serializers.CharField(max_length=32)
age = serializers.IntegerField()
def create(self, validated_data):
student = Student.objects.create(**validated_data)
return student
def update(self, instance, validated_data):
instance.name = validated_data.get('name')
instance.age = validated_data.get('age')
instance.save()
return instance
路由
from django.contrib import admin
from django.urls import path
from app01 import views
from django.views import View
urlpatterns = [
path('admin/', admin.site.urls),
path('student/', views.Student_serializers_APIView.as_view()),
path('student/<int:pk>', views.Student_Detial_serializers_APIView.as_view())
]
模型
from django.db import models
# Create your models here.
class Student(models.Model):
name = models.CharField('姓名', max_length=32)
age = models.IntegerField('年龄')
【3】Serializers高级使用(5星)
1 source字段的作用
# source:可以对应表模型的字段和方法(返回结果是什么,字段就是什么)
# 一般用来做一对多,多对多的字段返回
'''
注:
source如果是字段,会显示字段,如果是方法,会执行方法,不用加括号
'''
2 SerializerMethodFidld使用方法
# 一般用来添加需要查询该表以外的其他表(或关联表)的字段、方法
# 通过SerializerMethodField,看这个名字就知道,是需要通过序列化组件里的方法来定义,也就是在序列化组件里面重写某个方法,这个方法是`get_字段名`
course_detail = serializers.SerializerMethodField()
def get_course_detail(self, obj):
return {'name':obj.course.name,'teacher':obj.course.course_teacher}
3 数据校验
在做反序列化保存时就有一个问题了,因为前面我们自定义的样式的字段`publish_detail`和`authors_list`都只是用来序列化输出的,并不想将这两个字段也保存,这就犯难了,难道我们需要重新写一个序列化类单独用作反序列化保存吗?
当然是不需要的,drf提供了两个字段参数,分别是`write_only`,`read_only`
在 Django REST Framework (DRF) 的序列化器中,`read_only` 和 `write_only` 是两个常用的参数,用于控制字段在序列化和反序列化过程中的行为。
### read_only
当一个字段设置了read_only=True时,该字段仅在序列化时可见,在反序列化操作(更新,添加)时会被自动忽略
### write_only
反之,当一个字段设置了write_only=True时,该字段只在反序列化(更新,添加)等操作可见
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField(required=False,default=datetime.now)
#### 序列化的字段和反序列化字段不一致####
## 1 笨办法:再写个序列化类,单独用来反序列化
## 2 通过 read_only write_only 控制
# 这个字段用来做序列化
publish_detail = PublishSerializer(source='publish',read_only=True)
# 这个字段用来做序列化
authors_list = AuthorSerializer(source='authors', many=True,read_only=True)
# 反序列化字段--》只用来保存---》多表关联
publish=serializers.IntegerField(write_only=True) # 前端传入数字---》自动跟出版社id做对应
authors=serializers.ListField(write_only=True)
# 反序列化字段-->可以随意命名,跟表字段没关系--》但是后续保存和修改要对应好才行
# publish_id=serializers.IntegerField(write_only=True) # 前端传入数字---》自动跟出版社id做对应
# authors_xx=serializers.ListField(write_only=True)
# 保存
def create(self, validated_data):
publish_id=validated_data.pop('publish')
authors=validated_data.pop('authors')
book=Book.objects.create(**validated_data,publish_id=publish_id)
book.authors.add(*authors) # 向中间表中插入数据
return book
# 更新
def update(self, instance, validated_data):
publish_id=validated_data.pop('publish')
authors=validated_data.pop('authors')
book_qs=Book.objects.filter(pk=instance) # 查询qs对象
book_qs.update(**validated_data,publish_id=publish_id) # 使用qs更新
instance=book_qs.first() # 单个对象
instance.authors.set(authors) # 向中间表中插入数据
# instance.author.clear()
# instance.authors.add(*authors)
return instance
【4】模型类序列化器ModelSerializer(跟表模型有关联)(5星)
1 介绍
drf为我们提供了一个ModelSerializer模型序列化器来帮助我们快速创建一个Serializer类
2 ModelSerializer与Serializer的区别
1.基于模型类自动生成一系列字段
2.基于模型类自动为Serializer生成validators,比如unique_together
3.包含默认的create()和update()的实现
3 使用
class Publish_modelserializers(serializers.ModelSerializer): # 继承ModelSerializer
class Meta:
model = Publish # 表模型
fields = '__all__' # 1 序列化的字段
fields = ['id','name'] # 2 用来手动指定字段
exclude = ['id'] # 3 排除的字段(1、2、3只能同时使用一个)
depth # 深度,一般不用
4 重写字段
name = serializers.SerializerMethodField()
def get_name(self, obj):
return "出版社 : " + obj.name
5 扩写字段(表模型中没有的字段)
'''
注意:
嵌套反序列化时正常只能传pk值,需要两个字段(一个展示(read_only),一个添加数据(write_only))
否则反序列化嵌套数据时报错
'''
name2 = serializers.SerializerMethodField()
def get_name2(self, obj):
return " 出版社2:东京出版社"
6 反序列化时,字段自己的校验规则,是映射表模型的
7 局部钩子与全局钩子(与Serializer一样)
8 write_only与read_only
name=serializers.CharField(write_only=True) # 反序列化的时候使用,序列化时不用(只读)
name=serializers.CharField(read_only=True) # 序列化的时候使用,反序列化时不用(只写)
9 extra_kwargs(添加额外参数选项)
class Meta:
model = Publish
fields = '__all__'
extra_kwargs = {
'name':{
'required':True,
'min_length':3,
'write_only':True
'error_messages':{
'required':'必须填',
'min_length':'最少三位'
},
'addr':{
'read_only':True
'required':True
}
}
}
【5】序列化器字段和字段参数(2星)
常用字段类型
字段 | 字段构造方式 |
---|---|
BooleanField | BooleanField() |
NullBooleanField | NullBooleanField() |
CharField | CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) |
EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
RegexField | RegexField(regex, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ |
URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField(protocol=’both’, unpack_ipv4=False, **options) |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
MultipleChoiceField | MultipleChoiceField(choices) |
FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
【6】局部钩子和全局钩子源码分析(2星)
【7】序列化组件源码分析
【三】请求与响应
【1】drf请求全局与局部配置
请求默认支持三种编码格式
- urlencoded
- json
- formdata
# 默认支持三种
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser', # 解析application/json格式
'rest_framework.parsers.FormParser', # 解析application/x-www-form-urlencoded
'rest_framework.parsers.MultiPartParser' # multipart/form-data
]
# 局部配置
from rest_framework.parsers import JSONParser
class BookView(ViewSetMixin,ListAPIView,CreateAPIView):
parser_classes = [JSONParser,]
【2】Request
# rest-framework基于Django的request封装了一个新的Request类的request对象
rest-framework传入视图的request对象不再是Django默认的HttpRequest对象,而是rest-framework提供的扩展的HttpRequest类的Request类的对象
# Request对象的数据是根据前端发送数据的格式进行解析之后的结果(无论前端发送哪种格式的数据,都可以用统一的方式读取数据)
rest-framework提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单类型数据等)将请求数据进行parse解析,解析为类字典(QueryDict)对象保存在Request对象中
# 常用属性
1 request.data:
包含了对POST、PUT、PATCH请求方式解析后的数据
利用了rest-framework的parsers解析器,不仅支持表单类型数据,也支持JSON类型数据
2 request.query_params
request.query_params与Django的request.GET相同,只是更换了更正确的名字
'''
重点:
1 继承APIView后,视图类中的request对象是新的request对象
2 request.data、request.query_params的使用
'''
【3】Response
# drf中的Response对象
# 继承关系:Response---> SimSimpleTemplateResponse ---> django的HttpResponse
# 属性、参数
data=None # ser.data,传字典或列表,序列化成json格式字符串给前端
status=None # 状态码,http响应的状态码
template_name=None # 模板名字,在浏览器访问,看到的好看的模板(自定制)(用得少)
headers=None # 响应头,字典
exception=False # 异常(不用管)
content_type=None # 响应编码类型
'''
重点:
data
status
headers
'''
# 使用
response={'code':100, 'msg':'查询成功', 'request':ser.data}
return Response(response,status=status.HTTP_201_CREATED,headers={'xxx':"xxx"})
# 配置
1 通过配置,设置响应格式(浏览器模板样子、纯json)
# 1 后期,drf的所有配置,都写在这个字典中
# 2 drf有个默认配置文件(drf源码的 ---> setting.py),如果项目的配置文件配置了,优先使用项目的,如果没有配置,使用内置的
# 3 返回样式的配置,一般不配置
REST_FRAMEWORK={
#配置响应格式,默认有俩(json,浏览器的)
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
]
}
2 局部配置(只针对某一个视图函数)
在视图类上写
renderer_classes = [JSONRenderer]
3 配置的三个层次(优先级)
局部 ---> 项目 ---> drf默认
【四】视图组件
【1】2个视图基类
APIView
# APIView 是rest-framework提供的所有视图的基类,继承自Django的View父类
# 在APIView中仍以常规的类视图定义方法来实现get()、post()...请求方式的方法
APIView与View的区别
1.传入到视图方法中的是rest-framework的Request类的request对象,而不是Django的HttpResponse对象
2.视图方法返回的是rest-framework的Response类的response对象,视图会因为响应数据设置(rander)符合前端要求的格式
3.任何APIException异常都会被捕获到,并处理成合适的响应信息
4.在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制
GenericAPIView(通用视图类)
# 继承自APIView,主要增加了操作序列化器和数据库查询的方法,作用是为Mixin扩展类的执行提供方法支持,通常在使用时,可搭配一个或多个Mixin扩展类
'''
涉及到数据库操作,尽量选择GenericAPIView,减少代码量
'''
提供的关于序列化器使用的属性与方法
- 属性
serializer_class : 指明视图使用的序列化器
- 方法
get_serializer_class(self) : 当出现一个视图类中调用多个序列化器时,可以通过条件判断get_serializer_class方法中通过返回不同的序列器类名,就可以让视图方法执行不同的序列化器对象了
get_serializer(self, *args, **kwargs) : 返回序列化器对象,主要用来提供给Mixin扩展类使用(该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用)(可以传instance、data、many)
提供的关于数据库查询的属性与方法
- 属性
queryset :查询的表名.objects.all() (这里不写all会自动添加上all,但推荐都写上)
- 方法
get_object(self) :获取查询需要用到的单条数据 (若详情访问的模型类对象不存在,会返回404)
【2】5个视图拓展类
这五个扩展类需要搭配GenericAPIView父类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法
# CreateModelMixin
内部有create方法 :新增
# ListModelMixin
内部有list方法 :查询所有
# DestroyModelMixin
内部有destroy方法 :删除单条
# UpdateModelMixin
内部有update方法 :修改一条
# RetrieveModelMixin
内部有retrieve方法 :查询单条
【3】9个视图子类
ListAPIView
- list
CreateAPIView
- create
UpdateAPIView
- update
DestroyAPIView
- destroy
RetrieveAPIView
- retrieve
RetrieveUpdateDestroyAPIView
- retrieve+update+destroy
RetrieveDestroyAPIView
- retrieve+destroy
RetrieveUpdateAPIView
- retrieve+update
ListCreateAPIView
- list+create
【4】视图集
ViewSetMixin
# 核心:重写了as_view方法
# 能够实现:请求方式和视图类中方法的映射
# 结论:只要继承ViewSetMixin的视图类,路由中as_view()里就要传action参数(字典)
例
path('books/', views.BookView.as_view({'get':'list','post':'create'}))
ViewSet
- 继承自APIView与ViewSetMixin,作用与APIView类似
- 主要通过ViewSetMixin重写的as_view,来完成调用as_view()时传入字典的映射处理
- 没有提供任何action方法,也就是视图类里的方法
- 需要自己编写list、retrieve、create、update、destroy等方法,如as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})
GeneriViewSet
- 继承自GenericAPIView与ViewSetMixin
- 实现了调用as_view()时传入字典的映射处理
- 提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用
ModelViewSet
- 继承自GenericAPIView和五个视图扩展类
ReadOnlyModelViewSet
- 继承自GenericAPIView和ListModelMixin、RetrieveModelMixin
【五】路由组件(5星)
【1】Routers
对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系,还可以使用Routers类来快速实现路由的创建
- SimpleRouter(常用)
- DefaultRouter(用的少)
# 方式一
# 1.导入Routers类
from rest_framework.routers import DefaultRouter
# 2.实例化对象
router = DefaultRouter()
# 3.将视图集注册到路由器中: 第一个参数是url前缀,第二个是视图类 ,第三个是别名
router.register('user', UserView, 'user')
# 4.将路由添加到路由列表中
urlpatterns += router.urls
# 方式二
# 前三步骤一样
# 1.导入include函数
from django.urls import include
# 2.在 Django 项目的 URL 配置中使用 include() 函数: 这样可以多一层路由
urlpatterns = [
path('', include(router.urls)) # 将路由器生成的 URL 配置包含到根路径下
]
【2】试图类中派生的方法,自动生成路由(action)
class Login(ViewSetMixin, APIView):
authentication_classes = []
permission_classes = []
@action(methods=['POST'], detail=False) # 自己扩展的方法(派生)
def login(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user_alive = models.User.objects.filter(username=username, password=password).first()
if user_alive:
token = uuid.uuid4()
models.User_token.objects.update_or_create(defaults={'token':token}, user=user_alive)
return Response({'code':100, 'msg':'登录成功', 'token':token}, headers={'token':token})
else:
return Response({'code':101, 'msg':'登录失败'})
# 这样自动生成的路由是(books/login/) (如果prefix参数传空字符串,则是/login/)
'''
参数解释:
method: 用来指定请求方式,默认GET
detail: 用来指定是否传入pk值(如:查所有或查单条),False:不传pk,True:传pk
url_path: 用来指定url的路径,默认方法名
url_name: 用来指定url的别名
'''
'''
注意:
1 如果继承了APIView,那么想要自动创建路由,则必须写action动作并在urls.py中传basename参数来指定视图
2 必须继承ViewSetMixin
【六】认证组件(5星)
用于判断用户是否登录
【1】简单使用
# 1.创建一个任意名字的py文件
# 2.导入认证类
from rest_framework.authentication import BaseAuthentication
# 3.写一个类继承它并且重写authenticate方法
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
user_token = models.User_token.objects.filter(token=token).first()
if user_token:
return user_token.user, token
else:
raise AuthenticationFailed('请先登录')
# 4.在views.py导入这个自己写的类
from .authentication import LoginAuth
# 5.局部使用 在视图类下写authentication_classes属性,并且将自己写的认证类加到这个列表
class UserOperation():
authentication_classes = [LoginAuth]
# 6.全局使用 在settings.py文件下配置REST_FRAMEWORK
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':[
'app01.auth.Login_authentication'
]
}
# 7.局部禁用 在某个想要禁用的视图类下定义列表为空
authentication_classes = []
【2】认证类源码解析
在视图类执行as_view()时会先到dispath()封装一个新的request
dispatch
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# 通过initialize_request封装新的request
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
initialize_request
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
# !!!!!! 传入认证类对象
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
get_authenticators
def get_authenticators(self):
# 会通过遍历这个authentication_classes可迭代对象来以此调用里面的认证类对象
# 这就是为什么我们需要在视图类里面定义这么一个列表或者元组
return [auth() for auth in self.authentication_classes]
回到dispatch方法里面,查看三大认证操作
initial
def initial(self, request, *args, **kwargs):
# 这三行代码进行了三大认证,点到perform_authentication里面看一看
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
perform_authentication
def perform_authentication(self, request):
# 就这么一行代码 看不出什么
# 要想知道user是什么 从哪里来,就要去看看这个request的类是这么定义的
# 当前这个request是经过Request包装过的新的request
# 所以接下来去看看Request()这个类
request.user
Request
类
发现user是一个包装成属性的方法
@property
def user(self):
# 如果当前request没有_user这个属性,就执行_authenticate方法
if not hasattr(self, '_user'):
# 做了一个异常捕获
with wrap_attributeerrors():
self._authenticate()
# 最后是返回了一个_user 说明_authenticate一定对_user赋值了
return self._user
# 所以接下来点到_authenticate方法看一看
_authenticate
方法
def _authenticate(self):
# authenticators是实例化request对象时传入的参数,
# 他是一个列表,列表里面是一个个认证类的实例
for authenticator in self.authenticators:
try:
# 如果没有东西就会被异常捕获
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
# 如果user_auth_tuple非空,就说明认证类里面的authenticate方法返回东西了
if user_auth_tuple is not None:
# 把认证类实例赋值给_authenticator
self._authenticator = authenticator
# 分别把值赋给user和auth
# 也就是此时 request.user 就有值了
self.user, self.auth = user_auth_tuple
return
# 如果没有报错,且没有返回元组,那么就执行了这么个玩意
self._not_authenticated()
# 下面点进_not_authenticated
_not_authenticated
def _not_authenticated(self):
self._authenticator = None
# 如果配置文件配置了这个对象
if api_settings.UNAUTHENTICATED_USER:
# 就会执行某个函数,然后把返回值赋给user
# 这个函数点进去看就会发现时执行了__str__方法然后返回了个字符串AnonymousUser
# 也就是匿名用户的意思
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
手动设置user
@user.setter
def user(self, value):
# 这段源码解释了
# 当手动设置user对象的时候
# 还会把老的request.user 也设置成当前设置的user对象
self._user = value
self._request.user = value
【3】总结
当请求来的时候会执行dispach方法initialize_request方法包装新的request对象
Request会传入authenticators=self.get_authenticators(),如果你定义了序列化类,就会实例化这个类,把这个对象加到一个列表里,如果没有就不会触发校验
这时候新的request对象已经生成好了,然后视图类对象会执行initial
方法,这里面会触发perform_authentication
方法进行认证,
这个方法会执行request.user,然后执行Request类的_authenticate
方法,这里就会根据你的序列化类的authenticate
方法的返回值给request.user和request.auth赋值。
【七】权限组件(4星)
【1】权限类的编写
- 写一个类,继承BasePermission
- 重写has_permission 方法
- 在其内部进行权限校验的定义
- 有权限返回True
- 无权限返回False
- 定制返回提示
- 通过定义self.message = xxx
from rest_framework.permissions import BasePermission
class SuperPermission(BasePermission):
# 继承BasePermission类
# 重写has_permission方法
# 拿到当前用户 ---》 查看权限 ---》如果权限足够 return True ---》 不够 return False
def has_permission(self, request, view):
user_type = request.user.user_type
if user_type == '3':
return True
else:
# 定制返回提示
self.message = '权限不足'
return False
【2】权限类的使用
- 局部使用
- 全局使用
- 局部禁用
# 局部使用
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [CommonAuthentication]
# 重写 permission_classes 列表里面填自己定义的权限类
permission_classes = [SuperPermission]
# 全局使用
REST_FRAMEWORK = {
# 权限
'DEFAULT_PERMISSION_CLASSES': []
}
# 局部禁用
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [CommonAuthentication]
# 视图类里面定义空列表
permission_classes = []
【八】频率组件(5星)
他的作用是限制接口访问的频率
【1】频率类的编写
- 写一个类,继承SimpleRateThrottle
- 重写get_cache_key,返回唯一标识,返回什么就以什么做限制
- 重写类属性rate 控制频率
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
class CommonThrottling(SimpleRateThrottle):
# 每分钟限制访问三次
rate = '3/m'
def get_cache_key(self, request, view):
# 返回什么就以什么为限制
return request.META.get('REMOTE_ADDR')
【2】频率类的使用
- 局部使用
- 全局使用
- 局部禁用
# 局部使用
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 在视图类里重写throttle_classes属性,列表里填自己定义的频率类
throttle_classes = [CommonThrottling]
# 全局使用
REST_FRAMEWORK = {
# 频率
'DEFAULT_THROTTLE_CLASSES': [],
}
# 局部禁用
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
throttle_classes = []
【3】频率类源码分析
执行流程
当请求来的时候,首先会执行APIView里面的dispath里面的initial方法
在这里面会进行三大认证,频率限制就在这里执行
依次点进来看看什么情况
check_permissions
def check_throttles(self, request):
# 首先初始化一个throttle_durations列表
throttle_durations = []
# 点进get_throttles看就会知道
# 他就是对我们定义的频率类列表进行遍历,然后分别生成对象
for throttle in self.get_throttles():
# 所以这里的意思就是如果allow_request返回了false
# 就把throttle.wait()这个玩意的返回值加入到throttle_durations列表里
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
# 如果throttle_durations不为空
if throttle_durations:
# 把throttle_durations列表的None值过滤掉,然后把结果赋值给 durations列表
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
# 这里是抛出了一个异常
self.throttled(request, duration)
接下来看看内置的频率类源码
BaseThrottle
class BaseThrottle:
"""
Rate throttling of requests.
请求的速率限制
"""
def allow_request(self, request, view):
"""
返回True就是通过校验,反之不通过抛出异常
"""
raise NotImplementedError('.allow_request() must be overridden')
# 这个方法就是用于确定请求来源
def get_ident(self, request):
xff = request.META.get('HTTP_X_FORWARDED_FOR')
remote_addr = request.META.get('REMOTE_ADDR')
num_proxies = api_settings.NUM_PROXIES
if num_proxies is not None:
if num_proxies == 0 or xff is None:
return remote_addr
addrs = xff.split(',')
client_addr = addrs[-min(num_proxies, len(addrs))]
return client_addr.strip()
return ''.join(xff.split()) if xff else remote_addr
# 用来定义返回的描述的
def wait(self):
"""
Optionally, return a recommended number of seconds to wait before
the next request.
"""
return None
SimpleRateThrottle
class SimpleRateThrottle(BaseThrottle):
cache = default_cache
timer = time.time
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
# 这个方法必须被重写,且返回什么就什么为标准
def get_cache_key(self, request, view):
raise NotImplementedError('.get_cache_key() must be overridden')
def get_rate(self):
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
def parse_rate(self, rate):
# 判断是否为None,如果为None则返回两个None
if rate is None:
return (None, None)
# 如果不为空就切分一下
num, period = rate.split('/')
# 把次数转成数字整数类型
num_requests = int(num)
# 定义了一个时间单位字典
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
# 返回 一定时间内的次数 和 一定时间
return (num_requests, duration)
def allow_request(self, request, view):
# 如果rate为None,就是不做限制
if self.rate is None:
return True
# 获取限制标志
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
# 先把它当个列表看
self.history = self.cache.get(self.key, [])
self.now = self.timer()
# 当这个列表不为空,代表着有访问,且没有超过持续限制时间
# 且 列表的最后一个数据,也就是最早访问的时间小于现在的时间减去持续时间,也就是超过了持续时间
# 就把这条访问记录踢出列表
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
# 如果列表的长度,也就是访问记录条数大于规定的访问次数,就执行throttle_failure方法
if len(self.history) >= self.num_requests:
return self.throttle_failure()
# 否则执行throttle_success方法
return self.throttle_success方法()
def throttle_success(self):
# 代表访问成功,然后往访问列表插入一条数据
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
return True
def throttle_failure(self):
# 访问不成功 返回False
return False
# 看三大认证的源码可以发现,wait()方法是在访问不成功之后执行的
# 也就是allow_request 返回False执行的
def wait(self):
# 如果列表有值,也就是之前有访问记录
if self.history:
# 就计算出最早的一次访问还有多久解除限制,把值赋给remaining_duration
remaining_duration = self.duration - (self.now - self.history[-1])
else:
# 如果列表没有值,就代表自己定制的持续时间就是接触访问限制的时间
remaining_duration = self.duration
# 这里就是计算了一下还剩下多少次数可以访问
available_requests = self.num_requests - len(self.history) + 1
# 没有访问机会了就返回None
# 在三大认证里面会把None去除掉
if available_requests <= 0:
return None
# 最后返回 下次可以访问的时间 和 访问次数
return remaining_duration / float(available_requests)
【九】过滤与排序(4星)
查询所有才需要过滤(根据条件过滤),排序(按某个规则排序)
【1】内置类使用
# 内置过滤类(在视图类中配置)
from rest_framework.filters import SearchFilter
class BookView(ViewSetMixin,ListAPIView):
# 在视图类中配置,最顶层的类至少是GenericAPIView
filter_backends = [SearchFilter,]
# 过滤条件,按名字过滤
search_fields=['name','publish']
# 查询使用
http://127.0.0.1:8000/books/?search=达 # 出版社中或名字中有 达 就能查询出来
# 内置排序类(既有排序,又有过滤)
from rest_framework.filters import OrderingFilter,SearchFilter
class BookView(ViewSetMixin,ListAPIView):
# 在视图类中配置,最顶层的类至少是GenericAPIView
filter_backends = [SearchFilter,OrderingFilter,]
# 过滤条件,按名字过滤
search_fields=['name']
# 按哪个字段排序
ordering_fields=['price','id']
# 查询使用
http://127.0.0.1:8000/books/?ordering=price,-id
【2】第三方插件使用(django-filter)
# 视图类中
from django_filters.rest_framework import DjangoFilterBackend
class BookView(ViewSetMixin,ListAPIView):
# 在视图类中配置,最顶层的类至少是GenericAPIView
filter_backends = [DjangoFilterBackend,]
# 按名字、价格过滤
filter_fields=['name','price']
# 查询时
http://127.0.0.1:8000/books/?name=红楼梦
http://127.0.0.1:8000/books/?name=四方达&price=15
【十】异常处理(4星)
【1】简单使用
全局统一捕获异常,返回固定的格式
# 使用步骤
1 写一个函数
2 在配置文件中配置
1 写函数:
from rest_framework.views import exception_handler
from rest_framework.response import Response
def comment_exception_handler(exc, context):
response = exception_handler(exc, context)
if response:
data = {
'code':1001,
'msg':'错误',
'detail':response.data
}
return Response(data)
else:
data = {
'code':1002,
'msg':'未知错误'
}
return Response(data)
2 在配置文件中配置
REST_FRAMEWORK = {
'EXCEPTION_HANDLER':'utils.comment_exception_handler'
}
【十一】分页功能(5星)
查询所有,才有分页功能(例如网站的下一页功能,app的下滑加载更多)
【1】PageNumberPagination基本分页
重要类属性
- page_size = api_settings.PAGE_SIZE (每页显示条数)
- page_query_param = 'page' (查询时用的参数)
- page_size_query_param = None (更改返回条数)
- max_page_size = None (每页最大显示条数)
基本定义
# 1 导入 PageNumberPagination 类
from rest_framework.pagination import PageNumberPagination
# 2 写一个类继承PageNumberPagination类
class CommonPageNumberPagination(PageNumberPagination):
page_size = 2 # 每页正常显示2条
page_query_param = 'page' # 查询页数时用的参数
page_size_query_param = 'size' # 更改每页显示的条数
max_page_size = 5 # 每页最多显示的条数 即使 size = 99999 也最多是5条
简单使用
class NormalBookView(GenericViewSet, RetrieveModelMixin, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
# 要注意 在设置分页器的时候不是一个列表!!!
pagination_class = CommonPageNumberPagination
【2】LimitOffsetPagination偏移分页
偏移的意思就是--->偏移到第几条 就从第几条数据开始查
重要的类属性
- default_limit = api_settings.PAGE_SIZE (每页显示条数)
- limit_query_param = 'limit' (查询时用的参数)
- offset_query_param = 'offset' (offset偏移的查询参数)
- max_limit = None (每页最大显示条数)
基本定义
class CommonLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3 # 每页显示的条数
limit_query_param = 'limit' # 查询时使用的参数
offset_query_param = 'offset' # 偏移的参数
max_limit = '5' # 每页最大的显示条数
简单使用
class NormalBookView(GenericViewSet, RetrieveModelMixin, ListModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
# 要注意 在设置分页器的时候不是一个列表!!!
pagination_class = CommonLimitOffsetPagination
【3】CursorPagination游标分页
重要的类属性
- cursor_query_param = 'cursor' (查询条件)
- page_size = api_settings.PAGE_SIZE (每页显示条数)
- ordering = '-created' (排序)
- page_size_query_param = None (更改每页显示条数)
- max_page_size = None (每页最大显示条数)
使用
# 1 写一个类,继承LimitOffsetPagination,重写4个类属性
class CommonCursorPagination(CursorPagination):
cursor_query_param = 'cursor'
page_size = 2
ordering = '-id'
page_size_query_param = 'offset'
max_page_size = 5
# 2 在视图类中配置写好的分页类
class BookAPIView(ViewSetMixin, ListAPIView):
queryset = models.Books.objects.all()
serializer_class = BookModelSerializer
pagination_class = CommonCursorPagination
优缺点
# 优点:速度最快,数据量越大,越有优势(没有那么大的数据量,用的不多)
# 缺点:只能前一页和后一页,不能直接跳到某一页
【十二】自动生成接口文档
REST framework可以自动帮我们生成接口文档。
接口文档以网页的形式呈现
自动接口文档能生成的是继承自APIView及其子类的视图
【1】使用步骤
安装依赖
# REST framework生成接口文档需要coreapi库的支持
pip install coreapi
【2】设置接口文档访问路径
需要在总路由中添加接口文档路径
文档路由对应的视图配置为rest_framework.documentation.include_docs_urls
,
参数title
为接口文档网站的标题。
from rest_framework.documentation import include_docs_urls
urlpatterns = [
...
path('docs/', include_docs_urls(title='站点页面标题'))
]
【3】文档描述说明的定义位置
单一方法的视图,可直接使用类视图的文档字符串,如
class BookListView(generics.ListAPIView):
"""
返回所有图书信息.
"""
包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
class BookListCreateView(generics.ListCreateAPIView):
"""
get:
返回所有图书信息.
post:
新建图书.
"""
对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
"""
list:
返回图书列表数据
retrieve:
返回图书详情数据
latest:
返回最新的图书数据
read:
修改图书的阅读量
"""
【3】配置文件
#AttributeError: 'AutoSchema' object has no attribute 'get_link'
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
# 新版drf schema_class默认用的是rest_framework.schemas.openapi.AutoSchema
}
【4】两点说明
- 视图集ViewSet中的retrieve在文档网站内叫read
- 参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:
class Student(models.Model):
...
age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
...
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
extra_kwargs = {
'age': {
'required': True,
'help_text': '年龄'
}
}
【十三】RBAC
【1】RBAC:Role-Based Access Control (基于角色的访问控制)
原理
# 权限与角色(组)相关联,用户通过称为适当角色(组)的成员而得到这些角色(组)的权限
# 极大的简化了权限的管理(相互依赖)
# Django的Auth组件(app)采用的认证规则就是RBAC
1 User表 :存用户信息
2 Permission表 :存权限
3 Role表 :存角色(组)
4 Group_Role中间表 :权限赋予角色(多对多)
5 User_Group中间表 :角色赋予用户(多对多)
6 User_Permission中间表 :权限临时赋予角色(多对多)
'''
ps:
1 Django后台管理admin自带RBAC
2 基于admin做二次开发(simple-ui)
3 基于前后端分离实现RBAC(https://github.com/liqianglog/django-vue-admin)
4 前后端混合的后台前端模板(X-admin--->快速开发后台管理系统)
5 智慧大屏(https://gitee.com/kevin_chou/dataVIS.git)
【十四】JWT(5星)
【1】介绍(5星)
# 三段式:header,payload,signature
header(头):认证方式,加密方式,公司名字...
{
'typ':'JWT',
'alg':'HS256'
}
payload(荷载):用户信息,过期时间,签发时间...
{
"userid":"2",
"name":"Jerry",
"exp":121456
}
signature(签名):把前面的头和荷载通过设置好的加密方式得到的串
# 签发和校验
1 用户登录成功:
1.1 构造token的头(固定)
1.2 构造荷载
1.3 使用设置好的加密方式对头和荷载加密,得到签名,三者用.拼接起来,生成一个token串(使用base64编码)
例如: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
2 用户携带token,来到后端,把头和荷载再使用相同的加密方式加密,得到签名,比较新签名和旧签名是否一致,如果一致,说明该token可以信任,解析出当前用户(荷载),继续往后走
【2】base64编码
# 编码
import base64
import json
dic={'name':'lqz','id':1}
user_info_str=json.dumps(dic)
# res=base64.b64encode(bytes(user_info_str,encoding='utf-8'))
res=base64.b64encode(user_info_str.encode('utf-8'))
print(res) # eyJuYW1lIjogImxxeiIsICJpZCI6IDF9
# 解码
res=base64.b64decode('TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ='.encode('utf-8'))
print(res)
'''
ps:
base64长度是4的倍数,如果不足,需要用=号补齐
'''
【3】JWT的快速使用
使用第三方:
- djangorestframework-jwt:好久不维护,不推荐
- djangorestframework-simplejwt,推荐使用
注册app
在INSTALLED_APPS中添加djangorestframework_simplejwt应用程序:
INSTALLED_APPS = [
# ...
'rest_framework_simplejwt',
# 下面这个app用于刷新refresh_token后,将旧的加到到blacklist时使用
'rest_framework_simplejwt.token_blacklist'
# ...
]
添加simplejwt到身份验证类列表中:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}
添加URL路由
以下为根路由中的设置,可以根据需要进行调整
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView
)
urlpatterns = [
...
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
# 下面这个是用来验证token的,根据需要进行配置
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]
配置SIMPLE_JWT
在settings.py文件中添加以下配置:
# JWT配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access Token的有效期
'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期
# 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整
# 是否自动刷新Refresh Token
'ROTATE_REFRESH_TOKENS': False,
# 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
'BLACKLIST_AFTER_ROTATION': False,
'ALGORITHM': 'HS256', # 加密算法
'SIGNING_KEY': settings.SECRET_KEY, # 签名密匙,这里使用Django的SECRET_KEY
# 如为True,则在每次使用访问令牌进行身份验证时,更新用户最后登录时间
"UPDATE_LAST_LOGIN": False,
# 用于验证JWT签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。
"VERIFYING_KEY": "",
"AUDIENCE": None,# JWT中的"Audience"声明,用于指定该JWT的预期接收者。
"ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。
"JSON_ENCODER": None, # 用于序列化JWT负载的JSON编码器。默认为Django的JSON编码器。
"JWK_URL": None, # 包含公钥的URL,用于验证JWT签名。
"LEEWAY": 0, # 允许的时钟偏差量,以秒为单位。用于在验证JWT的过期时间和生效时间时考虑时钟偏差。
# 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer"
"AUTH_HEADER_TYPES": ("Bearer",),
# 包含JWT的HTTP请求头的名称。默认为"HTTP_AUTHORIZATION"
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
# 用户模型中用作用户ID的字段。默认为"id"。
"USER_ID_FIELD": "id",
# JWT负载中包含用户ID的声明。默认为"user_id"。
"USER_ID_CLAIM": "user_id",
# 用于指定用户身份验证规则的函数或方法。默认使用Django的默认身份验证方法进行身份验证。
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
# 用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"。
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
# JWT负载中包含令牌类型的声明。默认为"token_type"。
"TOKEN_TYPE_CLAIM": "token_type",
# 用于指定可以使用的用户模型类。默认为"rest_framework_simplejwt.models.TokenUser"。
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
# JWT负载中包含JWT ID的声明。默认为"jti"。
"JTI_CLAIM": "jti",
# 在使用滑动令牌时,JWT负载中包含刷新令牌过期时间的声明。默认为"refresh_exp"。
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
# 滑动令牌的生命周期。默认为5分钟。
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
# 滑动令牌可以用于刷新的时间段。默认为1天。
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
# 用于生成访问令牌和刷新令牌的序列化器。
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
# 用于刷新访问令牌的序列化器。默认
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
# 用于验证令牌的序列化器。
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
# 用于列出或撤销已失效JWT的序列化器。
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
# 用于生成滑动令牌的序列化器。
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
# 用于刷新滑动令牌的序列化器。
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
【3】自定义token中返回的信息
默认的信息
JSON Web Token由三部分组成,这些部分由点(.)分隔,分别是header(头部),payload(有效负载)和signature(签名)。
header(头部): 识别以何种算法来生成签名;
pyload(有效负载): 用来存放实际需要传递的数据;
signature(签名): 安全验证token有效性,防止数据被篡改。
默认的设置中,如果对第二部分进行解码,你就会发现,它保存了一个字典的数据,包括以下信息:
{
"token_type": "access", # 令牌类型
"exp": 1685341009, # 令牌到期时间戳
"iat": 1685340709, # 令牌生成时间戳
"jti": "561*****************03c", # JWT ID的声明
"user_id": 1, # django中用户对应的id
}
如果需要为它添加更多的用户信息,比如用户名称,用户手机号,email等,只要是系统后台中已存储的,都可以放在第二部分进行返回,由前端进行解码后,就可以提取使用了。具体步骤如下:
自定义返回信息
自定义序列化器
该序列化器需继承TokenObtainPairSerializer类,可以在任意一个app中的seralizers.py中增加该自定义的序列化器,并重写了get_token()方法。在这个方法中,我们可以自定义Payload,将用户的信息添加到Token中。
假定为app01,则在app01/seralizers.py中增加以下代码:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# 增加想要加到token中的信息
token['username'] = user.username
token['email'] = user.email
# ...
return token
【3】JWT使用auth表签发token,自定制格式(3星)
# 只需要写一个函数,在配置文件中配置
# 函数
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code':100,
'msg':'登录成功',
'username':user.username,
'token': token,
}
# 配置文件
JWT_AUTH ={
# token的过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
# 自定义认证结果:见下方序列化user和自定义response
# 如果不自定义,返回的格式是固定的,只有token字段
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
}
【4】多方式登录
可以通过多种方式登录,如用户名/手机号/邮箱
视图类
class UserView(GenericViewSet, CreateModelMixin):
serializer_class = None
def get_serializer_class(self):
if self.action == 'login':
return UserLSerializer
else:
return UserRSerializer
@action(methods=['POST'], detail=False)
def register(self, request):
res = super().create(request)
return Response({'code': 200, 'message': '注册成功', 'result': res.data})
@action(methods=['POST'], detail=False)
def login(self, request):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
refresh = serializer.context.get('refresh')
access = serializer.context.get('access')
return Response({'code': 200, 'message': '登录成功', 'refresh': refresh, 'access': access})
return Response(serializer.errors)
序列化类
class UserLSerializer(serializers.Serializer):
# 因为登录的时候只输入用户名密码
# 所以只需要序列化两个字段
username = serializers.CharField()
password = serializers.CharField()
# 这个方法用来拿到登录对象
def _get_user(self, attrs):
# 手机号正则表达式
phone_regex = r'^1[3-9]\d{9}$'
# 邮箱正则表达式
email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
# 拿到 手机号/邮箱/用户名
username = attrs.get('username')
# 拿到密码
password = attrs.get('password')
# 下面分别做判断 看用户名是以什么方式登录
if re.match(phone_regex, username):
user = User.objects.filter(mobile=username).first()
elif re.match(email_regex, username):
user = User.objects.filter(email=username).first()
else:
user = User.objects.filter(username=username).first()
if user and user.check_password(password):
return user
def validate(self, attrs):
user = self._get_user(attrs)
# 如果上面验证失败user就为None,就抛异常
if not user:
raise ValidationError('用户名或密码错误')
refresh = RefreshToken.for_user(user)
self.context['refresh'] = str(refresh)
self.context['access'] = str(refresh.access_token)
return attrs