Loading

DRF总结

【一】drf入门规范

【1】web应用模式

前后端不分离

# 模板渲染在后端完成

drf(详细)_django_03

前后端分离(主流)

# 后端就只负责写接口,前端来调用,通信使用json格式

# 多端(web、app...)都可以使用同一个接口

drf(详细)_字段_04

【2】API接口

前端可以通过访问得到数据的url被称为API接口

# 四大特点
# 1.url
长得像返回数据的url链接

# 2.请求方式
get、post、put、patch、delete

# 3.请求参数
json或xml(老项目)格式的key-value类型数据

# 4.响应结果
json或xml格式的数据

【3】接口则是工具

  1. Postman
  2. 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请求全局与局部配置

请求默认支持三种编码格式

  1. urlencoded
  2. json
  3. 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默认

【四】视图组件

drf(详细)_django_21

【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】权限类的编写

  1. 写一个类,继承BasePermission
  2. 重写has_permission 方法
  3. 在其内部进行权限校验的定义
    • 有权限返回True
    • 无权限返回False
  4. 定制返回提示
    • 通过定义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】权限类的使用

  1. 局部使用
  2. 全局使用
  3. 局部禁用
# 局部使用 
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】频率类的编写

  1. 写一个类,继承SimpleRateThrottle
  2. 重写get_cache_key,返回唯一标识,返回什么就以什么做限制
  3. 重写类属性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】频率类的使用

  1. 局部使用
  2. 全局使用
  3. 局部禁用
# 局部使用
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】两点说明

  1. 视图集ViewSet中的retrieve在文档网站内叫read
  2. 参数的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

drf(详细)_django_40

drf(详细)_序列化_41

【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
posted @ 2024-05-04 19:25  HuangQiaoqi  阅读(31)  评论(0编辑  收藏  举报