路飞学城之 drf

news/2024/12/5 11:48:46/

目录
  • 路飞学城之 drf
    • 相关内容复习
  • DRF框架知识总览
    • 接口
    • 开发阶段接口测试工具
    • 接口文档
    • 接口规范
    • CVB vs FBV
    • pip安装源
      • 介绍
      • 永久配置安装源
        • Windows
        • MacOS、Linux
        • 配置文件内容
    • DRF框架的安装
    • 图书资源准备
    • 基于restful接口规范的接口设计
    • 实现单查群查接口
    • 小结
  • 二、内容大纲
    • Django配置回顾
    • ORM配置回顾
    • APIView的请求声明周期
      • 重写的as_view方法
      • 重写的dispatch方法
        • 请求解析模块
        • 响应渲染模块
    • 序列化组件
      • 单表序列化
        • models.py
        • serializers.py
        • views.py
    • 小结
  • 三、内部类
    • DRF响应类:Response
    • 序列化基类控制的初始化参数
    • 反序列化
      • views.py
      • serializers.py
      • 课堂案例
        • settings.py
        • 主urls.py
        • 子urls.py
        • views.py
        • serializers.py
  • 四、内容大纲
    • 二次封装Response
    • 数据库关系分析
    • ORM操作关系
      • 案例
      • 测试
    • 基表
      • 应用
    • 序列化类其他配置(了解)
    • 子序列化
      • 案例
        • urls.py
        • serializers.py
        • views.py
    • 多表序列化与反序列化
      • 案例
        • urls.py
        • models.py
        • serializers.py
        • views.py
    • 小结
  • 五、内容大纲
    • 序列化类外键字段的覆盖
    • 十大接口序列化总结
      • 十大接口核心知识小结
      • 案例
        • models.py
        • urls.py
        • serializers.py
        • views.py
    • 视图家族
      • urls.py
      • views.py
  • 六、内容大纲
    • 复习
    • 准备工作
      • models.py
      • serializers.py
    • 基于GenericAPIView的十大接口
      • views.py
    • 基于generics包下工具视图类的六大基础接口
      • views.py
    • 视图集
      • 导入
      • 核心
      • 案例
        • urls.py
        • views.py
    • 路由组件:必须配合视图集使用
      • urls.py
      • views.py
    • 自定义路由组件(了解)
      • router.py
    • 上传文件接口
      • urls.py
      • serializers.py
      • views.py
    • 权限
      • models.py
      • settings.py
      • admin.py
      • 导入
      • 做项目是否要分表管理前后台用户
      • 权限六表:RBAC - Role-Based Access Control
        • 三表
        • 六表
      • 三大认证规则
      • 总结
    • 课程小结
  • 七、内容大纲
    • 复习
    • 项目初始化
      • settings.py
      • 主urls.py
      • 子urls.py
      • models.py
    • 注册接口
      • urls.py
      • views.py
      • serializers.py
    • 用户中心接口
      • urls.py
      • views.py
      • serializers.py
    • 图书资源接口
      • urls.py
      • views.py
      • serializers.py
    • 认证组件
      • 重点
      • 自定义认证类
    • 权限组件
      • 重点
      • 自定义权限类
    • jwt认证示意图
    • jwt认证算法:签发与校验
      • 签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
      • 校验:根据客户端带token的请求 反解出 user 对象
    • drf项目的jwt认证开发流程(重点)
    • drf-jwt框架基本使用
      • 安装(终端)
      • 签发token(登录接口):视图类已经写好了,配置一下路由就行(urls.py)
      • 校验token(认证组件):认证类已经写好了,全局配置一下认证组件就行了(settings.py)
      • 设置需要登录才能访问的接口进行测试(views.py)
      • 测试访问登录认证接口(Postman)
    • 今日小结
  • 八、复习
    • 项目初始化
      • settings.py
      • 主urls.py
      • 子urls.py
      • models.py
    • 注册接口
      • urls.py
      • views.py
      • serializers.py
    • 用户中心接口
      • urls.py
      • views.py
      • serializers.py
    • 图书资源接口
      • urls.py
      • views.py
      • serializers.py
    • 认证组件
      • 重点
      • 自定义认证类
    • 权限组件
      • 重点
      • 自定义权限类
    • jwt认证示意图
    • jwt认证算法:签发与校验
      • 签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
      • 校验:根据客户端带token的请求 反解出 user 对象
    • drf项目的jwt认证开发流程(重点)
    • drf-jwt框架基本使用
      • 安装(终端)
      • 签发token(登录接口):视图类已经写好了,配置一下路由就行(urls.py)
      • 校验token(认证组件):认证类已经写好了,全局配置一下认证组件就行了(settings.py)
      • 设置需要登录才能访问的接口进行测试(views.py)
      • 测试访问登录认证接口(Postman)
    • 小结
    • 复习
    • 系统权限类使用
      • 图书接口:游客只读,用户可增删改查权限使用
    • 特殊路由映射的请求
      • 实现用户中心信息自查,不带主键的get请求,走单查逻辑
        • urls.py
        • views.py
    • token刷新机制(了解)
      • drf-jwt直接提供刷新功能
        • settings.py
        • urls.py
        • Postman
    • 认证组件项目使用:多方式登录
      • urls.py
      • views.py
      • serializers.py
    • 权限组件项目使用:vip用户权限
      • 数据准备
      • permissions.py
      • views.py
      • serializers.py
      • urls.py
    • 频率组件
      • 重点
      • 自定义频率类
      • 系统频率类
    • 频率组件项目使用:请求方式频率限制
      • throttles.py
      • settings.py
      • views.py
    • 异常组件项目使用:记录异常信息到日志文件
      • exception.py
      • settings.py
    • 课程小结

路飞学城之 drf

相关内容复习

"""
1)指令:插值表达式:{{ 变量|常量|简单表达式 }} 文本指令:v-text | v-html事件指令:v-on:事件名 | @事件名属性指令:v-bind:属性名 | :属性名表单指令:v-model条件指令:v-show | v-if | v-else-if | v-else条件指令:v-for2)实例成员挂载点: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.jsvar 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 ViewCBV的请求生命周期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 djangorestframework2)在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.name2)在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 createsuperuseradmin | Admin1234)浏览器登录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链接 + 请求方法 + 请求数据 + 响应结果文档:将接口的四个组成部分书写成一个使用文档工具:Postman2)Restful接口规范:i)url链接:https、api、books、v1、?limit=3https://www.baidu.com/api/v1/books/?limit=3ii)请求方式: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()@propertydef img(self):return settings.BASE_URL + settings.MEDIA_URL + self.icon.namedef __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 Responsefrom . import models, serializersclass 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及ORM1)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):passdata:响应的数据 - 空、字符串、数字、列表、字段、布尔
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):passinstance:是要被赋值对象的 - 对象类型数据赋值给instance
data:是要被赋值数据的 - 请求来的数据赋值给data
kwargs:内部有三个属性:many、partial、contextmany:操作的对象或数据,是单个的还是多个的partial:在修改需求时使用,可以将所有校验字段required校验规则设置为Falsecontext:用于视图类和序列化类直接传参使用
"""# 常见使用
# 单查接口
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 statusfrom . import models, serializersclass 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):passinstance:是要被赋值对象的 - 对象类型数据赋值给instance
data:是要被赋值数据的 - 请求来的数据赋值给data
kwargs:内部有三个属性:many、partial、contextmany:操作的对象或数据,是单个的还是多个的partial:context:
""""""
响应类Responsedef __init__(self, data=None, status=None,template_name=None, headers=None,exception=False, content_type=None):passdata:响应的数据 - 空、字符串、数字、列表、字段、布尔
status:网络状态码
template_name:drf说自己也可以支持前后台不分离返回页面,但是不能和data共存(不会涉及)
headers:响应头(不用刻意去管)
exception:是否是异常响应(如果是异常响应,可以赋值True,没什么用)
content_type:响应的结果类型(如果是响应data,默认就是application/json,所有不用管)
"""
serializers.py
from rest_framework import serializers
from rest_framework import exceptionsfrom . 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 valuedef 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 Responseclass 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.AuthorDetailfields = ['phone']class BookModelSerializer(serializers.ModelSerializer):class Meta:model = models.Bookfields = ['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.Authorfields = ['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.phoneexcept:author_dic['phone'] = ''temp_author_list.append(author_dic)return temp_author_list@property  # 借助序列化类完成序列化过程def read_author_list(self):from .serializers import AuthorModelSerializerreturn AuthorModelSerializer(self.authors.all(), many=True).data
serializers.py
# 辅助序列化类
class AuthorDetailModelSerializer(serializers.ModelSerializer):class Meta:model = models.AuthorDetailfields = ['phone']# 辅助序列化类
class AuthorModelSerializer(serializers.ModelSerializer):detail = AuthorDetailModelSerializer(many=False, read_only=True)class Meta:model = models.Authorfields = ['name', 'detail']# 主序列化类
class BookModelSerializer(serializers.ModelSerializer):class Meta:model = models.Bookfields = ('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方法,在内部格式化data2)表关系分析:断关联:优点:提示增删改操作效率,不允许查效率缺点:增删改操作可能会导致脏数据,所以需要通过逻辑或是事务来保证3)ORM表关系处理语法:1)外键所在位置2)如何断关联db_constraint3)正向方向自定义名字:related_name4)表关系:on_delete四种4)基表:Meta中配置abstract=True,来被继承,提供共有字段5)多表连表Meta中的了解配置fields = '__all__'  # 不常用,将全部字段提供给外键exclude = ['is_delete', 'updated_time']  # 不常用,排除指定字段的其他所有字段depth = 2  # 不常用,主动深度,自动深度会显示关联表的所有字段6)子序列化i)通常只用于序列化过程,对外键字段进行了覆盖,影响外键字段的反序列化过程class SubSerializer:passclass SupSerializer:外键 = SubSerializer(many=True|False)7)多表的序列化与反序列化1)连表序列化用自定义@property完成:内部实现可以自定义逻辑,也可以走序列化类2)外键字段留给反序列化来使用8)单查、群查、单增、群增接口
"""

五、内容大纲

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

序列化类外键字段的覆盖

"""
1)在序列化类中自定义字段,名字与model类中属性名一致,就称之为覆盖操作(覆盖的是属性的所有规则:extra_kwargs中指定的简易规则、model字段提供的默认规则、数据库唯一约束等哪些规则)2)外键覆盖字段用PrimaryKeyRelatedField来实现,可以做到只读、只写、可读可写三种形式只读:read_only=True只写:queryset=关联表的queryset, write_only=True可读可写:queryset=关联表的queryset3)当外界关联的数据是多个时,需标识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.Bookfields = ('name', 'price', 'image', 'publish', 'authors')

十大接口序列化总结

"""
def __init__(self, instance=None, data=empty, **kwargs):passinstance:是要被赋值对象的 - 对象类型数据赋值给instance
data:是要被赋值数据的 - 请求来的数据赋值给data
kwargs:内部有三个属性:many、partial、contextmany:操作的对象或数据,是单个的还是多个的partial:在修改需求时使用,可以将所有校验字段required校验规则设置为Falsecontext:用于视图类和序列化类直接传参使用
"""# 常见使用
# 单查接口
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.phoneexcept:author_dic['phone'] = ''temp_author_list.append(author_dic)return temp_author_listclass 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 = BookListSerializermodel = models.Bookfields = ('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 viewsurlpatterns = [# ...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.BookModelSerializerlookup_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_fieldiii)三个方法:get_queryset、get_serializer、get_object过渡:单独继承GenericAPIView类的视图类,需要自己定义请求方法如get,还需要属性方法体完成请求mixins包:工具类 - eg:RetrieveModelMixin:retrieveretrieve、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.phoneexcept:author_dic['phone'] = ''temp_author_list.append(author_dic)return temp_author_listclass 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 = BookListSerializermodel = models.Bookfields = ['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 Responseclass BookV1APIView(GenericAPIView,mixins.RetrieveModelMixin,mixins.ListModelMixin,mixins.CreateModelMixin,mixins.UpdateModelMixin):queryset = models.Book.objects.filter(is_delete=False).all()serializer_class = serializers.BookModelSerializerdef 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.datatry: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.BookModelSerializerdef 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 = actionsfor method, action in actions.items():  # method:get | action:listhandler = 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 = actionsreturn 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, DynamicRouteclass 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.Bookfields = ['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组件 采用的认证规则就是RBACfrom django.contrib.auth.models import AbstractUser
class User(AbstractUser):mobile = models.CharField(max_length=11, unique=True)def __str__(self):return self.usernameclass Book(models.Model):name = models.CharField(max_length=64)def __str__(self):return self.nameclass 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 modelsfrom 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 token3)认证源码(了解)4)异常组件 + 日志组件
"""

复习

"""
1)APIView的请求生命周期as_view():路由配置,禁用csrfdispatch():请求分发,请求解析、三大认证、异常处理、响应渲染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.字段类型(规则):覆盖字段,可以设置为只读、只写、可读可写外键字段的字段类型:PrimaryKeyRalatedFieldvi)如果有群改操作自定义ListSerializer子类,重写update方法在相关ModelSerializer中用list_serializer_class配置进行关联重(难)点:i)在视图类中初始化序列化类对象是的参数选择ii)指定好所以的序列化与反序列化字段iii)全局校验钩子的逻辑(难点)iv)是否要重写入库方法(难点)3)视图家族两个基类:APIView、GenericAPIVAPIViewAPIView:禁用csrf、解析、认证、渲染...GenericAPIVAPIView:禁用csrf、解析、认证、渲染... + 三个属性三个方法工具类:mixin包下的五个类,使用GenericAPIVAPIView的三个属性三个方法(过渡)工具视图类:mixin包下的类 + GenericAPIVAPIView 的组合形成的类配置三个属性 + 是否需要重写get、post等方法视图集:ViewSetMixin重写as_view方法,可以自定义 请求方式 - 响应函数 的映射关系配置三个属性 + mixin包下的类 + GenericViewSetViewSet4)路由组件:SimpleRouter5)权限认证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 adminfrom 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 modelsclass 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.usernameclass 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.nameclass 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, serializersclass 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 modelsclass RegisterSerializer(serializers.ModelSerializer):re_password = serializers.CharField(write_only=True, min_length=8, max_length=18)class Meta:model = models.Userfields = ('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.Userfields = ('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.Bookfields = ('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():路由配置,禁用csrfdispatch():请求分发,请求解析、三大认证、异常处理、响应渲染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.字段类型(规则):覆盖字段,可以设置为只读、只写、可读可写外键字段的字段类型:PrimaryKeyRalatedFieldvi)如果有群改操作自定义ListSerializer子类,重写update方法在相关ModelSerializer中用list_serializer_class配置进行关联重(难)点:i)在视图类中初始化序列化类对象是的参数选择ii)指定好所以的序列化与反序列化字段iii)全局校验钩子的逻辑(难点)iv)是否要重写入库方法(难点)3)视图家族两个基类:APIView、GenericAPIVAPIViewAPIView:禁用csrf、解析、认证、渲染...GenericAPIVAPIView:禁用csrf、解析、认证、渲染... + 三个属性三个方法工具类:mixin包下的五个类,使用GenericAPIVAPIView的三个属性三个方法(过渡)工具视图类:mixin包下的类 + GenericAPIVAPIView 的组合形成的类配置三个属性 + 是否需要重写get、post等方法视图集:ViewSetMixin重写as_view方法,可以自定义 请求方式 - 响应函数 的映射关系配置三个属性 + mixin包下的类 + GenericViewSetViewSet4)路由组件:SimpleRouter5)权限认证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 adminfrom 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 modelsclass 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.usernameclass 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.nameclass 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, serializersclass 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 modelsclass RegisterSerializer(serializers.ModelSerializer):re_password = serializers.CharField(write_only=True, min_length=8, max_length=18)class Meta:model = models.Userfields = ('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.Userfields = ('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.Bookfields = ('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.signheader:基础信息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.UserCenterSerializerdef 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 datetimeJWT_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'] = tokenreturn attrs# 多方式登录def _get_user(self, attrs):username = attrs.get('username')password = attrs.get('password')import reif 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 userraise ValidationError({'user': 'user error'})

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

数据准备
"""
1)User表创建两条数据
2)Group表创建一条数据,name叫vip
3)操作User和Group的关系表,让1号用户属于1号vip组
"""
permissions.py
from rest_framework.permissions import BasePermissionfrom 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:passreturn 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.Carfields = ('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.pkelse:# 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)群查:搜索、排序、分页、分类、区间
"""

http://www.ppmy.cn/news/419722.html

相关文章

Day06笔记 -路飞学城学员

数据类型&#xff08;上&#xff09; 分为五个维度&#xff1a;定义&#xff0c;独有功能&#xff0c;公共功能&#xff0c;类型转换&#xff0c;其他。 1.整形 1.1 定义 整形实质上是现在所有10进制数的集合 1.2独有功能 用于计算二进制位的多少&#xff0c;例如&#xff1a;…

路飞学城结算中心实现

路飞学城结算中心的优惠券表结构 # ########################### 优惠券 ################################ class Coupon(models.Model):"""优惠券生成规则"""name models.CharField(max_length64, verbose_name"活动名称")brief m…

路飞学城项目前端笔记

目录 路飞学城项1. 项目分析2. 项目搭建2.1 创建项目目录2.2 初始化项目2.3 安装路由vue-router2.3.1 下载路由组件2.3.2 配置路由2.3.2.1 初始化路由对象2.3.2.2 注册路由信息2.3.2.3 在视图中显示路由对应的内容3. 引入ElementUI3.1 快速安装ElementUI3.2 配置ElementUI到项…

路飞学城前端Vue

创建项目 首先我们要创建项目并安装相关的组件 1. vue init webpack luffy2. 安装依赖&#xff1a;cd luffynpm install vuex --save 用于&#xff1a;多组件之间数据共享npm install vue-cookies --save 用于&#xff1a;操作cookienpm install axios --save …

macOS图片编辑开发

最近实现了一个在mac系统上对图片进行编辑的程序&#xff0c;先给大家看一下效果 大家可以看一下这张图片里拥有那些功能呢&#xff1f; 可以看到的是这边有绘制箭头&#xff0c;椭圆&#xff0c;圆角矩形框&#xff0c;线条&#xff0c;马赛克&#xff08;路飞的鞋底&#x…

161-路飞02前后端配置user表日志跨域问题解决

昨日回顾 1 软件开发规范 2 pip换源1、文件管理器文件路径地址栏敲&#xff1a;%APPDATA% 回车&#xff0c;快速进入 C:\Users\电脑用户\AppData\Roaming 文件夹中2、新建 pip 文件夹并在文件夹中新建 pip.ini 配置文件3、新增 pip.ini 配置文件内容 3 虚拟环境&#xff08;以…

Python游戏编程——路飞大战坚果

今天来做一款路飞打坚果的动图演示 先来搭建游戏框架 # coding:utf-8 import pygame, sys, time from pygame.locals import* # 初始化pygame环境 pygame.init() # 设置一个长为1250&#xff0c;宽为700的窗口 canvas pygame.display.set_mode((1200, 600)) canvas.fill([255…

路飞项目第三天

路飞项目第三天 安装JWTdev里配置一下JWTDjango REST framework JWT提供了登录获取token的视图&#xff0c;可以直接使用,在users/urls里配置一下路由 安装JWT pip install djangorestframework-jwt -i https://mirrors.aliyun.com/pypi/simple/dev里配置一下JWT REST_FRAMEW…