扩大
缩小
人生本来就短暂,为什么还要栽培苦涩。
返回顶部

路飞学城之 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\电脑用户\AppData\Roaming 文件夹中
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)群查:搜索、排序、分页、分类、区间
"""


posted @ 2020-04-04 19:52  晴天sky  阅读(285)  评论(0编辑  收藏  举报
左边日期 搜索的制定样式