zoukankan      html  css  js  c++  java
  • 路飞学城之 drf

    目录

    路飞学城之 drf

    相关内容复习

    """
    1)指令:
    	插值表达式:{{ 变量|常量|简单表达式 }} 
    	文本指令:v-text | v-html
    	事件指令:v-on:事件名 | @事件名
    	属性指令:v-bind:属性名 | :属性名
    	表单指令:v-model
    	条件指令:v-show | v-if | v-else-if | v-else
    	条件指令:v-for
    
    2)实例成员
    	挂载点:el
    	数据: data
    	方法:methods
    	计算(方法属性):computed
    	监听:watch
    	标识符:delimites
    	过滤器:filters
    	属性:props
    	声明周期钩子:created...
    
    3)vue项目
    	组件:结构 - template(一个根标签) | 逻辑 - script(export default) | 样式 - style(scoped)
    	组件交互:父传子 - 自定义属性 | 子传父 - 发送自定义事件($emit)
    	
    	路由:
    		router-view:页面组件占位
    		router-link:标签跳转
    		$router:控制逻辑跳转
    		$route:控制路由数据
    		配置:path: '/books/detail/:pk' | path: '/books/detail'
    		
    	自定义配置main.js:
    		import 'css文件路径'
    		
    		import 别名 from 'js文件路径'
    		Vue.prototype.$名字 = 别名
    		
    		import ElementUI from 'element-ui'
    		Vue.use(ElementUI)
    		import 'element-ui的全局css文件'
    		
    	vue项目逻辑:
    		ant.js
    		var Ant = {
    			// ...
    		}
    		export default Ant
    		
            <script>
    			import Ant from 'ant.js文件路径'
    			
    			export default {
    				data() {},
    				mounted() {
    					// Ant
    				}
    			}
            </script>
    """
    

    DRF框架知识总览

    """
    1)接口(api):
    	什么是接口
    	接口文档
    	接口规范
    	
    2)FBV => CBV:Function|Class Base View
    	CBV的请求生命周期
    	CBV比FBV的优势
    
    3)drf框架的基础试图类
    	APIView:请求模块、解析模块、渲染模块、响应模块、异常模块
    	
    4)drf核心组件
    	序列化组件:将后台的任何数据,进行序列化返回给前台;将前台的数据反序列化成后台model对象再入库
    	三大认证组件:认证组件、权限组件(RBAC)、频率组件
    	视图家族:View一系列组件
    	
    5)群查接口相关组件:
    	搜索、筛选、排序、分页
    	
    目的:
    	必须掌握:六大基础接口 - 单查、群查、单增、单局部改、单整体改、单删
    	一共十大接口 - 群增、群局部改、群整体改、群删
    """
    

    接口

    """
    接口概念:前台与后台进行信息交互的媒介 - url链接
    
    接口组成:
    	url链接 - 长得像返回数据的url链接
    	请求方式 - get(查)、post(增)、put(整体改)、patch(局部改)、delete(删)
    	请求参数 - 拼接参数、数据包参数(urlencoded、form-data、json)
    	响应结果 - 响应的json数据
    """
    

    开发阶段接口测试工具

    """
    Postman:
    	官网下载、傻瓜式安装
    """
    

    接口文档

    """
    1)为什么要写接口文档
    	为什么要写接口:作为后台开发者,要将后台数据通过url链接反馈给前台
    	为什么要写文档:作为后台开发者,一定知道改url链接应该采用什么请求方式、提交哪些数据、返回了哪些结果
    就像后台要将url链接改前台一样,前台知道应该访问什么链接,所以前台也应该知道采用什么请求方式,以及提交什么数据。
    	换而言之,接口文档是给 后台开发者、前台开发者、测试等各个项目相关项目组同时查看的,方便团队开发(规则是后台指定的,文档后台来写)
    	
    2)编写文档
    	i)采用word编写
    	ii)drf框架有插件,可以根据cbv的类快速生成文档
    	iii)采用写文档的平台
    	
    3)书写过程
    	i)先安装开发需要,完成接口的开发(设置后台url链接,设置请求方式、请求数据、响应结果)
    	ii)选择一个接口平台,将后台url链接,设置请求方式、请求数据、响应结果信息变成成文档即可
    """
    

    接口规范

    """
    1)为什么要指定接口规范
    	在前后台分离情况下,后台可以采用不同的后台运用,开发出类似的功能,所以前后台请求响应的规则是一致的;如果安装一套标准来编写接口,后台不管是什么语言,前台都可以采用一样的方式进行交互。反过来,后台也不需要管前台到底采用何种方式请求(页面、工具、代码)
    	
    2)通用的接口规范:Restful接口规范 - 规定了url如何编写;请求方式的含义;响应的数据规则
    	i)url编写
    		https协议 - 保证数据安全性
    		api字眼 - 标识操作的是数据
    		v1、v2字眼 - 数据的不同版本共存
    		资源复数 - 请求的数据称之为资源
    		拼接条件 - 过滤群查接口数据(https://api.baidu.com/books/?limit=3&ordering=-price)
    		
    	ii)请求方式
    		/books/ - get - 群查
    		/books/(pk)/ - get - 单查
    		/books/ - post - 单增
    		/books/(pk)/ - put - 单整体改
    		/books/(pk)/ - patch - 单局部改
    		/books/(pk)/ - delete - 单删
    	
    	iii)响应结果
    		网络状态码与状态信息:2xx | 3xx | 4xx | 5xx
    		数据状态码:前后台约定规则 - 0:成功 1:失败 2:成功无结果
    		数据状态信息:自定义成功失败的信息解释(英文)
    		数据本体:json数据
    		数据子资源:头像、视频等,用资源的url链接
    """
    

    CVB vs FBV

    """
    1)路由绑定
    urlpatterns = [
        # 1)项目启动,将test函数地址绑定给/test/路由
        # 2)请求/test/访问后台,后台就会调用绑定的test函数
        url(r'^test/$', views.test),
        
        # 1)项目启动,将as_view()函数执行结果返回的view函数地址绑定给/test/路由
        # 2)请求/test/访问后台,后台就会调用绑定的view函数
        # 3)view函数会将请求交给dispatch方法完成分发,分发(反射)给视图类的不同方法处理请求
        url(r'^test/$', views.Test.as_view()),
    ]
    
    2)业务处理:
    	fbv没一个接口都会对应一个函数来响应请求
    	cbv可以将一个资源的增删改查所有操放在一个类中管理,在内部再分方法逐一处理 (高内聚低耦合:六个接口和一个类有关,但都能在类内部处理)
    
    -------
    三个View其实是同一个类
    继承View的目的:
    	i)继承as_view()方法,完成路由的配置
    	ii)继承dispath()方法,完成请求分发
    	注:如果自己写as_view()和dispath()方法,自定义视图类可以不用继承任何类的
    from django.http import JsonResponse
    from django.views import View
    from django.views.generic import View
    from django.views.generic.base import View
    class Test(View):  
        def get(self, request, *args, **kwargs):
            return JsonResponse('cbv ok', safe=False)
    """
    

    pip安装源

    介绍

    """
    1、采用国内源,加速下载模块的速度
    2、常用pip源:
    	-- 豆瓣:https://pypi.douban.com/simple
    	-- 阿里:https://mirrors.aliyun.com/pypi/simple
    3、加速安装的命令:
    	-- >: pip install -i https://pypi.douban.com/simple 模块名
    """
    

    永久配置安装源

    Windows
    """
    1、文件管理器文件路径地址栏敲:%APPDATA% 回车,快速进入 C:Users电脑用户AppDataRoaming 文件夹中
    2、新建 pip 文件夹并在文件夹中新建 pip.ini 配置文件
    3、新增 pip.ini 配置文件内容
    """
    
    
    MacOS、Linux
    """
    1、在用户根目录下 ~ 下创建 .pip 隐藏文件夹,如果已经有了可以跳过
    	-- mkdir ~/.pip
    2、进入 .pip 隐藏文件夹并创建 pip.conf 配置文件
    	-- cd ~/.pip && touch pip.conf
    3、启动 Finder(访达) 按 cmd+shift+g 来的进入,输入 ~/.pip 回车进入
    4、新增 pip.conf 配置文件内容
    """
    
    
    配置文件内容
    """
    [global]
    index-url = http://pypi.douban.com/simple
    [install]
    use-mirrors =true
    mirrors =http://pypi.douban.com/simple/
    trusted-host =pypi.douban.com
    """
    
    

    DRF框架的安装

    """
    1)安装
    >: pip install djangorestframework
    
    2)在settings中注册(为什么要注册,后期会讲)
    INSTALLED_APPS = [
        # ...
        'rest_framework',
    ]
    
    3)在settings中自定义drf配置
    注:drf配置查找顺序,自定义settings的REST_FRAMEWORK配置字典 => drf默认settings的DEFAULTS
    REST_FRAMEWORK = {}
    
    4)drf的封装特点:特点功能在特点模块下
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.request import Request
    """
    
    

    图书资源准备

    """
    1)在models.py创建Model类
    from django.db import models
    class Book(models.Model):
        name = models.CharField(max_length=64, verbose_name='书名')
        price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='价格')
    
        # 配置类
        class Meta:
            verbose_name_plural = '图书'
    
        def __str__(self):
            return self.name
            
    2)在admin.py中注册Model类,方便在后台(admin)可视化管理数据
    from django.contrib import admin
    from . import models
    admin.site.register(models.Book)  # admin要控制的model都需要注册一下
    
    3)数据库迁移操作
    >: python manage.py makemigrations
    >: python manage.py migrate
    >: python manage.py createsuperuser
    	admin | Admin123
    	
    4)浏览器登录admin录入数据:http://localhost:8000/admin
    """
    
    

    基于restful接口规范的接口设计

    urlpatterns = [
        # 资源books接口的设计
        url(r'^books/$', views.BookAPIView.as_view()),  # 群查、单增
        url(r'^books/(?P<pk>d+)/$', views.BookAPIView.as_view()),  # 单查、单删、单(整体|局部)改
    ]
    
    
    

    实现单查群查接口

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from . import models
    class BookAPIView(APIView):
        def _multiple_get(self):
            # 不能返回,数据是QuerySet[Obj, Obj],也不能强制转换list
            # return list(models.Book.objects.all())
    
            # QuerySet[dict, dict]
            # print(models.Book.objects.values('name', 'price'))
            # QuerySet[tuple, tuple]
            # print(models.Book.objects.values_list('name', 'price'))
    
            return Response(list(models.Book.objects.values('name', 'price')))
    
        def _single_get(self, pk):
            # return Response(models.Book.objects.filter(pk=pk).values('name', 'price').first())
    
            # 开发阶段建议能用try的地方,不要使用if判断
            try:
                return Response(models.Book.objects.values('name', 'price').get(pk=pk))
            except:
                return Response('资源不存在')
    
        # 单查、群查
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:
                # 单查逻辑
                return self._single_get(pk)
            else:
                # 群查逻辑
                return self._multiple_get()
    
    
    

    小结

    """
    1)接口:
    	概念:前后台数据交互的桥梁
    	组成:url链接 + 请求方法 + 请求数据 + 响应结果
    	文档:将接口的四个组成部分书写成一个使用文档
    	工具:Postman
    	
    2)Restful接口规范:
    	i)url链接:https、api、books、v1、?limit=3
    		https://www.baidu.com/api/v1/books/?limit=3
    	ii)请求方式:get、post、put、patch、delete
    		用不同请求方式代表操作资源的不同目的
    	iii)响应结果
    		网络状态码,状态信息 - 规定好的
    		数据状态码,状态信息 - 开发自己约定的
    		数据本地 - json数据
    		子资源 - 图片、视频返回资源的url链接
    
    3)cbv与fbv
    	高内聚低耦合、生命周期 as_view() => dispatch() => 视图类响应方法
    	
    4)drf的安装
    	pip install djangorestframework
    	封装特点:特点功能在特点模块下
    	
    5)pip换源
    
    6)基于restful规定的接口设计
    	url(r'^books/$', views.BookAPIView.as_view()),  # 群查、单增
        url(r'^books/(?P<pk>d+)/$', views.BookAPIView.as_view()),  # 单查、单删、单(整体|局部)改
        
    7)资源的单查群查案例(orm知识回顾)
    
    """
    
    
    

    二、内容大纲

    """
    1)drf框架的请求生命周期:as_view和dispatch方法
    
    2)请求、解析、渲染、响应、异常
    
    3)序列化组件
    
    django:ORM、表单、序列化、认证(auth)、路由、模板、分页器、中间件
    """
    
    

    Django配置回顾

    """
    1)应用是否需要在INSTALLED_APPS中注册
    	在没有使用到app的一些特殊操作时(比如数据库相关),可以不用注册,但是注册后,应用的所有功能都能使用
    	结论:所有应用都可以完成注册
    	
    	
    2)数据库配置(全部在settings文件中完成即可)
    import pymysql
    pymysql.install_as_MySQLdb()
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': '数据库名',
            'USER': '账号',
            'PASSWORD': '密码',
            'HOST': '如果是127.0.0.1,该配置可以省略',
            "PORT": 3306,  # 如果是3306,该配置可以省略
        }
    }
    
    3)路由分发
    主路由:将应用自己逻辑相关的路由交给自己处理
    from django.conf.urls import url, include
    urlpatterns = [
    	# ...
        url(r'^api/', include('api.urls')),    # /api/test/
    ]
    
    """
    
    

    ORM配置回顾

    # models.py
    from django.db import models
    class User(models.Model):
        SEX_CHOICES = ((0, '男'), (1, '女'))
        name = models.CharField(max_length=64, verbose_name='姓名')
        age = models.IntegerField()
        height = models.DecimalField(max_digits=5, decimal_places=2, default=0)
        sex = models.IntegerField(choices=SEX_CHOICES, default=0)
        # sex = models.CharField(choices=[('0', '男'), ('1', '女')])
        icon = models.ImageField(upload_to='icon', default='icon/default.png')
        
        
    # settings.py
    # root就是将文件夹添加到 os.path 中
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    # url就是配置路由 /路由名/
    MEDIA_URL = '/media/'
    
    
    # 主路由最下方
    url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
    
    

    APIView的请求声明周期

    重写的as_view方法

    """
    1)as_view方法完成路由配置,返回配置函数是 csrf_exempt(view),也就是禁用了csrf认证规则
    结论:所有继承APIView的子类,都不受csrf认证规则的限制
    
    2)将请求处理的任务交给dispath方法完成
    """
    
    

    重写的dispatch方法

    """
    完成了三大核心任务:
    1)请求对象的处理:请求渲染模块
    
    2)请求过程的处理:三大认证模块 => 自己代码完成处理
    
    3)请求结果的响应:异常模块处理异常响应 | 响应渲染模块处理正常响应
    """
    
    

    请求解析模块

    """
    1)二次封装了原生Django的wsgi协议的request对象,并做了向下兼容(原来request对象的内容,用现在的request对象都能访问)
    
    2)将所有拼接参数都放在request.query_params中,将所有数据包参数都放在request.data中
    
    3)路由的有名无名分组的数据还是保存在args和kwargs中
    """
    
    # 解析模块可以在settings.py自定义解析配置
    REST_FRAMEWORK = {
        # 解析模块
        'DEFAULT_PARSER_CLASSES': [
            'rest_framework.parsers.JSONParser',  # json
            'rest_framework.parsers.FormParser',  # urlencoded
            'rest_framework.parsers.MultiPartParser'  # form-data
        ],
        # 渲染模块
        'DEFAULT_RENDERER_CLASSES': [
            'rest_framework.renderers.JSONRenderer',
            'rest_framework.renderers.BrowsableAPIRenderer',  # 上线后会注释
        ],
    }
    
    

    响应渲染模块

    """
    1)当三大认证模块和自己处理请求的视图逻辑没有出现异常时,会执行响应渲染模块
    
    2)响应的数据会交给渲染模块来完成数据的渲染,渲染方式有两种:Json格式数据渲染、Brower格式数据渲染
    """
    # 渲染模块可以在settings.py自定义解析配置
    REST_FRAMEWORK = {
        # 渲染模块
        'DEFAULT_RENDERER_CLASSES': [
            'rest_framework.renderers.JSONRenderer',
            # 浏览器渲染,上线后会注释,不然浏览器请求接口就暴露了后台语言
            'rest_framework.renderers.BrowsableAPIRenderer',  
        ],
    }
    
    

    序列化组件

    单表序列化

    models.py
    from django.db import models
    from django.conf import settings
    class User(models.Model):
        SEX_CHOICES = ((0, '男'), (1, '女'))
        name = models.CharField(max_length=64, verbose_name='姓名')
        password = models.CharField(max_length=64)
        age = models.IntegerField()
        height = models.DecimalField(max_digits=5, decimal_places=2, default=0)
        sex = models.IntegerField(choices=SEX_CHOICES, default=0)
        # sex = models.CharField(choices=[('0', '男'), ('1', '女')])
        icon = models.ImageField(upload_to='icon', default='icon/default.png')
    
        # 自定义序列化给前台的字段
        # 优点:1)可以格式化数据库原有字段的数据 2)可以对外隐藏原有数据库字段名 3)可以直接连表操作
        @property  # 制造插头
        def gender(self):
            return self.get_sex_display()
    
        @property
        def img(self):
            return settings.BASE_URL + settings.MEDIA_URL + self.icon.name
    
        def __str__(self):
            return self.name
    
    

    serializers.py

    from rest_framework import serializers
    from . import models
    class UserModelSerializer(serializers.ModelSerializer):
        class Meta:
            # 该序列化类是辅助于那个Model类的
            model = models.User
            # 设置参与序列化与反序列化字段
            # 插拔式:可以选择性返回给前台字段(插头都是在Model类中制造)
            # fields = ['name', 'age', 'height', 'sex', 'icon]
            fields = ['name', 'age', 'height', 'gender', 'img']
    
    

    views.py

    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    from . import models, serializers
    
    class UserAPIView(APIView):
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:  # 单查
                # 1)数据库交互拿到资源obj或资源objs
                # 2)数据序列化成可以返回给前台的json数据
                # 3)将json数据返回给前台
                obj = models.User.objects.get(pk=pk)
                serializer = serializers.UserModelSerializer(obj, many=False)
                return Response(serializer.data)
    
            else:  # 群查
                # 1)数据库交互拿到资源obj或资源objs
                # 2)数据序列化成可以返回给前台的json数据
                # 3)将json数据返回给前台
                queryset = models.User.objects.all()
                # many操作的数据是否是多个
                serializer = serializers.UserModelSerializer(queryset, many=True)
                return Response(serializer.data)
    
        def post(self, request, *args, **kwargs):
            # 单增
            # 1)从请求request中获得前台提交的数据
            # 2)将数据转换成Model对象,并完成数据库入库操作
            # 3)将入库成功的对象列化成可以返回给前台的json数据(请求与响应数据不对等:请求需要提交密码,响应一定不展示密码)
            # 4)将json数据返回给前台
    
            return Response()
    
    

    小结

    """
    0)回顾Django及ORM
    
    1)APIView的生命周期
    禁用csrf(√) => 请求解析模块(√) => 三大认证模块 => 自己代码处理请求 => 异常响应|正常响应渲染模块(√)
    
    2)单表序列化:ModelSerializer
    """
    
    

    三、内部类

    # 概念:将类定义在一个类的内部,被定义的类就是内部类
    # 特点:内部类及内部类的所以名称空间,可以直接被外部类访问的
    # 应用:通过内部类的名称空间,给外部类额外拓展一些特殊的属性(配置),典型的Meta内部类 - 配置类
    
    class Book(model.Model):
        class Meta:
            db_model = "owen_book"  # 配置自定义表名
            
    class BookSerializer(serializers.ModelSerializer):
        class Meta:
            model = "Book"  # 配置序列化类绑定的Model表
    
    

    DRF响应类:Response

    """
    def __init__(self, data=None, status=None, template_name=None, headers=None, 								exception=False, content_type=None):
        pass
        
    data:响应的数据 - 空、字符串、数字、列表、字段、布尔
    status:网络状态码
    template_name:drf说自己也可以支持前后台不分离返回页面,但是不能和data共存(不会涉及)
    headers:响应头(不用刻意去管)
    exception:是否是异常响应(如果是异常响应,可以赋值True,没什么用)
    content_type:响应的结果类型(如果是响应data,默认就是application/json,所有不用管)
    """
    
    # 常见使用
    Response(
        data={
            'status': 0,
            'msg': 'ok',
            'result': '正常数据'
        }
    )
    
    Response(
        data={
            'status': 1,
            'msg': '客户端错误提示',
        },
    	status=status.HTTP_400_BAD_REQUEST,
    	exception=True
    )
    
    

    序列化基类控制的初始化参数

    """
    def __init__(self, instance=None, data=empty, **kwargs):
        pass
    
    instance:是要被赋值对象的 - 对象类型数据赋值给instance
    data:是要被赋值数据的 - 请求来的数据赋值给data
    kwargs:内部有三个属性:many、partial、context
        many:操作的对象或数据,是单个的还是多个的
        partial:在修改需求时使用,可以将所有校验字段required校验规则设置为False
        context:用于视图类和序列化类直接传参使用
    """
    
    # 常见使用
    # 单查接口
    UserModelSerializer(instance=user_obj)
    
    # 群查接口
    UserModelSerializer(instance=user_query, many=True)
    
    # 增接口
    UserModelSerializer(data=request.data)
    
    # 整体改接口
    UserModelSerializer(instance=user_obj, data=request.data)
    
    # 局部改接口
    UserModelSerializer(instance=user_obj, data=request.data, partial=True)
    
    # 删接口,用不到序列化类
    
    

    反序列化

    views.py

    class UserAPIView(APIView):
        def post(self, request, *args, **kwargs):
            # 单增
            # 1)将前台请求的数据交给序列化类处理
            # 2)序列化类执行校验方法,对前台提交的所有数据进行数据校验:校验失败就是异常返回,成功才能继续
            # 3)序列化组件完成数据入库操作,得到入库对象
            # 4)响应结果给前台
            serializer = serializers.UserModelSerializer(data=request.data)
            if serializer.is_valid():
                # 校验成功 => 入库 => 正常响应
                obj = serializer.save()
                return Response({
                    'status': 0,
                    'msg': 'ok',
                    'result': '新增的那个对象'
                }, status=status.HTTP_201_CREATED)
            else:
                # 校验失败 => 异常响应
                return Response({
                    'status': 1,
                    'msg': serializer.errors,
                }, status=status.HTTP_400_BAD_REQUEST)
    
    

    serializers.py

    """
    标注:序列化 => 后台到前台(读)  |  反序列化 => 前台到后台(写)
    1)不管是序列化还是反序列化字段,都必须在fields中进行声明,没有声明的不会参与任何过程(数据都会被丢弃)
    
    2)用 read_only 表示只读,用 write_only 表示只写,不标注两者,代表可读可写
    
    3)
    i)自定义只读字段,在model类中用@property声明,默认就是read_only
    @property
    def gender(self):
    	return self.get_sex_display()
    	
    ii)自定义只写字段,在serializer类中声明,必须手动明确write_only
    re_password = serializers.CharField(write_only=True)
    
    特殊)在serializer类中声明,没有明确write_only,是对model原有字段的覆盖,且可读可写
    password = serializers.CharField()
    
    4)用 extra_kwargs 来为 写字段 制定基础校验规则(了解)
    
    5)每一个 写字段 都可以用局部钩子 validate_字段(self, value) 方法来自定义校验规则,成功返回value,失败抛出 exceptions.ValidationError('异常信息') 异常
    
    6)需要联合校验的 写字段们,用 validate(self, attrs) 方法来自定义校验规则,,成功返回attrs,失败抛出 exceptions.ValidationError({'异常字段': '异常信息'}) 异常
    
    7)extra_kwargs中一些重要的限制条件
    	i)'required':代表是否必须参与写操作,有默认值或可以为空的字段,该值为False;反之该值为True;也可以手动修改值
    """
    
    """
    开发流程:
    1)在model类中自定义 读字段,在serializer类中自定义 写字段
    2)将model自带字段和所以自定义字段书写在fields中,用write_only和read_only区别model自带字段
    3)可以写基础校验规则,也可以省略
    4)制定局部及全局钩子
    """
    
    

    课堂案例

    settings.py
    # root就是将文件夹添加到 os.path 中
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    # url就是配置路由 /路由名/
    MEDIA_URL = '/media/'
    # 后台服务器地址
    BASE_URL = 'http://localhost:8000'
    
    
    主urls.py
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^api/', include('api.urls')),    # /api/test/
    
        # icon/default.png => path变量
        # document_root必须指向icon/default.png所在路径 - media文件路径
        url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
    ]
    
    
    子urls.py
    from django.conf.urls import url
    from . import views
    urlpatterns = [
        url(r'^users/$', views.UserAPIView.as_view()),
        url(r'^users/(?P<pk>d+)/$', views.UserAPIView.as_view()),
    ]
    
    
    
    views.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework import status
    
    from . import models, serializers
    
    class UserAPIView(APIView):
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:  # 单查
                # 1)数据库交互拿到资源obj或资源objs
                # 2)数据序列化成可以返回给前台的json数据
                # 3)将json数据返回给前台
                try:
                    obj = models.User.objects.get(pk=pk)
                    serializer = serializers.UserModelSerializer(obj, many=False)
                    return Response({
                        'status': 0,
                        'msg': 'ok',
                        'result': serializer.data
                    })
                except:
                    return Response(
                        data={
                            'status': 1,
                            'msg': 'pk error'
                        },
                        status=status.HTTP_400_BAD_REQUEST,  # 比直接写400更具有描述性
                        exception=True
                    )
    
            else:  # 群查
                # 1)数据库交互拿到资源obj或资源objs
                # 2)数据序列化成可以返回给前台的json数据
                # 3)将json数据返回给前台
                queryset = models.User.objects.all()
                serializer = serializers.UserModelSerializer(queryset, many=True)
                return Response({
                        'status': 0,
                        'msg': 'ok',
                        'results': serializer.data
                    })
    
        def post(self, request, *args, **kwargs):
            # 单增
            # 1)从请求request中获得前台提交的数据
            # 2)将数据转换成Model对象,并完成数据库入库操作(序列化组件完成)
            # 3)将入库成功的对象列化成可以返回给前台的json数据(请求与响应数据不对等:请求需要提交密码,响应一定不展示密码)
            # 4)将json数据返回给前台
    
            # 1)将前台请求的数据交给序列化类处理
            # 2)序列化类执行校验方法,对前台提交的所有数据进行数据校验:校验失败就是异常返回,成功才能继续
            # 3)序列化组件完成数据入库操作,得到入库对象
            # 4)响应结果给前台
            serializer = serializers.UserModelSerializer(data=request.data)
            if serializer.is_valid():
                # 校验成功 => 入库 => 正常响应
                obj = serializer.save()
                return Response({
                    'status': 0,
                    'msg': 'ok',
                    'result': '新增的那个对象'
                }, status=status.HTTP_201_CREATED)
            else:
                # 校验失败 => 异常响应
                return Response({
                    'status': 1,
                    'msg': serializer.errors,
                }, status=status.HTTP_400_BAD_REQUEST)
    
    """
    序列化类BaseSerializer
    def __init__(self, instance=None, data=empty, **kwargs):
        pass
    
    instance:是要被赋值对象的 - 对象类型数据赋值给instance
    data:是要被赋值数据的 - 请求来的数据赋值给data
    kwargs:内部有三个属性:many、partial、context
        many:操作的对象或数据,是单个的还是多个的
        partial:
        context:
    """
    
    """
    响应类Response
    
    def __init__(self, data=None, status=None,
                     template_name=None, headers=None,
                     exception=False, content_type=None):
        pass
        
    data:响应的数据 - 空、字符串、数字、列表、字段、布尔
    status:网络状态码
    template_name:drf说自己也可以支持前后台不分离返回页面,但是不能和data共存(不会涉及)
    headers:响应头(不用刻意去管)
    exception:是否是异常响应(如果是异常响应,可以赋值True,没什么用)
    content_type:响应的结果类型(如果是响应data,默认就是application/json,所有不用管)
    """
    
    
    serializers.py
    from rest_framework import serializers
    from rest_framework import exceptions
    
    from . import models
    class UserModelSerializer(serializers.ModelSerializer):
        # 自定义反序列化字段(所有校验规则自己定义,也可以覆盖model已有的字段)
        # 覆盖model有的字段,不明确write_only会参与序列化过程
        password = serializers.CharField(min_length=4, max_length=8, write_only=True)
        # 自定义字段,不明确write_only序列化会报错,序列化会从model中强行反射自定义字段,但是model没有对于字段
        re_password = serializers.CharField(min_length=4, max_length=8, write_only=True)
    
    
        class Meta:
            # 该序列化类是辅助于那个Model类的
            model = models.User
            # 设置参与序列化与反序列化字段
            # 插拔式:可以选择性返回给前台字段(插头都是在Model类中制造)
            # fields = ['name', 'age', 'height', 'sex', 'icon]
    
            # 第一波分析:
            # 1)name和age,在fields中标明了,且没有默认值,也不能为空,入库时必须提供,所有校验时必须提供
            # 2)height,在fields中标明了,但是有默认值,所有前台不提供,也能在入库时采用默认值(可以为空的字段同理)
            # 3)password,没有在fields中标明了,所以校验规则无法检测password情况,但是即使数据校验提供了,
            #       也不能完成入库,原因是password是入库的必备条件
            # 4)gender和img是自定义插拔@property字段,默认就不会进行校验(不参与校验)
            # fields = ['name', 'age', 'height', 'gender', 'img']
    
            # 第二波分析:
            # 1)如何区分 序列化反序列化字段 | 只序列化字段(后台到前台) | 只反序列化字段(前台到后台)
            #       不做write_only和read_only任何限制 => 序列化反序列化字段
            #       只做read_only限制 => 只序列化字段(后台到前台)
            #       只做write_only限制 => 只反序列化字段(前台到后台)
    
            # 2)对前台到后台的数据,制定基础的校验规则(了解)
            #       CharField(max_length, min_length, errors_kwargs)
            #       DecimalField(min_value, max_value, max_digits, decimal_places,error_messages)
    
            # 3)如果一个字段有默认值或是可以为空,比如height,如何做限制
            #       虽然有默认值或是可以为空,能不能强制限制必须提供,可以通过required是True来限制
            #       前台提交了该字段,我就校验,没提交我就不校验:1)required是False 2)有校验规则
            fields = ['name', 'age', 'password', 'height', 'gender', 'img', 're_password']
    
            # 第三波分析
            # 1)制定的简易校验规则(没有制定)后,可以再通过字段的 局部钩子 对该字段进行复杂校验
            # 2)每个字段进行逐一复杂校验后,还可以进行集体的 全局钩子 校验
            #       涉及对自定义反序列化字段的校验:re_password(要参与校验,但是不会入库)
    
            # 校验规则
            extra_kwargs = {
                'name': {
                    # 'write_only': True,
                    # 'read_only': True
                },
                'password': {
                    'write_only': True,
                    'min_length': 3,
                    'max_length': 8,
                    'error_messages': {  # 可以被国际化配置替代
                        'min_length': '太短',
                        'max_length': '太长'
                    }
                },
                'height': {
                    # 'required': True,
                    'min_value': 0,
                }
            }
    
        # 注:局部全局钩子,是和Meta同缩减,属于序列化类的
        # 局部钩子:validate_要校验的字段(self, 要校验的字段值)
        # 全局钩子:validate(self, 所以字段值的字典)
        # 校验规则:成功返回传来的数据 | 失败抛出异常
    
        def validate_name(self, value):
            if 'g' in value.lower():
                raise exceptions.ValidationError("这个g不行")
            else:
                return value
    
        def validate(self, attrs):
            print(attrs)
            password = attrs.get('password')  # 只是拿出来校验
            re_password = attrs.pop('re_password')  # 必须取出校验,因为不能入库
    
            if password != re_password:
                raise exceptions.ValidationError({'re_password': '两次密码不一致'})
            else:
                return attrs
    
    

    四、内容大纲

    """
    1)Response二次封装
    
    2)多表关系优化
    
    3)多表序列化
    
    4)十大接口
    """
    
    

    二次封装Response

    # 新建response.py文件
    from rest_framework.response import Response
    
    class APIResponse(Response):
        def __init__(self, status=0, msg='ok', http_status=None, headers=None, exception=False, **kwargs):
            # 将外界传入的数据状态码、状态信息以及其他所有额外存储在kwargs中的信息,都格式化成data数据
            data = {
                'status': status,
                'msg': msg
            }
            # 在外界数据可以用result和results来存储
            if kwargs:
                data.update(kwargs)
    
            super().__init__(data=data, status=http_status, headers=headers, exception=exception)
    
    
    # 使用:
    # APIResponse() 代表就返回 {"status": 0, "msg": "ok"}
    
    # APIResponse(result="结果") 代表返回 {"status": 0, "msg": "ok", "result": "结果"}
    
    # APIResponse(status=1, msg='error', http_status=400, exception=True) 异常返回 {"status": 1, "msg": "error"}
    
    

    数据库关系分析

    """
    1)之间有关系的两个表,增删改操作会相互影响(效率低),查询操作就是正常的连表操作
    2)之间有关系的两个表,断开关联,但所有数据保持与原来一致
    	每个表都可以单独操作,增删改操作效率极高,但是容易出现脏数据(开发中完全可以避免)
    	由于数据没有任何变化,所以查询的连表操作不会受到任何影响
    	
    3)Django的ORM支持断关联操作关系表,且所有的操作方式和没有断关联操作一致
    """
    
    

    ORM操作关系

    """
    外键位置:
    1)一对多:ForeignKey必须放在多的一方,书与出版社,外键应该放在书表
    2)多对多:ManyToManyField放在任何一方都可以,因为会创建关系表,在关系表中用两个外键分别关联两个表
    3)一对一:OneToOneField放在依赖的表,作者与作者详情,放在详情表,OneToOneField会被转换为 外键 + 唯一约束
    """
    
    """
    ORM关系Field:
    ForeignKey可以设置related_name, db_constraint, on_delete
    OneToOneField可以设置related_name, db_constraint, on_delete
    ManyToManyField只能设置related_name, db_constraint
        不能设置on_delete的原因:不管是关联的A表,还是B表,数据修改,都会影响到关系表(默认级联),
        如果想控制,只能自定义关系表,在关系表的两个外键分别设置on_delete
    """
    
    """
    参数含义
    related_name:表之间反向访问的名字,默认是 表名小写|表名小写_set
    db_constraint:表之间的关联关系,默认为True,代表关联,设置False,可以提高增删改的效率,且不影响查等其他操作
    on_delete:在django 1.x下默认是CASCADE,在django 2.x下必须手动明确
    """
    
    """
    表关系:
    作者没,作者详情一定没:CASCADE  *****
    作者没,书还是该作者出的:DO_NOTHING
    部门们,部门内的员工全部进入未分组部门:SET_DEFAULT (需要配合default属性使用)  
    部门们,部门内的员工部门外键字段设置为空:SET_NULL (需要配合null=True属性使用)  *****
    """
    
    
    案例
    class Author(models.Model):
        name = models.CharField(max_length=64)
    
    
    class AuthorDetail(models.Model):
        phone = models.CharField(max_length=11)
        author = models.OneToOneField(
            to=Author,
            related_name='detail',
            db_constraint=False,
            on_delete=models.SET_NULL,
            null=True
        )
    
    
    测试
    import os, django
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "d_proj.settings")
    django.setup()
    
    from api.models import Author, AuthorDetail
    
    # 测试正向反向查询
    # a1 = Author.objects.first()
    # print(a1.name)
    # print(a1.detail.phone)
    
    # ad2 = AuthorDetail.objects.last()
    # print(ad2.phone)
    # print(ad2.author.name)
    
    # 级联关系测试
    # ad2 = AuthorDetail.objects.last()  # type: AuthorDetail
    # ad2.delete()
    
    # Author.objects.first().delete()
    
    
    

    基表

    # 基类:是抽象的(不会完成数据库迁移),目的是提供共有字段的
    class BaseModel(models.Model):
        is_delete = models.BooleanField(default=False)
        updated_time = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            abstract = True  # 必须完成该配置
    
    
    应用
    class Book(BaseModel):
        name = models.CharField(max_length=64)
        price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
        image = models.ImageField(upload_to='img', default='img/default.png')
    
        publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
        authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
    
    
    class Publish(BaseModel):
        name = models.CharField(max_length=64)
    
    
    class Author(BaseModel):
        name = models.CharField(max_length=64)
    
    
    class AuthorDetail(BaseModel):
        phone = models.CharField(max_length=11)
        author = models.OneToOneField(
            to=Author,
            related_name='detail',
            db_constraint=False,
            on_delete=models.SET_NULL,
            null=True
        )
    
    

    序列化类其他配置(了解)

    class AuthorModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Author
            # 不常用,将全部字段提供给外界
            fields = '__all__' 
            
    # ------------------------------------------------------------------
    
    class AuthorModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Author
            # 不常用,排除指定字段的其他所有字段,不能自动包含 外键反向 字段
            exclude = ['is_delete', 'updated_time']  
            
    # ------------------------------------------------------------------
    
    class AuthorModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Author
            # 'detail', 'books' 是 外键(正向|反向) 字段
            fields = ['name', 'detail', 'books']
            # 不常用,自动深度,自动深度会显示外键关联表的所有字段
            depth = 2  
    # 正向外键字段:就是外键的属性名
    # 反向外键字段:就是外键属性设置的related_name
    
    

    子序列化

    """
    1)子序列化的字段,必须是 外键(正向|反向) 字段
    2)子序列化对应的数据是单个many=False,数据对应是多个many=True
    3)子序列化其实就是自定义序列化字段,覆盖了原有 外键(正向|反向)字段 的规则,所以不能进行反序列化
    """
    
    

    案例

    urls.py
    url(r'^authors/$', views.AuthorAPIView.as_view()),
    url(r'^authors/(?P<pk>d+)/$', views.AuthorAPIView.as_view()),
    
    
    serializers.py
    from rest_framework import serializers
    from . import models
    class AuthorDetailModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.AuthorDetail
            fields = ['phone']
    
    class BookModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Book
            fields = ['name', 'price']
    
    class AuthorModelSerializer(serializers.ModelSerializer):
        # 子序列化:子序列化类必须写在上方,且只能对 外键(正向反向)字段 进行覆盖
        # 注:运用了子序列化的外键字段,就不能进行数据库的反序列化过程
        detail = AuthorDetailModelSerializer(many=False, read_only=True)
        books = BookModelSerializer(many=True, read_only=True)
        # 问题:
        # 1)不设置read_only时,就相当于允许反序列化,反序列化是就会报错
        # 2)设置read_only时,可以完成反序列化,但是新增的数据再序列化了,就没有外键关联的数据,与原来数据格式就不一致了
        class Meta:
            model = models.Author
            fields = ['name', 'detail', 'books']
    
    
    views.py
    # 实际开发,资源的大量操作都是查询操作,只有查需求的资源,可以采用子序列化
    class AuthorAPIView(APIView):
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:
                obj = models.Author.objects.filter(is_delete=False, pk=pk).first()
                serializer = serializers.AuthorModelSerializer(instance=obj)
                return APIResponse(result=serializer.data)
            else:
                queryset = models.Author.objects.filter(is_delete=False).all()
                serializer = serializers.AuthorModelSerializer(instance=queryset, many=True)
                return APIResponse(results=serializer.data)
    
        # 测试子序列化外键字段,不能参与反序列化,因为
        def post(self, request, *args, **kwargs):
            serializer = serializers.AuthorModelSerializer(data=request.data)
            if serializer.is_valid():
                obj = serializer.save()
                return APIResponse(result=serializers.AuthorModelSerializer(instance=obj).data, http_status=201)
            else:
                # 校验失败 => 异常响应
                return APIResponse(1, serializer.errors, http_status=400)
    
    

    多表序列化与反序列化

    """
    1)外键字段要参与反序列化,所以外键字段设置为write_only
    2)外键关系需要连表序列化结果给前台,可以用@property来自定义连表序列化
    """
    
    

    案例

    urls.py
    url(r'^books/$', views.BookAPIView.as_view()),
    url(r'^books/(?P<pk>d+)/$', views.BookAPIView.as_view()),
    
    
    models.py
    class Book(BaseModel):
        # ...
        
        @property  # @property字段默认就是read_only,且不允许修改
        def publish_name(self):
            return self.publish.name
    
        @property  # 自定义序列化过程
        def author_list(self):
            temp_author_list = []
            for author in self.authors.all():
                author_dic = {
                    "name": author.name
                }
                try:
                    author_dic['phone'] = author.detail.phone
                except:
                    author_dic['phone'] = ''
                temp_author_list.append(author_dic)
            return temp_author_list
    
        @property  # 借助序列化类完成序列化过程
        def read_author_list(self):
            from .serializers import AuthorModelSerializer
            return AuthorModelSerializer(self.authors.all(), many=True).data
    
    
    serializers.py
    # 辅助序列化类
    class AuthorDetailModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.AuthorDetail
            fields = ['phone']
    
    # 辅助序列化类
    class AuthorModelSerializer(serializers.ModelSerializer):
        detail = AuthorDetailModelSerializer(many=False, read_only=True)
        class Meta:
            model = models.Author
            fields = ['name', 'detail']
    
    
    # 主序列化类
    class BookModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Book
            fields = ('name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list', 'read_author_list')
            extra_kwargs = {
                'image': {
                    'read_only': True,
                },
                'publish': {  # 系统原有的外键字段,要留给反序列化过程使用,序列化外键内容,用@property自定义
                    'write_only': True,
                },
                'authors': {
                    'write_only': True,
                }
            }
    
    
    views.py
    # 六个必备接口:单查、群查、单增、单删、单整体改(了解)、单局部改
    # 四个额外接口:群增、群删、群整体改、群局部改
    class BookAPIView(APIView):
        # 单查群查
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:
                obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
                serializer = serializers.BookModelSerializer(instance=obj)
                return APIResponse(result=serializer.data)
            else:
                queryset = models.Book.objects.filter(is_delete=False).all()
                serializer = serializers.BookModelSerializer(instance=queryset, many=True)
                return APIResponse(results=serializer.data)
    
    
        # 单增群增
        def post(self, request, *args, **kwargs):
            # 如何区别单增群增:request.data是{}还是[]
            if not isinstance(request.data, list):
                # 单增
                serializer = serializers.BookModelSerializer(data=request.data)
                serializer.is_valid(raise_exception=True)  # 如果校验失败,会直接抛异常,返回给前台
                obj = serializer.save()
                # 为什么要将新增的对象重新序列化给前台:序列化与反序列化数据不对等
                return APIResponse(result=serializers.BookModelSerializer(obj).data, http_status=201)
            else:
                # 群增
                serializer = serializers.BookModelSerializer(data=request.data, many=True)
                serializer.is_valid(raise_exception=True)  # 如果校验失败,会直接抛异常,返回给前台
                objs = serializer.save()
                # 为什么要将新增的对象重新序列化给前台:序列化与反序列化数据不对等
                return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data, http_status=201)
    
    # 友情注释:群增其实是借助了ListSerializer来的create方法完成的
    
    

    小结

    """
    1)二次封装Response:
    	自定义类继承Response,重写init方法,在内部格式化data
    	
    2)表关系分析:
    	断关联:
    		优点:提示增删改操作效率,不允许查效率
    		缺点:增删改操作可能会导致脏数据,所以需要通过逻辑或是事务来保证
    		
    3)ORM表关系处理语法:
    	1)外键所在位置
    	2)如何断关联db_constraint
    	3)正向方向自定义名字:related_name
    	4)表关系:on_delete四种
    	
    4)基表:Meta中配置abstract=True,来被继承,提供共有字段
    
    5)多表连表Meta中的了解配置
    	fields = '__all__'  # 不常用,将全部字段提供给外键
        exclude = ['is_delete', 'updated_time']  # 不常用,排除指定字段的其他所有字段
        depth = 2  # 不常用,主动深度,自动深度会显示关联表的所有字段
        
    6)子序列化
    	i)通常只用于序列化过程,对外键字段进行了覆盖,影响外键字段的反序列化过程
    	class SubSerializer:
    		pass
    	class SupSerializer:
    		外键 = SubSerializer(many=True|False)
    	
    7)多表的序列化与反序列化
    	1)连表序列化用自定义@property完成:内部实现可以自定义逻辑,也可以走序列化类
    	2)外键字段留给反序列化来使用
    	
    8)单查、群查、单增、群增接口
    """
    
    

    五、内容大纲

    """
    1、十大接口:序列化 - partial、context、ListSerializer
    
    2、视图家族:views、mixins、generics、viewsets
    
    3、路由组件:结合viewsets使用
    """
    
    

    序列化类外键字段的覆盖

    """
    1)在序列化类中自定义字段,名字与model类中属性名一致,就称之为覆盖操作
    	(覆盖的是属性的所有规则:extra_kwargs中指定的简易规则、model字段提供的默认规则、数据库唯一约束等哪些规则)
    
    2)外键覆盖字段用PrimaryKeyRelatedField来实现,可以做到只读、只写、可读可写三种形式
    	只读:read_only=True
    	只写:queryset=关联表的queryset, write_only=True
    	可读可写:queryset=关联表的queryset
    	
    3)当外界关联的数据是多个时,需标识many=True条件
    """
    
    
    class BookModelSerializer(serializers.ModelSerializer):
        # 如何覆盖外键字段
        # publish = serializers.PrimaryKeyRelatedField(read_only=True)  # 只读
        # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all(), write_only=True)  # 只写
        # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())  # 可读可写
    
        publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())
        authors = serializers.PrimaryKeyRelatedField(queryset=models.Author.objects.all(), many=True)
    
        class Meta:
            model = models.Book
            fields = ('name', 'price', 'image', 'publish', 'authors')
    
    

    十大接口序列化总结

    """
    def __init__(self, instance=None, data=empty, **kwargs):
        pass
    
    instance:是要被赋值对象的 - 对象类型数据赋值给instance
    data:是要被赋值数据的 - 请求来的数据赋值给data
    kwargs:内部有三个属性:many、partial、context
        many:操作的对象或数据,是单个的还是多个的
        partial:在修改需求时使用,可以将所有校验字段required校验规则设置为False
        context:用于视图类和序列化类直接传参使用
    """
    
    # 常见使用
    # 单查接口
    UserModelSerializer(instance=user_obj)
    
    # 群查接口
    UserModelSerializer(instance=user_query, many=True)
    
    # 单增接口,request.data是字典
    UserModelSerializer(data=request.data) 
    
    # 群增接口,request.data是列表
    UserModelSerializer(data=request.data, many=True)
    
    # 单整体改接口,request.data是字典
    UserModelSerializer(instance=user_obj, data=request.data)
    
    # 群整体改接口,request.data是列表,且可以分离出pks,转换成user_queryset
    UserModelSerializer(instance=user_queryset, data=request.data, many=True)
    
    # 单局部改接口,request.data是字典
    UserModelSerializer(instance=user_obj, data=request.data, partial=True)
    
    # 群局部改接口,request.data是列表,且可以分离出pks,转换成user_queryset
    UserModelSerializer(instance=user_queryset, data=request.data, partial=True, many=True)
    
    # 删接口,用不到序列化类
    
    

    十大接口核心知识小结

    """
    1)初始化序列化类,设置partial=True可以将所有反序列化字段的 required 设置为 False(提供就校验,不提供不校验),可以运用在局部修改接口
    
    2)初始化序列化类,设置context={...},在序列化类操作self.context,实现视图类和序列化类数据互通
    
    3)只有要完成资源的群改这种特殊需求时,才需要自定义ListSerializer绑定给自定义的ModelSerializer,重写update方法,来完成需求
    """
    
    

    案例

    models.py
    # 基类:是抽象的(不会完成数据库迁移),目的是提供共有字段的
    class BaseModel(models.Model):
        is_delete = models.BooleanField(default=False)
        updated_time = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            abstract = True  # 必须完成该配置
    
    class Book(BaseModel):
        name = models.CharField(max_length=64)
        price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
        image = models.ImageField(upload_to='img', default='img/default.png')
    
        publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
        authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
    	
        @property  # @property字段默认就是read_only,且不允许修改
        def publish_name(self):
            return self.publish.name
    
        @property  # 自定义序列化过程
        def author_list(self):
            temp_author_list = []
            for author in self.authors.all():
                author_dic = {
                    "name": author.name
                }
                try:
                    author_dic['phone'] = author.detail.phone
                except:
                    author_dic['phone'] = ''
                temp_author_list.append(author_dic)
            return temp_author_list
        
    
    class Publish(BaseModel):
        name = models.CharField(max_length=64)
    
    
    class Author(BaseModel):
        name = models.CharField(max_length=64)
    
    
    class AuthorDetail(BaseModel):
        phone = models.CharField(max_length=11)
        author = models.OneToOneField(to=Author, related_name='detail', db_constraint=False, on_delete=models.CASCADE)
    
    
    urls.py
    url(r'^books/$', views.BookAPIView.as_view()),
    url(r'^books/(?P<pk>d+)/$', views.BookAPIView.as_view()),
    
    
    serializers.py
    from rest_framework import serializers
    from . import models
    
    # 群增群改辅助类(了解)
    class BookListSerializer(serializers.ListSerializer):
        """
        1)create群增方法不需要重新
        2)update群改方法需要重写,且需要和views中处理request.data的逻辑配套使用
        3)self.child就代表该ListSerializer类绑定的ModelSerializer类
            BookListSerializer的self.child就是BookModelSerializer
        """
        # 重新update方法
        def update(self, queryset, validated_data_list):
            return [
                self.child.update(queryset[index], validated_data) for index, validated_data in enumerate(validated_data_list)
            ]
    
    # 主序列化类
    class BookModelSerializer(serializers.ModelSerializer):
        class Meta:
            # 配置自定义群增群改序列化类
            list_serializer_class = BookListSerializer
    
            model = models.Book
            fields = ('name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list')
            # fields = ('name', 'price', 'image', 'publish', 'authors', 'abc')
            extra_kwargs = {
                'image': {
                    'read_only': True,
                },
                'publish': {  # 系统原有的外键字段,要留给反序列化过程使用,序列化外键内容,用@property自定义
                    'write_only': True,
                },
                'authors': {
                    'write_only': True,
                },
            }
    
        # 需求:内外传参
        # 1)在钩子函数中,获得请求请求对象request 视图类 => 序列化类
        # 2)序列化钩子校验过程中,也会产生一些数据,这些数据能不能传递给外界使用:序列化类 => 视图类
        # 序列化类的context属性,被视图类与序列化类共享
        def validate(self, attrs):
            print(self.context)  # 可以获得视图类在初始化序列化对象时传入的context
            # self.context.update({'a': 10})  # 序列化类内部更新context,传递给视图类
            return attrs
    
    
    
    views.py
    from rest_framework.views import APIView
    from . import models, serializers
    from .response import APIResponse
    
    # 六个必备接口:单查、群查、单增、单删、单整体改(了解)、单局部改
    # 四个额外接口:群增、群删、群整体改、群局部改
    class BookAPIView(APIView):
        # 单查群查
        """
        单查:接口:/books/(pk)/
        群查:接口:/books/
        """
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:
                obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
                serializer = serializers.BookModelSerializer(instance=obj)
                return APIResponse(result=serializer.data)
            else:
                queryset = models.Book.objects.filter(is_delete=False).all()
                serializer = serializers.BookModelSerializer(instance=queryset, many=True)
                return APIResponse(results=serializer.data)
    
        # 单增群增
        """
        单增:接口:/books/   数据:dict
        群增:接口:/books/   数据:list
        """
        def post(self, request, *args, **kwargs):
            # 如何区别单增群增:request.data是{}还是[]
            if not isinstance(request.data, list):
                # 单增
                serializer = serializers.BookModelSerializer(data=request.data)
                serializer.is_valid(raise_exception=True)  # 如果校验失败,会直接抛异常,返回给前台
                obj = serializer.save()
                # 为什么要将新增的对象重新序列化给前台:序列化与反序列化数据不对等
                return APIResponse(result=serializers.BookModelSerializer(obj).data, http_status=201)
            else:
                # 群增
                serializer = serializers.BookModelSerializer(data=request.data, many=True)
                serializer.is_valid(raise_exception=True)  # 如果校验失败,会直接抛异常,返回给前台
                objs = serializer.save()
                # 为什么要将新增的对象重新序列化给前台:序列化与反序列化数据不对等
                return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data, http_status=201)
    
        # 单删群删
        """
        单删:接口:/books/(pk)/
        群删:接口:/books/   数据:[pk1, ..., pkn]
        """
        def delete(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:
                pks = [pk]  # 将单删伪装成群删一条
            else:
                pks = request.data  # 群删的数据就是群删的主键们
    
            try:  # request.data可能提交的是乱七八糟的数据,所以orm操作可能会异常
                rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
            except:
                return APIResponse(1, '数据有误')
    
            if rows:  # 只要有受影响的行,就代表删除成功
                return APIResponse(0, '删除成功')
    
            return APIResponse(2, '删除失败')
    
        # 单整体改群整体改
        """
        单整体改:接口:/books/(pk)/ 数据:dict
        群整体改:接口:/books/   数据:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]}
        """
        def put(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:  # 单
                try:
                    instance = models.Book.objects.get(is_delete=False, pk=pk)
                except:
                    return APIResponse(1, 'pk error', http_status=400)
                # 序列化类同时赋值instance和data,代表用data重新更新instance => 修改
                serializer = serializers.BookModelSerializer(instance=instance, data=request.data)
                serializer.is_valid(raise_exception=True)
                obj = serializer.save()
                return APIResponse(result=serializers.BookModelSerializer(obj).data)
            else:  # 群
                """ 分析request.data数据 [{'pk':1, 'name': '', 'publish': 1, 'authors': [1, 2]}, ...]
                1)从 request.data 中分离出 pks 列表
                2)pks中存放的pk在数据库中没有对应数据,或者对应的数据已经被删除了,这些不合理的pk要被剔除
                3)pks最终转换得到的 objs 列表长度与 request.data 列表长度不一致,就是数据有误
                """
                pks = []
                try:  # 只要不是要求的标准数据,一定会在下方四行代码某一行抛出异常
                    for dic in request.data:
                        pks.append(dic.pop('pk'))
                    objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
                    assert len(objs) == len(request.data)  # 两个列表长度必须一致
                except:
                    return APIResponse(1, '数据有误', http_status=400)
    
                serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True)
                serializer.is_valid(raise_exception=True)
                objs = serializer.save()
                return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)
    
    
        # 单局部改群局部改
        """
        单局部改:接口:/books/(pk)/ 数据:dict
        群局部改:接口:/books/   数据:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]}
        """
        def patch(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:  # 单
                try:
                    instance = models.Book.objects.get(is_delete=False, pk=pk)
                except:
                    return APIResponse(1, 'pk error', http_status=400)
                # partial=True就是将所有反序列化字段的 required 设置为 False(提供就校验,不提供不校验)
                serializer = serializers.BookModelSerializer(instance=instance, data=request.data, partial=True)
                serializer.is_valid(raise_exception=True)
                obj = serializer.save()
                return APIResponse(result=serializers.BookModelSerializer(obj).data)
            else:  # 群
                pks = []
                try:  # 只要不是要求的标准数据,一定会在下方三行代码某一行抛出异常
                    for dic in request.data:
                        pks.append(dic.get('pk'))
                    objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
                    assert len(objs) == len(request.data)  # 两个列表长度必须一致
                except:
                    return APIResponse(1, '数据有误', http_status=400)
    
                serializer = serializers.BookModelSerializer(
                    instance=objs,
                    data=request.data,
                    many=True,
                    partial=True,
                    context={'request': request}  # 初始化时,对context赋值,将视图类中数据传递给序列化类
                )
    
                serializer.is_valid(raise_exception=True)
                objs = serializer.save()
    
                print(serializer.context)  # 在完成序列化类校验后,可以重新拿到序列化类内部对context做的值更新
                return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)
    
    

    视图家族

    """
    视图基类:APIView、GenericAPIView
    视图工具类:mixins包下的五个类(六个方法)
    工具视图类:generics包下的所有GenericAPIView的子类
    视图集:viewsets包下的类
    """
    
    """ GenericAPIView基类(基本不会单独使用,了解即可,但是是高级视图类的依赖基础)
    1)GenericAPIView继承APIView,所有APIView子类写法在继承GenericAPIView时可以保持一致
    2)GenericAPIView给我们提供了三个属性 queryset、serializer_class、lookup_field
    3)GenericAPIView给我们提供了三个方法 get_queryset、get_serializer、get_obj
    """
    
    
    """ mixins包存放了视图工具类(不能单独使用,必须配合GenericAPIView使用)
    CreateModelMixin:单增工具类
    	create方法
    	
    ListModelMixin:群查工具类
    	list方法
    
    RetrieveModelMixin:单查工具类
    	retrieve方法
    
    UpdateModelMixin:单整体局部改工具类
    	update方法
    
    DestroyModelMixin:单删工具类
    	destory方法
    """
    
    """ generics包下的所有GenericAPIView的子类(就是继承GenericAPIView和不同mixins下的工具类的组合)
    1)定义的视图类,继承generics包下已有的特点的GenericAPIView子类,可以在只初始化queryset和serializer_class两个类属性后,就获得特定的功能
    
    2)定义的视图类,自己手动继承GenericAPIView基类,再任意组合mixins包下的一个或多个工具类,可以实现自定义的工具视图类,获得特定的功能或功能们
    
    注:
    i)在这些模式下,不能实现单查群查共存(可以加逻辑区分,也可以用视图集知识)
    ii)DestroyModelMixin工具类提供的destory方法默认是从数据库中删除数据,所以一般删除数据的需求需要自定义逻辑
    """
    
    
    urls.py
    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        # ...
        
        url(r'^v1/books/$', views.BookV1APIView.as_view()),
        url(r'^v1/books/(?P<pk>d+)/$', views.BookV1APIView.as_view()),
    
        url(r'^v2/books/$', views.BookV2APIView.as_view()),
        url(r'^v2/books/(?P<pk>d+)/$', views.BookV2APIView.as_view()),
    
        url(r'^v3/books/$', views.BookV3APIView.as_view()),
        url(r'^v3/books/(?P<pk>d+)/$', views.BookV3APIView.as_view()),
    ]
    
    
    views.py
    # ----------------------------- 过渡写法:了解 -----------------------------
    
    from rest_framework.generics import GenericAPIView
    class BookV1APIView(GenericAPIView):
        # 将数据和序列化提示为类属性,所有的请求方法都可以复用
        queryset = models.Book.objects.filter(is_delete=False).all()
        serializer_class = serializers.BookModelSerializer
        lookup_field = 'pk'  # 可以省略,默认是pk,与url有名分组对应的
    
        # 群查
        def get(self, request, *args, **kwargs):
            # queryset = models.Book.objects.filter(is_delete=False).all()  # => 方法+属性两行代码
            queryset = self.get_queryset()
            # serializer = serializers.BookModelSerializer(instance=queryset, many=True)  # => 方法+属性两行代码
            serializer = self.get_serializer(instance=queryset, many=True)
            return APIResponse(results=serializer.data)
    
        # 单查
        # def get(self, request, *args, **kwargs):
        #     obj = self.get_object()
        #     serializer = self.get_serializer(obj)
        #     return APIResponse(results=serializer.data)
    
        # 单增
        def post(self, request, *args, **kwargs):
            # serializer = serializers.BookModelSerializer(data=request.data)
            serializer = self.get_serializer(data=request.data)  # 同样的步骤多了,好处就来了
            serializer.is_valid(raise_exception=True)
            obj = serializer.save()
            return APIResponse(result=self.get_serializer(obj).data, http_status=201)
        
    # ----------------------------- 过渡写法:了解 -----------------------------
    
    from rest_framework.generics import GenericAPIView
    from rest_framework import mixins
    class BookV2APIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.CreateModelMixin):
        queryset = models.Book.objects.filter(is_delete=False).all()
        serializer_class = serializers.BookModelSerializer
    
        # 单查
        def get(self, request, *args, **kwargs):
            # obj = self.get_object()
            # serializer = self.get_serializer(obj)
            # return APIResponse(results=serializer.data)
    
            # return self.retrieve(request, *args, **kwargs)
    
            response = self.retrieve(request, *args, **kwargs)
            return APIResponse(result=response.data)
    
        # 单增
        def post(self, request, *args, **kwargs):
            return self.create(request, *args, **kwargs)
    
    
    # ----------------------------- 开发写法:常用 -----------------------------
    
    from rest_framework.generics import RetrieveAPIView
    class BookV3APIView(RetrieveAPIView):
        queryset = models.Book.objects.filter(is_delete=False).all()
        serializer_class = serializers.BookModelSerializer
    
        # 单查
        pass
    
    

    六、内容大纲

    """
    1)视图集与路由组件(开发最常用、最高级)
    
    2)三大认证原理
    
    3)RBAC认证规则
    
    4)jwt认证规则
    
    5)drf的三大认证实现
    """
    
    

    复习

    """
    1)序列化类初始化各参数含义:
    	instance:被赋值对象
    	data:被赋值数据
    	many:表示操作的对象或数据是否是多个
    	partial:修改校验字段均为选填
    	context:视图类与序列化类间传参
    	
    	查:instance | instance, many
    	增:data
    	改:instance, data | instance, data, partial
    	删:orm操作
    	
    2)覆盖外键字段(认证规则)
    	字段 = serializers.PrimaryKeyRelatedField(queryset)
    	字段 = serializers.PrimaryKeyRelatedField(read_only)
    	字段 = serializers.PrimaryKeyRelatedField(queryset, write_only)
    	
    3)十大接口:
    	群增群改需要ListSerializer类辅助,在ModelSerializer的Meta list_serializer_class配置中建立关联,create方法可以继承,update方法需要重写
    	
    4)视图家族:
    	APIView:基类
    	
    	GenericAPIView:基类
    		i)继承APIView的,所以拥有APIView的全部
    		ii)三个类属性:queryset、serializer_class、lookup_field
    		iii)三个方法:get_queryset、get_serializer、get_object
    		过渡:单独继承GenericAPIView类的视图类,需要自己定义请求方法如get,还需要属性方法体完成请求
    		
    	mixins包:工具类 - eg:RetrieveModelMixin:retrieve
    		retrieve、list、create、update、partial_update、destory
    		过渡:因为上方六个方法体都是依赖与GenericAPIView类的,所以必须配合GenericAPIView类使用
    		
    	generics包:工具视图类
    		i)系统完成的GenericAPIView与mixins包下工具类的不同组合
    			只需要配置三个类属性
    			
    		ii)自己完成GenericAPIView与mixins包下工具类的不同组合
    			需要配置三个类属性,需要书写请求方法,如post,内部直接调用self.create方法
    			
    		iii)自己继承一堆generics包下的工具视图,完成组合
    			只需要配置三个类属性,但是单查、群查不能共存
    """
    
    

    准备工作

    models.py
    from django.db import models
    
    # 基类:是抽象的(不会完成数据库迁移),目的是提供共有字段的
    class BaseModel(models.Model):
        is_delete = models.BooleanField(default=False)
        updated_time = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            abstract = True  # 必须完成该配置
    
    class Book(BaseModel):
        name = models.CharField(max_length=64)
        price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
        image = models.ImageField(upload_to='img', default='img/default.png')
    
        publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
        authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
    
        @property  # @property字段默认就是read_only,且不允许修改
        def publish_name(self):
            return self.publish.name
    
        @property  # 自定义序列化过程
        def author_list(self):
            temp_author_list = []
            for author in self.authors.all():
                author_dic = {
                    "name": author.name
                }
                try:
                    author_dic['phone'] = author.detail.phone
                except:
                    author_dic['phone'] = ''
                temp_author_list.append(author_dic)
            return temp_author_list
    
    
    
    class Publish(BaseModel):
        name = models.CharField(max_length=64)
    
    
    class Author(BaseModel):
        name = models.CharField(max_length=64)
    
    
    class AuthorDetail(BaseModel):
        phone = models.CharField(max_length=11)
        author = models.OneToOneField(to=Author, related_name='detail', db_constraint=False, on_delete=models.CASCADE)
    
    
    serializers.py
    from rest_framework import serializers
    from . import models
    
    # 只有在资源需要提供群改,才需要定义ListSerializer,重写update方法
    class BookListSerializer(serializers.ListSerializer):
        def update(self, queryset, validated_data_list):
            return [
                self.child.update(queryset[index], validated_data) for index, validated_data in enumerate(validated_data_list)
            ]
    
    class BookModelSerializer(serializers.ModelSerializer):
        class Meta:
            list_serializer_class = BookListSerializer
            model = models.Book
            fields = ['name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list']
            extra_kwargs = {
                'publish': {
                    'write_only': True
                },
                'authors': {
                    'write_only': True
                }
            }
    
    

    基于GenericAPIView的十大接口

    views.py
    # 十大接口:
    # 1)单查、群查、单增、单整体改、单局部改都可以直接使用
    # 2)单删不能直接使用,因为默认提供的功能是删除数据库数据,不是我们自定义is_delete字段值修改
    # 3)除了群查以为的接口,都要自己来实现
    
    # 注:给序列化类context赋值{'request': request},序列化类就可以自动补全后台图片链接
    
    
    from rest_framework.generics import GenericAPIView
    from rest_framework import mixins
    from . import models, serializers
    from rest_framework.response import Response
    
    class BookV1APIView(GenericAPIView,
                        mixins.RetrieveModelMixin,
                        mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        mixins.UpdateModelMixin):
    
        queryset = models.Book.objects.filter(is_delete=False).all()
        serializer_class = serializers.BookModelSerializer
    
        def get(self, request, *args, **kwargs):
            if 'pk' in kwargs:
                return self.retrieve(request, *args, **kwargs)  # 单查
    		
            # queryset = models.Book.objects.filter(is_delete=False).all()
            # 注:给序列化类context赋值{'request': request},序列化类就可以自动补全后台图片链接
            # serializer = serializers.BookModelSerializer(queryset, many=True, context={'request': request})
            # return Response(serializer.data)
            return self.list(request, *args, **kwargs)  # 群查
    
        def post(self, request, *args, **kwargs):
            if not isinstance(request.data, list):
                return self.create(request, *args, **kwargs)
    
            serializer = self.get_serializer(data=request.data, many=True)
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=201, headers=headers)
    
        def delete(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:
                pks = [pk]
            else:
                pks = request.data
            try:
                rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
            except:
                return Response(status=400)
            if rows:
                return Response(status=204)
            return Response(status=400)
    
        def put(self, request, *args, **kwargs):
            if 'pk' in kwargs:
                return self.update(request, *args, **kwargs)
    
            pks = []
            try:
                for dic in request.data:
                    pks.append(dic.pop('pk'))
                objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
                assert len(objs) == len(request.data)
            except:
                return Response(status=400)
            serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True)
            serializer.is_valid(raise_exception=True)
            objs = serializer.save()
            return Response(serializers.BookModelSerializer(objs, many=True).data)
    
        def patch(self, request, *args, **kwargs):
            if 'pk' in kwargs:
                return self.partial_update(request, *args, **kwargs)
    
            pks = []
            try:
                for dic in request.data:
                    pks.append(dic.pop('pk'))
                objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
                assert len(objs) == len(request.data)
            except:
                return Response(status=400)
            serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True, partial=True)
            serializer.is_valid(raise_exception=True)
            objs = serializer.save()
            return Response(serializers.BookModelSerializer(objs, many=True).data)
    
    

    基于generics包下工具视图类的六大基础接口

    views.py
    # 六大基础接口
    # 1)直接继承generics包下的工具视图类,可以完成六大基础接口
    # 2)单查群查不能共存
    # 3)单删一般会重写
    
    
    from . import models, serializers
    from rest_framework.response import Response
    from rest_framework import generics
    class BookV2APIView(generics.ListAPIView,
                        generics.RetrieveAPIView,
                        generics.CreateAPIView,
                        generics.UpdateAPIView,
                        generics.DestroyAPIView):
        queryset = models.Book.objects.filter(is_delete=False).all()
        serializer_class = serializers.BookModelSerializer
    
        def get(self, request, *args, **kwargs):
            if 'pk' in kwargs:
                return self.retrieve(request, *args, **kwargs)
            return self.list(request, *args, **kwargs)
    
        def delete(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True)
            return Response(status=204)
    
    

    视图集

    导入

    """ ViewSetMixin类存在理由推到
    1)工具视图类,可以完成应付六大基础接口,唯一缺点是单查与群查接口无法共存
    	(只配置queryset、serializer_class、lookup_field)
    2)不能共存的原因:RetrieveAPIView和ListAPIView都是get方法,不管带不带pk的get请求,只能映射给一个get方法
    3)能不能修改映射关系:
    	比如将/books/的get请求映射给list方法,
    	将/books/(pk)/的get请求映射给retrieve方法,
    	甚至可以随意自定义映射关系
    """
    
    """ 继承视图集的视图类的as_view都是走ViewSetMixin类的,核心源码分析
    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        # ...
    
        # 没有actions,也就是调用as_view()没有传参,像as_view({'get': 'list'})
        if not actions:
            raise TypeError("The `actions` argument must be provided when "
                            "calling `.as_view()` on a ViewSet. For example "
                            "`.as_view({'get': 'list'})`")
    
            # ...
    
            # 请求来了走view函数
            def view(request, *args, **kwargs):
                # ...
                # 解析actions,修改 请求分发 - 响应函数 映射关系
                self.action_map = actions
                for method, action in actions.items():  # method:get | action:list
                    handler = getattr(self, action)  # 从我们视图类用action:list去反射,所以handler是list函数,不是get函数
                    setattr(self, method, handler)  # 将get请求对应list函数,所以在dispath分发请求时,会将get请求分发给list函数
    
                    # ...
                    # 通过视图类的dispatch完成最后的请求分发
                    return self.dispatch(request, *args, **kwargs)
    
                # ...
                # 保存actions映射关系,以便后期使用
                view.actions = actions
                return csrf_exempt(view)
    """
    
    
    

    核心

    """
    视图集的使用总结
    1)可以直接继承ModelViewSet,实现六大继承接口(是否重写destroy方法,或其他方法,根据需求决定)
    2)可以直接继承ReadOnlyModelViewSet,实现只读需求(只有单查群查)
    3)继承ViewSet类,与Model类关系不是很密切的接口:登录的post请求,是查询操作;短信验证码发生接口,借助第三方平台
    4)继承GenericViewSet类,就代表要配合mixins包,自己完成任意组合
    5)继承以上4个视图集任何一个,都可以与路由as_view({映射})配合,完成自定义请求响应方法
    """
    
    

    案例

    urls.py
    url(r'^v3/books/$', views.BookV3APIView.as_view(
        {'get': 'list', 'post': 'create', 'delete': 'multiple_destroy'}
    	)),
    
    url(r'^v3/books/(?P<pk>d+)/$', views.BookV3APIView.as_view(
            {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}
        )),
    
    
    views.py
    from rest_framework.viewsets import ModelViewSet
    from rest_framework.response import Response
    class BookV3APIView(ModelViewSet):
        queryset = models.Book.objects.filter(is_delete=False).all()
        serializer_class = serializers.BookModelSerializer
    
        # 可以在urls.py中as_view({'get': 'my_list'})自定义请求映射
        def my_list(self, request, *args, **kwargs):
            return Response('ok')
    
        # 需要完成字段删除,不是重写delete方法,而是重写destroy方法
        def destroy(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True)
            return Response(status=204)
    
    
        # 群删接口
        def multiple_destroy(self, request, *args, **kwargs):
            try:
                models.Book.objects.filter(is_delete=False, pk__in=request.data).update(is_delete=True)
            except:
                return Response(status=400)
            return Response(status=204)
    
    

    路由组件:必须配合视图集使用

    urls.py
    from django.conf.urls import url, include
    from . import views
    # 路由组件,必须配合视图集使用
    from rest_framework.routers import SimpleRouter
    router = SimpleRouter()
    
    # 以后就写视图集的注册即可:BookV3APIView和BookV4APIView都是视图集
    router.register('v3/books', views.BookV3APIView, 'book')
    router.register('v4/books', views.BookV4APIView, 'book')
    
    urlpatterns = [
        url('', include(router.urls))
    ]
    
    
    views.py
    from rest_framework.viewsets import ReadOnlyModelViewSet
    class BookV4APIView(ReadOnlyModelViewSet):
        queryset = models.Book.objects.filter(is_delete=False).all()
        serializer_class = serializers.BookModelSerializer
    
    

    自定义路由组件(了解)

    router.py
    from rest_framework.routers import SimpleRouter as DrfSimpleRouter
    from rest_framework.routers import Route, DynamicRoute
    
    class SimpleRouter(DrfSimpleRouter):
        routes = [
            # List route.
            Route(
                url=r'^{prefix}{trailing_slash}$',
                mapping={
                    'get': 'list',
                    'post': 'create',  # 注:群增只能自己在视图类中重写create方法,完成区分
                    'delete': 'multiple_destroy',  # 新增:群删
                    'put': 'multiple_update',  # 新增:群整体改
                    'patch': 'multiple_partial_update'  # 新增:群局部改
                },
                name='{basename}-list',
                detail=False,
                initkwargs={'suffix': 'List'}
            ),
            # Dynamically generated list routes. Generated using
            # @action(detail=False) decorator on methods of the viewset.
            DynamicRoute(
                url=r'^{prefix}/{url_path}{trailing_slash}$',
                name='{basename}-{url_name}',
                detail=False,
                initkwargs={}
            ),
            # Detail route.
            Route(
                url=r'^{prefix}/{lookup}{trailing_slash}$',
                mapping={
                    'get': 'retrieve',
                    'put': 'update',
                    'patch': 'partial_update',
                    'delete': 'destroy'
                },
                name='{basename}-detail',
                detail=True,
                initkwargs={'suffix': 'Instance'}
            ),
            # Dynamically generated detail routes. Generated using
            # @action(detail=True) decorator on methods of the viewset.
            DynamicRoute(
                url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
                name='{basename}-{url_name}',
                detail=True,
                initkwargs={}
            ),
        ]
    
    

    上传文件接口

    urls.py
    from django.conf.urls import url, include
    from . import views
    # 路由组件,必须配合视图集使用
    from rest_framework.routers import SimpleRouter
    router = SimpleRouter()
    
    # /books/image/(pk) 提交 form-data:用image携带图片
    router.register('books/image', views.BookUpdateImageAPIView, 'book')
    
    urlpatterns = [
        url('', include(router.urls))
    ]
    
    
    serializers.py
    class BookUpdateImageModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Book
            fields = ['image']
    
    
    views.py
    # 上次文件 - 修改头像 - 修改海报
    from rest_framework.viewsets import GenericViewSet
    from rest_framework import mixins
    class BookUpdateImageAPIView(GenericViewSet, mixins.UpdateModelMixin):
        queryset = models.Book.objects.filter(is_delete=False).all()
        serializer_class = serializers.BookUpdateImageModelSerializer
    
    

    权限

    models.py
    from django.db import models
    
    # RBAC - Role-Based Access Control
    # Django的 Auth组件 采用的认证规则就是RBAC
    
    from django.contrib.auth.models import AbstractUser
    class User(AbstractUser):
        mobile = models.CharField(max_length=11, unique=True)
    
        def __str__(self):
            return self.username
    
    
    class Book(models.Model):
        name = models.CharField(max_length=64)
    
        def __str__(self):
            return self.name
    
    
    class Car(models.Model):
        name = models.CharField(max_length=64)
    
        def __str__(self):
            return self.name
    
    
    settings.py
    # 自定义User表,要配置
    AUTH_USER_MODEL = 'api.User'
    
    
    admin.py
    from django.contrib import admin
    from . import models
    
    from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
    
    # 自定义User表后,admin界面管理User类
    class UserAdmin(DjangoUserAdmin):
        # 添加用户课操作字段
        add_fieldsets = (
            (None, {
                'classes': ('wide',),
                'fields': ('username', 'password1', 'password2', 'is_staff', 'mobile', 'groups', 'user_permissions'),
            }),
        )
        # 展示用户呈现的字段
        list_display = ('username', 'mobile', 'is_staff', 'is_active', 'is_superuser')
    
    
    admin.site.register(models.User, UserAdmin)
    admin.site.register(models.Book)
    admin.site.register(models.Car)
    
    

    导入

    # 1)像专门做人员权限管理的系统(CRM系统)都是公司内部使用,所以数据量都在10w一下,一般效率要求也不是很高
    # 2)用户量极大的常规项目,会分两种用户:前台用户(三大认证) 和 后台用户(BRAC来管理)
    # 结论:没有特殊要求的Django项目可以直接采用Auth组件的权限六表,不需要自定义六个表,也不需要断开表关系,但可能需要自定义User表
    
    

    做项目是否要分表管理前后台用户

    """
    1)是否需要分表
    答案:不需要
    理由:前后台用户共存的项目,后台用户量都是很少;做人员管理的项目,基本上都是后台用户;前后台用户量都大的会分两个项目处理
    
    2)用户权限六表是否需要断关联
    答案:不需要
    理由:前台用户占主导的项目,几乎需求只会和User一个表有关;后台用户占主导的项目,用户量不会太大
    
    3)Django项目有没有必须自定义RBAC六表
    答案:不需要
    理由:auth组件功能十分强大且健全(验证密码,创建用户等各种功能);admin、xadmin、jwt、drf-jwt组件都是依赖auth组件的(自定义RBAC六表,插件都需要自定义,成本极高)
    """
    
    

    权限六表:RBAC - Role-Based Access Control

    三表

    六表

    三大认证规则

    总结

    # 1)后台用户对各表操作,是后台项目完成的,我们可以直接借助admin后台项目(Django自带的)
    # 2)后期也可以用xadmin框架来做后台用户权限管理
    
    # 3)前台用户的权限管理如何处理
    #   定义了一堆数据接口的视图类,不同的登录用户是否能访问这些视图类,能就代表有权限,不能就代表无权限
    #   前台用户权限用drf框架的 三大认证
    
    # 注:前台用户权限会基于 jwt 认证
    
    

    课程小结

    """
    1)基于GenericAPIView类 加 mixins包 实现十大接口
    class MyAPIView(GenericAPIView, mixins.RetrieveModelMixin, ...):
    	# 配置queryset、serializer_class、lookup_field
    	# 要自己定义get、post等方法,内部调用retrieve、create方法
    	
    2)基于 generics包 实现六大基础接口
    class MyAPIView(generics.RetrieveAPIView, ...):
    	# 配置queryset、serializer_class、lookup_field
    	# 重写get处理单查群查共存即可
    	# delete方法是否重写看需求
    
    3)视图集(重点)
    i)ViewSetMixin类:重写as_view方法
    	作用:as_view({"get": "list"})来自定义请求与响应的映射关系
    	
    ii)两个视图集基类:
    	ViewSet:与Model关系不是特别紧密
    	GenericViewSet:与Model关系特别紧密
    	
    iii)两个GenericViewSet的子类:
    	ModelViewSet:六大基础接口共存
    	ReadOnlyModelViewSet:只读接口
    	注:都只需要配置queryset、serializer_class、lookup_field;根据需求决定是否重写某些方法
    	
    iv)自己用两个视图集基类与mixins包形成自定义组合
    
    4)上传图片:前台提交form-data,类型选择文件类型,后台用request.data和request.FILES都可以访问
    
    5)路由:
    from rest_framework.routers import SimpleRouter  (默认只提供了六大基础接口的配置)
    router = SimpleRouter()
    # 注册视图集
    router.register('books', views.BookViewSet, 'book')
    urlpatterns = [
        url('', include(router.urls))
    ]
    
    6)三大认证规则(后期分析)
    
    7)RBAC认证六表
    
    8)admin管理后台用户权限
    	AdminUser辅助自定义User表,完成admin系统的注册
    	
    9)前台用户访问接口的权限交给 三大认证 
    """
    
    

    七、内容大纲

    """
    1)三大认证组件的书写方式(了解如何自定义三大认证类)
    
    2)jwt认证规则(重中之重):json web token
    
    3)认证源码(了解)
    
    4)异常组件 + 日志组件
    """
    
    

    复习

    """
    1)APIView的请求生命周期
    	as_view():路由配置,禁用csrf
    	dispatch():请求分发,请求解析、三大认证、异常处理、响应渲染
    	request._request、request.query_params、request.data、request.META(请求头等诸多配置)
    	
    2)序列化组件
    	i)视图类中使用序列化
    	serializer = UserModelSerializer(
    		instance="对象(们)",
    		data="数据(们)",
    		many=False|True,  # 与数据或对象配套,代表操作的是否是多个
    		partial=False|True,  # 运用在局部修改中,所以校验可以选填(required=False)
    		context={'request': request}  # 视图、序列化传参 
    	)
    	serializer.is_valid(raise_exception=False|True)  # 校验
    	serializer.save()  # 入库
    	serializer.data  # 序列化后的数据
    	serializer.errors  # 校验失败的信息
    	
    	ii)Meta配置类中的配置
    	model:关联的Model类
    	fields:所以序列化与反序列化字段
    	extra_kwargs:简单的校验规则
    	exclude:除某些字段
    	depth:连表深度
    	
    	iii)自定义校验规则
    	validate_字段名:局部校验钩子
    	validate:全局校验钩子
    	
    	iv)入库方法
    	create:增数据入库
    	update:改数据入库
    	后期这两个方法可能会被重写:涉及一些字段加密解密处理、不仅仅是单表入库操作
    			user = User.create()
    			UserDetail.create(user_id=user.id)
    	
    	v)自定义字段
    	@property:在model类中自定义序列化字段
    	自定义字段 = serializers.字段类型(write_only=True, 其他规则):在serializer类中的自定义反序列化字段
    	系统字段 = serializers.字段类型(规则):覆盖字段,可以设置为只读、只写、可读可写
    			外键字段的字段类型:PrimaryKeyRalatedField
    	
    	vi)如果有群改操作
    		自定义ListSerializer子类,重写update方法
    		在相关ModelSerializer中用list_serializer_class配置进行关联
    		
    	重(难)点:
    	i)在视图类中初始化序列化类对象是的参数选择
    	ii)指定好所以的序列化与反序列化字段
    	iii)全局校验钩子的逻辑(难点)
    	iv)是否要重写入库方法(难点)
    
    
    3)视图家族
    	两个基类:APIView、GenericAPIVAPIView
    		APIView:禁用csrf、解析、认证、渲染...
    		GenericAPIVAPIView:禁用csrf、解析、认证、渲染... + 三个属性三个方法
    		
    	工具类:mixin包下的五个类,使用GenericAPIVAPIView的三个属性三个方法(过渡)
    	
    	工具视图类:mixin包下的类 + GenericAPIVAPIView 的组合形成的类
    		配置三个属性 + 是否需要重写get、post等方法
    	
    	视图集:ViewSetMixin重写as_view方法,可以自定义 请求方式 - 响应函数 的映射关系
    		配置三个属性 + mixin包下的类 + GenericViewSet
    		ViewSet
    
    4)路由组件:SimpleRouter
    
    5)权限认证
    	i)RBAC:Role-Based Access Control - 基于角色权限的认证规则
    		权限六表:用户表、分组表、权限表 + 三个关系表
    		
    	ii)auth六表是否需要重写或断关联
    		不需要
    		
    	iii)admin|xadmin|自定义 提供的后台管理项目
    		
    	iv)前台接口权限:三大认证
    """
    
    

    项目初始化

    settings.py
    INSTALLED_APPS = [
        # ...
        'rest_framework',
    ]
    
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    
    # 自定义用户表
    AUTH_USER_MODEL = 'api.User'
    
    
    主urls.py
    from django.conf.urls import url, include
    from django.contrib import admin
    
    from django.views.static import serve
    from django.conf import settings
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        # 路由分发
        url(r'^api/', include('api.urls')),
    
        url(r'^media/(?P<path>).*', serve, {'document_root': settings.MEDIA_ROOT}),
    ]
    
    
    子urls.py
    from django.conf.urls import url, include
    from rest_framework.routers import SimpleRouter
    router = SimpleRouter()
    from . import views
    
    # 路由注册
    # router.register('register', views.RegisterViewSet, 'register')
    
    urlpatterns = [
    
        url('', include(router.urls))
    ]
    
    
    models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    
    class User(AbstractUser):
        mobile = models.CharField(max_length=11, unique=True, verbose_name='移动电话')
        icon = models.ImageField(upload_to='icon', default='icon/default.png', verbose_name='头像')
    
        class Meta:
            db_table = 'o_user'
            verbose_name_plural = '用户表'
    
        def __str__(self):
            return self.username
    
    
    class Book(models.Model):
        name = models.CharField(max_length=64, verbose_name='书名')
    
        class Meta:
            db_table = 'o_book'
            verbose_name_plural = '书表'
    
        def __str__(self):
            return self.name
    
    
    class Car(models.Model):
        name = models.CharField(max_length=64, verbose_name='车名')
    
        class Meta:
            db_table = 'o_car'
            verbose_name_plural = '车表'
    
        def __str__(self):
            return self.name
    
    

    注册接口

    urls.py
    router.register('register', views.RegisterViewSet, 'register')
    
    
    views.py
    from rest_framework.viewsets import GenericViewSet, ModelViewSet
    from rest_framework import mixins
    from . import models, serializers
    
    class RegisterViewSet(GenericViewSet, mixins.CreateModelMixin):
        queryset = models.User.objects.filter(is_active=True).all()
        serializer_class = serializers.RegisterSerializer
    
    
    
    serializers.py
    from rest_framework import serializers
    from rest_framework.exceptions import ValidationError
    from . import models
    
    
    class RegisterSerializer(serializers.ModelSerializer):
        re_password = serializers.CharField(write_only=True, min_length=8, max_length=18)
        class Meta:
            model = models.User
            fields = ('username', 'password', 're_password', 'mobile')
            extra_kwargs = {
                'password': {
                    'write_only': True,
                    'min_length': 8,
                    'max_length': 18
                }
            }
    
        # username和mobile可以自定义局部钩子校验(省略)
    
        def validate(self, attrs):
            password = attrs.get('password')
            re_password = attrs.pop('re_password')
            if password != re_password:
                raise ValidationError({'re_password': 'password confirm error'})
            return attrs
    
        # 需要重写create,创建用户需要密文
        def create(self, validated_data):
            return models.User.objects.create_user(**validated_data)
    
    
    

    用户中心接口

    urls.py
    router.register('user/center', views.UserCenterViewSet, 'center')
    
    
    
    views.py
    class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
        queryset = models.User.objects.filter(is_active=True).all()
        serializer_class = serializers.UserCenterSerializer
    
    
    
    serializers.py
    class UserCenterSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.User
            fields = ('username', 'mobile', 'icon', 'email')
    
    
    

    图书资源接口

    urls.py
    router.register('books', views.BookViewSet, 'book')
    
    
    
    views.py
    class BookViewSet(ModelViewSet):
        queryset = models.Book.objects.all()
        serializer_class = serializers.BookSerializer
    
    
    
    serializers.py
    class BookSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Book
            fields = ('name', )
    
    
    

    认证组件

    重点

    """
    1)认证规则
    2)如何自定义认证类
    3)我们一般不需要自定义认证类,在settings中全局配置第三方 jwt 认证组件提供的认证类即可
    """
    
    
    

    自定义认证类

    """
    1)自定义认证类,继承 BaseAuthentication 类
    2)必须重写 authenticate(self, request) 方法
    	没有认证信息,返回None:匿名用户(游客) => 匿名用户request.user也有值,就是"匿名对象(Anonymous)"
    	有认证信息,且通过,返回(user, token):合法用户 => user对象会存到request.user中
    	有认证信息,不通过,抛异常:非法用户
    """
    
    
    

    权限组件

    重点

    """
    1)权限规则
    2)如何自定义权限类
    3)我们一般在视图类中局部配置drf提供的权限类,但是也会自定义权限类完成局部配置
    """
    
    
    

    自定义权限类

    """
    1)自定义权限类,继承 BasePermission 类
    2)必须重写 has_permission(self, request, view): 方法
    	设置权限条件,条件通过,返回True:有权限
    	设置权限条件,条件失败,返回False:有权限
    	
    3)drf提供的权限类:
    AllowAny:匿名与合法用户都可以
    IsAuthenticated:必须登录,只有合法用户可以
    IsAdminUser:必须是admin后台用户
    IsAuthenticatedOrReadOnly:匿名只读,合法用户无限制
    """
    
    
    

    jwt认证示意图

    """ jwt优势
    1)没有数据库写操作,高效
    2)服务器不存token,低耗
    3)签发校验都是算法,集群
    """
    
    
    

    jwt认证算法:签发与校验

    """
    1)jwt分三段式:头.体.签名 (head.payload.sgin)
    2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
    3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
    4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
    {
    	"company": "公司信息",
    	...
    }
    5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
    {
    	"user_id": 1,
    	...
    }
    6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
    {
    	"head": "头的加密字符串",
    	"payload": "体的加密字符串",
    	"secret_key": "安全码"
    }
    """
    
    
    
    签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
    """
    1)用基本信息存储json字典,采用base64算法加密得到 头字符串
    2)用关键信息存储json字典,采用base64算法加密得到 体字符串
    3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串
    
    账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
    """
    
    
    
    校验:根据客户端带token的请求 反解出 user 对象
    """
    1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
    2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
    3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
    """
    
    
    

    drf项目的jwt认证开发流程(重点)

    """
    1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
    
    2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
    
    注:登录接口需要做 认证 + 权限 两个局部禁用
    """
    
    
    

    drf-jwt框架基本使用

    安装(终端)
    >: pip install djangorestframework-jwt
    
    
    
    签发token(登录接口):视图类已经写好了,配置一下路由就行(urls.py)
    # api/urls.py
    urlpatterns = [
        # ...
        url('^login/$', ObtainJSONWebToken.as_view()),
    ]
    
    # Postman请求:/api/login/,提供username和password即可
    
    
    
    校验token(认证组件):认证类已经写好了,全局配置一下认证组件就行了(settings.py)
    # drf-jwt的配置
    import datetime
    JWT_AUTH = {
        # 配置过期时间
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    }
    
    
    # drf配置(把配置放在最下方)
    REST_FRAMEWORK = {
        # 自定义三大认证配置类们
        'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_jwt.authentication.JSONWebTokenAuthentication'],
        # 'DEFAULT_PERMISSION_CLASSES': [],
        # 'DEFAULT_THROTTLE_CLASSES': [],
    }
    
    
    
    设置需要登录才能访问的接口进行测试(views.py)
    from rest_framework.permissions import IsAuthenticated
    class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
        # 设置必须登录才能访问的权限类
        permission_classes = [IsAuthenticated, ]
    
        queryset = models.User.objects.filter(is_active=True).all()
        serializer_class = serializers.UserCenterSerializer
    
    
    
    测试访问登录认证接口(Postman)
    """
    1)用 {"username": "你的用户", "password": "你的密码"} 访问 /api/login/ 接口等到 token 字符串
    
    2)在请求头用 Authorization 携带 "jwt 登录得到的token" 访问 /api/user/center/1/ 接口访问个人中心
    """
    
    
    

    今日小结

    """
    1)注册接口、个人中心接口、图书接口
    
    2)认证组件
    	i)如何自定义认证类:继承谁、实现什么方法、方法体逻辑就是认证规则
    	ii)认证规则:游客、合法用户、方法用户
    	
    3)权限组件
    	i)如何自定义权限类:继承谁、实现什么方法、方法体逻辑就是权限规则
    	ii)权限规则:有权限、无权限
    
    4)认证权限配置:局部 > 全局 > 默认
    	认证一般都是全局配置,所有登录注册接口要局部禁用
    	权限一般做局部配置 => 电商类项目,90%以上接口游客可以访问
    	权限一般做全局配置 => 邮箱项目,几乎所有接口都需要登录才能访问
    	
    5)jwt签发校验规则
    
    6)drf-jwt框架的简单使用
    """
    
    
    

    八、复习

    """
    1)APIView的请求生命周期
    	as_view():路由配置,禁用csrf
    	dispatch():请求分发,请求解析、三大认证、异常处理、响应渲染
    	request._request、request.query_params、request.data、request.META(请求头等诸多配置)
    	
    2)序列化组件
    	i)视图类中使用序列化
    	serializer = UserModelSerializer(
    		instance="对象(们)",
    		data="数据(们)",
    		many=False|True,  # 与数据或对象配套,代表操作的是否是多个
    		partial=False|True,  # 运用在局部修改中,所以校验可以选填(required=False)
    		context={'request': request}  # 视图、序列化传参 
    	)
    	serializer.is_valid(raise_exception=False|True)  # 校验
    	serializer.save()  # 入库
    	serializer.data  # 序列化后的数据
    	serializer.errors  # 校验失败的信息
    	
    	ii)Meta配置类中的配置
    	model:关联的Model类
    	fields:所以序列化与反序列化字段
    	extra_kwargs:简单的校验规则
    	exclude:除某些字段
    	depth:连表深度
    	
    	iii)自定义校验规则
    	validate_字段名:局部校验钩子
    	validate:全局校验钩子
    	
    	iv)入库方法
    	create:增数据入库
    	update:改数据入库
    	后期这两个方法可能会被重写:涉及一些字段加密解密处理、不仅仅是单表入库操作
    			user = User.create()
    			UserDetail.create(user_id=user.id)
    	
    	v)自定义字段
    	@property:在model类中自定义序列化字段
    	自定义字段 = serializers.字段类型(write_only=True, 其他规则):在serializer类中的自定义反序列化字段
    	系统字段 = serializers.字段类型(规则):覆盖字段,可以设置为只读、只写、可读可写
    			外键字段的字段类型:PrimaryKeyRalatedField
    	
    	vi)如果有群改操作
    		自定义ListSerializer子类,重写update方法
    		在相关ModelSerializer中用list_serializer_class配置进行关联
    		
    	重(难)点:
    	i)在视图类中初始化序列化类对象是的参数选择
    	ii)指定好所以的序列化与反序列化字段
    	iii)全局校验钩子的逻辑(难点)
    	iv)是否要重写入库方法(难点)
    
    
    3)视图家族
    	两个基类:APIView、GenericAPIVAPIView
    		APIView:禁用csrf、解析、认证、渲染...
    		GenericAPIVAPIView:禁用csrf、解析、认证、渲染... + 三个属性三个方法
    		
    	工具类:mixin包下的五个类,使用GenericAPIVAPIView的三个属性三个方法(过渡)
    	
    	工具视图类:mixin包下的类 + GenericAPIVAPIView 的组合形成的类
    		配置三个属性 + 是否需要重写get、post等方法
    	
    	视图集:ViewSetMixin重写as_view方法,可以自定义 请求方式 - 响应函数 的映射关系
    		配置三个属性 + mixin包下的类 + GenericViewSet
    		ViewSet
    
    4)路由组件:SimpleRouter
    
    5)权限认证
    	i)RBAC:Role-Based Access Control - 基于角色权限的认证规则
    		权限六表:用户表、分组表、权限表 + 三个关系表
    		
    	ii)auth六表是否需要重写或断关联
    		不需要
    		
    	iii)admin|xadmin|自定义 提供的后台管理项目
    		
    	iv)前台接口权限:三大认证
    """
    
    

    项目初始化

    settings.py
    INSTALLED_APPS = [
        # ...
        'rest_framework',
    ]
    
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    
    # 自定义用户表
    AUTH_USER_MODEL = 'api.User'
    
    
    主urls.py
    from django.conf.urls import url, include
    from django.contrib import admin
    
    from django.views.static import serve
    from django.conf import settings
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        # 路由分发
        url(r'^api/', include('api.urls')),
    
        url(r'^media/(?P<path>).*', serve, {'document_root': settings.MEDIA_ROOT}),
    ]
    
    
    子urls.py
    from django.conf.urls import url, include
    from rest_framework.routers import SimpleRouter
    router = SimpleRouter()
    from . import views
    
    # 路由注册
    # router.register('register', views.RegisterViewSet, 'register')
    
    urlpatterns = [
    
        url('', include(router.urls))
    ]
    
    
    models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    
    class User(AbstractUser):
        mobile = models.CharField(max_length=11, unique=True, verbose_name='移动电话')
        icon = models.ImageField(upload_to='icon', default='icon/default.png', verbose_name='头像')
    
        class Meta:
            db_table = 'o_user'
            verbose_name_plural = '用户表'
    
        def __str__(self):
            return self.username
    
    
    class Book(models.Model):
        name = models.CharField(max_length=64, verbose_name='书名')
    
        class Meta:
            db_table = 'o_book'
            verbose_name_plural = '书表'
    
        def __str__(self):
            return self.name
    
    
    class Car(models.Model):
        name = models.CharField(max_length=64, verbose_name='车名')
    
        class Meta:
            db_table = 'o_car'
            verbose_name_plural = '车表'
    
        def __str__(self):
            return self.name
    
    

    注册接口

    urls.py
    router.register('register', views.RegisterViewSet, 'register')
    
    
    views.py
    from rest_framework.viewsets import GenericViewSet, ModelViewSet
    from rest_framework import mixins
    from . import models, serializers
    
    class RegisterViewSet(GenericViewSet, mixins.CreateModelMixin):
        queryset = models.User.objects.filter(is_active=True).all()
        serializer_class = serializers.RegisterSerializer
    
    
    
    serializers.py
    from rest_framework import serializers
    from rest_framework.exceptions import ValidationError
    from . import models
    
    
    class RegisterSerializer(serializers.ModelSerializer):
        re_password = serializers.CharField(write_only=True, min_length=8, max_length=18)
        class Meta:
            model = models.User
            fields = ('username', 'password', 're_password', 'mobile')
            extra_kwargs = {
                'password': {
                    'write_only': True,
                    'min_length': 8,
                    'max_length': 18
                }
            }
    
        # username和mobile可以自定义局部钩子校验(省略)
    
        def validate(self, attrs):
            password = attrs.get('password')
            re_password = attrs.pop('re_password')
            if password != re_password:
                raise ValidationError({'re_password': 'password confirm error'})
            return attrs
    
        # 需要重写create,创建用户需要密文
        def create(self, validated_data):
            return models.User.objects.create_user(**validated_data)
    
    
    

    用户中心接口

    urls.py
    router.register('user/center', views.UserCenterViewSet, 'center')
    
    
    
    views.py
    class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
        queryset = models.User.objects.filter(is_active=True).all()
        serializer_class = serializers.UserCenterSerializer
    
    
    
    serializers.py
    class UserCenterSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.User
            fields = ('username', 'mobile', 'icon', 'email')
    
    
    

    图书资源接口

    urls.py
    router.register('books', views.BookViewSet, 'book')
    
    
    
    views.py
    class BookViewSet(ModelViewSet):
        queryset = models.Book.objects.all()
        serializer_class = serializers.BookSerializer
    
    
    
    serializers.py
    class BookSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Book
            fields = ('name', )
    
    
    

    认证组件

    重点

    """
    1)认证规则
    2)如何自定义认证类
    3)我们一般不需要自定义认证类,在settings中全局配置第三方 jwt 认证组件提供的认证类即可
    """
    
    
    

    自定义认证类

    """
    1)自定义认证类,继承 BaseAuthentication 类
    2)必须重写 authenticate(self, request) 方法
    	没有认证信息,返回None:匿名用户(游客) => 匿名用户request.user也有值,就是"匿名对象(Anonymous)"
    	有认证信息,且通过,返回(user, token):合法用户 => user对象会存到request.user中
    	有认证信息,不通过,抛异常:非法用户
    """
    
    
    

    权限组件

    重点

    """
    1)权限规则
    2)如何自定义权限类
    3)我们一般在视图类中局部配置drf提供的权限类,但是也会自定义权限类完成局部配置
    """
    
    
    

    自定义权限类

    """
    1)自定义权限类,继承 BasePermission 类
    2)必须重写 has_permission(self, request, view): 方法
    	设置权限条件,条件通过,返回True:有权限
    	设置权限条件,条件失败,返回False:有权限
    	
    3)drf提供的权限类:
    AllowAny:匿名与合法用户都可以
    IsAuthenticated:必须登录,只有合法用户可以
    IsAdminUser:必须是admin后台用户
    IsAuthenticatedOrReadOnly:匿名只读,合法用户无限制
    """
    
    
    

    jwt认证示意图

    """ jwt优势
    1)没有数据库写操作,高效
    2)服务器不存token,低耗
    3)签发校验都是算法,集群
    """
    
    
    

    jwt认证算法:签发与校验

    """
    1)jwt分三段式:头.体.签名 (head.payload.sgin)
    2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
    3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
    4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
    {
    	"company": "公司信息",
    	...
    }
    5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
    {
    	"user_id": 1,
    	...
    }
    6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
    {
    	"head": "头的加密字符串",
    	"payload": "体的加密字符串",
    	"secret_key": "安全码"
    }
    """
    
    
    
    签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
    """
    1)用基本信息存储json字典,采用base64算法加密得到 头字符串
    2)用关键信息存储json字典,采用base64算法加密得到 体字符串
    3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串
    
    账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
    """
    
    
    
    校验:根据客户端带token的请求 反解出 user 对象
    """
    1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
    2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
    3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
    """
    
    
    

    drf项目的jwt认证开发流程(重点)

    """
    1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
    
    2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
    
    注:登录接口需要做 认证 + 权限 两个局部禁用
    """
    
    
    

    drf-jwt框架基本使用

    安装(终端)
    >: pip install djangorestframework-jwt
    
    
    
    签发token(登录接口):视图类已经写好了,配置一下路由就行(urls.py)
    # api/urls.py
    urlpatterns = [
        # ...
        url('^login/$', ObtainJSONWebToken.as_view()),
    ]
    
    # Postman请求:/api/login/,提供username和password即可
    
    
    
    校验token(认证组件):认证类已经写好了,全局配置一下认证组件就行了(settings.py)
    # drf-jwt的配置
    import datetime
    JWT_AUTH = {
        # 配置过期时间
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    }
    
    
    # drf配置(把配置放在最下方)
    REST_FRAMEWORK = {
        # 自定义三大认证配置类们
        'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_jwt.authentication.JSONWebTokenAuthentication'],
        # 'DEFAULT_PERMISSION_CLASSES': [],
        # 'DEFAULT_THROTTLE_CLASSES': [],
    }
    
    
    
    设置需要登录才能访问的接口进行测试(views.py)
    from rest_framework.permissions import IsAuthenticated
    class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
        # 设置必须登录才能访问的权限类
        permission_classes = [IsAuthenticated, ]
    
        queryset = models.User.objects.filter(is_active=True).all()
        serializer_class = serializers.UserCenterSerializer
    
    
    
    测试访问登录认证接口(Postman)
    """
    1)用 {"username": "你的用户", "password": "你的密码"} 访问 /api/login/ 接口等到 token 字符串
    
    2)在请求头用 Authorization 携带 "jwt 登录得到的token" 访问 /api/user/center/1/ 接口访问个人中心
    """
    
    
    

    小结

    """
    1)注册接口、个人中心接口、图书接口
    
    2)认证组件
    	i)如何自定义认证类:继承谁、实现什么方法、方法体逻辑就是认证规则
    	ii)认证规则:游客、合法用户、方法用户
    	
    3)权限组件
    	i)如何自定义权限类:继承谁、实现什么方法、方法体逻辑就是权限规则
    	ii)权限规则:有权限、无权限
    
    4)认证权限配置:局部 > 全局 > 默认
    	认证一般都是全局配置,所有登录注册接口要局部禁用
    	权限一般做局部配置 => 电商类项目,90%以上接口游客可以访问
    	权限一般做全局配置 => 邮箱项目,几乎所有接口都需要登录才能访问
    	
    5)jwt签发校验规则
    
    6)drf-jwt框架的简单使用
    """
    
    
    

    复习

    """
    1)前后台权限管理:
    	后台管理:基于RBAC(auth模块的六表),用admin|xadmin来管理
    	前台管理:基于三大认证权限管理,认证采用的是jwt认证,jwt一般也是依赖auth模块的六表
    
    2)认证模块:
    	继承BaseAuthentication,实现authenticate方法,规则:
    		没认证信息,返回None:游客
    		有认证信息,认证成功,返回(user, token):合法用户
    		有认证信息,认证失败,抛异常:非法用户
    	
    3)权限模块:
    	继承BasePermission,实现has_permission方法,规则:
    		权限条件判断成立,返回True:有权限
    		权限条件判断失败,返回Fasle:无权限
    		
    4)jwt认证规则:
    	json web token
    	组成:header.payload.sign
    		header:基础信息
    		payload:核心信息 - 用户主键、客户端设备信息、过期时间(token过期时间,token可刷新过期时间)
    		sign:安全信息 - 前两段加密结果 + 服务器安全码
    	优势:
    		没有数据库写:高效
    		数据库不存:低耗
    		只是算法:集群
    	
    5)三大认证使用:
    	i)是否要自定义三大认证类:
    		认证类采用第三方jwt,所以自己就不写了
    		权限类drf通过了一些,如果够用,直接使用,不够用会自定义
    		频率类系统也提供了一些,一般也会自定义
    	ii)配置:
    		局部配置:在视图类中初始化类属性:permission_classes
    		全局配置:在settings中自定义drf的配置:DEFAULT_AUTHENTICATION_CLASSES
    """
    
    
    """
    1)三大认证
    
    2)异常、日志
    
    3)群查接口:搜索、排序、分页、分类、区间
    """
    
    

    系统权限类使用

    图书接口:游客只读,用户可增删改查权限使用

    from rest_framework.permissions import IsAuthenticatedOrReadOnly
    class BookViewSet(ModelViewSet):
        # 游客只读,用户可增删改查
        permission_classes = [IsAuthenticatedOrReadOnly]
    
        queryset = models.Book.objects.all()
        serializer_class = serializers.BookSerializer
    
    

    特殊路由映射的请求

    实现用户中心信息自查,不带主键的get请求,走单查逻辑

    urls.py
    # 用路由组件配置,形成的映射关系是 /user/center/ => list  |  user/center/(pk)/ => retrieve
    # router.register('user/center', views.UserCenterViewSet, 'center')
    
    urlpatterns = [
        # ...
        # /user/center/ => 单查,不能走路由组件,只能自定义配置映射关系
        url('^user/center/$', views.UserCenterViewSet.as_view({'get': 'user_center'})),
    ]
    
    
    views.py
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.response import Response
    class UserCenterViewSet(GenericViewSet):
        permission_classes = [IsAuthenticated, ]
        queryset = models.User.objects.filter(is_active=True).all()
        serializer_class = serializers.UserCenterSerializer
    
        def user_center(self, request, *args, **kwargs):
            # request.user就是前台带token,在经过认证组件解析出来的,
            # 再经过权限组件IsAuthenticated的校验,所以request.user一定有值,就是当前登录用户
            serializer = self.get_serializer(request.user)
            return Response(serializer.data)
    
    

    token刷新机制(了解)

    drf-jwt直接提供刷新功能

    """
    1)运用在像12306这样极少数安全性要求高的网站
    2)第一个token由登录签发
    3)之后的所有正常逻辑,都需要发送两次请求,第一次是刷新token的请求,第二次是正常逻辑的请求
    """
    
    
    settings.py
    import datetime
    
    JWT_AUTH = {
        # 配置过期时间
        'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=5),
    
        # 是否可刷新
        'JWT_ALLOW_REFRESH': True,
        # 刷新过期时间
        'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    }
    
    
    urls.py
    from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken
    urlpatterns = [
        url('^login/$', ObtainJSONWebToken.as_view()),  # 登录签发token接口
        url('^refresh/$', RefreshJSONWebToken.as_view()),  # 刷新toekn接口
    ]
    
    
    Postman
    # 接口:/api/refresh/
    # 方法:post
    # 数据:{"token": "登录签发的token"}
    
    
    

    认证组件项目使用:多方式登录

    urls.py
    # 自定义登录(重点):post请求 => 查操作(签发token返回给前台) - 自定义路由映射
    url('^user/login/$', views.LoginViewSet.as_view({'post': 'login'})),
    
    
    
    views.py
    # 重点:自定义login,完成多方式登录
    from rest_framework.viewsets import ViewSet
    from rest_framework.response import Response
    class LoginViewSet(ViewSet):
        # 登录接口,要取消所有的认证与权限规则,也就是要做局部禁用操作(空配置)
        authentication_classes = []
        permission_classes = []
    
        # 需要和mixins结合使用,继承GenericViewSet,不需要则继承ViewSet
        # 为什么继承视图集,不去继承工具视图或视图基类,因为视图集可以自定义路由映射:
        #       可以做到get映射get,get映射list,还可以做到自定义(灵活)
        def login(self, request, *args, **kwargs):
            serializer = serializers.LoginSerializer(data=request.data, context={'request': request})
            serializer.is_valid(raise_exception=True)
            token = serializer.context.get('token')
            return Response({"token": token})
    
    
    
    serializers.py
    from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
    
    # 重点:自定义login,完成多方式登录
    class LoginSerializer(serializers.ModelSerializer):
        # 登录请求,走的是post方法,默认post方法完成的是create入库校验,所以唯一约束的字段,会进行数据库唯一校验,导致逻辑相悖
        # 需要覆盖系统字段,自定义校验规则,就可以避免完成多余的不必要校验,如唯一字段校验
        username = serializers.CharField()
        class Meta:
            model = models.User
            # 结合前台登录布局:采用账号密码登录,或手机密码登录,布局一致,所以不管账号还是手机号,都用username字段提交的
            fields = ('username', 'password')
    
        def validate(self, attrs):
            # 在全局钩子中,才能提供提供的所需数据,整体校验得到user
            # 再就可以调用签发token算法(drf-jwt框架提供的),将user信息转换为token
            # 将token存放到context属性中,传给外键视图类使用
            user = self._get_user(attrs)
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            self.context['token'] = token
            return attrs
    
        # 多方式登录
        def _get_user(self, attrs):
            username = attrs.get('username')
            password = attrs.get('password')
            import re
            if re.match(r'^1[3-9][0-9]{9}$', username):
                # 手机登录
                user = models.User.objects.filter(mobile=username, is_active=True).first()
            elif re.match(r'^.+@.+$', username):
                # 邮箱登录
                user = models.User.objects.filter(email=username, is_active=True).first()
            else:
                # 账号登录
                user = models.User.objects.filter(username=username, is_active=True).first()
            if user and user.check_password(password):
                return user
    
            raise ValidationError({'user': 'user error'})
    
    
    

    权限组件项目使用:vip用户权限

    数据准备
    """
    1)User表创建两条数据
    2)Group表创建一条数据,name叫vip
    3)操作User和Group的关系表,让1号用户属于1号vip组
    """
    
    
    
    permissions.py
    from rest_framework.permissions import BasePermission
    
    from django.contrib.auth.models import Group
    class IsVipUser(BasePermission):
        def has_permission(self, request, view):
            if request.user and request.user.is_authenticated:  # 必须是合法用户
                try:
                    vip_group = Group.objects.get(name='vip')
                    if vip_group in request.user.groups.all():  # 用户可能不属于任何分组
                        return True  # 必须是vip分组用户
                except:
                    pass
    
            return False
    
    
    
    views.py
    from .permissions import IsVipUser
    class CarViewSet(ModelViewSet):
        permission_classes = [IsVipUser]
    
        queryset = models.Car.objects.all()
        serializer_class = serializers.CarSerializer
    
    
    
    serializers.py
    class CarSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Car
            fields = ('name', )
    
    
    
    urls.py
    router.register('cars', views.CarViewSet, 'car')
    
    
    

    频率组件

    重点
    """
    1)如何自定义频率类
    2)频率校验的规则
    3)自定义频率类是最常见的:短信接口一分钟只能发生一条短信
    """
    
    
    
    自定义频率类
    """
    1)自定义类继承SimpleRateThrottle
    2)设置类实现scope,值就是一个字符串,与settings中的DEFAULT_THROTTLE_RATES进行对应
    	DEFAULT_THROTTLE_RATES就是设置scope绑定的类的频率规则:1/min 就代表一分钟只能访问一次
    3)重写 get_cache_key(self, request, view) 方法,指定限制条件
    	不满足限制条件,返回None:代表对这类请求不进行频率限制
    	满足限制条件,返回一个字符串(是动态的):代表对这类请求进行频率限制
    		短信频率限制类,返回 "throttling_%(mobile)s" % {"mobile": 实际请求来的电话}
    """
    
    
    
    系统频率类
    #1)UserRateThrottle: 限制所有用户访问频率
    #2)AnonRateThrottle:只限制匿名用户访问频率
    
    
    

    频率组件项目使用:请求方式频率限制

    throttles.py
    from rest_framework.throttling import SimpleRateThrottle
    # 只限制查接口的频率,不限制增删改的频率
    class MethodRateThrottle(SimpleRateThrottle):
        scope = 'method'
        def get_cache_key(self, request, view):
            # 只有对get请求进行频率限制
            if request.method.lower() not in ('get', 'head', 'option'):
                return None
    
            # 区别不同的访问用户,之间的限制是不冲突的
            if request.user.is_authenticated:
                ident = request.user.pk
            else:
                # get_ident是BaseThrottle提供的方法,会根据请求头,区别匿名用户,
                # 保证不同客户端的请求都是代表一个独立的匿名用户
                ident = self.get_ident(request)
            return self.cache_format % {'scope': self.scope, 'ident': ident}
    
    
    
    settings.py
    REST_FRAMEWORK = {
        #  ...
        # 频率规则配置
        'DEFAULT_THROTTLE_RATES': {
            # 只能设置 s,m,h,d,且只需要第一个字母匹配就ok,m = min = maaa 就代表分钟
            'user': '3/min',  # 配合drf提供的 UserRateThrottle 使用,限制所有用户访问频率
            'anon': '3/min',  # 配合drf提供的 AnonRateThrottle 使用,只限制匿名用户访问频率
            'method': '3/min',
        },
    }
    
    
    
    views.py
    from .permissions import IsVipUser
    from .throttles import MethodRateThrottle
    class CarViewSet(ModelViewSet):
        permission_classes = [IsVipUser]
        throttle_classes = [MethodRateThrottle]
    
        queryset = models.Car.objects.all()
        serializer_class = serializers.CarSerializer
    
    
    

    异常组件项目使用:记录异常信息到日志文件

    exception.py
    from rest_framework.views import exception_handler as drf_exception_handler
    from rest_framework.response import Response
    def exception_handler(exc, context):
        # 只处理客户端异常,不处理服务器异常,
        # 如果是客户端异常,response就是可以直接返回给前台的Response对象
        response = drf_exception_handler(exc, context)
    
        if response is None:
            # 没有处理的服务器异常,处理一下
            # 其实给前台返回 服务器异常 几个字就行了
            # 那我们处理异常模块的目的是 不管任何错误,都有必要进行日志记录(线上项目只能通过记录的日志查看出现过的错误)
            response = Response({'detail': '%s' % exc})
    
        # 需要结合日志模块进行日志记录的:项目中讲
        return response
    
    
    
    settings.py
    REST_FRAMEWORK = {
        # ...
        # 异常模块
        # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',  # 原来的,只处理客户端异常
        'EXCEPTION_HANDLER': 'api.exception.exception_handler',
    }
    
    
    

    课程小结

    """
    1)认证模块:多方式登录
    2)权限组件:vip权限认证
    3)频率组件:请求方式的频率限制
    4)异常组件:目的是记录异常日志
    
    
    drf整体总结:
    1)APIView生命周期:请求解析、响应渲染、异常响应(日志)
    2)序列化组件
    3)视图家族
    4)三大认证
    5)群查:搜索、排序、分页、分类、区间
    """
    
    
    
  • 相关阅读:
    python基础
    用纯css改变下拉列表select框的默认样式兼容ie9需要用js
    如何阻止360浏览器backspace键的默认快捷键
    js判断undefined类型,undefined,null, 的区别详细解析
    jquery事件日历插件e-calendar 的修改
    each和for一起使用
    2019.9.24 网络ip小知识
    数据库SQL开始学习_5
    2019.9.22 java小知识
    数据库SQL开始学习_4
  • 原文地址:https://www.cnblogs.com/WQ577098649/p/12633731.html
Copyright © 2011-2022 走看看