首页 > 技术文章 > drf(详细)

chijintao 2021-07-13 15:31 原文

DRF

DRF APIView请求生命周期流程图

img

img

drf入门规范

Web应用模式

前后端不分离

# 模板渲染在后端完成

前后端不分离

前后端分离(主流)

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

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

前后端分离

API接口

通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介

四大特点

url

  • 长得像返回数据的url链接

请求方式

  • get、post、put、patch、delete

请求参数

  • json或xml(老项目)格式的key-value类型数据

相应结果

  • json或xml格式的数据

接口测试工具:Postman

官网下载https://www.getpostman.com/downloads/

RESTful API规范(3星)

写接口的规范,大部分接口都会按照这个规范去写(Web API接口的设计风格)

# 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"
        }
		]
}

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(老项目)

CBV源码分析(2星)

1 views.Book.as_view()执行完,一定是一个函数的内存地址

2 as_view是View类的类方法,类来调用

image-20210712230515567.png

3 as_view中的view是一个闭包函数

t6yClDvJ1mSqBz7.png

4 执行了View类的dispatch

YnVcwmbhOkAz1NC.png

X4UrdcEtFkosxhm.png

拓展

# 如果在当前视图类中重写dispatch方法,则可以实现:在get、post等方法执行之前或执行之后去执行重写的代码,完成类似装饰器的效果

例如
	def dispatch(self, request, *args, **kwargs):
        # 代码(get、post等方法执行之前执行)
        response = super().dispatch(request, *args, **kwargs)
        # 代码(get、post等方法执行之后执行)
        return response

drf基本使用及流程分析(3星)

继承APIView使用

from rest_framework.views import APIView
from .models import Student
from .serializers import Student_serializers
from rest_framework.response import Response


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)

APIView的执行流程

1 APIView继承了django的View

EuNedGk5KbrJHwx.png

2 APIView中重写了as_view

rXMFCHUbcgnSOVh.png

3 执行self.dispatch() ---> APIView的dispatch

ti937vJdkYojVL1.png

WwzN8m5tXhKVcID.png

4 在视图类中使用的request对象是新的request对象,而老的则是request._request

dhVZj9XbIBoweHx.png

dMvBYCriDjX93z4.png

5 新的request对象中有一个属性:data

'''
1 data是post请求携带的数据 ---> 字典
2 无论是什么编码格式,只要是post提交的数据,都在request.data中
3 以后再取值,都从request.data中取
'''

6 结论:继承了APIView后,request对象变成新的request,其他大部分和原来一样使用

1 以后如果使用了drf,继承APIView(drf提供了很多view,他们都是继承自APIView)
	注意:
    '''
    包装出了一个新的request,在视图函数中使用时,跟原先没有区别
    
    取post提交的数据,不要再从request.POST中取了,要从request.data中取
    
    取get提交的参数,尽量不从request。GET中取了,要从request.query_params中取
    '''
   
2 Request类(drf的)需要掌握
	
    2.1 request.data			# 方法包装成了数据属性
    2.2 request.query_params	# 就是request._request.GET
    2.3 request.FILES			# 上传的文件
    
    # 大部分用起来和原来一样
    
3 APIView类
	'''
	包装了新的request
	执行了认证、权限、频率...
	处理了全局异常
	包装了response对象
	'''

drf配置

# 配置
    # 1 后期,drf的所有配置,都写在这个字典中
    # 2 drf有个默认配置文件(drf源码的 ---> setting.py),如果项目的配置文件配置了,优先使用项目的,如果没有配置,使用内置的
    # 3 返回样式的配置,一般不配置
    REST_FRAMEWORK={
		...
    }

序列化组件

介绍

作用

1 序列化,序列化器(类)会把模型对象(Book对象,Queryset对象)转换成字典,经过response以后变成json字符串

2 反序列化,把客户端发送过来的数据,经过request以后变成字典(request.data),序列化器(类)可以把字典转成模型

3 反序列化,完成数据校验功能

本质

# 本质就是写一个类,继承一个基类,可以完成序列化、反序列化和数据校验

序列化器之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('年龄')

Serializers高级使用(5星)

1 source字段的作用

# source:可以对应表模型的字段和方法(返回结果是什么,字段就是什么)
	# 一般用来做一对多,多对多的字段返回
    '''
    注:
    	source如果是字段,会显示字段,如果是方法,会执行方法,不用加括号
    '''

2 SerializerMethodFidld使用方法

# 一般用来添加需要查询该表以外的其他表(或关联表)的字段、方法

course_detail = serializers.SerializerMethodField()
def get_course_detail(self, obj):
	return {'name':obj.course.name,'teacher':obj.course.course_teacher}

3 数据校验

反序列化时需要进行数据校验

'使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或者保存成模型类对象'

# 在获取反序列化的数据前,必须调用is_valid()方法进行验证,验证成功返回True,否者返回False

# 1 验证失败,可以通过序列化器对象的error属性获取错误信息,返回字典,包含了字段和字段的错误.如果是非字段错误,可以通过修改REST-framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的key值

# 2 验证成功,可以通过序列化器对象validdated_data属性获取数据


# 3 字段自己的校验规则

class BookInfoSerializer(serializers.Serializer):
    """图书数据序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名称', max_length=20)
    bpub_date = serializers.DateField(label='发布日期', required=False)
    bread = serializers.IntegerField(label='阅读量', required=False)
    bcomment = serializers.IntegerField(label='评论量', required=False)
    image = serializers.ImageField(label='图片', required=False)
    
# 3.1 通过构造序列化器对象,并将要反序列化的数据传递给data构造参数,进行验证(views.py)
	
def post(self, request, *args, **kwargs):
    ser = Publish_modelserializers(request.data)
    if ser.is_valid():
        ser.save()
        return Response(ser.data)
    else:
        return Response({msg:ser.errors.get('name')})
    
ps: is_valid()方法还会在验证失败时抛出异常serializers.ValidationError,可以通过传递raise_exception=True参数开启,REST-framework接收到此异常,会向前端返回HTTP 400 Bad Request
        
# 3.2 补充定义验证行为

'注意'
	验证顺序是:字段自己的限制 ---> 自己的局部钩子 --->(全部字段以及局部验证完毕以后再验证全局钩子)

	3.2.1 validate_字段名(局部钩子)
    
	def validate_name(self,name):
        if name.startswith('sb'):
            raise ValidationError('不能以sb开头')
        else:
            return name
    
    3.2.2 validate(全局钩子)

    def validate(self, attrs):
        bread = attrs['bread']
        bcomment = attrs['bcomment']
        if bread < bcomment:
            raise serializers.ValidationError('阅读量小于评论量')
        return attrs
    
    3.2.3 validators(在字段中添加validators参数,补充验证行为)
    
    def about_django(value):
    if 'django' not in value.lower():
        raise serializers.ValidationError("图书不是关于Django的")

	class BookInfoSerializer(serializers.Serializer):
    	"""图书数据序列化器"""
        id = serializers.IntegerField(label='ID', read_only=True)
        btitle = serializers.CharField(label='名称', max_length=20, validators=[about_django])
        bpub_date = serializers.DateField(label='发布日期', required=False)
        bread = serializers.IntegerField(label='阅读量', required=False)
        bcomment = serializers.IntegerField(label='评论量', required=False)
        image = serializers.ImageField(label='图片', required=False)
    

模型类序列化器ModelSerializer(跟表模型有关联)(5星)

1 介绍

drf为我们提供了一个ModelSerializer模型序列化器来帮助我们快速创建一个Serializer类

2 ModelSerializer与Serializer的区别

  • 基于模型类自动生成一系列字段
  • 基于模型类自动为Serializer生成validators,比如unique_together
  • 包含默认的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
            } 
            }
        }

序列化器字段和字段参数(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=)

选项参数:

参数名称 作用
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
max_value 最小值
min_value 最大值

通用参数:

参数名称 说明
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
validators 该字段使用的验证器
error_messages 包含错误编号与错误信息的字典
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息

局部钩子和全局钩子源码分析(2星)

1 入口是ser.is_valid(),是BaseSerializer的方法

7dILKc5ksVWO4NR.png

2 找到全局钩子

OdbuBatSUYpG73C.png

3 找到局部钩子

OHmK9NrponcjDXq.png

ImHyxpcjvD14gXC.png

序列化组件部分源码分析(2星)

# 前戏:对象的实例化过程:__new__在__init__之前执行

# 1 序列化类在实例化的时候,先调用的是BaseSerializer中的__new__方法

# 2 ListSerializer与自己Serializer有什么联系?
	[ser1,ser2,ser3]	ser

jgpMR7V3YW6BIdn.png

HxlAO1sVUpLuiq6.png

请求与响应(3星)

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,]

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的使用
'''

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默认

视图组件(5星)

Uc5s1hblaJnoNqA.png

两个视图基类

APIView

# APIView 是rest-framework提供的所有视图的基类,继承自Django的View父类

# 在APIView中仍以常规的类视图定义方法来实现get()、post()...请求方式的方法

APIView与View的区别

  • 传入到视图方法中的是rest-framework的Request类的request对象,而不是Django的HttpResponse对象
  • 视图方法返回的是rest-framework的Response类的response对象,视图会因为响应数据设置(rander)符合前端要求的格式
  • 任何APIException异常都会被捕获到,并处理成合适的响应信息
  • 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制

支持定义的类属性

  • authentication_classes列表或元组(身份认证类)
  • permissoin_classes列表或元组(权限检查类)
  • throttle_classes列表或元组(流量/频率控制类)

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)
      • request :当前视图的请求对象
      • view :当前请求的类视图对象
      • format :当前请求期望返回的数据格式

提供的关于数据库查询的属性与方法

  • 属性
    • queryset :查询的表名.objects.all() (这里不写all会自动添加上all,但推荐都写上)
  • 方法
    • get_queryset(self) :获取查询需要用到的所有数据
    • get_object(self) :获取查询需要用到的单条数据 (若详情访问的模型类对象不存在,会返回404)

五个视图扩展类

这五个扩展类需要搭配GenericAPIView父类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法

CreateModelMixin

  • 内部有create方法 :新增

ListModelMixin

  • 内部有list方法 :查询所有

DestroyModelMixin

  • 内部有destroy方法 :删除单条

UpdateModelMixin

  • 内部有update方法 :修改一条

RetrieveModelMixin

  • 内部有retrieve方法 :查询单条

九个视图子类

ListAPIView

  • list

CreateAPIView

  • create

UpdateAPIView

  • update

DestroyAPIView

  • destroy

RetrieveAPIView

  • retrieve

ListCreateAPIView

  • list+create

RetrieveUpdateDestroyAPIView

  • retrieve+update+destroy

RetrieveDestroyAPIView

  • retrieve+destroy

RetrieveUpdateAPIView

  • retrieve+update

视图集

ViewSetMixin

# 核心:重写了as_view方法

# 能够实现:请求方式和视图类中方法的映射

# 结论:只要继承ViewSetMixin的视图类,路由中as_view()里就要传action参数(字典)

例
	path('books/', views.BookView.as_view({'get':'list','post':'create'}))

Pt2UsRL61ycJu8W.png

ViewSet

  • 继承自APIView与ViewSetMixin,作用与APIView类似,提供了身份认证、权限校验、流量管理...
  • 主要通过继承ViewSetMixin来实现在调用as_view()时传入字典的映射处理
  • 没有提供任何动作action方法,需要我们自己实现action方法
  • 需要自己编写list、retrieve、create、update、destroy等方法

GeneriViewSet

  • 继承自GenericAPIView与ViewSetMixin
  • 实现了调用as_view()时传入字典的映射处理
  • 提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用

ModelViewSet

  • 继承自GenericViewSet,包括了五个视图扩展类

ReadOnlyModelViewSet

  • 继承自GenericViewSet,包括了ListModelMixin、RetrieveModelMixin

路由组件(5星)

Routers

对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来快速实现路由创建

  • SimpleRouter(常用)

  • DefaultRouter(多了一些花里胡哨的路由,用得少)

使用方法

from django.contrib import admin
from django.urls import path, include
from app01 import views
from rest_framework.routers import SimpleRouter


router = SimpleRouter()
router.register('book', views.Book_modelviewset)
router.register('publish', views.Publish_modelviewset)
router.register('', views.Login, basename='login')


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls))		# 方式一
]


urlpatterns += router.urls				# 方式二(选一种使用)

试图类中派生的方法,自动生成路由(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星)

判断用户是否登录

简单使用

# auth.py(自己建的任意名称的py文件)

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models


class Login_authentication(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('请先登录')
            
# views.py

class Login(ViewSetMixin, APIView):
    authentication_classes = [Login_authentication]		# 局部使用
    @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':'登录失败'})
        
'''
全局配置:
	REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':[
        'app01.auth.Login_authentication'
    ]
}

局部禁用:
	authentication_classes = []

'''

源码分析

YcjEfW3CunFB9K4.png

image-20210705201337157.png

image-20210705201527608.png

image-20210705201731741.png

image-20210705205949346.png

image-20210705210247174.png

image-20210705210417976.png

image-20210705210543266.png

image-20210705210958562.png

权限组件(4星)

简单使用

# 自己建的任意名字的py文件(auth.py)

from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import BasePermission
from app01 import models

class User_permission(BasePermission):
    def has_permission(self, request, view):
        if models.User.user_type == 1:
            return True
        else:
            raise PermissionDenied(request.user.get_user_type_display()+'权限不够')
            
# views.py

class Login(ViewSetMixin, APIView):
    permission_classes = [User_permission]		# 局部使用
    @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':'登录失败'})
        
'''
全局配置:
	REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES':[
        'app01.auth.User_permission'
    ]
}

局部禁用:
	permission_classes = []	
'''

源码分析

image-20210706172319765.png

image-20210706173120291.png

频率组件(5星)

自定义频率类

# 自定义的逻辑
#(1)取出访问者ip
#(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
#(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
#(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
#(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
class MyThrottles():
    VISIT_RECORD = {}
    def __init__(self):
        self.history=None
    def allow_request(self,request, view):
        #(1)取出访问者ip
        # print(request.META)
        ip=request.META.get('REMOTE_ADDR')
        import time
        ctime=time.time()
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip]=[ctime,]
            return True
        self.history=self.VISIT_RECORD.get(ip)
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        while self.history and ctime-self.history[-1]>60:
            self.history.pop()
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        if len(self.history)<3:
            self.history.insert(0,ctime)
            return True
        else:
            return False
    def wait(self):
        import time
        ctime=time.time()
        return 60-(ctime-self.history[-1])

简单使用

# until.py(任意名称py文件)

class User_throttle(SimpleRateThrottle):
    scope = 'user'
    def get_cache_key(self, request, view):
        return request.user.username		# 返回谁,就以谁做限制

# views.py
throttle_classes = [User_base_throttle,]

# 全局配置
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'user':'5/m'
    },
}

# 局部禁用
throttle_classes = []

源码分析

image-20210706172319765.png

image-20210706185151028.png

image-20210706185449845.png

image-20210706185753175.png

image-20210706190151150.png

image-20210706190316846.png

过滤与排序(4星)

简单使用

查询所有才需要过滤(根据条件过滤),排序(按某个规则排序)

内置类使用

# 内置过滤类(在视图类中配置)

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

第三方插件使用(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 写一个函数
    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的下滑加载更多)

PageNumberPagination基本分页

  • 重要类属性
    • page_size = api_settings.PAGE_SIZE (每页显示条数)
    • page_query_param = 'page' (查询时用的参数)
    • page_size_query_param = None (更改返回条数)
    • max_page_size = None (每页最大显示条数)
  • 使用
# 1 写一个类,继承PageNumberPagination,重写4个类属性

class CommonPageNumberPagination(PageNumberPagination):
    page_size = 2
    page_query_param = 'page'
    page_size_query_param = 'search'
    max_page_size = 5
    
# 2 在视图类中配置写好的分页类

class BookAPIView(ViewSetMixin, ListAPIView):
    queryset = models.Books.objects.all()
    serializer_class = BookModelSerializer
    pagination_class = CommonPageNumberPagination

LimitOffsetPagination偏移分页

  • 重要的类属性
    • default_limit = api_settings.PAGE_SIZE (每页显示条数)
    • limit_query_param = 'limit' (查询时用的参数)
    • offset_query_param = 'offset' (offset偏移的查询参数)
    • max_limit = None (每页最大显示条数)
  • 使用
# 1 写一个类,继承LimitOffsetPagination,重写4个类属性

class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 3
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 5
    
# 2 在视图类中配置写好的分页类

class BookAPIView(ViewSetMixin, ListAPIView):
    queryset = models.Books.objects.all()
    serializer_class = BookModelSerializer
    pagination_class = CommonLimitOffsetPagination

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
  • 优缺点
# 优点:速度最快,数据量越大,越有优势(没有那么大的数据量,用的不多)

# 缺点:只能前一页和后一页,不能直接跳到某一页

继承APIView实现三种分页方式

class BookAPIView2(APIView):
    def get(self, request, *args, **kwargs):
        book_list = models.Books.objects.all()
        pagination = CommonPageNumberPagination()
        book_list2 = pagination.paginate_queryset(book_list, request, self)
        ser = BookModelSerializer(instance=book_list2, many=True)
        return pagination.get_paginated_response(ser.data)

自动生成接口文档(3星)

  • coreapi
  • swagger

使用coreapi自动生成

# 第一步:安装coreapi

# 第二步:路由中配置

from rest_framework.documentation import include_docs_urls

urlpatterns = [
    path('docs/', include_docs_urls(title='查询所有')),
]

# 第三步:配置文件中配置

REST_FRAMEWORK = {
    ## 接口文档配置
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}

ps:'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',
    
# 第四步:写接口,加注释

# 第五步:访问

ps:extra_kwargs = {
    '字段':{'help_text':'xxxx'}
}

RBAC(4星)

image-20210708172129848.png

img

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

介绍(5星)

Json Web Token(JWT):一种前后端的认证方式

构成和工作原理

# 三段式: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可以信任,解析出当前用户(荷载),继续往后走

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的倍数,如果不足,需要用=号补齐
'''

JWT快速使用(2星)

使用第三方:

签发

# 只需要在路由中加入,向这个地址发送post请求,携带用户名密码,就可以签发token

	path('login/', obtain_jwt_token)

# 内部使用的是Django自带的auth组件(app)的user表

# 只需要在前端向配置的地址,发送post请求,携带用户名密码,就可以签发token

认证

# 在视图类中配置,认证类和权限类(simplejwt只需要认证类)

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated

class BookView(ViewSetMixin,ListAPIView):
    authentication_classes = [JSONWebTokenAuthentication,]
    permission_classes = [IsAuthenticated,]
    
# 前端访问,需要在请求头中加入,如果不携带,或者篡改了,认证就无法通过
	key:Authorization
    value:jwt xxx(以空格切割取token)

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',
        }

obtain_jwt_token源码分析(2星)

image-20210708200119777.png

image-20210708201115115.png

image-20210708202015404.png

image-20210708202554768.png

image-20210708202924385.png

image-20210708203058131.png

image-20210708203535421.png

image-20210708204823746.png

基于jwt的认证类(5星)

'''
重点逻辑在authenticate方法中:
	1 取出客户端传入的token(后端自己规定是从头/路径中拿)
	2 验证jwt的签名(使用模块提供的)
	3 通过payload得到当前登录用户对象(使用模块提供的)
	4 返回user,token
'''

class JWTAuthentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        print(request.META)
        # token=request.query_params.get('HTTP_AUTHORIZATION',None)
        token=request.META.get('HTTP_AUTHORIZATION',None)
        if token:
            # 校验token是不是过期了,是不是合法,
            try:
                payload = jwt_decode_handler(token)
            except jwt.ExpiredSignature:

                raise AuthenticationFailed('token过期')
            except jwt.DecodeError:

                raise AuthenticationFailed('token认证失败')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('token不合法')
        else:
           raise AuthenticationFailed('token没有携带')
        user = self.authenticate_credentials(payload)

        return (user, token)
        
'''
三种方式得到user:
	1 继承,直接使用该类的方法
	2 把方法copy出来,导入一箩筐
	3 完全自己写(copy出来改吧改吧)
'''

基于自定义User表,签发token(5星)

# urls.py

router=SimpleRouter()
router.register('books',views.BookView)
router.register('user',views.UserInfoView,basename='user')
urlpatterns = [
    path('', include(router.urls)),
]

# views.py

class UserInfoView(ViewSet):
    @action(methods=['POST'],detail=False)
    def login(self,request):
        username=request.data.get('username')
        password=request.data.get('password')
        res={'code':'100','msg':'登录成功'}
        user=User.objects.filter(username=username,password=password).first()
        if user:
            # 登录成功,生成token,提供了(去找)
            payload = jwt_payload_handler(user)
            token=jwt_encode_handler(payload)
            res['token']=token

        else:
            res['code']=101
            res['msg']='用户名或密码错误'
        return Response(res)
    
# until.py(名字随意)

class JWTMyUserAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token=request.META.get('HTTP_AUTHORIZATION',None)
        if token:
            try:
                payload = jwt_decode_handler(token)
                print(payload)
            except jwt.ExpiredSignature:

                raise AuthenticationFailed('token过期')
            except jwt.DecodeError:

                raise AuthenticationFailed('token认证失败')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('token不合法')
        else:
           raise AuthenticationFailed('token没有携带')

        user=User.objects.get(pk=payload.get('user_id'))
        # user=User(id=payload.get('user_id'),username=payload.get('username'))
        # 优化,减少数据库压力()
        # user={'id':payload.get('user_id'),'username':payload.get('username')}
        return (user, token)

多方式登录(5星)

1 使用用户名、邮箱、手机号 + 密码都能登录成功

2 可以使用auth的User表,也可以自定义用户表

3 扩写auth的User表(没有迁移之前使用--->继承AbstractUser)
	硬写:
    	3.1 删库(要有备份)
        3.2 删除迁移记录(app的迁移记录,auth、admin的迁移记录(源码中))
        
  

# views.py

class Userinfo(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        response = {'code':1000, 'msg':'登录成功', 'token':None}
        ser = UserModelSerializer(data=request.data)
        if ser.is_valid():
            response['token'] = ser.context.get('token')
        else:
            response['code'] = 1001
            response['msg'] = ser.errors
        return Response(response)

    
# serializer.py(名字随意)

class UserModelSerializer(serializers.ModelSerializer):
    username = serializers.CharField()
    class Meta:
        model = Userinfo
        fields = ['username', 'password']

    def validate(self, attrs):
        user = self._get_user(attrs)
        token = self._get_token(user)
        self.context['token'] = token
        return attrs

    def _get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        if re.match('^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$', username):
            user = Userinfo.objects.filter(phone=username).first()
        elif re.match('^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$', username):
            user = Userinfo.objects.filter(email=username).first()
        else:
            user = Userinfo.objects.filter(username=username).first()
        if user:
            if user.check_password(password):
                return user
            else:raise ValidationError('用户名或密码错误')
        else:
            raise ValidationError('用户名或密码错误')


    def _get_token(self, user):
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        return token

    
# until.py(名字随意)

class User_jwt_authentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        # print(request.META)
        token = request.META.get('HTTP_AUTHORIZATION')
        # return None
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('签名过期,请重新登录')
        except jwt.DecodeError:
            raise AuthenticationFailed('认证错误')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('认证不合法')
        user = self.authenticate_credentials(payload)
        return (user, token)

    def authenticate_credentials(self, payload):
        username = payload.get('username')
        if not username:
            raise AuthenticationFailed('用户名不存在')

        try:
            user = Userinfo.objects.filter(username=username).first()
        except Userinfo.DoesNotExist:
            raise AuthenticationFailed('无效的签名')

        if not user.is_active:
            raise AuthenticationFailed('用户已注销')

        return user

推荐阅读