Django-DRF

1 DRF

1.1 简介

Django Rest Framework 依赖于Django框架 是构建web restful api的工具
可以快速将Django Orm 进行序列化与反序列化
提供多种类视图 Mixin扩展类 子类视图 视图集等
支持多种身份认证和权限、限流的管理方式

# 序列化:模型类对象转换成json、xml等格式化数据的过程(查询输出)
# 反序列化:json、xml等格式数据装换成模型类对象的过程(修改 删除 添加)

1.2 安装

$ pip install django
$ pip install djangorestframework

1.3 使用

# 在django项目settings.py添加
INSTALLED_APPS = [
    'django.contrib.admin',  # 管理员站点
    'django.contrib.auth',  # 认证授权系统
    'django.contrib.contenttypes',  # 内容类型框架
    'django.contrib.sessions',  # 会话框架
    'django.contrib.messages',  # 消息框架
    'django.contrib.staticfiles',  # 管理静态文件的框架
    'rest_framework',  # 新增扩展drf应用
    'classApp',  # 新增自定义应用app
]
--相关数据入库
insert into tb_group(gp_name) values ('g1'),('g2');
insert into tb_cls(cls_name, cls_group_id) values ('c1g1', 1), ('c1g2', 1), ('c1g2', 2),('c2g2', 2);
insert into tb_stu(stu_name, stu_sex, stu_scores, stu_cls_id) values ('s1', 'male', 100, 1),('s2', 'male', 100, 1),
                                                                     ('s3', 'female', 100, 2),('s4', 'male', 100, 2),
                                                                     ('s5', 'female', 100, 3),('s6', 'female', 100, 3),
                                                                     ('s7', 'male', 100, 4),('s8', 'male', 100, 4);

1.4 字段类型和入参

一、字段类型
1.布尔类型
	BooleanField:布尔类型(True|False)
	NullBooleanField: 布尔类型(True|False|Null)
        
2.字符串类型
    CharField: 用于表示文本数据
    EmailField:用于表示电子邮件地址
    RegexField:字符串匹配特定正则表达式
    SlugField: CharField子类 正则字段
    URLField: 用于URL地址存储
    UUIDField(format='hex_verbose|hex|int|urn'): UUID类型  用于表示UUID(通用唯一识别码)类型的数据 根据输入字符判断是否符合uuid格式
        'hex_verbose':"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
        'hex' : "5ce0e9a55ffa654bcee01238041fb31a"
        'int': "123456789012312313134124512351145145114"
        'urn': "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
    IPAddressField(protocol='both', unpack_ipv4=False, **options):IP地址类型 根据输入字符自动校验是否符合IP格式

3.整数类型
    IntegerField:整数类型 表示数字数据
    FloatField:浮点数类型 表示小数数据
    DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None):高精度浮点类型
    	-max_digits: 最多位数
        -decimal_palces: 小数点位置
            
4.日期时间类型
	DateTimeField:日期时间类型 表示时间戳等时间数据
    DateField:日期类型 表示日期数据
    TimeField:时间类型 表示时间数据
    DurationField:用于表示时间间隔类型的数据 可以表示一段时间的长度 根据输入的字符串转换为时间间隔类型
    
5.枚举类型
    ChoiceField(choices=):枚举类型 可以定义一个选择列表 用于表示一组固定的选项
    MultipleChoiceField(choices):用于表示多选框类型的数据 它可以定义一个选项列表 用于表示可选的选项
    
6.文本类型
    FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL):文件类型 表示文件数据
    ImageField:图像类型 表示图片数据
    
7.其他类型
    ListField(child=None, min_length=None, max_length=None):列表类型 表示一个列表数据
    DictField(child=None):字典类型 表示一个字典数据
    
二、常规入参选项
1. 通用
	read_only: 
        默认False
        用于指定字段是否只能用于序列化输出 不能用于反序列化输入
        通常用于表示只读数据 在反序列化输入时 该字段的值会被忽略
    write_only:
        默认False 
        用于指定字段是否只能用于反序列化 不能用于序列化输出
        在序列化输出时 该字段的值会被忽略 
    required:
        默认True
        表明该字段在反序列化时必须输入
    default:
        序列化和反序列化时使用的默认值
    error_message:
        包含错误编号与错误信息的字典
    label:
        用于api页面展示详情时 显示的字段名
    allow_blank:
        设置字段是否允许为空字符串 如果设置为True 反序列化时如果该字段的值为空字符串会被认为是有效值
    validators:
        设置字段的验证器  验证器是一个可调用对象 用于验证输入的数据是否符合要求
    allow_null:
        字段值是否允许为None
    help_text:
        api页面中的帮助文本信息
    source:
        指向实际的模型类字段名 可用于别名使用|引用其他模型类字段等

        
2. 特殊
	max_length|min_length:
        默认None
        用于字符串类型
    max_value|min_value:
        默认None
        用于整性类型
        
3. 其他
	序列化器类的入参
    	context:
            在序列化器类中的局部(全局)钩子、create|update方法中 可使用context获取视图类传递过来的数据
        	
        
ps: 定义序列化器类的字段时 如果没有指定read_only和write_only 则这两个参数默认值都为False 表明对应的字段既在序列化时使用 也在反序列化时使用

1.5 实例

# 在目录下创建django项目
$ django-admin startproject djangoTest  
$ cd djangoTest

# 在School项目下添加class应用
$ python manage.py startapp classApp  
$ django-admin startapp classApp

# 使用默认sqlite数据库并做模型类迁移文件初始化
$ python manage.py makemigrations classApp
$ python manage.py migrate 

# 交互式界面创建超管用户(后续各个应用站点页面登录授权需要用到>http://localhost:8000/admin/)  配置完站点可对模型类数据进行数据透视和增上改查操作
python manage.py createsuperuser
# 模型类对象 /classApp/models.py
from django.db import models


class GroupInfo(models.Model):
    """年级类"""
    gp_name = models.CharField(max_length=10, verbose_name='年级名')
    
    class Meta:
        db_table = 'tb_group'
        verbose_name = '年级信息'
        verbose_name_plural = verbose_name  # 保障站点名


class ClsInfo(models.Model):
    cls_name = models.CharField(max_length=10, verbose_name='班级名')
    cls_group = models.ForeignKey(to='GroupInfo', on_delete=models.CASCADE, related_name='clsInfo')
    
    class Meta:
        db_table = 'tb_cls'
        verbose_name = '班级信息'
        verbose_name_plural = verbose_name
        
    def col_formate(self):
        """字段预处理输出且可添加到list_display"""
        return self.cls_name + "-20230101"
    col_formate.short_description = "cls_name字段预处理输出"
    col_formate.admin_order_field = 'id'  # 指定排序字段


class StuInfo(models.Model):
    """学生类"""
    SEX_CHOICE = (('male', '男',),
                  ('female', '女'))
    stu_name = models.CharField(max_length=10, verbose_name='学生名')
    stu_sex = models.CharField(max_length=6, choices=SEX_CHOICE, verbose_name='性别')
    stu_scores = models.PositiveSmallIntegerField(verbose_name='语数英总分')
    stu_cls = models.ForeignKey(ClsInfo, on_delete=models.CASCADE, verbose_name='所属班级', related_name='stuInfo')
    
    class Meta:
        db_table = 'tb_stu'
        verbose_name = '学生信息'
        verbose_name_plural = verbose_name
# 序列化器 classApp/serializers.py
from rest_framework import serializers
from Class.models import ClsInfo, StuInfo, GroupInfo


class GroupModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = GroupInfo  # 序列化器对应的模型类
        fields = ('gp_name',)  # 输出的字段


class ClsInfoModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = ClsInfo
        fields = '__all__'


class StuInfoModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = StuInfo
        fields = '__all__'
# 视图类 classApp/views.py
from rest_framework.viewsets import ModelViewSet
from .serializers import ClsInfoModelSerializer, StuInfoModelSerializer, GroupModelSerializer
from .models import ClsInfo, StuInfo, GroupInfo


class ClsInfoViewSet(ModelViewSet):
    """班级视图集"""
    queryset = ClsInfo.objects.all()  # 查询集
    serializer_class = ClsInfoModelSerializer  # 指定序列化器类


class StuInfoViewSet(ModelViewSet):
    """学生视图集"""
    queryset = StuInfo.objects.all()
    serializer_class = StuInfoModelSerializer


class GroupInfoViewSet(ModelViewSet):
    """年级视图集"""
    queryset = GroupInfo.objects.all()
    serializer_class = GroupModelSerializer
# 应用路由配置 classApp/urls.py
from rest_framework.routers import DefaultRouter
from Class import views

urlpatterns = []
router = DefaultRouter()
router.register('cls', views.ClsInfoViewSet)
router.register('stu', views.StuInfoViewSet)
router.register('group', views.GroupInfoViewSet)
urlpatterns += router.urls
# 项目路由配置-settings.py同级目录下urls.py
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('classApp.urls')),
]
# 应用站点信息配置-classApp/admin.py
from django.contrib import admin
from .models import GroupInfo, ClsInfo, StuInfo


# 模型的管理器
class GroupInfoAdmin(admin.ModelAdmin):
    list_display = ("id", "gp_name")  # 显示字段
    list_per_page = 50  # 每页展示记录 默认100
    ordering = ("-id", )  # 排序 负号标识降序 且支持多个字段
    list_editable = ['gp_name']  # 可编辑字段
    fk_fields = []  # 设置显示外键字段


class ClsInfoAdmin(admin.ModelAdmin):
    #  基础配置
    list_display = ("id", "cls_name", "col_formate", "cls_group")  # 显示字段
    list_per_page = 50  # 每页展示记录 默认100
    ordering = ("-id",)  # 排序 负号标识降序 且支持多个字段
    list_editable = ["cls_name", "cls_group"]  # 可编辑字段
    fk_fields = ["cls_group_id"]  # 设置显示外键字段

    # 操作选项
    actions_on_top = True  # 列表顶部显示
    actions_on_bottom = False  # 列表底部显示

    def my_action(self, queryset):
        """自定义操作"""
        queryset.update(cls_group_id=1)
    my_action.short_description = "自定义action"
    actions = [my_action, ]  # 自定义操作

    # 可在model.py添加col_formate方法 对字段预处理并作为输出列展示

    # 站点增加搜索框
    search_fields = ['id']

    # 站点页面右侧新增过滤器
    list_filter = ["id"]
    # date_hierarchy = ''  # 详细时间分层筛选


class StuInfoAdmin(admin.ModelAdmin):
    list_display = ("id", "stu_name", "stu_sex", "stu_scores", "stu_cls")  # 显示字段
    list_per_page = 50  # 每页展示记录 默认100
    ordering = ("-id",)  # 排序 负号标识降序 且支持多个字段
    list_editable = ["stu_name", "stu_sex", "stu_scores", "stu_cls"]  # 可编辑字段
    fk_fields = ["stu_cls_id"]  # 设置显示外键字段


# 在admin中注册绑定
admin.site.register(GroupInfo, GroupInfoAdmin)
admin.site.register(ClsInfo, ClsInfoAdmin)
admin.site.register(StuInfo, StuInfoAdmin)

# 修改后台管理界面头部希纳是内容和页面标题
admin.site.site_header = 'classApp站点后台'
admin.site.title = 'classApp站点'




2 序列化器

序列化器种类:
	Serializer
        字段
        	需要和模型类匹配手写输入做后续序列化和反序列化
            支持每个字段的自定义验证器validators、可重写validate_<字段名>方法对单个字段做特定校验或者重写validate方法对多个字段校验
        方法
        	调用save()方法需要重写create()和update()方法
        	反序列化提供is_valid()方法验证、验证失败信息通过序列化器类实例对象.errors查看
        外键相关
        	支持正向嵌套序列化器对象serializer 获取外键所有内容|反向嵌套通过related_name实现
        	支持自定义序列化的字段内容get_<字段名>
        
		
	ModelSerializer
    	Serializer子类
     	字段:
        	基于模型类字段生成序列化器类的字段 通过在Meta中fields|exclude定义字段 
            支持每个字段的自定义验证器validators、可重写validate_<字段名>方法对单个字段做特定校验或者重写validate方法对多个字段校验
        方法:
        	调用save()保存不需要重写 ModelSerializer已封装完毕  包含默认的create()和update()方法实现
        外键相关:
        	同Serializer且支持在Meta中配置depath参数获取关联深度
ps:
    序列化过程:
    	orm实体类对象->json|xml等结构化数据
    
    反序列化过程:
        json|xml等结构化数据->orm实体类对象
# 创建模型类(后续模型类基本都是以下面内容为准)
from django.db import models


class GroupInfo(models.Model):
    """年级类"""
    gp_name = models.CharField(max_length=10, verbose_name='年级名')
    
    class Meta:
        db_table = 'group'
        verbose_name = '年级信息'
        
    def __str__(self):
        return self.gp_name
        
    
class ClsInfo(models.Model):
    cls_name = models.CharField(max_length=10, verbose_name='班级名')
    cls_group = models.ForeignKey(to='GroupInfo', on_delete=models.CASCADE, related_name='clsInfo')
    
    class Meta:
        db_table = 'cls'
        verbose_name = '班级信息'


class StuInfo(models.Model):
    """学生类"""
    SEX_CHOICE = (('male', '男',),
                  ('female', '女'))
    stu_name = models.CharField(max_length=10, verbose_name='学生名')
    stu_sex = models.CharField(max_length=6, choices=SEX_CHOICE, verbose_name='性别')
    stu_scores = models.PositiveSmallIntegerField(verbose_name='语数英总分')
    stu_cls = models.ForeignKey(ClsInfo, on_delete=models.CASCADE, verbose_name='所属班级', related_name='stu')
    
    class Meta:
        db_table = 'stu'
        verbose_name = '学生信息'

2.1 序列化-Serializer

# 序列化数据转换过程: 模型类对象->xml/json等结构化数据
# 序列化步骤: 1. 创建orm实体类对象  2.创建对应的序列化器类并关联对象  3. 获取序列化数据
# 创建对应的序列化器类
class GroupSerializer(serializers.Serializer):
    gp_name = serializers.CharField(label='年级名', max_length=10)
    
    WWWW
class StuInfoSerializer(serializers.Serializer):
    SEX_CHOICE = (('male', '男',),
                  ('female', '女'))
    stu_name = serializers.CharField(label='学生名', max_length=10)
    stu_sex = serializers.ChoiceField(choices=SEX_CHOICE, label='性别')
    stu_scores = serializers.IntegerField(label='总分')
    stu_cls = serializers.PrimaryKeyRelatedField(label='所属班级', read_only=True)


class ClsInfoSerializer(serializers.Serializer):
    """数据序列化器"""
    id = serializers.IntegerField(label='id', read_only=True)
    cls_name = serializers.CharField(label='班名', max_length=10)
    # queryset&read_only获取的是关联对象的id
    # cls_group = serializers.PrimaryKeyRelatedField(label='年级', queryset=GroupInfo.objects.all())  # queryset 字段在反序列化使用
    # cls_group = serializers.PrimaryKeyRelatedField(label='年级', read_only=True)  # read_only 字段在序列化使用
    # 获取的是关联的对象
    # cls_group = GroupSerializer()
    
    # 输出的是关联对象对应的__str__值
    # cls_group = serializers.StringRelatedField(label='年级名')  # 相关模型类实现了__str__方法
    # 针对关联多个对象的输出 使用many=True
    # stu = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
    stu = StuInfoSerializer(many=True)
    
$ python manage.py shell

from classApp.models import StuInfo
from classApp.serializers import StuInfoSerializer
stu = StuInfo.objects.get(id=1)  # 创建实体类对象
serializer = StuInfoSerializer(stu)  # 创建序列化器对象
serializer.data  # 获取序列化数据
{'stu_name': 's1', 'stu_sex': 'male', 'stu_scores': 100, 'stu_cls': 1}


2.2 反序列化-Serializer

# 反序列化实际是讲json|xml等结构化数据转换成实体类对象过程
# 基本流程: 1. 创建序列化器对象-s 2.s.is_valid()进行数据校验 3.s.errors获取校验失败信息 4.s.save()持久化数据 5.s.validated_data()获取校验数据(可选)
# ps: 创建序列化器对象时未传递instance参数则s.save()会调用序列化器的create()方法;反之则update()方法被调用 且Serializer需要自己实现creat
# 创建对应的序列化器
class GroupSerializer(serializers.Serializer):
    gp_name = serializers.CharField(label='年级名', max_length=10)


class ClsInfoSerializer(serializers.Serializer):
    """数据序列化器"""
    id = serializers.IntegerField(label='id')
    cls_name = serializers.CharField(label='班名', max_length=10)
    # queryset&read_only获取的是关联对象的id
    # cls_group = serializers.PrimaryKeyRelatedField(label='年级', queryset=GroupInfo.objects.all())  # queryset 字段在反序列化使用
    # cls_group = serializers.PrimaryKeyRelatedField(label='年级', read_only=True)  # read_only 字段在序列化使用
    # 获取的是关联的对象
    # cls_group = GroupSerializer()
    
    # 输出的是关联对象对应的__str__值
    # cls_group = serializers.StringRelatedField(label='年级名')  # 相关模型类实现了__str__方法
    # 针对关联多个对象的输出 使用many=True
    # stu = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    # stu = StuInfoSerializer(many=True)
    
    
# 扩展特定序列化器单个字段校验-方式1(需要在序列化器对象特定字段新增入参validators=[standard_stu_name])
def standard_stu_name(val):
    if not val or not val.startswith("s"):
        raise serializers.ValidationError("stu_name不是s开头")
    return val


class StuInfoSerializer(serializers.Serializer):
    SEX_CHOICE = (('male', '男',),
                  ('female', '女'))
    stu_name = serializers.CharField(label='学生名', max_length=10, validators=[standard_stu_name])
    stu_sex = serializers.ChoiceField(choices=SEX_CHOICE, label='性别', required=True)
    stu_scores = serializers.IntegerField(label='总分', required=True)
    stu_cls = serializers.PrimaryKeyRelatedField(label='所属班级', many=False, queryset=ClsInfo.objects.all())
    # stu_cls = ClsInfoSerializer(many=False, read_only=True)
    
    # 扩展特定序列化器单个字段校验-方式2
    def validated_stu_sex(self, val):
        if not val or val not in ['female', 'male']:
            raise serializers.ValidationError("stu_sex非法")
        return val
    
    # 扩展特定序列化器多个字段校验
    def validate(self, attrs):
        stu_scores = attrs.get("stu_scores")
        stu_cls = attrs.get("stu_cls")
        if stu_scores < 60:
            raise serializers.ValidationError("总分小于60非法")
        try:
            if stu_cls.id > 5:
                raise serializers.ValidationError("所属班级大于5非法")
            return attrs
        except Exception as e:
            raise serializers.ValidationError(f"cls_id非法:{e}")
        
    def create(self, validated_data):
        # print("StuInfoSerializer.create方法被调用")
        return StuInfo.objects.create(**validated_data)
    
    def update(self, instance, validated_data):
        # print("StuInfoSerializer.update方法被调用")
        instance.stu_name = validated_data.get("stu_name", instance.stu_name)
        instance.stu_sex = validated_data.get("stu_sex", instance.stu_sex)
        instance.stu_scores = validated_data.get("stu_scores", instance.stu_scores)
        instance.stu_cls = validated_data.get("stu_cls", instance.stu_cls)
        instance.save()
        return instance
$ python manage.py shell

from classApp.models import StuInfo,ClsInfo
from classApp.serializers import StuInfoSerializer

# 1. 新增
valid_data = {"stu_name": "s11", "stu_sex": "female", "stu_scores": 300, "stu_cls": 2}
ser = StuInfoSerializer(instance=None, data=valid_data)  # 创建序列化器对象
ser.is_valid()  # 数据校验
ser.errors  # 获取校验失败后的提示信息
ser.validated_data  # 获取校验通过后的数据
ser.save()  # 保存数据 调用序列化器对象的create方法
ser.data  # 获取序列化数据

# 2. 更新
invalid_data = {"stu_name": "11", "stu_sex": "f", "stu_scores": 59, "stu_cls": 6}
stu = StuInfo.objects.get(id=1)
ser = StuInfoSerializer(instance=stu, data=invalid_data)  # 创建序列化器对象
ser.is_valid()  # 数据校验
ser.errors  # 获取校验失败后的提示信息
#ser.validated_data  # 获取校验通过后的数据
#ser.save()  # 保存数据 调用序列化器对象的update方法
#ser.data  # 获取序列化数据



2.3 ModelSerializer

# 模型类参考上面
# 序列化器类
# ======== ModelSerializer序列化器
#1.Serializer子类;2.提供默认create()和update()方法 3.基于模型类字段自动生成序列化器类的字段
class GroupModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = GroupInfo  # 序列化器对应的模型类
        fields = ('gp_name',)  # 输出的字段


class ClsInfoModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = ClsInfo  # 指定序列化器类对应的模型类
        fields = '__all__'  # 指定模型类的某些字段生成序列化器类的字段(__all__表示全部)


class StuInfoModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = StuInfo
        # fields = '__all__'
        exclude = ('stu_cls', )  # 不需要序列化器生成的字段且不可和fields共存只能选其一
        read_only_fields = ('id', )  # 只读字段(只用于序列化的字段)
        extra_kwargs = {  # 为自动生成的序列化器类字段添加或者修改原有字段的选项参数
            "stu_scores": {"min_value": 0, "required": True},
        }
$ python manage.py shell
from classApp.serializers import StuInfoModelSerializer
ser = StuInfoModelSerializer() 
ser   # 根据序列化器类meta下fields或者exclude定义的字段生成对应的需要序列化或反序列化字段



3 视图

3.1 视图类

3.1.1 APIView

1.ApiView介绍
    APIView是Django REST Framework提供的所有视图的基类 继承自Django的View类
    APIView与View的区别:
        请求对象:传入到视图中的request对象是REST framework的Request对象 而不再是Django原始的HttpRequest对象 
        响应对象:视图可以直接返回REST framework的Response对象 响应数据会根据客户端请求头Accpet自动转换为对应的格式进行返回
        异常处理:任何APIException的子异常都会被DRF框架默认的异常处理机制处理成对应的响应信息返回给客户端
        其他功能:认证、权限、限流
        	类属性
            	authentication_classes: 认证
                permission_classes: 权限
                throttle_classes: 限流
        

2. Request对象
	视图继承APIView之后 传入视图的request对象是DRF框架提供的Request类的对象 Request类的对象有两个属性 request.data和request.query_params
		request.data
			包含解析之后的请求体数据 已经解析为了字典或类字典 相当于Django原始request对象的body|POST|FILES属性
		request.query_params	
			包含解析之后的查询字符串数据 相当于Django原始request对象的GET属性

3. Response对象
	视图继承APIView之后 响应时可以统一返回Response对象 格式如下:
	from rest_framework.response import Response
	response = Response(<原始响应数据>)
	原始的响应数据 会根据客户端请求头的Accpet 自动转换为对应的格式并进行返回 如:
        application/json	
        	服务器会将原始响应数据转换为json数据进行返回 没指定Accept时 默认返回json
        text/html	
        	服务器会将原始响应数据转换为html网页进行返回
#----classApp/serializers.py
Class GroupSerializer(serializers.Serializer):
    gp_name = serializers.CharField(label='年级名', max_length=10)


class ClsInfoSerializer(serializers.Serializer):
    """数据序列化器"""
    id = serializers.IntegerField(label='id')
    cls_name = serializers.CharField(label='班名', max_length=10)
    # queryset&read_only获取的是关联对象的id
    # cls_group = serializers.PrimaryKeyRelatedField(label='年级', queryset=GroupInfo.objects.all())  # queryset 字段在反序列化使用
    # cls_group = serializers.PrimaryKeyRelatedField(label='年级', read_only=True)  # read_only 字段在序列化使用
    # 获取的是关联的对象
    # cls_group = GroupSerializer()
    
    # 输出的是关联对象对应的__str__值
    # cls_group = serializers.StringRelatedField(label='年级名')  # 相关模型类实现了__str__方法
    # 针对关联多个对象的输出 使用many=True
    # stu = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    # stu = StuInfoSerializer(many=True)
    
    
# 扩展特定序列化器单个字段校验-方式1(需要在序列化器对象特定字段新增入参validators=[standard_stu_name])
def standard_stu_name(val):
    if not val or not val.startswith("s"):
        raise serializers.ValidationError("stu_name不是s开头")
    return val


class StuInfoSerializer(serializers.Serializer):
    SEX_CHOICE = (('male', '男',),
                  ('female', '女'))
    stu_name = serializers.CharField(label='学生名', max_length=10, validators=[standard_stu_name])
    stu_sex = serializers.ChoiceField(choices=SEX_CHOICE, label='性别', required=True)
    stu_scores = serializers.IntegerField(label='总分', required=True)
    stu_cls = serializers.PrimaryKeyRelatedField(label='所属班级', many=False, queryset=ClsInfo.objects.all())
    # stu_cls = ClsInfoSerializer(many=False, read_only=True)
    
    # 扩展特定序列化器单个字段校验-方式2
    def validated_stu_sex(self, val):
        if not val or val not in ['female', 'male']:
            raise serializers.ValidationError("stu_sex非法")
        return val
    
    # 扩展特定序列化器多个字段校验
    def validate(self, attrs):
        stu_scores = attrs.get("stu_scores")
        stu_cls = attrs.get("stu_cls")
        if stu_scores < 60:
            raise serializers.ValidationError("总分小于60非法")
        try:
            if stu_cls.id > 5:
                raise serializers.ValidationError("所属班级大于5非法")
            return attrs
        except Exception as e:
            raise serializers.ValidationError(f"cls_id非法:{e}")
        
    def create(self, validated_data):
        # print("StuInfoSerializer.create方法被调用")
        return StuInfo.objects.create(**validated_data)
    
    def update(self, instance, validated_data):
        # print("StuInfoSerializer.update方法被调用")
        instance.stu_name = validated_data.get("stu_name", instance.stu_name)
        instance.stu_sex = validated_data.get("stu_sex", instance.stu_sex)
        instance.stu_scores = validated_data.get("stu_scores", instance.stu_scores)
        instance.stu_cls = validated_data.get("stu_cls", instance.stu_cls)
        instance.save()
        return instance
#---classApp/urls.py
# PS: 后续url基本一致 只是视图类做更改 直到《视图》-《视图集》-《路由配置优化》章节会有不同写法
from django.conf.urls import url
from . import apiViews

urlpatterns = [
    url(r'^stu$', apiViews.StuInfoListView.as_view()),
    url(r'^stu/(?P<pk>\d+)$', apiViews.StuInfoDetailView.as_view())
]
#---settings.py同级目录的urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'', include('classApp.urls')),  # 配置classApp应用路由
]
#---classApp/apiViews.py
# -*- coding:utf-8 -*-
"""
ApiView使用
"""
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from django.http import Http404

from .serializers import StuInfoSerializer
from .models import StuInfo


class StuInfoListView(APIView):
    
    def get(self, request):
        """
        获取所有学生信息
        :param request:
        :return:
        """
        # 获取所有学生信息
        queryset = StuInfo.objects.all()
        # 序列化所有学生数据
        serializer = StuInfoSerializer(queryset, many=True)
        return Response(serializer.data)
    
    def post(self, request):
        """
        新增一个学生
        :param request:
        :return:
        """
        # 反序列化-数据校验
        serializer = StuInfoSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 反序列化-数据保存(save内部会调用序列化器类的create方法)
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)


class StuInfoDetailView(APIView):
    """
    获取、修改、删除指定学生信息
    """
    def get(self, request, pk):
        """
        获取特定学生信息
        :param request:
        :param pk:
        :return:
        """
        try:
            stu = StuInfo.objects.get(pk=pk)
        except StuInfo.DoesNotExist:
            raise Http404

        # 将学生信息数据进行序列化
        serializer = StuInfoSerializer(stu)
        return Response(serializer.data)

    def put(self, request, pk):
        """
        修改指定学生信息
        :param request:
        :param pk:
        :return:
        """
        try:
            stu = StuInfo.objects.get(pk=pk)
        except StuInfo.DoesNotExist:
            raise Http404

        # 反序列化-数据校验
        serializer = StuInfoSerializer(stu, data=request.data)
        serializer.is_valid(raise_exception=True)
        # 反序列化-数据保存(save内部会调用序列化器类的update方法)
        serializer.save()
        return Response(serializer.data)

    def delete(self, request, pk):
        """
         删除指定学生信息
        :param request:
        :param pk:
        :return:
        """
        try:
            stu = StuInfo.objects.get(pk=pk)
        except StuInfo.DoesNotExist:
            raise Http404
        stu.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


3.1.2 GenericAPIView

1.GenericAPIView介绍
	GenericAPIView继承自APIView 在APIView功能基础上 主要增加了操作序列化器和数据库查询的属性和方法 (ListModelMixin)支持搜索、排序、分页等功能
    视图类可多继承Mixin扩展类一起使用
    
2. 序列化器操作相关(视图类可指定的东西)
	属性:
        serializer_class:声明视图类使用的序列化器
        pagination_class: 声明分页控制类
            PageNumberPagination: 基础分页器
            LimitOffsetPagination: 偏移量分页器
            CursorPagination: 游标分页器
        filter_backends: 声明过滤控制后端
            SearchFileter:搜索过滤器(支持的字段类型:CharField或者TextField);通过在视图类声明serach_fields对特定字段搜索?search=xxx
            	search_fields = ['^f1']
                	字段前置支持特殊字符匹配
                    	^:以指定内容开始
                        =:全匹配
                        @:全文搜索(当前只支持postgreSql驱动)
                        $:正则搜索
            OrderingFilter:排序过滤器(通过声明ordering_fields对特定或者多个字段排序?ordering=-a,b 字段可多个用英文逗号隔开  符号表示降序)
            	ordering_fields = []
                
            自定义过滤器: 1.重写GenericAPIView类的filter_queryset()方法  2.继承BaseFilterBackend 
	方法:
		get_serializer_class(self):返回序列化器类 默认返回serializer_class 可以重写
		get_serializer(self, args, **kwargs):返回创建序列化器类的对象 如果我们在视图中想要创建序列化器对象 可以直接调用此方法
        
3. 数据库查询操作相关(视图类可指定的东西)
	属性:
		queryset:指明视图类使用的查询集
	方法:
		get_queryset:返回视图使用的查询集 默认返回queryset属性 可以重写
		get_object:返回从视图使用的查询集中查询指定的对象(默认根据url地址中提取的pk进行查询) 如查询不到 此方法会抛出Http404异常     
         get_paginated_response: 分页响应数据(除了序列化器对象的字段还有其他分页相关字段展示)
         
class MyPagePagination(PageNumberPagination):
    # 查询字符串变量名-第几页
    page_query_param = 'page'
    # 默认每页数据量
    page_size = 3
    # 查询字符串变量名-每页数据量
    page_size_query_param = 'page_size'
    # 每页最大数据量
    max_page_size = 5


class StuInfoListView(GenericAPIView):
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    pagination_class = MyPagePagination  # 指定分页器 ?page=xx&page_size=yy

    
    # def get_queryset(self):
    #     """重写查询集"""
    #     queryset = queryset = StuInfo.objects.all()
    #     stu_name = self.request.query_params.get("search")
    #     if stu_name is not None:
    #         queryset = queryset.filter(stu_name=stu_name)
    #     return queryset

    def get(self, request):
        """
        获取所有的学生信息数据
        :param request:
        :return:
        """
        queryset = self.get_queryset()  # 获取所有学生查询结果集
        page = self.paginate_queryset(queryset)  # 查询结果集分页处理
        serializer = self.get_serializer(page, many=True)  # 序列化所有学生信息数据
        return Response(serializer.data)

    def post(self, request):
        """
        新增一个学生
        :param request:
        :return:
        """
        # 反序列化-数据校验
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            # 反序列化-数据保存(save内部会调用序列化器类的create方法)
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response({"msg": "fail"}, status=status.HTTP_400_BAD_REQUEST)


class StuInfoDetailView(GenericAPIView):
    # 指定视图所使用的序列化器类
    serializer_class = StuInfoSerializer
    # 指定视图所使用的查询集
    queryset = StuInfo.objects.all()

    def get(self, request, pk):
        """
        获取指定学生信息
        :param request:
        :param pk:
        :return:
        """
        instance = self.get_object()
        # 将学生数据进行序列化
        serializer = StuInfoSerializer(instance)
        return Response(serializer.data)

    def put(self, request, pk):
        """
        修改指定学生信息
        :param request:
        :param pk:
        :return:
        """
        instance = self.get_object()
        # 反序列化-数据校验
        serializer = StuInfoSerializer(instance, data=request.data)
        serializer.is_valid(raise_exception=True)
        # 反序列化-数据保存(save内部会调用序列化器类的update方法)
        serializer.save()
        return Response(serializer.data)

    def delete(self, request, pk):
        """
        删除指定学生
        :param request:
        :param pk:
        :return:
        """
        instance = self.get_object()
        instance.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


3.1.3 Mixin扩展类

Mixin扩展类
	ListModelMixin	
    	list(request,*args,**kwargs):
            封装获取一组数据操作
            数据支持过滤和分页
	CreateModelMixin	
    	create():
            封装新增一条数据操作
	RetrieveModelMixin	
    	retrieve():
            封装获取指定数据操作
	UpdateModelMixin	
    	update():
            封装更新指定数据操作
	DestroyModelMixin	
    	destroy():
            封装删除指定数据操作

Mixin扩展类相关源码
class CreateModelMixin:
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}


class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)


class RetrieveModelMixin:
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)


class UpdateModelMixin:
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)


class DestroyModelMixin:
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()
class MyPagePagination(PageNumberPagination):
    # 查询字符串变量名-第几页
    page_query_param = 'page'
    # 默认每页数据量
    page_size = 3
    # 查询字符串变量名-每页数据量
    page_size_query_param = 'pageSize'
    # 每页最大数据量
    max_page_size = None


class StuInfoListView(GenericAPIView, CreateModelMixin, ListModelMixin):
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    pagination_class = MyPagePagination  # 指定分页器 ?page=xx&page_size=yy

    filter_backends = [SearchFilter, OrderingFilter]  # 指定过滤器(drf框架自集成) 需要重写方法
    search_fields = ['stu_name', ]  # 指定搜索字段(需要是序列化器fields字段内部定义的)?search=xxx
    ordering_fields = ['stu_scores']  # 指定排序字段?ordering=-stu_scores
    # lookup_field = "pk"  # 查询单个模型实例对象的特定模型字段(默认pk)
    # lookup_url_kwarg = None  # url关键字参数(url conf需要包含与此值对应的关键字参数)
    
    def get(self, request):
        """
        获取所有的学生信息数据
        :param request:
        :return:
        """
        return self.list(request)
    
    def post(self, request):
        """
        新增一个学生
        :param request:
        :return:
        """
        return self.create(request)


class StuInfoDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    # 指定视图所使用的序列化器类
    serializer_class = StuInfoSerializer
    # 指定视图所使用的查询集
    queryset = StuInfo.objects.all()
    
    def get(self, request, pk):
        """
        获取指定学生信息
        :param request:
        :param pk:
        :return:
        """
        return self.retrieve(request, pk)
    
    def put(self, request, pk):
        """
        修改指定学生信息
        :param request:
        :param pk:
        :return:
        """
        return self.update(request, pk)
    
    def delete(self, request, pk):
        """
        删除指定学生
        :param request:
        :param pk:
        :return:
        """
        return self.destroy(request, pk)


3.1.4 子类视图

子类视图类 直接对请求方法GET|POST|PUT|DELETE封装 不用再显式声名

子类视图类	
	ListAPIView	
        继承:
            GenericAPIView|ListModelMixin	
        封装好get请求->get()方法-全量
        
	CreateAPIView	
        继承:
        	GenericAPIView|CreateModelMixin	
        封装post请求->post()方法
        
	RetrieveAPIView	
        继承:
        	GenericAPIView、RetrieveModelMixin	
        封装get请求->get(pk=)方法-特定
        
	UpdateAPIView	
        继承:
        	GenericAPIView、UpdateModelMixin	
        封装put请求->put()方法
        
	DestroyAPIView	
        继承:
        	GenericAPIView、DestroyModelMixin	
        封装delete请求->delete()方法
        
	ListCreateAPIView	
        继承:
        	GenericAPIView、ListModelMixin、CreateModelMixin	
        封装get、post请求->get()&post()方法
        
	RetrieveUpdateAPIView	
        继承:
        	GenericAPIView、RetrieveModelMixin、UpdateModelMixin	
        封装get、put请求->get()&put()方法
        
	RetrieveDestroyAPIView	
        继承:
        	GenericAPIView、RetrieveModelMixin、DestroyModelMixin	
        封装get、delete请求->get()&delete()方法
        
	RetrieveUpdateDestroyAPIView	
        继承:
        	GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin	
        封装get、put、delete请求->get()&put()&delete()方法
class MyPagePagination(PageNumberPagination):
    # 查询字符串变量名-第几页
    page_query_param = 'page'
    # 默认每页数据量
    page_size = 3
    # 查询字符串变量名-每页数据量
    page_size_query_param = 'pageSize'
    # 每页最大数据量
    max_page_size = 5


class StuInfoListView(ListCreateAPIView):
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    pagination_class = MyPagePagination  # 指定分页器 ?page=xx&page_size=yy
    
    filter_backends = [SearchFilter, OrderingFilter]  # 指定过滤器(drf框架自集成) 需要重写方法
    search_fields = ['stu_name', ]  # 指定搜索字段(需要是序列化器fields字段内部定义的)?search=xxx
    ordering_fields = ['stu_scores']  # 指定排序字段?ordering=-stu_scores
    
    
class StuInfoDetailView(RetrieveUpdateDestroyAPIView):
    serializer_class = StuInfoSerializer   # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    # lookup_field = "pk"  # 查询单个模型实例对象的默认的模型字段(默认pk)
    # lookup_url_kwarg = None  # url关键字参数(url conf需要包含与此值对应的关键字参数)


3.2 视图集

视图集
    简介:
    	将操作同一组资源相关的处理方法放在同一个类中 这个类叫做视图集 精简类视图使用且URL配置需要映射请求方法和视图集方法
    父类:
        ViewSet(继承ViewSetMixin&APIView 和APIView类似  提供认证|权限|限流等)
        GenericViewSet(继承GenericAPIView&ViewSetMixin 可搭配Mixin扩展类使用)
        ModelViewSet(继承GenericViewSet且包含ListModelMixin|CreateModelMixin|RetrieveModelMixin|UpdateModelMixin|DestroyModelMixin)
        ReadOnlyModelViewSet(继承GenericViewSet且包含ListModelMixin|RetrieveModelMixin)
    方法:
        list:获取数据
        create:新增数据
        retrieve:获取特定的数据
        update:更新特定的数据
        destroy:删除特定的数据
        支持自定义
        PS: 可通过重写方法实现特定字段展示
    使用:    
        1.继承视图集相关父类
        2.视图集中方法通过list|create|update|destroy等映射请求方式(get|post|put|delete)等
        3.URL配置 需要指定请求方法和对应视图集方法映射关系
        
PS: 学生API管理需要2个类视图StuInfoListView&StuInfoDetailView但是视图集一个StuInfoViewSet就能实现

3.2.1 视图集实例

# 应用urls.py
from django.conf.urls import url
from . import myViewSet

urlpatterns = [
    url(r'^stu$', myViewSet.StuInfoViewSet.as_view({
        'get': 'list',
        'post': 'create',
    })),
    url(r'^stu/(?P<pk>\d+)$', myViewSet.StuInfoViewSet.as_view({
        'get': 'retrieve',
        'put': 'update',
        'delete': 'destroy'
    })),
    url(r'^stu/last$', myViewSet.StuInfoViewSet.as_view({
        'get': 'last'
    }))
]
# 应用views.py(myViewSet.py)
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from .serializers import StuInfoSerializer
from .models import StuInfo


class StuInfoViewSet(ModelViewSet):
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    
    def last(self, request):
        """
        自定义方法-获取最后一条记录
        :param request:
        :return:
        """
        stu = StuInfo.objects.last()
        serializer = self.get_serializer(stu)
        return Response(serializer.data)
    
    # def get_queryset(self):
    #     """
    #     重写查询集queryset
    #     可根据不同action指定不同的查询集或者序列化器类
    #     :return:
    #     """
    #     if self.action == 'list':
    #         pass
    #     else:
    #         pass


3.2.2 路由配置优化

路由配置优化目的是为了优化视图集action方法和请求方式的映射关系 动态管理url配置项
相关类
	SimpleRouter
	DefaultRouter
使用
	router = SimpleRouter()
	router.register(prefix=,viewset=,base_name=)
        -prefix:该视图集所有处理函数url地址的统一前缀
        -viewset:视图集
        -base_name:该视图集所有处理函数路由name的统一前缀
        
ps: 通过可以通过action装饰器修饰视图集相关action方法(create|list|retrieve|update|destroy等)
action装饰器相关入参:
	methods:list->表明该处理方法对应的请求方式
	detail:boolean->表明生成url配置项时 是否需要从路径参数中提取pk数据
*URL路径需要/结尾 否则会出现301跳转情况即 api会请求2次(301->200):xxx->xxx/

# 新路由配置优化(应用urls.py)
from rest_framework.routers import DefaultRouter,SimpleRouter

from . import myViewSet

urlpatterns = []
# router = DefaultRouter()
router = SimpleRouter()
router.register(prefix='stu', viewset=myViewSet.StuInfoViewSet, basename='stu')
urlpatterns += router.urls
for _url in router.urls:
    print(_url)
# 应用视图集myViewSet.py
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from .serializers import StuInfoSerializer
from .models import StuInfo


class StuInfoViewSet(ModelViewSet):
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    lookup_value_regex = '\d+'  # 指定路由Router生成url配置项时 从路径中提取参数的正则表达式
    
    @action(methods=["get"], detail=False)
    def last(self, request):
        """
        自定义方法-获取最后一条记录
        :param request:
        :return:
        """
        stu = StuInfo.objects.last()
        serializer = self.get_serializer(stu)
        return Response(serializer.data)



4 扩展功能

4.1 文档

4.1.1 django-rest-swagger

pip install django-rest-swagger
# https://django-rest-swagger.readthedocs.io/en/latest/
#====settings.py
INSTALLED_APPS = [
    'rest_framework_swagger'  # 注册swagger应用
]
# swagger配置(可选)
SWAGGER_SETTINGS = {
    # 安全定义(认证)-提供三种方式basic|apikey|oauth2
    'SECURITY_DEFINITIONS': {
        "basic": {
            'type': 'basic'
        }
    },
    'USE_SESSION_AUTH': True,  # 提供login/logout按钮并携带csrf_token认证
    'LOGIN_URL': 'rest_framework:login',  # session方式认证-登录(默认django.conf.global_settings.LOGIN_URL)
    'LOGOUT_URL': 'rest_framework:logout',  # 默认django.conf.global_settings.LOGOUT_URL
    'APIS_SORTER': 'alpha',   # 接口api排序方式(alpha按照字母表顺序|默认None)
    'DOC_EXPANSION': None,  # 控制api列表页展示(None|list|full|默认None)
    'JSON_EDITOR': True,  # 是否可在api界面编辑请求体(默认False)
    'OPERATIONS_SORTER': None,  # api列表页排序(alpha:字母表排序|method:http方法排序)
    'SHOW_REQUEST_HEADERS': True,  # 是否展示请求头信息(默认False)
    # 'SUPPORTED_SUBMIT_METHODS': []  # 可进行交互式请求的请求方法(默认get|post|put|delete|patch)-实际就是try it out
    # 'VALIDATOR_URL': ''  # 线上校验器URL默认https://online.swagger.io/validator
}

# Django3.0+重新定义静态模板(新增libraries)
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
            'libraries': {
                'staticfiles' : 'django.templatetags.static', 
            },
        },
    },
]

#====项目主路由配置urls.py
from rest_framework import permissions
from rest_framework.schemas import get_schema_view
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer

schema_view = get_schema_view(title='DocApi',  # 文档默认标题
                              renderer_classes=[SwaggerUIRenderer,  # swaggerUI(html/js/css)渲染处理
                                                OpenAPIRenderer  # OpenAPIRenderer负责处理json格式数据
                                                ],
                              authentication_classes=(),  # 配置认证(也可在视图类中局部配置生效)
                              permission_classes=(permissions.AllowAny, ),  # 配置授权访问
                              )
urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'), name='auth'),
    path('swagger/', schema_view, name='swagger'),
]


4.1.2 coreApi

pip install coreapi
# https://www.coreapi.org/
#====全局配置settings.py
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
#====项目主路由配置urls.py
from django.conf.urls import url
from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls


urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^coreApi/', include_docs_urls(title="apiCore")),  # 配置api文档路由
    path(r'', include('classApp.urls')),  # 配置Class应用路由
]

# 可选装客户端 pip install coreapi-cli
# coreapi get http://localhost:8000/apiCore/
'''
<apiCore "http://localhost:8000/coreApi/">
    stu: {
        list()
        create(stu_name, stu_sex, stu_scores, stu_cls)
        last()
        read(id)
        update(id, stu_name, stu_sex, stu_scores, stu_cls)
        partial_update(id, [stu_name], [stu_sex], [stu_scores], [stu_cls])
        delete(id)
    }
'''


4.1.3 drf-yasg2

drf-yasg2特色:
	完全支持嵌套序列化器和模式(Full support for nested Serializers and Schemas)
	响应模式和描述(Response schemas and descriptions)
	与编码工具兼容的模型定义(Model definitions compatible with codegen tools)
	定制在规范生成过程的所有点上都挂钩(Customization hooks at all points in the spec generation process)
	JSON和YAML格式的规范(JSON and YAML format for spec)
	捆绑最新版本的swagger-ui和redoc,用于查看生成的文档(Bundles latest version of swagger-ui and redoc for viewing the generated documentation)
	架构视图是开箱即用的(Schema view is cacheable out of the box)
	生成的Swagger模式可以通过Swagger-spec-validator自动验证(Generated Swagger schema can be automatically validated by swagger-spec-validator)
	通过URLPathVersioning和NamespaceVersioning支持Django REST框架API版本控制;目前不支持其他DRF或自定义版本控制方案(Supports Django REST Framework API versioning with URLPathVersioning and NamespaceVersioning; other DRF or custom versioning schemes are not currently supported)

ps: 
	URL: https://pypi.org/project/drf-yasg2/
	pip install drf-yasg2[validation] 扩展Swagger-spec-validator
	截至到20230425 drf-yasg2==1.19.4 最高设配的django==3.1  djangorestframework==3.12 
pip install drf-yasg2==1.19.4
pip install django==3.1 djangorestframework==3.12
#====全局配置settings.py
INSTALLED_APPS = [
    'drf_yasg2',  # drf-yasg2
]
#====项目主路由配置urls.py
from django.conf.urls import url
from django.contrib import admin
from django.urls import path, include, re_path
from rest_framework import permissions


from drf_yasg2.views import get_schema_view
from drf_yasg2 import openapi
drf_yasg2_schema_view = get_schema_view(
    openapi.Info(
        title='drfYasg2-SwaggerUi',
        default_version="v1",
        description="SwaggerUi-yasg2",
        terms_of_service="https://www.google.com/policies/terms/",
        contact=openapi.Contact(email="xxx@163.com"),
        license=openapi.License(name="BSD License")
    ),
    public=True,
    permission_classes=(permissions.AllowAny, )
)



urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'', include('classApp.urls')),  # 配置Class应用路由
    
    # drf-yasg2(swaggerUi)
    re_path(r'^swagger2(?P<format>\.json|\.yaml)$', drf_yasg2_schema_view.without_ui(cache_timeout=0)),
    path('swagger2/', drf_yasg2_schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui2'),
    path('redoc2/', drf_yasg2_schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc2')
]


4.2 认证

****局部配置优先级高于全局配置
DRF框架认证方案:
    BasicAuthentication: 基于HTTP Basic Authentication协议实现的认证方式 用户需要在请求头中携带base64编码后的用户名和密码(生产环境只建议https协议)
    SessionAuthentication: 基于django默认的session会话机制实现的认证方式 在使用时需要先登录获取sessionid 然后发送请求时携带该sessionid和csrftoken
    TokenAuthentication: 基于Token的认证方式 用户需要先通过用户相关信息(账号+密码)获取一个Token 然后在每次请求时携带该Token进行认证 token保存于请求头Authorization 
    自定义认证: 通过继承BaseAuthentication 重写authenticate方法实现
    
    
    JWTAuthentication: 
        基于JsonWebToken(JWT)的认证方式 具有无状态 可扩展性高等优点(第三方扩展包)
        pip install djangorestframework-simplejwt[crypto]
        
    OAuth2Authentication:
        drf框架OAuth包为REST框架提供了OAuth1和OAuth2支持
        pip install django-oauth-toolkit
    
使用:
	1.全局配置(settings.py):
       REST_FRAMEWORK = {
    	'DEFAULT_AUTHENTICATION_CLASSES': [
        	'rest_framework.authentication.BasicAuthentication',
    	]
	}
        
    2.局部配置(视图类.py)
    	视图类类属性authentication_classes = [] 或者@permission_classes装饰器
    	方式一:
    	class MyAuthView(APIView):
    		authentication_classes = [SessionAuthentication, BasicAuthentication]
    		permission_classes = [IsAuthenticated]
            def get(self, request, format=None):
                content = {
                    'user': str(request.user),  # `django.contrib.auth.User` instance.
                    'auth': str(request.auth),  # None
                }
                return Response(content)
        方式二:
        @api_view(['GET'])
        @authentication_classes([SessionAuthentication, BasicAuthentication])
        @permission_classes([IsAuthenticated])
        def auth_view(request, format=None):
            content = {
                'user': str(request.user),  # `django.contrib.auth.User` instance.
                'auth': str(request.auth),  # None
            }
            return Response(content)
     
    	
PS: https://www.django-rest-framework.org/api-guide/authentication/

4.2.1 SessionAuthentication

# 全局配置settings.py
REST_FRAMEWORK = {  # DRF配置
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
    # 授权
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated', ]
}


4.2.2 BasicAuthentication

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
    ],
    # 授权
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated']
}


4.2.3 自定义认证

# djangoTest/settings.py
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'utils.myAuth.MyInitAuthentication',
    ],
    # 授权
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated']
}
# utils/myAuth.py 和应用同级目录
from django.contrib.auth.models import User
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions


class MyInitAuthentication(BaseAuthentication):
    """自定义认证"""
    def authenticate(self, request):
        username = request.GET.get('myAuthKey')
        if not username:
            return None
        try:
            user = User.objects.get(username=username)  # 不做密码校验
        except User.DoesNotExist:
            raise exceptions.AuthenticationFailed('invalidUser')
        return (user, None)


4.2.4 TokenAuthentication

# 1.全局配置settings.py
INSTALLED_APPS = [
    'rest_framework.authtoken',  # drf-默认token认证(rest_framework.authentication.TokenAuthentication) 已实现相关api接口(有对应的模型类Token|序列化器对象AuthTokenSerializer和视图类ObtainAuthToken)
]

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',  # token认证
    ],
    # 授权
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated']
}

# 2. 生成新的迁移文件
$ python manage.py migrate 

# 3. 主路由配置
from rest_framework.authtoken import views as _builtInAuthViews
urlpatterns = [
    path('admin/', admin.site.urls), 
    # TokenAuthentication认证提供的内置api
    path('api-token-auth/', _builtInAuthViews.obtain_auth_token)  # 路径不要更改api-token-auth
]

# 4.请求头Authorization携带token请求
{"Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"}


4.2.4 JWTAuthentication

1. 安装
$ pip install djangorestframework-simplejwt[crypto]

2. 相关认证方案
	JWTAuthentication
	JWTStatelessUserAuthentication(JWTTokenUserAuthentication)

3. 相关文档
# https://django-rest-framework-simplejwt.readthedocs.io/en/latest/

4. 扩展(可选)
若需要持久化token相关信息 
INSTALLED_APPS = [
    'rest_framework_simplejwt',  # simplejwt应用注册
]
# ====settings.py
INSTALLED_APPS = [  # 应用加载-非强制(可选)
    'rest_framework_simplejwt',  # 第三方token认证(rest_framework_simplejwt.authentication.JWTAuthentication)
]

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',  # jwt认证
    ],
    # 授权
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',  # 允许认证通过的用户
                                   ]
}


from datetime import timedelta
# rest_framework_simplejwt.authentication.JWTAuthentication 配置(可选-非强制)
SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=1),  # token有效期
    "REFRESH_TOKEN_LIFETIME": timedelta(days=1),  # 用于更新的token的refreshToken有效期
    "ROTATE_REFRESH_TOKENS": False,  # 轮询更新用的token
    "BLACKLIST_AFTER_ROTATION": False,  # 值是True;Install_app需要添加rest_framework_simplejwt.token_blacklist
    "UPDATE_LAST_LOGIN": False,  # 更新auth_user.last_login字段
    "ALGORITHM": "HS256",  # 算法(HAMC可以忽略verifying_key|RSA则必须设置verifying_key)
    # HAMC: HS256|HS384|HS512; RSA:RS256|RS384|RS512
    "SIGNING_KEY": SECRET_KEY,  # 密钥
    "VERIFYING_KEY": "",  # 校验密钥
    "AUDIENCE": None,  # 字段值参与到token生成和校验过程;None则该值不参与过程
    "ISSUER": None,
    "JSON_ENCODER": None,
    "JWK_URL": None,
    "LEEWAY": 0,
    "AUTH_HEADER_TYPES": ("Bearer",),  # 认证信息在请求头的类型(可自定义)
    "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",  # 认证信息在请求头的名称
    "USER_ID_FIELD": "id",
    "USER_ID_CLAIM": "user_id",
    # 用户认证规则-默认校验auth_user.is_active=True
    "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
    "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),  # 若使用sliding_token则设置为rest_framework_simplejwt.tokens.slidingtoken;apiConf需要重新配置
    "TOKEN_TYPE_CLAIM": "token_type",  # token载荷payload声明(默认token_type;可以是access|sliding|refresh)
    "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
    "JTI_CLAIM": "jti",
    "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
    "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
    "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",
    "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",
}




# ====全局路由urls.py
from django.contrib import admin
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView

urlpatterns = [
    path('admin/', admin.site.urls),
    # jwt认证
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),  # 获取token
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),  # 更新token
    path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify')  # 可选-校验token(需要配置rest_framework_simplejwt.token_blacklist) 入参(json):token
]


4.2.5 OAuth2Authentication

1. 安装
pip install django-oauth-toolkit==2.2.0

2. 文档
https://django-oauth-toolkit.readthedocs.io/en/latest/install.html

3. oauth授权方案
	Authorization Code(授权码-通过授权码获取accessToken)(√)
	PKCE(授权码的扩展-支持csrf和授权码注入防护)
	Client Credentials(客户端证书)(√)
	Device Code(设备编码)
	Refresh Token(刷新令牌)
	ps: 实际需要通过以上某种方案获取到对应的accessToken
		现阶段只研究授权码和客户端证书
		
4. 可全局配置oauth2相关参数(https://django-oauth-toolkit.readthedocs.io/en/latest/settings.html)
    OAUTH2_PROVIDER = {
        'SCOPES': {
            'read': 'Read scope',
            'write': 'Write scope',
        },

        'CLIENT_ID_GENERATOR_CLASS': 'oauth2_provider.generators.ClientIdGenerator',
        'ACCESS_TOKEN_EXPIRE_SECONDS': 3600,  # token有效期
        'AUTHORIZATION_CODE_EXPIRE_SECONDS': 60 # 授权码有效期

    }
1. 配置settings.py
INSTALLED_APPS = [
    'oauth2_provider',  # 认证-oauth2
]
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',  # oauth2认证
    
    ],
    # 授权
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',  # 允许认证通过的用户
                                   ]
}
LOGIN_URL='/admin/login/'

2. 同步到数据库
$ python manage.py migrate

3. 配置路由urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
]

4. 授权认证
4.1 授权码形式
4.1.1 浏览器访问配置应用授权码
	http://127.0.0.1:8000/o/applications/register/ 
            
4.1.2 记录client_id和client_secret
	client_id: 
        9omo0yWi2Cx3wJuI37uCLSLkesNnosyRxsJv8iNp
	client_secret:
    	GJEmxnW7nUBDq3M8OAOqfdvKasMaNyrfgKL6Vx6lUNwjn61PzyZDH1CQkppoOOHOzEokGL61VQ1Yk2AJT1fJxskLUqtw3mduhtgFdbkvjE93K7NGJcHG4uLgNUZYLSah

4.1.3 sha256生成特定code_verifier和code_challenge
	# -*- coding:utf-8 -*-
    import random
    import string
    import base64
    import hashlib

    code_verifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(43, 128)))
    code_verifier = base64.urlsafe_b64encode(code_verifier.encode('utf-8'))

    code_challenge = hashlib.sha256(code_verifier).digest()
    code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')

    if __name__ == '__main__':
        print("code_verifier=", code_verifier)
        print("code_challenge=", code_challenge)
        
code_verifier= b'T0ZDTk1FRVc0VEk1MUxKVEFJSEZXMjVTOEU5NVg3UEFaNzJNN0FDWUU5TDBGR0E2NjA5RkZQVVU4NzgySjk4QUtQRUlBUlVYMjExQVVOWllLR0dNQg=='
code_challenge= tHx4MQUVgiMgVj6ykcuGDSLXy4xlhuIX-Q28RvQiBAA

4.1.4 浏览器访问获取授权码(302跳转会返回对应的code)
	http://127.0.0.1:8000/o/authorize/?response_type=code&code_challenge=tHx4MQUVgiMgVj6ykcuGDSLXy4xlhuIX-Q28RvQiBAA&code_challenge_method=S256&client_id=9omo0yWi2Cx3wJuI37uCLSLkesNnosyRxsJv8iNp&redirect_uri=http://127.0.0.1:8000/noexist/callback
            
4.1.5 授权码信息
	http://127.0.0.1:8000/noexist/callback?code=HvW8gYoU4SrzPevnvlqnIhkrYuHDHb
            
4.1.6 oauth2-授权码形式认证-验证(即获取认证token)
	url: 
        http://127.0.0.1:8000/o/token/
        
	Header: 
        application/x-www-form-urlencoded
        
    data: 
        client_id:9omo0yWi2Cx3wJuI37uCLSLkesNnosyRxsJv8iNp
        client_secret:GJEmxnW7nUBDq3M8OAO省略
        code:HvW8gYoU4SrzPevnvlqnIhkrYuHDHb
        code_verifier:T0ZDTk1FRVc0VEk1MUxKVEFJSEZXMjVTOEU5省略
        redirect_uri:http://127.0.0.1:8000/noexist/callback
        grant_type:authorization_code
            
    Response:
        {
        "access_token": "leAa0ErWZB39NElDEYwM2O4OCpnagB",
        "expires_in": 36000,
        "token_type": "Bearer",
        "scope": "read write",
        "refresh_token": "oBVCInmLAmKA8tIyhBqZh2sV8yPaa7"
    	}
            
4.1.7 授权页面访问每次携带请求头Authorization: Bearer leAa0ErWZB39NElDEYwM2O4OCpnagB
    curl -X GET http://localhost:8000/stu/ -H 'Authorization: Bearer leAa0ErWZB39NElDEYwM2O4OCpnagB'            
         

5.扩展-可自定义users类
$ python manage.py startapp users

----users/models.py
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
    pass

INSTALLED_APPS = [
    'users',
]

AUTH_USER_MODEL='users.MyUser'

$ python manage.py makemigrations
$ python manage.py migrate



4.3 权限

1.权限类别:
    1.1 IsAuthenticated: 仅允许已经认证的用户访问
        
    1.2 AllowAny: 允许任何人访问
        
    1.3 IsAdminUser: 仅允许管理员用户访问(保证user.is_staff=True)
        
    1.4 IsAuthenticatedOrReadOnly: 对于未认证的用户只允许进行读取操作(GET|HEAD|OPTIONS) 已认证用户可以执行任意操作
        
    1.5 DjangoModelPermissions: 基于Django模型的权限控制(django.contrib.authmodel) 与Django admin中的权限控制类似  此权限只能应用于具有.queryset属性集的视图 只有在用户通过身份验证并分配了相关模型权限的情况下 才会被授予权限
		POST 请求要求用户对模型具有添加权限
		PUT|PATCH 请求要求用户对模型具有更改权限
		DELETE 请求想要求用户对模型具有删除权限
         要使用自定义模型权限 覆盖DjangoModelPermissions并设置.perms_map属性
        
    1.6DjangoObjectPermissions: 基于Django模型对象的权限控制
        此权限类与Django的标准对象权限框架相关联 该框架允许模型上的每个对象的权限 为了使用此权限类 你还需要添加支持对象级权限的权限后端 例如django-guardian
		与DjangoModelPermissions一样 此权限只能应用于具有.queryset属性或.get_queryset()方法的视图 只有在用户通过身份验证并且具有相关的每个对象权限和相关的模型权限后 才会被授予权限
        POST 请求要求用户对模型实例具有添加权限
        PUT 和PATCH 请求要求用户对模型示例具有更改权限
        DELETE 请求要求用户对模型示例具有删除权限
        请注意,DjangoObjectPermissions 不需要 django-guardian软件包,并且应当同样支持其他对象级别的后端
		与DjangoModelPermissions一样 你可以通过重写DjangoObjectPermissions并设置.perms_map属性来使用自定义模型权限
        
    1.7自定义权限
    	要实现自定义权限 继承BasePermission并实现以下方法中的一个或两个
		.has_permission(self, request, view)
		.has_object_permission(self, request, view, obj)
		如果请求被授予访问权限 方法应该返回True 否则返回False	
        如果你需要测试请求是读取操作还是写入操作 则应该根据常量SAFE_METHODS检查请求方法 SAFE_METHODS是包含'GET' 'OPTIONS' 'HEAD'的元组 例如:
        if request.method in permissions.SAFE_METHODS:
            # 检查只读请求的权限
        else:
            # 检查读取请求的权限	
        eg:
            class TestPermission(BasePermission):
                def has_permission(self, request, view):
                    '''修饰的视图类是否有访问权限'''
                    return True
    
                def has_object_permission(self, request, view, obj):
                    '''修饰的视图类是否有对某个实体orm对象有访问权限'''
                    return True

	
            class TestViewSet(ReadOnlyModelViewSet):
            	'''视图类'''
                queryset = Stu.object.all()
                serializer_class = StuInfoSerializer
                permission_classes = [TestPermission]
    
    
2.使用(通常和认证一起使用):
	2.1全局配置
		REST_FRAMEWORK = {
            # 授权
            'DEFAULT_PERMISSION_CLASSES': [
                # 'rest_framework.permissions.AllowAny',  # 允许所有用户
                # 'rest_framework.permissions.IsAuthenticated',  # 允许认证通过的用户
                # 'rest_framework.permissions.IsAdminUser',  # 允许管理员用户
                'rest_framework.permissions.IsAuthenticatedOrReadOnly',  # 认证用户可全操作否则只有只读权限(get)                             				]
               }
	2.2局部配置
        视图类指定类属性permission_classes = [IsAuthenticated]
# ----settings.py
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 'rest_framework.authentication.BasicAuthentication',  # 基础认证
        # 'rest_framework.authentication.SessionAuthentication',  # session认证
        # 'utils.myAuth.MyInitAuthentication',  # 自定义认证
        'rest_framework.authentication.TokenAuthentication',  # token认证
    ],
    # 授权
    'DEFAULT_PERMISSION_CLASSES': [
        # 'rest_framework.permissions.AllowAny',  # 允许所有用户
        # 'rest_framework.permissions.IsAuthenticated',  # 允许认证通过的用户
        # 'rest_framework.permissions.IsAdminUser',  # 允许管理员用户
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',  # 认证用户可全操作否则只有只读权限(get)
                                   ]
}


4.4 限流

1.限流类:
    AnonRateThrottle: 适用于任何用户(即使未登录的匿名用户)的限制类
    UserRateThrottle: 适用于认证通过后的用户的限制类
    ScopedRateThrottle: 适用于对不同视图类的特定资源的扩展访问限制类(视图类需要指定throttle_scope属性才生效)
    自定义限流类:继承BaseThrottle|SimpleRateThrottle|UserRateThrottle其一 重写allow_request()方法
		
2.使用:
    全局配置
        REST_FRAMEWORK = {
        # 限流
        # 指定限流配置类
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.AnonRateThrottle',  # 适用于任何用户(即使未登录的匿名用户)的限制类
            'rest_framework.throttling.UserRateThrottle',  # 适用于认证通过后的用户的限制类
            'rest_framework.throttling.ScopedRateThrottle'],  # 适用于对不同视图类的特定部分的扩展访问限制类(视图类需要指定throttle_scope属性才生效)

        # 指定限流频率(second|minute|hour|day)
        'DEFAULT_THROTTLE_RATES': {
            'anon': '1/minute',  # 匿名用户的限流配置
            'user': '2/minute',  # 认证用户的限流配置
        },

    	}
    局部配置
        视图类指定throttle_classes属性
        视图函数通过@throttle_classes([])修饰
        
3. ps:
    当限制的维度是多个 即每分钟|每小时都有限制时
    可通过自定义限流类 并指定属性scope 并在REST_FRAMEWORK.DEFAULT_THROTTLE_RATES配置scope指定的限流维度
    class TestRateThrottle(UserRateThrottle):
        scope = 'haha'
        
    'DEFAULT_THROTTLE_RATES': {
            'haha': '2/hour',  
        },
#----settings.py
# DRF配置
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 'rest_framework.authentication.BasicAuthentication',  # 基础认证
        # 'rest_framework.authentication.SessionAuthentication',  # session认证
        # 'utils.myAuth.MyInitAuthentication',  # 自定义认证
        # 'rest_framework.authentication.TokenAuthentication',  # token认证
        # 'rest_framework_simplejwt.authentication.JWTAuthentication',  # jwt认证
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',  # oauth2认证
    
    ],
    # 授权
    'DEFAULT_PERMISSION_CLASSES': [
        # 'rest_framework.permissions.AllowAny',  # 允许所有用户
        'rest_framework.permissions.IsAuthenticated',  # 允许认证通过的用户
        # 'rest_framework.permissions.IsAdminUser',  # 允许管理员用户
        # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',  # 认证用户可全操作否则只有只读权限(get)
                                   ],
    
    # 限流
    # 指定限流配置类
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',  # 适用于任何用户(即使未登录的匿名用户)的限制类
        'rest_framework.throttling.UserRateThrottle',  # 适用于认证通过后的用户的限制类
        'rest_framework.throttling.ScopedRateThrottle'],  # 适用于对不同视图类的特定部分的扩展访问限制类(视图类需要指定throttle_scope属性才生效)
    
    # 指定限流频率(second|minute|hour|day)
    'DEFAULT_THROTTLE_RATES': {
        'anon': '1/minute',  # 匿名用户的限流配置
        'user': '2/minute',  # 认证用户的限流配置
    },

}


4.5 过滤

# 1. 安装
pip install django-filter==2.4.0

# 2. 过滤器类
	SearchFileter:搜索过滤器(支持的字段类型:CharField或者TextField);通过在视图类声明serach_fields对特定字段搜索?search=xxx
            	search_fields = ['^f1']
                	字段前置支持特殊字符匹配
                    	^:以指定内容开始
                        =:全匹配
                        @:全文搜索(当前只支持postgreSql驱动)
                        $:正则搜索
    OrderingFilter:排序过滤器(通过声明ordering_fields对特定或者多个字段排序?ordering=-a,b 字段可多个用英文逗号隔开  符号表示降序)
            	ordering_fields = []
                
    原生自定义过滤器: 1.重写GenericAPIView类的filter_queryset()方法  2.继承BaseFilterBackend
    
    django-filter扩展衍生的类-
    	DjangoFilterBackend
    	自定义: 1.继承FilterSet 
    		class Test(FilterSet):
    			stu_name = filters.CharFilter(field_name='stu_name', lookup_expr='icontains')
    			class Meta:
    				model = StuInfo
    				fields = ['字段1']
    	ps: 自定义FilterSet中字段属性相关参数
    		field_name: 过滤字段名 对应模型类字段名
			lookup_expr: 指定ORM查询运算符 lt|gt|gte等等

# 3. 使用
	3.1 原生
		3.1.1 可全局配置或者局部配置
		3.1.2 局部配置在视图类指定filter_backends属性
		3.1.3 全局配置在settings.py配置
            REST_FRAMEWORK = {
                'DEFAULT_FILTER_BACKENDS':[]
            }
         3.1.4 api访问
         	?search=xxx
         	
		
		
	3.2 django-filter扩展
		3.2.1 settings.py
            INSTALLED_APPS = [
            'django_filters',  # 注册应用
            ]

            REST_FRAMEWORK = {
            'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend',]  # 配置过滤器类
            }
		3.2.2 视图类
            class TestViewSet(ListApiView):
                filter_fields=('字段1', '字段2')
         3.2.3 api访问
         	?字段名=xxx
         	

# ps: 需要注意django和drf框架的版本
pip install django==3.1 django-filter==2.4.0 djangorestframework==3.12
# 应用视图MixinViews.py
class StuInfoListViewV2(GenericAPIView, ListModelMixin):
    '''原生SearchFilter'''
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    
    filter_backends = [SearchFilter]  # 指定过滤器(drf框架自集成) 需要重写方法
    search_fields = ['stu_name', ]  # 指定搜索字段(需要是序列化器fields字段内部定义的)?search=xxx

    def get(self, request):
        """
        获取所有的学生信息数据
        :param request:
        :return:
        """
        return self.list(request)


class StuInfoListViewV3(GenericAPIView, ListModelMixin):
    '''django-filter'''
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    
    filter_backends = [DjangoFilterBackend]  # 指定过滤器(drf框架自集成) 需要重写方法
    filter_fields = ['stu_scores', ]  # 指定搜索字段(需要是序列化器fields字段内部定义的)?字段名=xxx
    
    def get(self, request):
        """
        获取所有的学生信息数据
        :param request:
        :return:
        """
        return self.list(request)
    
# 应用路由配置urls.py
from django.conf.urls import url
from . import MixinViews

urlpatterns = [
    url(r'^stu2$', MixinViews.StuInfoListViewV2.as_view()),
    url(r'^stu3$', MixinViews.StuInfoListViewV3.as_view()), 
]


4.6 排序

使用
    1.视图类指定属性filter_backends=[OrderingFilter,] 用于指定相关类
    2.视图类指定属性ordering_fields 用于排序字段
    3. ?ordering=-xxx,yyy   # -表示降序排序 通过,分隔多个字段排序 
    4.ps:
        相关类
            rest_framework.filters.OrderingFilter
#----应用视图MixinViews.py
class StuInfoListViewV4(GenericAPIView, ListModelMixin):
    '''排序'''
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    filter_backends = [OrderingFilter]  # 指定过滤器(drf框架自集成) 需要重写方法
    ordering_fields = ['stu_scores']  # 指定排序字段?ordering=-stu_scores
    
    def get(self, request):
        """
        获取所有的学生信息数据
        :param request:
        :return:
        """
        return self.list(request)
    
    
    
#----应用路由配置urls.py
from django.conf.urls import url
from . import MixinViews

urlpatterns = [
    url(r'^stu4$', MixinViews.StuInfoListViewV4.as_view()),
]


4.6 分页

1.分页器类
    PageNumberPagination
    LimitOffsetPagination 
    CursorPagination 
    自定义分页器类(可继承以上三种类或者其父类BasePagination)
    
2.使用
	全局配置
    	REST_FRAMEWORK = {
            'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 
            'PAGE_SIZE': 5, 
		}
    局部配置
    	视图类(继承GenericAPIView或其子类)指定pagination_class属性(局部配置优先级高于全局配置) 
#----应用路由配置urls.py
from django.conf.urls import url
from rest_framework.routers import DefaultRouter, SimpleRouter

from . import myViewSet

urlpatterns = []
# router = DefaultRouter()
router = SimpleRouter()
router.register(prefix='stu1', viewset=myViewSet.StuInfoViewSetV1, basename='stu1')
router.register(prefix='stu2', viewset=myViewSet.StuInfoViewSetV2, basename='stu2')
router.register(prefix='stu3', viewset=myViewSet.StuInfoViewSetV3, basename='stu3')
urlpatterns += router.urls

#----应用视图类配置myViewSet.py
class MyPageNumberPagination(PageNumberPagination):
    """从page页展示page_size数据量"""
    page_size = 1  # 每页的数据量
    page_size_query_param = 'page_size'  # 查询字符串中每页数据量的参数名
    page_query_param = 'page'  # 查询字段串中第几页的参数名
    max_page_size = None  # 每页最多显示的数据量
    
    def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('count', self.page.paginator.count),
            ('data', data)
        ]))


class MyLimitOffsetPagination(LimitOffsetPagination):
    """偏移offset 展示limit条数据"""
    default_limit = 1  # 默认的数据量
    limit_query_param = 'limit'  # 查询字符串中数据量的参数名
    offset_query_param = 'offset'  # 查询字符串中偏移量的参数量
    max_limit = None  # 最大数据量
    
    def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('count', self.count),
            ('data', data)
        ]))
    
    
class MyCursorPagination(CursorPagination):
    """游标分页只支持展示邻近的上一页或下一页数据 不支持跳转到特定页"""
    cursor_query_param = 'cursor'  # 查询字符串中参数名
    page_size = 1  # 每页展示的数据量
    ordering = 'id'  # 默认排序字段
    page_size_query_param = 'cursor_size'  # 查询字符串中每页数据量的参数名
    max_page_size = 2  # 和page_size_query_param配合使用  最大数据量展示(即cursor_size<=max_page_size)
    offset_cutoff = 1000  # 偏移量上限
    

class StuInfoViewSetV1(ModelViewSet):
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    pagination_class = MyPageNumberPagination  # 指定分页器类
    
    
class StuInfoViewSetV2(ModelViewSet):
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    pagination_class = MyLimitOffsetPagination  # 指定分页器类
    

class StuInfoViewSetV3(ModelViewSet):
    serializer_class = StuInfoSerializer  # 指定视图所使用的序列化器类
    queryset = StuInfo.objects.all()  # 指定视图所使用的查询集
    pagination_class = MyCursorPagination  # 指定分页器类


4.7 异常

1.异常类
	默认:rest_framework.views.exception_handler
    自定义:
    
2.异常类处理的范围
    APIException 所有异常的父类
    ParseError 解析异常
    AuthenticationFailed 认证失败
    NotAuthenticated 未认证
    PermissionDenied 没有权限
    NotFound 未找到资源
    MethodNotAllowed 请求方式不允许
    NotAcceptable 获取的数据格式不支持
    Throttled 超过限流次数
    ValidationError 校验失败
    Http404 资源不存在
  
3. 使用
	REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
	}
	
#----utils/myException.py
# -*- coding:utf-8 -*-
from rest_framework.response import Response
from rest_framework.views import exception_handler


def _exception_handler(exc, context):
    '''自定义异常'''
    data = {"code": -1, "msg": repr(exc)}
    response = Response(data=data, status=500)
    # response = exception_handler(exc, context)  # 默认异常类返回的信息
    return response

#----settings.py
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'utils.myException._exception_handler'
	}


#----应用视图apiViews.py
class StuInfoListViewV1(APIView):
    
    def get(self, request):
        """
        获取所有学生信息
        :param request:
        :return:
        """
        raise ZeroDivisionError('division by zero')
        
#----应用路由配置urls.py
from django.conf.urls import url
from . import apiViews

urlpatterns = [
    url(r'^stu$', apiViews.StuInfoListViewV1.as_view()),
]




5 帮助文档

[1] Django中文文档 https://docs.djangoproject.com/zh-hans/4.2/

[2] DRF https://www.django-rest-framework.org/

[3] swagger-ui-drf https://github.com/axnsan12/drf-yasg/

posted @   爱编程_喵  阅读(95)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
jQuery火箭图标返回顶部代码

jQuery火箭图标返回顶部代码

滚动滑动条后,查看右下角查看效果。很炫哦!!

适用浏览器:IE8、360、FireFox、Chrome、Safari、Opera、傲游、搜狗、世界之窗.

点击右上角即可分享
微信分享提示