目录
- 路飞学城之 drf
- DRF框架知识总览
- 二、内容大纲
- 三、内部类
- 四、内容大纲
- 五、内容大纲
- 六、内容大纲
- 七、内容大纲
- 八、复习
路飞学城之 drf
相关内容复习
"""
1)指令:
插值表达式:{{ 变量|常量|简单表达式 }}
文本指令:v-text | v-html
事件指令:v-on:事件名 | @事件名
属性指令:v-bind:属性名 | :属性名
表单指令:v-model
条件指令:v-show | v-if | v-else-if | v-else
条件指令:v-for
2)实例成员
挂载点:el
数据: data
方法:methods
计算(方法属性):computed
监听:watch
标识符:delimites
过滤器:filters
属性:props
声明周期钩子:created...
3)vue项目
组件:结构 - template(一个根标签) | 逻辑 - script(export default) | 样式 - style(scoped)
组件交互:父传子 - 自定义属性 | 子传父 - 发送自定义事件($emit)
路由:
router-view:页面组件占位
router-link:标签跳转
$router:控制逻辑跳转
$route:控制路由数据
配置:path: '/books/detail/:pk' | path: '/books/detail'
自定义配置main.js:
import 'css文件路径'
import 别名 from 'js文件路径'
Vue.prototype.$名字 = 别名
import ElementUI from 'element-ui'
Vue.use(ElementUI)
import 'element-ui的全局css文件'
vue项目逻辑:
ant.js
var Ant = {
// ...
}
export default Ant
<script>
import Ant from 'ant.js文件路径'
export default {
data() {},
mounted() {
// Ant
}
}
</script>
"""
DRF框架知识总览
"""
1)接口(api):
什么是接口
接口文档
接口规范
2)FBV => CBV:Function|Class Base View
CBV的请求生命周期
CBV比FBV的优势
3)drf框架的基础试图类
APIView:请求模块、解析模块、渲染模块、响应模块、异常模块
4)drf核心组件
序列化组件:将后台的任何数据,进行序列化返回给前台;将前台的数据反序列化成后台model对象再入库
三大认证组件:认证组件、权限组件(RBAC)、频率组件
视图家族:View一系列组件
5)群查接口相关组件:
搜索、筛选、排序、分页
目的:
必须掌握:六大基础接口 - 单查、群查、单增、单局部改、单整体改、单删
一共十大接口 - 群增、群局部改、群整体改、群删
"""
接口
"""
接口概念:前台与后台进行信息交互的媒介 - url链接
接口组成:
url链接 - 长得像返回数据的url链接
请求方式 - get(查)、post(增)、put(整体改)、patch(局部改)、delete(删)
请求参数 - 拼接参数、数据包参数(urlencoded、form-data、json)
响应结果 - 响应的json数据
"""
开发阶段接口测试工具
"""
Postman:
官网下载、傻瓜式安装
"""
接口文档
"""
1)为什么要写接口文档
为什么要写接口:作为后台开发者,要将后台数据通过url链接反馈给前台
为什么要写文档:作为后台开发者,一定知道改url链接应该采用什么请求方式、提交哪些数据、返回了哪些结果
就像后台要将url链接改前台一样,前台知道应该访问什么链接,所以前台也应该知道采用什么请求方式,以及提交什么数据。
换而言之,接口文档是给 后台开发者、前台开发者、测试等各个项目相关项目组同时查看的,方便团队开发(规则是后台指定的,文档后台来写)
2)编写文档
i)采用word编写
ii)drf框架有插件,可以根据cbv的类快速生成文档
iii)采用写文档的平台
3)书写过程
i)先安装开发需要,完成接口的开发(设置后台url链接,设置请求方式、请求数据、响应结果)
ii)选择一个接口平台,将后台url链接,设置请求方式、请求数据、响应结果信息变成成文档即可
"""
接口规范
"""
1)为什么要指定接口规范
在前后台分离情况下,后台可以采用不同的后台运用,开发出类似的功能,所以前后台请求响应的规则是一致的;如果安装一套标准来编写接口,后台不管是什么语言,前台都可以采用一样的方式进行交互。反过来,后台也不需要管前台到底采用何种方式请求(页面、工具、代码)
2)通用的接口规范:Restful接口规范 - 规定了url如何编写;请求方式的含义;响应的数据规则
i)url编写
https协议 - 保证数据安全性
api字眼 - 标识操作的是数据
v1、v2字眼 - 数据的不同版本共存
资源复数 - 请求的数据称之为资源
拼接条件 - 过滤群查接口数据(https://api.baidu.com/books/?limit=3&ordering=-price)
ii)请求方式
/books/ - get - 群查
/books/(pk)/ - get - 单查
/books/ - post - 单增
/books/(pk)/ - put - 单整体改
/books/(pk)/ - patch - 单局部改
/books/(pk)/ - delete - 单删
iii)响应结果
网络状态码与状态信息:2xx | 3xx | 4xx | 5xx
数据状态码:前后台约定规则 - 0:成功 1:失败 2:成功无结果
数据状态信息:自定义成功失败的信息解释(英文)
数据本体:json数据
数据子资源:头像、视频等,用资源的url链接
"""
CVB vs FBV
"""
1)路由绑定
urlpatterns = [
# 1)项目启动,将test函数地址绑定给/test/路由
# 2)请求/test/访问后台,后台就会调用绑定的test函数
url(r'^test/$', views.test),
# 1)项目启动,将as_view()函数执行结果返回的view函数地址绑定给/test/路由
# 2)请求/test/访问后台,后台就会调用绑定的view函数
# 3)view函数会将请求交给dispatch方法完成分发,分发(反射)给视图类的不同方法处理请求
url(r'^test/$', views.Test.as_view()),
]
2)业务处理:
fbv没一个接口都会对应一个函数来响应请求
cbv可以将一个资源的增删改查所有操放在一个类中管理,在内部再分方法逐一处理 (高内聚低耦合:六个接口和一个类有关,但都能在类内部处理)
-------
三个View其实是同一个类
继承View的目的:
i)继承as_view()方法,完成路由的配置
ii)继承dispath()方法,完成请求分发
注:如果自己写as_view()和dispath()方法,自定义视图类可以不用继承任何类的
from django.http import JsonResponse
from django.views import View
from django.views.generic import View
from django.views.generic.base import View
class Test(View):
def get(self, request, *args, **kwargs):
return JsonResponse('cbv ok', safe=False)
"""
pip安装源
介绍
"""
1、采用国内源,加速下载模块的速度
2、常用pip源:
-- 豆瓣:https://pypi.douban.com/simple
-- 阿里:https://mirrors.aliyun.com/pypi/simple
3、加速安装的命令:
-- >: pip install -i https://pypi.douban.com/simple 模块名
"""
永久配置安装源
Windows
"""
1、文件管理器文件路径地址栏敲:%APPDATA% 回车,快速进入 C:Users电脑用户AppDataRoaming 文件夹中
2、新建 pip 文件夹并在文件夹中新建 pip.ini 配置文件
3、新增 pip.ini 配置文件内容
"""
MacOS、Linux
"""
1、在用户根目录下 ~ 下创建 .pip 隐藏文件夹,如果已经有了可以跳过
-- mkdir ~/.pip
2、进入 .pip 隐藏文件夹并创建 pip.conf 配置文件
-- cd ~/.pip && touch pip.conf
3、启动 Finder(访达) 按 cmd+shift+g 来的进入,输入 ~/.pip 回车进入
4、新增 pip.conf 配置文件内容
"""
配置文件内容
"""
[global]
index-url = http://pypi.douban.com/simple
[install]
use-mirrors =true
mirrors =http://pypi.douban.com/simple/
trusted-host =pypi.douban.com
"""
DRF框架的安装
"""
1)安装
>: pip install djangorestframework
2)在settings中注册(为什么要注册,后期会讲)
INSTALLED_APPS = [
# ...
'rest_framework',
]
3)在settings中自定义drf配置
注:drf配置查找顺序,自定义settings的REST_FRAMEWORK配置字典 => drf默认settings的DEFAULTS
REST_FRAMEWORK = {}
4)drf的封装特点:特点功能在特点模块下
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
"""
图书资源准备
"""
1)在models.py创建Model类
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=64, verbose_name='书名')
price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='价格')
# 配置类
class Meta:
verbose_name_plural = '图书'
def __str__(self):
return self.name
2)在admin.py中注册Model类,方便在后台(admin)可视化管理数据
from django.contrib import admin
from . import models
admin.site.register(models.Book) # admin要控制的model都需要注册一下
3)数据库迁移操作
>: python manage.py makemigrations
>: python manage.py migrate
>: python manage.py createsuperuser
admin | Admin123
4)浏览器登录admin录入数据:http://localhost:8000/admin
"""
基于restful接口规范的接口设计
urlpatterns = [
# 资源books接口的设计
url(r'^books/$', views.BookAPIView.as_view()), # 群查、单增
url(r'^books/(?P<pk>d+)/$', views.BookAPIView.as_view()), # 单查、单删、单(整体|局部)改
]
实现单查群查接口
from rest_framework.views import APIView
from rest_framework.response import Response
from . import models
class BookAPIView(APIView):
def _multiple_get(self):
# 不能返回,数据是QuerySet[Obj, Obj],也不能强制转换list
# return list(models.Book.objects.all())
# QuerySet[dict, dict]
# print(models.Book.objects.values('name', 'price'))
# QuerySet[tuple, tuple]
# print(models.Book.objects.values_list('name', 'price'))
return Response(list(models.Book.objects.values('name', 'price')))
def _single_get(self, pk):
# return Response(models.Book.objects.filter(pk=pk).values('name', 'price').first())
# 开发阶段建议能用try的地方,不要使用if判断
try:
return Response(models.Book.objects.values('name', 'price').get(pk=pk))
except:
return Response('资源不存在')
# 单查、群查
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
# 单查逻辑
return self._single_get(pk)
else:
# 群查逻辑
return self._multiple_get()
小结
"""
1)接口:
概念:前后台数据交互的桥梁
组成:url链接 + 请求方法 + 请求数据 + 响应结果
文档:将接口的四个组成部分书写成一个使用文档
工具:Postman
2)Restful接口规范:
i)url链接:https、api、books、v1、?limit=3
https://www.baidu.com/api/v1/books/?limit=3
ii)请求方式:get、post、put、patch、delete
用不同请求方式代表操作资源的不同目的
iii)响应结果
网络状态码,状态信息 - 规定好的
数据状态码,状态信息 - 开发自己约定的
数据本地 - json数据
子资源 - 图片、视频返回资源的url链接
3)cbv与fbv
高内聚低耦合、生命周期 as_view() => dispatch() => 视图类响应方法
4)drf的安装
pip install djangorestframework
封装特点:特点功能在特点模块下
5)pip换源
6)基于restful规定的接口设计
url(r'^books/$', views.BookAPIView.as_view()), # 群查、单增
url(r'^books/(?P<pk>d+)/$', views.BookAPIView.as_view()), # 单查、单删、单(整体|局部)改
7)资源的单查群查案例(orm知识回顾)
"""
二、内容大纲
"""
1)drf框架的请求生命周期:as_view和dispatch方法
2)请求、解析、渲染、响应、异常
3)序列化组件
django:ORM、表单、序列化、认证(auth)、路由、模板、分页器、中间件
"""
Django配置回顾
"""
1)应用是否需要在INSTALLED_APPS中注册
在没有使用到app的一些特殊操作时(比如数据库相关),可以不用注册,但是注册后,应用的所有功能都能使用
结论:所有应用都可以完成注册
2)数据库配置(全部在settings文件中完成即可)
import pymysql
pymysql.install_as_MySQLdb()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '数据库名',
'USER': '账号',
'PASSWORD': '密码',
'HOST': '如果是127.0.0.1,该配置可以省略',
"PORT": 3306, # 如果是3306,该配置可以省略
}
}
3)路由分发
主路由:将应用自己逻辑相关的路由交给自己处理
from django.conf.urls import url, include
urlpatterns = [
# ...
url(r'^api/', include('api.urls')), # /api/test/
]
"""
ORM配置回顾
# models.py
from django.db import models
class User(models.Model):
SEX_CHOICES = ((0, '男'), (1, '女'))
name = models.CharField(max_length=64, verbose_name='姓名')
age = models.IntegerField()
height = models.DecimalField(max_digits=5, decimal_places=2, default=0)
sex = models.IntegerField(choices=SEX_CHOICES, default=0)
# sex = models.CharField(choices=[('0', '男'), ('1', '女')])
icon = models.ImageField(upload_to='icon', default='icon/default.png')
# settings.py
# root就是将文件夹添加到 os.path 中
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# url就是配置路由 /路由名/
MEDIA_URL = '/media/'
# 主路由最下方
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
APIView的请求声明周期
重写的as_view方法
"""
1)as_view方法完成路由配置,返回配置函数是 csrf_exempt(view),也就是禁用了csrf认证规则
结论:所有继承APIView的子类,都不受csrf认证规则的限制
2)将请求处理的任务交给dispath方法完成
"""
重写的dispatch方法
"""
完成了三大核心任务:
1)请求对象的处理:请求渲染模块
2)请求过程的处理:三大认证模块 => 自己代码完成处理
3)请求结果的响应:异常模块处理异常响应 | 响应渲染模块处理正常响应
"""
请求解析模块
"""
1)二次封装了原生Django的wsgi协议的request对象,并做了向下兼容(原来request对象的内容,用现在的request对象都能访问)
2)将所有拼接参数都放在request.query_params中,将所有数据包参数都放在request.data中
3)路由的有名无名分组的数据还是保存在args和kwargs中
"""
# 解析模块可以在settings.py自定义解析配置
REST_FRAMEWORK = {
# 解析模块
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser', # json
'rest_framework.parsers.FormParser', # urlencoded
'rest_framework.parsers.MultiPartParser' # form-data
],
# 渲染模块
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer', # 上线后会注释
],
}
响应渲染模块
"""
1)当三大认证模块和自己处理请求的视图逻辑没有出现异常时,会执行响应渲染模块
2)响应的数据会交给渲染模块来完成数据的渲染,渲染方式有两种:Json格式数据渲染、Brower格式数据渲染
"""
# 渲染模块可以在settings.py自定义解析配置
REST_FRAMEWORK = {
# 渲染模块
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
# 浏览器渲染,上线后会注释,不然浏览器请求接口就暴露了后台语言
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
序列化组件
单表序列化
models.py
from django.db import models
from django.conf import settings
class User(models.Model):
SEX_CHOICES = ((0, '男'), (1, '女'))
name = models.CharField(max_length=64, verbose_name='姓名')
password = models.CharField(max_length=64)
age = models.IntegerField()
height = models.DecimalField(max_digits=5, decimal_places=2, default=0)
sex = models.IntegerField(choices=SEX_CHOICES, default=0)
# sex = models.CharField(choices=[('0', '男'), ('1', '女')])
icon = models.ImageField(upload_to='icon', default='icon/default.png')
# 自定义序列化给前台的字段
# 优点:1)可以格式化数据库原有字段的数据 2)可以对外隐藏原有数据库字段名 3)可以直接连表操作
@property # 制造插头
def gender(self):
return self.get_sex_display()
@property
def img(self):
return settings.BASE_URL + settings.MEDIA_URL + self.icon.name
def __str__(self):
return self.name
serializers.py
from rest_framework import serializers
from . import models
class UserModelSerializer(serializers.ModelSerializer):
class Meta:
# 该序列化类是辅助于那个Model类的
model = models.User
# 设置参与序列化与反序列化字段
# 插拔式:可以选择性返回给前台字段(插头都是在Model类中制造)
# fields = ['name', 'age', 'height', 'sex', 'icon]
fields = ['name', 'age', 'height', 'gender', 'img']
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from . import models, serializers
class UserAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk: # 单查
# 1)数据库交互拿到资源obj或资源objs
# 2)数据序列化成可以返回给前台的json数据
# 3)将json数据返回给前台
obj = models.User.objects.get(pk=pk)
serializer = serializers.UserModelSerializer(obj, many=False)
return Response(serializer.data)
else: # 群查
# 1)数据库交互拿到资源obj或资源objs
# 2)数据序列化成可以返回给前台的json数据
# 3)将json数据返回给前台
queryset = models.User.objects.all()
# many操作的数据是否是多个
serializer = serializers.UserModelSerializer(queryset, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
# 单增
# 1)从请求request中获得前台提交的数据
# 2)将数据转换成Model对象,并完成数据库入库操作
# 3)将入库成功的对象列化成可以返回给前台的json数据(请求与响应数据不对等:请求需要提交密码,响应一定不展示密码)
# 4)将json数据返回给前台
return Response()
小结
"""
0)回顾Django及ORM
1)APIView的生命周期
禁用csrf(√) => 请求解析模块(√) => 三大认证模块 => 自己代码处理请求 => 异常响应|正常响应渲染模块(√)
2)单表序列化:ModelSerializer
"""
三、内部类
# 概念:将类定义在一个类的内部,被定义的类就是内部类
# 特点:内部类及内部类的所以名称空间,可以直接被外部类访问的
# 应用:通过内部类的名称空间,给外部类额外拓展一些特殊的属性(配置),典型的Meta内部类 - 配置类
class Book(model.Model):
class Meta:
db_model = "owen_book" # 配置自定义表名
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = "Book" # 配置序列化类绑定的Model表
DRF响应类:Response
"""
def __init__(self, data=None, status=None, template_name=None, headers=None, exception=False, content_type=None):
pass
data:响应的数据 - 空、字符串、数字、列表、字段、布尔
status:网络状态码
template_name:drf说自己也可以支持前后台不分离返回页面,但是不能和data共存(不会涉及)
headers:响应头(不用刻意去管)
exception:是否是异常响应(如果是异常响应,可以赋值True,没什么用)
content_type:响应的结果类型(如果是响应data,默认就是application/json,所有不用管)
"""
# 常见使用
Response(
data={
'status': 0,
'msg': 'ok',
'result': '正常数据'
}
)
Response(
data={
'status': 1,
'msg': '客户端错误提示',
},
status=status.HTTP_400_BAD_REQUEST,
exception=True
)
序列化基类控制的初始化参数
"""
def __init__(self, instance=None, data=empty, **kwargs):
pass
instance:是要被赋值对象的 - 对象类型数据赋值给instance
data:是要被赋值数据的 - 请求来的数据赋值给data
kwargs:内部有三个属性:many、partial、context
many:操作的对象或数据,是单个的还是多个的
partial:在修改需求时使用,可以将所有校验字段required校验规则设置为False
context:用于视图类和序列化类直接传参使用
"""
# 常见使用
# 单查接口
UserModelSerializer(instance=user_obj)
# 群查接口
UserModelSerializer(instance=user_query, many=True)
# 增接口
UserModelSerializer(data=request.data)
# 整体改接口
UserModelSerializer(instance=user_obj, data=request.data)
# 局部改接口
UserModelSerializer(instance=user_obj, data=request.data, partial=True)
# 删接口,用不到序列化类
反序列化
views.py
class UserAPIView(APIView):
def post(self, request, *args, **kwargs):
# 单增
# 1)将前台请求的数据交给序列化类处理
# 2)序列化类执行校验方法,对前台提交的所有数据进行数据校验:校验失败就是异常返回,成功才能继续
# 3)序列化组件完成数据入库操作,得到入库对象
# 4)响应结果给前台
serializer = serializers.UserModelSerializer(data=request.data)
if serializer.is_valid():
# 校验成功 => 入库 => 正常响应
obj = serializer.save()
return Response({
'status': 0,
'msg': 'ok',
'result': '新增的那个对象'
}, status=status.HTTP_201_CREATED)
else:
# 校验失败 => 异常响应
return Response({
'status': 1,
'msg': serializer.errors,
}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
"""
标注:序列化 => 后台到前台(读) | 反序列化 => 前台到后台(写)
1)不管是序列化还是反序列化字段,都必须在fields中进行声明,没有声明的不会参与任何过程(数据都会被丢弃)
2)用 read_only 表示只读,用 write_only 表示只写,不标注两者,代表可读可写
3)
i)自定义只读字段,在model类中用@property声明,默认就是read_only
@property
def gender(self):
return self.get_sex_display()
ii)自定义只写字段,在serializer类中声明,必须手动明确write_only
re_password = serializers.CharField(write_only=True)
特殊)在serializer类中声明,没有明确write_only,是对model原有字段的覆盖,且可读可写
password = serializers.CharField()
4)用 extra_kwargs 来为 写字段 制定基础校验规则(了解)
5)每一个 写字段 都可以用局部钩子 validate_字段(self, value) 方法来自定义校验规则,成功返回value,失败抛出 exceptions.ValidationError('异常信息') 异常
6)需要联合校验的 写字段们,用 validate(self, attrs) 方法来自定义校验规则,,成功返回attrs,失败抛出 exceptions.ValidationError({'异常字段': '异常信息'}) 异常
7)extra_kwargs中一些重要的限制条件
i)'required':代表是否必须参与写操作,有默认值或可以为空的字段,该值为False;反之该值为True;也可以手动修改值
"""
"""
开发流程:
1)在model类中自定义 读字段,在serializer类中自定义 写字段
2)将model自带字段和所以自定义字段书写在fields中,用write_only和read_only区别model自带字段
3)可以写基础校验规则,也可以省略
4)制定局部及全局钩子
"""
课堂案例
settings.py
# root就是将文件夹添加到 os.path 中
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# url就是配置路由 /路由名/
MEDIA_URL = '/media/'
# 后台服务器地址
BASE_URL = 'http://localhost:8000'
主urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include('api.urls')), # /api/test/
# icon/default.png => path变量
# document_root必须指向icon/default.png所在路径 - media文件路径
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
]
子urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^users/$', views.UserAPIView.as_view()),
url(r'^users/(?P<pk>d+)/$', views.UserAPIView.as_view()),
]
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from . import models, serializers
class UserAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk: # 单查
# 1)数据库交互拿到资源obj或资源objs
# 2)数据序列化成可以返回给前台的json数据
# 3)将json数据返回给前台
try:
obj = models.User.objects.get(pk=pk)
serializer = serializers.UserModelSerializer(obj, many=False)
return Response({
'status': 0,
'msg': 'ok',
'result': serializer.data
})
except:
return Response(
data={
'status': 1,
'msg': 'pk error'
},
status=status.HTTP_400_BAD_REQUEST, # 比直接写400更具有描述性
exception=True
)
else: # 群查
# 1)数据库交互拿到资源obj或资源objs
# 2)数据序列化成可以返回给前台的json数据
# 3)将json数据返回给前台
queryset = models.User.objects.all()
serializer = serializers.UserModelSerializer(queryset, many=True)
return Response({
'status': 0,
'msg': 'ok',
'results': serializer.data
})
def post(self, request, *args, **kwargs):
# 单增
# 1)从请求request中获得前台提交的数据
# 2)将数据转换成Model对象,并完成数据库入库操作(序列化组件完成)
# 3)将入库成功的对象列化成可以返回给前台的json数据(请求与响应数据不对等:请求需要提交密码,响应一定不展示密码)
# 4)将json数据返回给前台
# 1)将前台请求的数据交给序列化类处理
# 2)序列化类执行校验方法,对前台提交的所有数据进行数据校验:校验失败就是异常返回,成功才能继续
# 3)序列化组件完成数据入库操作,得到入库对象
# 4)响应结果给前台
serializer = serializers.UserModelSerializer(data=request.data)
if serializer.is_valid():
# 校验成功 => 入库 => 正常响应
obj = serializer.save()
return Response({
'status': 0,
'msg': 'ok',
'result': '新增的那个对象'
}, status=status.HTTP_201_CREATED)
else:
# 校验失败 => 异常响应
return Response({
'status': 1,
'msg': serializer.errors,
}, status=status.HTTP_400_BAD_REQUEST)
"""
序列化类BaseSerializer
def __init__(self, instance=None, data=empty, **kwargs):
pass
instance:是要被赋值对象的 - 对象类型数据赋值给instance
data:是要被赋值数据的 - 请求来的数据赋值给data
kwargs:内部有三个属性:many、partial、context
many:操作的对象或数据,是单个的还是多个的
partial:
context:
"""
"""
响应类Response
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
pass
data:响应的数据 - 空、字符串、数字、列表、字段、布尔
status:网络状态码
template_name:drf说自己也可以支持前后台不分离返回页面,但是不能和data共存(不会涉及)
headers:响应头(不用刻意去管)
exception:是否是异常响应(如果是异常响应,可以赋值True,没什么用)
content_type:响应的结果类型(如果是响应data,默认就是application/json,所有不用管)
"""
serializers.py
from rest_framework import serializers
from rest_framework import exceptions
from . import models
class UserModelSerializer(serializers.ModelSerializer):
# 自定义反序列化字段(所有校验规则自己定义,也可以覆盖model已有的字段)
# 覆盖model有的字段,不明确write_only会参与序列化过程
password = serializers.CharField(min_length=4, max_length=8, write_only=True)
# 自定义字段,不明确write_only序列化会报错,序列化会从model中强行反射自定义字段,但是model没有对于字段
re_password = serializers.CharField(min_length=4, max_length=8, write_only=True)
class Meta:
# 该序列化类是辅助于那个Model类的
model = models.User
# 设置参与序列化与反序列化字段
# 插拔式:可以选择性返回给前台字段(插头都是在Model类中制造)
# fields = ['name', 'age', 'height', 'sex', 'icon]
# 第一波分析:
# 1)name和age,在fields中标明了,且没有默认值,也不能为空,入库时必须提供,所有校验时必须提供
# 2)height,在fields中标明了,但是有默认值,所有前台不提供,也能在入库时采用默认值(可以为空的字段同理)
# 3)password,没有在fields中标明了,所以校验规则无法检测password情况,但是即使数据校验提供了,
# 也不能完成入库,原因是password是入库的必备条件
# 4)gender和img是自定义插拔@property字段,默认就不会进行校验(不参与校验)
# fields = ['name', 'age', 'height', 'gender', 'img']
# 第二波分析:
# 1)如何区分 序列化反序列化字段 | 只序列化字段(后台到前台) | 只反序列化字段(前台到后台)
# 不做write_only和read_only任何限制 => 序列化反序列化字段
# 只做read_only限制 => 只序列化字段(后台到前台)
# 只做write_only限制 => 只反序列化字段(前台到后台)
# 2)对前台到后台的数据,制定基础的校验规则(了解)
# CharField(max_length, min_length, errors_kwargs)
# DecimalField(min_value, max_value, max_digits, decimal_places,error_messages)
# 3)如果一个字段有默认值或是可以为空,比如height,如何做限制
# 虽然有默认值或是可以为空,能不能强制限制必须提供,可以通过required是True来限制
# 前台提交了该字段,我就校验,没提交我就不校验:1)required是False 2)有校验规则
fields = ['name', 'age', 'password', 'height', 'gender', 'img', 're_password']
# 第三波分析
# 1)制定的简易校验规则(没有制定)后,可以再通过字段的 局部钩子 对该字段进行复杂校验
# 2)每个字段进行逐一复杂校验后,还可以进行集体的 全局钩子 校验
# 涉及对自定义反序列化字段的校验:re_password(要参与校验,但是不会入库)
# 校验规则
extra_kwargs = {
'name': {
# 'write_only': True,
# 'read_only': True
},
'password': {
'write_only': True,
'min_length': 3,
'max_length': 8,
'error_messages': { # 可以被国际化配置替代
'min_length': '太短',
'max_length': '太长'
}
},
'height': {
# 'required': True,
'min_value': 0,
}
}
# 注:局部全局钩子,是和Meta同缩减,属于序列化类的
# 局部钩子:validate_要校验的字段(self, 要校验的字段值)
# 全局钩子:validate(self, 所以字段值的字典)
# 校验规则:成功返回传来的数据 | 失败抛出异常
def validate_name(self, value):
if 'g' in value.lower():
raise exceptions.ValidationError("这个g不行")
else:
return value
def validate(self, attrs):
print(attrs)
password = attrs.get('password') # 只是拿出来校验
re_password = attrs.pop('re_password') # 必须取出校验,因为不能入库
if password != re_password:
raise exceptions.ValidationError({'re_password': '两次密码不一致'})
else:
return attrs
四、内容大纲
"""
1)Response二次封装
2)多表关系优化
3)多表序列化
4)十大接口
"""
二次封装Response
# 新建response.py文件
from rest_framework.response import Response
class APIResponse(Response):
def __init__(self, status=0, msg='ok', http_status=None, headers=None, exception=False, **kwargs):
# 将外界传入的数据状态码、状态信息以及其他所有额外存储在kwargs中的信息,都格式化成data数据
data = {
'status': status,
'msg': msg
}
# 在外界数据可以用result和results来存储
if kwargs:
data.update(kwargs)
super().__init__(data=data, status=http_status, headers=headers, exception=exception)
# 使用:
# APIResponse() 代表就返回 {"status": 0, "msg": "ok"}
# APIResponse(result="结果") 代表返回 {"status": 0, "msg": "ok", "result": "结果"}
# APIResponse(status=1, msg='error', http_status=400, exception=True) 异常返回 {"status": 1, "msg": "error"}
数据库关系分析
"""
1)之间有关系的两个表,增删改操作会相互影响(效率低),查询操作就是正常的连表操作
2)之间有关系的两个表,断开关联,但所有数据保持与原来一致
每个表都可以单独操作,增删改操作效率极高,但是容易出现脏数据(开发中完全可以避免)
由于数据没有任何变化,所以查询的连表操作不会受到任何影响
3)Django的ORM支持断关联操作关系表,且所有的操作方式和没有断关联操作一致
"""
ORM操作关系
"""
外键位置:
1)一对多:ForeignKey必须放在多的一方,书与出版社,外键应该放在书表
2)多对多:ManyToManyField放在任何一方都可以,因为会创建关系表,在关系表中用两个外键分别关联两个表
3)一对一:OneToOneField放在依赖的表,作者与作者详情,放在详情表,OneToOneField会被转换为 外键 + 唯一约束
"""
"""
ORM关系Field:
ForeignKey可以设置related_name, db_constraint, on_delete
OneToOneField可以设置related_name, db_constraint, on_delete
ManyToManyField只能设置related_name, db_constraint
不能设置on_delete的原因:不管是关联的A表,还是B表,数据修改,都会影响到关系表(默认级联),
如果想控制,只能自定义关系表,在关系表的两个外键分别设置on_delete
"""
"""
参数含义
related_name:表之间反向访问的名字,默认是 表名小写|表名小写_set
db_constraint:表之间的关联关系,默认为True,代表关联,设置False,可以提高增删改的效率,且不影响查等其他操作
on_delete:在django 1.x下默认是CASCADE,在django 2.x下必须手动明确
"""
"""
表关系:
作者没,作者详情一定没:CASCADE *****
作者没,书还是该作者出的:DO_NOTHING
部门们,部门内的员工全部进入未分组部门:SET_DEFAULT (需要配合default属性使用)
部门们,部门内的员工部门外键字段设置为空:SET_NULL (需要配合null=True属性使用) *****
"""
案例
class Author(models.Model):
name = models.CharField(max_length=64)
class AuthorDetail(models.Model):
phone = models.CharField(max_length=11)
author = models.OneToOneField(
to=Author,
related_name='detail',
db_constraint=False,
on_delete=models.SET_NULL,
null=True
)
测试
import os, django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "d_proj.settings")
django.setup()
from api.models import Author, AuthorDetail
# 测试正向反向查询
# a1 = Author.objects.first()
# print(a1.name)
# print(a1.detail.phone)
# ad2 = AuthorDetail.objects.last()
# print(ad2.phone)
# print(ad2.author.name)
# 级联关系测试
# ad2 = AuthorDetail.objects.last() # type: AuthorDetail
# ad2.delete()
# Author.objects.first().delete()
基表
# 基类:是抽象的(不会完成数据库迁移),目的是提供共有字段的
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
updated_time = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True # 必须完成该配置
应用
class Book(BaseModel):
name = models.CharField(max_length=64)
price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
image = models.ImageField(upload_to='img', default='img/default.png')
publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
class Publish(BaseModel):
name = models.CharField(max_length=64)
class Author(BaseModel):
name = models.CharField(max_length=64)
class AuthorDetail(BaseModel):
phone = models.CharField(max_length=11)
author = models.OneToOneField(
to=Author,
related_name='detail',
db_constraint=False,
on_delete=models.SET_NULL,
null=True
)
序列化类其他配置(了解)
class AuthorModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
# 不常用,将全部字段提供给外界
fields = '__all__'
# ------------------------------------------------------------------
class AuthorModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
# 不常用,排除指定字段的其他所有字段,不能自动包含 外键反向 字段
exclude = ['is_delete', 'updated_time']
# ------------------------------------------------------------------
class AuthorModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
# 'detail', 'books' 是 外键(正向|反向) 字段
fields = ['name', 'detail', 'books']
# 不常用,自动深度,自动深度会显示外键关联表的所有字段
depth = 2
# 正向外键字段:就是外键的属性名
# 反向外键字段:就是外键属性设置的related_name
子序列化
"""
1)子序列化的字段,必须是 外键(正向|反向) 字段
2)子序列化对应的数据是单个many=False,数据对应是多个many=True
3)子序列化其实就是自定义序列化字段,覆盖了原有 外键(正向|反向)字段 的规则,所以不能进行反序列化
"""
案例
urls.py
url(r'^authors/$', views.AuthorAPIView.as_view()),
url(r'^authors/(?P<pk>d+)/$', views.AuthorAPIView.as_view()),
serializers.py
from rest_framework import serializers
from . import models
class AuthorDetailModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.AuthorDetail
fields = ['phone']
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ['name', 'price']
class AuthorModelSerializer(serializers.ModelSerializer):
# 子序列化:子序列化类必须写在上方,且只能对 外键(正向反向)字段 进行覆盖
# 注:运用了子序列化的外键字段,就不能进行数据库的反序列化过程
detail = AuthorDetailModelSerializer(many=False, read_only=True)
books = BookModelSerializer(many=True, read_only=True)
# 问题:
# 1)不设置read_only时,就相当于允许反序列化,反序列化是就会报错
# 2)设置read_only时,可以完成反序列化,但是新增的数据再序列化了,就没有外键关联的数据,与原来数据格式就不一致了
class Meta:
model = models.Author
fields = ['name', 'detail', 'books']
views.py
# 实际开发,资源的大量操作都是查询操作,只有查需求的资源,可以采用子序列化
class AuthorAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
obj = models.Author.objects.filter(is_delete=False, pk=pk).first()
serializer = serializers.AuthorModelSerializer(instance=obj)
return APIResponse(result=serializer.data)
else:
queryset = models.Author.objects.filter(is_delete=False).all()
serializer = serializers.AuthorModelSerializer(instance=queryset, many=True)
return APIResponse(results=serializer.data)
# 测试子序列化外键字段,不能参与反序列化,因为
def post(self, request, *args, **kwargs):
serializer = serializers.AuthorModelSerializer(data=request.data)
if serializer.is_valid():
obj = serializer.save()
return APIResponse(result=serializers.AuthorModelSerializer(instance=obj).data, http_status=201)
else:
# 校验失败 => 异常响应
return APIResponse(1, serializer.errors, http_status=400)
多表序列化与反序列化
"""
1)外键字段要参与反序列化,所以外键字段设置为write_only
2)外键关系需要连表序列化结果给前台,可以用@property来自定义连表序列化
"""
案例
urls.py
url(r'^books/$', views.BookAPIView.as_view()),
url(r'^books/(?P<pk>d+)/$', views.BookAPIView.as_view()),
models.py
class Book(BaseModel):
# ...
@property # @property字段默认就是read_only,且不允许修改
def publish_name(self):
return self.publish.name
@property # 自定义序列化过程
def author_list(self):
temp_author_list = []
for author in self.authors.all():
author_dic = {
"name": author.name
}
try:
author_dic['phone'] = author.detail.phone
except:
author_dic['phone'] = ''
temp_author_list.append(author_dic)
return temp_author_list
@property # 借助序列化类完成序列化过程
def read_author_list(self):
from .serializers import AuthorModelSerializer
return AuthorModelSerializer(self.authors.all(), many=True).data
serializers.py
# 辅助序列化类
class AuthorDetailModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.AuthorDetail
fields = ['phone']
# 辅助序列化类
class AuthorModelSerializer(serializers.ModelSerializer):
detail = AuthorDetailModelSerializer(many=False, read_only=True)
class Meta:
model = models.Author
fields = ['name', 'detail']
# 主序列化类
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ('name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list', 'read_author_list')
extra_kwargs = {
'image': {
'read_only': True,
},
'publish': { # 系统原有的外键字段,要留给反序列化过程使用,序列化外键内容,用@property自定义
'write_only': True,
},
'authors': {
'write_only': True,
}
}
views.py
# 六个必备接口:单查、群查、单增、单删、单整体改(了解)、单局部改
# 四个额外接口:群增、群删、群整体改、群局部改
class BookAPIView(APIView):
# 单查群查
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
serializer = serializers.BookModelSerializer(instance=obj)
return APIResponse(result=serializer.data)
else:
queryset = models.Book.objects.filter(is_delete=False).all()
serializer = serializers.BookModelSerializer(instance=queryset, many=True)
return APIResponse(results=serializer.data)
# 单增群增
def post(self, request, *args, **kwargs):
# 如何区别单增群增:request.data是{}还是[]
if not isinstance(request.data, list):
# 单增
serializer = serializers.BookModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True) # 如果校验失败,会直接抛异常,返回给前台
obj = serializer.save()
# 为什么要将新增的对象重新序列化给前台:序列化与反序列化数据不对等
return APIResponse(result=serializers.BookModelSerializer(obj).data, http_status=201)
else:
# 群增
serializer = serializers.BookModelSerializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True) # 如果校验失败,会直接抛异常,返回给前台
objs = serializer.save()
# 为什么要将新增的对象重新序列化给前台:序列化与反序列化数据不对等
return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data, http_status=201)
# 友情注释:群增其实是借助了ListSerializer来的create方法完成的
小结
"""
1)二次封装Response:
自定义类继承Response,重写init方法,在内部格式化data
2)表关系分析:
断关联:
优点:提示增删改操作效率,不允许查效率
缺点:增删改操作可能会导致脏数据,所以需要通过逻辑或是事务来保证
3)ORM表关系处理语法:
1)外键所在位置
2)如何断关联db_constraint
3)正向方向自定义名字:related_name
4)表关系:on_delete四种
4)基表:Meta中配置abstract=True,来被继承,提供共有字段
5)多表连表Meta中的了解配置
fields = '__all__' # 不常用,将全部字段提供给外键
exclude = ['is_delete', 'updated_time'] # 不常用,排除指定字段的其他所有字段
depth = 2 # 不常用,主动深度,自动深度会显示关联表的所有字段
6)子序列化
i)通常只用于序列化过程,对外键字段进行了覆盖,影响外键字段的反序列化过程
class SubSerializer:
pass
class SupSerializer:
外键 = SubSerializer(many=True|False)
7)多表的序列化与反序列化
1)连表序列化用自定义@property完成:内部实现可以自定义逻辑,也可以走序列化类
2)外键字段留给反序列化来使用
8)单查、群查、单增、群增接口
"""
五、内容大纲
"""
1、十大接口:序列化 - partial、context、ListSerializer
2、视图家族:views、mixins、generics、viewsets
3、路由组件:结合viewsets使用
"""
序列化类外键字段的覆盖
"""
1)在序列化类中自定义字段,名字与model类中属性名一致,就称之为覆盖操作
(覆盖的是属性的所有规则:extra_kwargs中指定的简易规则、model字段提供的默认规则、数据库唯一约束等哪些规则)
2)外键覆盖字段用PrimaryKeyRelatedField来实现,可以做到只读、只写、可读可写三种形式
只读:read_only=True
只写:queryset=关联表的queryset, write_only=True
可读可写:queryset=关联表的queryset
3)当外界关联的数据是多个时,需标识many=True条件
"""
class BookModelSerializer(serializers.ModelSerializer):
# 如何覆盖外键字段
# publish = serializers.PrimaryKeyRelatedField(read_only=True) # 只读
# publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all(), write_only=True) # 只写
# publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all()) # 可读可写
publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())
authors = serializers.PrimaryKeyRelatedField(queryset=models.Author.objects.all(), many=True)
class Meta:
model = models.Book
fields = ('name', 'price', 'image', 'publish', 'authors')
十大接口序列化总结
"""
def __init__(self, instance=None, data=empty, **kwargs):
pass
instance:是要被赋值对象的 - 对象类型数据赋值给instance
data:是要被赋值数据的 - 请求来的数据赋值给data
kwargs:内部有三个属性:many、partial、context
many:操作的对象或数据,是单个的还是多个的
partial:在修改需求时使用,可以将所有校验字段required校验规则设置为False
context:用于视图类和序列化类直接传参使用
"""
# 常见使用
# 单查接口
UserModelSerializer(instance=user_obj)
# 群查接口
UserModelSerializer(instance=user_query, many=True)
# 单增接口,request.data是字典
UserModelSerializer(data=request.data)
# 群增接口,request.data是列表
UserModelSerializer(data=request.data, many=True)
# 单整体改接口,request.data是字典
UserModelSerializer(instance=user_obj, data=request.data)
# 群整体改接口,request.data是列表,且可以分离出pks,转换成user_queryset
UserModelSerializer(instance=user_queryset, data=request.data, many=True)
# 单局部改接口,request.data是字典
UserModelSerializer(instance=user_obj, data=request.data, partial=True)
# 群局部改接口,request.data是列表,且可以分离出pks,转换成user_queryset
UserModelSerializer(instance=user_queryset, data=request.data, partial=True, many=True)
# 删接口,用不到序列化类
十大接口核心知识小结
"""
1)初始化序列化类,设置partial=True可以将所有反序列化字段的 required 设置为 False(提供就校验,不提供不校验),可以运用在局部修改接口
2)初始化序列化类,设置context={...},在序列化类操作self.context,实现视图类和序列化类数据互通
3)只有要完成资源的群改这种特殊需求时,才需要自定义ListSerializer绑定给自定义的ModelSerializer,重写update方法,来完成需求
"""
案例
models.py
# 基类:是抽象的(不会完成数据库迁移),目的是提供共有字段的
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
updated_time = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True # 必须完成该配置
class Book(BaseModel):
name = models.CharField(max_length=64)
price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
image = models.ImageField(upload_to='img', default='img/default.png')
publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
@property # @property字段默认就是read_only,且不允许修改
def publish_name(self):
return self.publish.name
@property # 自定义序列化过程
def author_list(self):
temp_author_list = []
for author in self.authors.all():
author_dic = {
"name": author.name
}
try:
author_dic['phone'] = author.detail.phone
except:
author_dic['phone'] = ''
temp_author_list.append(author_dic)
return temp_author_list
class Publish(BaseModel):
name = models.CharField(max_length=64)
class Author(BaseModel):
name = models.CharField(max_length=64)
class AuthorDetail(BaseModel):
phone = models.CharField(max_length=11)
author = models.OneToOneField(to=Author, related_name='detail', db_constraint=False, on_delete=models.CASCADE)
urls.py
url(r'^books/$', views.BookAPIView.as_view()),
url(r'^books/(?P<pk>d+)/$', views.BookAPIView.as_view()),
serializers.py
from rest_framework import serializers
from . import models
# 群增群改辅助类(了解)
class BookListSerializer(serializers.ListSerializer):
"""
1)create群增方法不需要重新
2)update群改方法需要重写,且需要和views中处理request.data的逻辑配套使用
3)self.child就代表该ListSerializer类绑定的ModelSerializer类
BookListSerializer的self.child就是BookModelSerializer
"""
# 重新update方法
def update(self, queryset, validated_data_list):
return [
self.child.update(queryset[index], validated_data) for index, validated_data in enumerate(validated_data_list)
]
# 主序列化类
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
# 配置自定义群增群改序列化类
list_serializer_class = BookListSerializer
model = models.Book
fields = ('name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list')
# fields = ('name', 'price', 'image', 'publish', 'authors', 'abc')
extra_kwargs = {
'image': {
'read_only': True,
},
'publish': { # 系统原有的外键字段,要留给反序列化过程使用,序列化外键内容,用@property自定义
'write_only': True,
},
'authors': {
'write_only': True,
},
}
# 需求:内外传参
# 1)在钩子函数中,获得请求请求对象request 视图类 => 序列化类
# 2)序列化钩子校验过程中,也会产生一些数据,这些数据能不能传递给外界使用:序列化类 => 视图类
# 序列化类的context属性,被视图类与序列化类共享
def validate(self, attrs):
print(self.context) # 可以获得视图类在初始化序列化对象时传入的context
# self.context.update({'a': 10}) # 序列化类内部更新context,传递给视图类
return attrs
views.py
from rest_framework.views import APIView
from . import models, serializers
from .response import APIResponse
# 六个必备接口:单查、群查、单增、单删、单整体改(了解)、单局部改
# 四个额外接口:群增、群删、群整体改、群局部改
class BookAPIView(APIView):
# 单查群查
"""
单查:接口:/books/(pk)/
群查:接口:/books/
"""
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
serializer = serializers.BookModelSerializer(instance=obj)
return APIResponse(result=serializer.data)
else:
queryset = models.Book.objects.filter(is_delete=False).all()
serializer = serializers.BookModelSerializer(instance=queryset, many=True)
return APIResponse(results=serializer.data)
# 单增群增
"""
单增:接口:/books/ 数据:dict
群增:接口:/books/ 数据:list
"""
def post(self, request, *args, **kwargs):
# 如何区别单增群增:request.data是{}还是[]
if not isinstance(request.data, list):
# 单增
serializer = serializers.BookModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True) # 如果校验失败,会直接抛异常,返回给前台
obj = serializer.save()
# 为什么要将新增的对象重新序列化给前台:序列化与反序列化数据不对等
return APIResponse(result=serializers.BookModelSerializer(obj).data, http_status=201)
else:
# 群增
serializer = serializers.BookModelSerializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True) # 如果校验失败,会直接抛异常,返回给前台
objs = serializer.save()
# 为什么要将新增的对象重新序列化给前台:序列化与反序列化数据不对等
return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data, http_status=201)
# 单删群删
"""
单删:接口:/books/(pk)/
群删:接口:/books/ 数据:[pk1, ..., pkn]
"""
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
pks = [pk] # 将单删伪装成群删一条
else:
pks = request.data # 群删的数据就是群删的主键们
try: # request.data可能提交的是乱七八糟的数据,所以orm操作可能会异常
rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
except:
return APIResponse(1, '数据有误')
if rows: # 只要有受影响的行,就代表删除成功
return APIResponse(0, '删除成功')
return APIResponse(2, '删除失败')
# 单整体改群整体改
"""
单整体改:接口:/books/(pk)/ 数据:dict
群整体改:接口:/books/ 数据:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]}
"""
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk: # 单
try:
instance = models.Book.objects.get(is_delete=False, pk=pk)
except:
return APIResponse(1, 'pk error', http_status=400)
# 序列化类同时赋值instance和data,代表用data重新更新instance => 修改
serializer = serializers.BookModelSerializer(instance=instance, data=request.data)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
return APIResponse(result=serializers.BookModelSerializer(obj).data)
else: # 群
""" 分析request.data数据 [{'pk':1, 'name': '', 'publish': 1, 'authors': [1, 2]}, ...]
1)从 request.data 中分离出 pks 列表
2)pks中存放的pk在数据库中没有对应数据,或者对应的数据已经被删除了,这些不合理的pk要被剔除
3)pks最终转换得到的 objs 列表长度与 request.data 列表长度不一致,就是数据有误
"""
pks = []
try: # 只要不是要求的标准数据,一定会在下方四行代码某一行抛出异常
for dic in request.data:
pks.append(dic.pop('pk'))
objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
assert len(objs) == len(request.data) # 两个列表长度必须一致
except:
return APIResponse(1, '数据有误', http_status=400)
serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True)
serializer.is_valid(raise_exception=True)
objs = serializer.save()
return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)
# 单局部改群局部改
"""
单局部改:接口:/books/(pk)/ 数据:dict
群局部改:接口:/books/ 数据:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]}
"""
def patch(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk: # 单
try:
instance = models.Book.objects.get(is_delete=False, pk=pk)
except:
return APIResponse(1, 'pk error', http_status=400)
# partial=True就是将所有反序列化字段的 required 设置为 False(提供就校验,不提供不校验)
serializer = serializers.BookModelSerializer(instance=instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
obj = serializer.save()
return APIResponse(result=serializers.BookModelSerializer(obj).data)
else: # 群
pks = []
try: # 只要不是要求的标准数据,一定会在下方三行代码某一行抛出异常
for dic in request.data:
pks.append(dic.get('pk'))
objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
assert len(objs) == len(request.data) # 两个列表长度必须一致
except:
return APIResponse(1, '数据有误', http_status=400)
serializer = serializers.BookModelSerializer(
instance=objs,
data=request.data,
many=True,
partial=True,
context={'request': request} # 初始化时,对context赋值,将视图类中数据传递给序列化类
)
serializer.is_valid(raise_exception=True)
objs = serializer.save()
print(serializer.context) # 在完成序列化类校验后,可以重新拿到序列化类内部对context做的值更新
return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)
视图家族
"""
视图基类:APIView、GenericAPIView
视图工具类:mixins包下的五个类(六个方法)
工具视图类:generics包下的所有GenericAPIView的子类
视图集:viewsets包下的类
"""
""" GenericAPIView基类(基本不会单独使用,了解即可,但是是高级视图类的依赖基础)
1)GenericAPIView继承APIView,所有APIView子类写法在继承GenericAPIView时可以保持一致
2)GenericAPIView给我们提供了三个属性 queryset、serializer_class、lookup_field
3)GenericAPIView给我们提供了三个方法 get_queryset、get_serializer、get_obj
"""
""" mixins包存放了视图工具类(不能单独使用,必须配合GenericAPIView使用)
CreateModelMixin:单增工具类
create方法
ListModelMixin:群查工具类
list方法
RetrieveModelMixin:单查工具类
retrieve方法
UpdateModelMixin:单整体局部改工具类
update方法
DestroyModelMixin:单删工具类
destory方法
"""
""" generics包下的所有GenericAPIView的子类(就是继承GenericAPIView和不同mixins下的工具类的组合)
1)定义的视图类,继承generics包下已有的特点的GenericAPIView子类,可以在只初始化queryset和serializer_class两个类属性后,就获得特定的功能
2)定义的视图类,自己手动继承GenericAPIView基类,再任意组合mixins包下的一个或多个工具类,可以实现自定义的工具视图类,获得特定的功能或功能们
注:
i)在这些模式下,不能实现单查群查共存(可以加逻辑区分,也可以用视图集知识)
ii)DestroyModelMixin工具类提供的destory方法默认是从数据库中删除数据,所以一般删除数据的需求需要自定义逻辑
"""
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
# ...
url(r'^v1/books/$', views.BookV1APIView.as_view()),
url(r'^v1/books/(?P<pk>d+)/$', views.BookV1APIView.as_view()),
url(r'^v2/books/$', views.BookV2APIView.as_view()),
url(r'^v2/books/(?P<pk>d+)/$', views.BookV2APIView.as_view()),
url(r'^v3/books/$', views.BookV3APIView.as_view()),
url(r'^v3/books/(?P<pk>d+)/$', views.BookV3APIView.as_view()),
]
views.py
# ----------------------------- 过渡写法:了解 -----------------------------
from rest_framework.generics import GenericAPIView
class BookV1APIView(GenericAPIView):
# 将数据和序列化提示为类属性,所有的请求方法都可以复用
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
lookup_field = 'pk' # 可以省略,默认是pk,与url有名分组对应的
# 群查
def get(self, request, *args, **kwargs):
# queryset = models.Book.objects.filter(is_delete=False).all() # => 方法+属性两行代码
queryset = self.get_queryset()
# serializer = serializers.BookModelSerializer(instance=queryset, many=True) # => 方法+属性两行代码
serializer = self.get_serializer(instance=queryset, many=True)
return APIResponse(results=serializer.data)
# 单查
# def get(self, request, *args, **kwargs):
# obj = self.get_object()
# serializer = self.get_serializer(obj)
# return APIResponse(results=serializer.data)
# 单增
def post(self, request, *args, **kwargs):
# serializer = serializers.BookModelSerializer(data=request.data)
serializer = self.get_serializer(data=request.data) # 同样的步骤多了,好处就来了
serializer.is_valid(raise_exception=True)
obj = serializer.save()
return APIResponse(result=self.get_serializer(obj).data, http_status=201)
# ----------------------------- 过渡写法:了解 -----------------------------
from rest_framework.generics import GenericAPIView
from rest_framework import mixins
class BookV2APIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.CreateModelMixin):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
# 单查
def get(self, request, *args, **kwargs):
# obj = self.get_object()
# serializer = self.get_serializer(obj)
# return APIResponse(results=serializer.data)
# return self.retrieve(request, *args, **kwargs)
response = self.retrieve(request, *args, **kwargs)
return APIResponse(result=response.data)
# 单增
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
# ----------------------------- 开发写法:常用 -----------------------------
from rest_framework.generics import RetrieveAPIView
class BookV3APIView(RetrieveAPIView):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
# 单查
pass
六、内容大纲
"""
1)视图集与路由组件(开发最常用、最高级)
2)三大认证原理
3)RBAC认证规则
4)jwt认证规则
5)drf的三大认证实现
"""
复习
"""
1)序列化类初始化各参数含义:
instance:被赋值对象
data:被赋值数据
many:表示操作的对象或数据是否是多个
partial:修改校验字段均为选填
context:视图类与序列化类间传参
查:instance | instance, many
增:data
改:instance, data | instance, data, partial
删:orm操作
2)覆盖外键字段(认证规则)
字段 = serializers.PrimaryKeyRelatedField(queryset)
字段 = serializers.PrimaryKeyRelatedField(read_only)
字段 = serializers.PrimaryKeyRelatedField(queryset, write_only)
3)十大接口:
群增群改需要ListSerializer类辅助,在ModelSerializer的Meta list_serializer_class配置中建立关联,create方法可以继承,update方法需要重写
4)视图家族:
APIView:基类
GenericAPIView:基类
i)继承APIView的,所以拥有APIView的全部
ii)三个类属性:queryset、serializer_class、lookup_field
iii)三个方法:get_queryset、get_serializer、get_object
过渡:单独继承GenericAPIView类的视图类,需要自己定义请求方法如get,还需要属性方法体完成请求
mixins包:工具类 - eg:RetrieveModelMixin:retrieve
retrieve、list、create、update、partial_update、destory
过渡:因为上方六个方法体都是依赖与GenericAPIView类的,所以必须配合GenericAPIView类使用
generics包:工具视图类
i)系统完成的GenericAPIView与mixins包下工具类的不同组合
只需要配置三个类属性
ii)自己完成GenericAPIView与mixins包下工具类的不同组合
需要配置三个类属性,需要书写请求方法,如post,内部直接调用self.create方法
iii)自己继承一堆generics包下的工具视图,完成组合
只需要配置三个类属性,但是单查、群查不能共存
"""
准备工作
models.py
from django.db import models
# 基类:是抽象的(不会完成数据库迁移),目的是提供共有字段的
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
updated_time = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True # 必须完成该配置
class Book(BaseModel):
name = models.CharField(max_length=64)
price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
image = models.ImageField(upload_to='img', default='img/default.png')
publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
@property # @property字段默认就是read_only,且不允许修改
def publish_name(self):
return self.publish.name
@property # 自定义序列化过程
def author_list(self):
temp_author_list = []
for author in self.authors.all():
author_dic = {
"name": author.name
}
try:
author_dic['phone'] = author.detail.phone
except:
author_dic['phone'] = ''
temp_author_list.append(author_dic)
return temp_author_list
class Publish(BaseModel):
name = models.CharField(max_length=64)
class Author(BaseModel):
name = models.CharField(max_length=64)
class AuthorDetail(BaseModel):
phone = models.CharField(max_length=11)
author = models.OneToOneField(to=Author, related_name='detail', db_constraint=False, on_delete=models.CASCADE)
serializers.py
from rest_framework import serializers
from . import models
# 只有在资源需要提供群改,才需要定义ListSerializer,重写update方法
class BookListSerializer(serializers.ListSerializer):
def update(self, queryset, validated_data_list):
return [
self.child.update(queryset[index], validated_data) for index, validated_data in enumerate(validated_data_list)
]
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = BookListSerializer
model = models.Book
fields = ['name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list']
extra_kwargs = {
'publish': {
'write_only': True
},
'authors': {
'write_only': True
}
}
基于GenericAPIView的十大接口
views.py
# 十大接口:
# 1)单查、群查、单增、单整体改、单局部改都可以直接使用
# 2)单删不能直接使用,因为默认提供的功能是删除数据库数据,不是我们自定义is_delete字段值修改
# 3)除了群查以为的接口,都要自己来实现
# 注:给序列化类context赋值{'request': request},序列化类就可以自动补全后台图片链接
from rest_framework.generics import GenericAPIView
from rest_framework import mixins
from . import models, serializers
from rest_framework.response import Response
class BookV1APIView(GenericAPIView,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.UpdateModelMixin):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
def get(self, request, *args, **kwargs):
if 'pk' in kwargs:
return self.retrieve(request, *args, **kwargs) # 单查
# queryset = models.Book.objects.filter(is_delete=False).all()
# 注:给序列化类context赋值{'request': request},序列化类就可以自动补全后台图片链接
# serializer = serializers.BookModelSerializer(queryset, many=True, context={'request': request})
# return Response(serializer.data)
return self.list(request, *args, **kwargs) # 群查
def post(self, request, *args, **kwargs):
if not isinstance(request.data, list):
return self.create(request, *args, **kwargs)
serializer = self.get_serializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=201, headers=headers)
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
pks = [pk]
else:
pks = request.data
try:
rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
except:
return Response(status=400)
if rows:
return Response(status=204)
return Response(status=400)
def put(self, request, *args, **kwargs):
if 'pk' in kwargs:
return self.update(request, *args, **kwargs)
pks = []
try:
for dic in request.data:
pks.append(dic.pop('pk'))
objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
assert len(objs) == len(request.data)
except:
return Response(status=400)
serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True)
serializer.is_valid(raise_exception=True)
objs = serializer.save()
return Response(serializers.BookModelSerializer(objs, many=True).data)
def patch(self, request, *args, **kwargs):
if 'pk' in kwargs:
return self.partial_update(request, *args, **kwargs)
pks = []
try:
for dic in request.data:
pks.append(dic.pop('pk'))
objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
assert len(objs) == len(request.data)
except:
return Response(status=400)
serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True, partial=True)
serializer.is_valid(raise_exception=True)
objs = serializer.save()
return Response(serializers.BookModelSerializer(objs, many=True).data)
基于generics包下工具视图类的六大基础接口
views.py
# 六大基础接口
# 1)直接继承generics包下的工具视图类,可以完成六大基础接口
# 2)单查群查不能共存
# 3)单删一般会重写
from . import models, serializers
from rest_framework.response import Response
from rest_framework import generics
class BookV2APIView(generics.ListAPIView,
generics.RetrieveAPIView,
generics.CreateAPIView,
generics.UpdateAPIView,
generics.DestroyAPIView):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
def get(self, request, *args, **kwargs):
if 'pk' in kwargs:
return self.retrieve(request, *args, **kwargs)
return self.list(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True)
return Response(status=204)
视图集
导入
""" ViewSetMixin类存在理由推到
1)工具视图类,可以完成应付六大基础接口,唯一缺点是单查与群查接口无法共存
(只配置queryset、serializer_class、lookup_field)
2)不能共存的原因:RetrieveAPIView和ListAPIView都是get方法,不管带不带pk的get请求,只能映射给一个get方法
3)能不能修改映射关系:
比如将/books/的get请求映射给list方法,
将/books/(pk)/的get请求映射给retrieve方法,
甚至可以随意自定义映射关系
"""
""" 继承视图集的视图类的as_view都是走ViewSetMixin类的,核心源码分析
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
# ...
# 没有actions,也就是调用as_view()没有传参,像as_view({'get': 'list'})
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
# ...
# 请求来了走view函数
def view(request, *args, **kwargs):
# ...
# 解析actions,修改 请求分发 - 响应函数 映射关系
self.action_map = actions
for method, action in actions.items(): # method:get | action:list
handler = getattr(self, action) # 从我们视图类用action:list去反射,所以handler是list函数,不是get函数
setattr(self, method, handler) # 将get请求对应list函数,所以在dispath分发请求时,会将get请求分发给list函数
# ...
# 通过视图类的dispatch完成最后的请求分发
return self.dispatch(request, *args, **kwargs)
# ...
# 保存actions映射关系,以便后期使用
view.actions = actions
return csrf_exempt(view)
"""
核心
"""
视图集的使用总结
1)可以直接继承ModelViewSet,实现六大继承接口(是否重写destroy方法,或其他方法,根据需求决定)
2)可以直接继承ReadOnlyModelViewSet,实现只读需求(只有单查群查)
3)继承ViewSet类,与Model类关系不是很密切的接口:登录的post请求,是查询操作;短信验证码发生接口,借助第三方平台
4)继承GenericViewSet类,就代表要配合mixins包,自己完成任意组合
5)继承以上4个视图集任何一个,都可以与路由as_view({映射})配合,完成自定义请求响应方法
"""
案例
urls.py
url(r'^v3/books/$', views.BookV3APIView.as_view(
{'get': 'list', 'post': 'create', 'delete': 'multiple_destroy'}
)),
url(r'^v3/books/(?P<pk>d+)/$', views.BookV3APIView.as_view(
{'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}
)),
views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
class BookV3APIView(ModelViewSet):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
# 可以在urls.py中as_view({'get': 'my_list'})自定义请求映射
def my_list(self, request, *args, **kwargs):
return Response('ok')
# 需要完成字段删除,不是重写delete方法,而是重写destroy方法
def destroy(self, request, *args, **kwargs):
pk = kwargs.get('pk')
models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True)
return Response(status=204)
# 群删接口
def multiple_destroy(self, request, *args, **kwargs):
try:
models.Book.objects.filter(is_delete=False, pk__in=request.data).update(is_delete=True)
except:
return Response(status=400)
return Response(status=204)
路由组件:必须配合视图集使用
urls.py
from django.conf.urls import url, include
from . import views
# 路由组件,必须配合视图集使用
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
# 以后就写视图集的注册即可:BookV3APIView和BookV4APIView都是视图集
router.register('v3/books', views.BookV3APIView, 'book')
router.register('v4/books', views.BookV4APIView, 'book')
urlpatterns = [
url('', include(router.urls))
]
views.py
from rest_framework.viewsets import ReadOnlyModelViewSet
class BookV4APIView(ReadOnlyModelViewSet):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
自定义路由组件(了解)
router.py
from rest_framework.routers import SimpleRouter as DrfSimpleRouter
from rest_framework.routers import Route, DynamicRoute
class SimpleRouter(DrfSimpleRouter):
routes = [
# List route.
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={
'get': 'list',
'post': 'create', # 注:群增只能自己在视图类中重写create方法,完成区分
'delete': 'multiple_destroy', # 新增:群删
'put': 'multiple_update', # 新增:群整体改
'patch': 'multiple_partial_update' # 新增:群局部改
},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
# Dynamically generated list routes. Generated using
# @action(detail=False) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=False,
initkwargs={}
),
# Detail route.
Route(
url=r'^{prefix}/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
# Dynamically generated detail routes. Generated using
# @action(detail=True) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
),
]
上传文件接口
urls.py
from django.conf.urls import url, include
from . import views
# 路由组件,必须配合视图集使用
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
# /books/image/(pk) 提交 form-data:用image携带图片
router.register('books/image', views.BookUpdateImageAPIView, 'book')
urlpatterns = [
url('', include(router.urls))
]
serializers.py
class BookUpdateImageModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ['image']
views.py
# 上次文件 - 修改头像 - 修改海报
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
class BookUpdateImageAPIView(GenericViewSet, mixins.UpdateModelMixin):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookUpdateImageModelSerializer
权限
models.py
from django.db import models
# RBAC - Role-Based Access Control
# Django的 Auth组件 采用的认证规则就是RBAC
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
mobile = models.CharField(max_length=11, unique=True)
def __str__(self):
return self.username
class Book(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return self.name
settings.py
# 自定义User表,要配置
AUTH_USER_MODEL = 'api.User'
admin.py
from django.contrib import admin
from . import models
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
# 自定义User表后,admin界面管理User类
class UserAdmin(DjangoUserAdmin):
# 添加用户课操作字段
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'password1', 'password2', 'is_staff', 'mobile', 'groups', 'user_permissions'),
}),
)
# 展示用户呈现的字段
list_display = ('username', 'mobile', 'is_staff', 'is_active', 'is_superuser')
admin.site.register(models.User, UserAdmin)
admin.site.register(models.Book)
admin.site.register(models.Car)
导入
# 1)像专门做人员权限管理的系统(CRM系统)都是公司内部使用,所以数据量都在10w一下,一般效率要求也不是很高
# 2)用户量极大的常规项目,会分两种用户:前台用户(三大认证) 和 后台用户(BRAC来管理)
# 结论:没有特殊要求的Django项目可以直接采用Auth组件的权限六表,不需要自定义六个表,也不需要断开表关系,但可能需要自定义User表
做项目是否要分表管理前后台用户
"""
1)是否需要分表
答案:不需要
理由:前后台用户共存的项目,后台用户量都是很少;做人员管理的项目,基本上都是后台用户;前后台用户量都大的会分两个项目处理
2)用户权限六表是否需要断关联
答案:不需要
理由:前台用户占主导的项目,几乎需求只会和User一个表有关;后台用户占主导的项目,用户量不会太大
3)Django项目有没有必须自定义RBAC六表
答案:不需要
理由:auth组件功能十分强大且健全(验证密码,创建用户等各种功能);admin、xadmin、jwt、drf-jwt组件都是依赖auth组件的(自定义RBAC六表,插件都需要自定义,成本极高)
"""
权限六表:RBAC - Role-Based Access Control
三表
六表
三大认证规则
总结
# 1)后台用户对各表操作,是后台项目完成的,我们可以直接借助admin后台项目(Django自带的)
# 2)后期也可以用xadmin框架来做后台用户权限管理
# 3)前台用户的权限管理如何处理
# 定义了一堆数据接口的视图类,不同的登录用户是否能访问这些视图类,能就代表有权限,不能就代表无权限
# 前台用户权限用drf框架的 三大认证
# 注:前台用户权限会基于 jwt 认证
课程小结
"""
1)基于GenericAPIView类 加 mixins包 实现十大接口
class MyAPIView(GenericAPIView, mixins.RetrieveModelMixin, ...):
# 配置queryset、serializer_class、lookup_field
# 要自己定义get、post等方法,内部调用retrieve、create方法
2)基于 generics包 实现六大基础接口
class MyAPIView(generics.RetrieveAPIView, ...):
# 配置queryset、serializer_class、lookup_field
# 重写get处理单查群查共存即可
# delete方法是否重写看需求
3)视图集(重点)
i)ViewSetMixin类:重写as_view方法
作用:as_view({"get": "list"})来自定义请求与响应的映射关系
ii)两个视图集基类:
ViewSet:与Model关系不是特别紧密
GenericViewSet:与Model关系特别紧密
iii)两个GenericViewSet的子类:
ModelViewSet:六大基础接口共存
ReadOnlyModelViewSet:只读接口
注:都只需要配置queryset、serializer_class、lookup_field;根据需求决定是否重写某些方法
iv)自己用两个视图集基类与mixins包形成自定义组合
4)上传图片:前台提交form-data,类型选择文件类型,后台用request.data和request.FILES都可以访问
5)路由:
from rest_framework.routers import SimpleRouter (默认只提供了六大基础接口的配置)
router = SimpleRouter()
# 注册视图集
router.register('books', views.BookViewSet, 'book')
urlpatterns = [
url('', include(router.urls))
]
6)三大认证规则(后期分析)
7)RBAC认证六表
8)admin管理后台用户权限
AdminUser辅助自定义User表,完成admin系统的注册
9)前台用户访问接口的权限交给 三大认证
"""
七、内容大纲
"""
1)三大认证组件的书写方式(了解如何自定义三大认证类)
2)jwt认证规则(重中之重):json web token
3)认证源码(了解)
4)异常组件 + 日志组件
"""
复习
"""
1)APIView的请求生命周期
as_view():路由配置,禁用csrf
dispatch():请求分发,请求解析、三大认证、异常处理、响应渲染
request._request、request.query_params、request.data、request.META(请求头等诸多配置)
2)序列化组件
i)视图类中使用序列化
serializer = UserModelSerializer(
instance="对象(们)",
data="数据(们)",
many=False|True, # 与数据或对象配套,代表操作的是否是多个
partial=False|True, # 运用在局部修改中,所以校验可以选填(required=False)
context={'request': request} # 视图、序列化传参
)
serializer.is_valid(raise_exception=False|True) # 校验
serializer.save() # 入库
serializer.data # 序列化后的数据
serializer.errors # 校验失败的信息
ii)Meta配置类中的配置
model:关联的Model类
fields:所以序列化与反序列化字段
extra_kwargs:简单的校验规则
exclude:除某些字段
depth:连表深度
iii)自定义校验规则
validate_字段名:局部校验钩子
validate:全局校验钩子
iv)入库方法
create:增数据入库
update:改数据入库
后期这两个方法可能会被重写:涉及一些字段加密解密处理、不仅仅是单表入库操作
user = User.create()
UserDetail.create(user_id=user.id)
v)自定义字段
@property:在model类中自定义序列化字段
自定义字段 = serializers.字段类型(write_only=True, 其他规则):在serializer类中的自定义反序列化字段
系统字段 = serializers.字段类型(规则):覆盖字段,可以设置为只读、只写、可读可写
外键字段的字段类型:PrimaryKeyRalatedField
vi)如果有群改操作
自定义ListSerializer子类,重写update方法
在相关ModelSerializer中用list_serializer_class配置进行关联
重(难)点:
i)在视图类中初始化序列化类对象是的参数选择
ii)指定好所以的序列化与反序列化字段
iii)全局校验钩子的逻辑(难点)
iv)是否要重写入库方法(难点)
3)视图家族
两个基类:APIView、GenericAPIVAPIView
APIView:禁用csrf、解析、认证、渲染...
GenericAPIVAPIView:禁用csrf、解析、认证、渲染... + 三个属性三个方法
工具类:mixin包下的五个类,使用GenericAPIVAPIView的三个属性三个方法(过渡)
工具视图类:mixin包下的类 + GenericAPIVAPIView 的组合形成的类
配置三个属性 + 是否需要重写get、post等方法
视图集:ViewSetMixin重写as_view方法,可以自定义 请求方式 - 响应函数 的映射关系
配置三个属性 + mixin包下的类 + GenericViewSet
ViewSet
4)路由组件:SimpleRouter
5)权限认证
i)RBAC:Role-Based Access Control - 基于角色权限的认证规则
权限六表:用户表、分组表、权限表 + 三个关系表
ii)auth六表是否需要重写或断关联
不需要
iii)admin|xadmin|自定义 提供的后台管理项目
iv)前台接口权限:三大认证
"""
项目初始化
settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 自定义用户表
AUTH_USER_MODEL = 'api.User'
主urls.py
from django.conf.urls import url, include
from django.contrib import admin
from django.views.static import serve
from django.conf import settings
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 路由分发
url(r'^api/', include('api.urls')),
url(r'^media/(?P<path>).*', serve, {'document_root': settings.MEDIA_ROOT}),
]
子urls.py
from django.conf.urls import url, include
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
from . import views
# 路由注册
# router.register('register', views.RegisterViewSet, 'register')
urlpatterns = [
url('', include(router.urls))
]
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
mobile = models.CharField(max_length=11, unique=True, verbose_name='移动电话')
icon = models.ImageField(upload_to='icon', default='icon/default.png', verbose_name='头像')
class Meta:
db_table = 'o_user'
verbose_name_plural = '用户表'
def __str__(self):
return self.username
class Book(models.Model):
name = models.CharField(max_length=64, verbose_name='书名')
class Meta:
db_table = 'o_book'
verbose_name_plural = '书表'
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=64, verbose_name='车名')
class Meta:
db_table = 'o_car'
verbose_name_plural = '车表'
def __str__(self):
return self.name
注册接口
urls.py
router.register('register', views.RegisterViewSet, 'register')
views.py
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework import mixins
from . import models, serializers
class RegisterViewSet(GenericViewSet, mixins.CreateModelMixin):
queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializers.RegisterSerializer
serializers.py
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from . import models
class RegisterSerializer(serializers.ModelSerializer):
re_password = serializers.CharField(write_only=True, min_length=8, max_length=18)
class Meta:
model = models.User
fields = ('username', 'password', 're_password', 'mobile')
extra_kwargs = {
'password': {
'write_only': True,
'min_length': 8,
'max_length': 18
}
}
# username和mobile可以自定义局部钩子校验(省略)
def validate(self, attrs):
password = attrs.get('password')
re_password = attrs.pop('re_password')
if password != re_password:
raise ValidationError({'re_password': 'password confirm error'})
return attrs
# 需要重写create,创建用户需要密文
def create(self, validated_data):
return models.User.objects.create_user(**validated_data)
用户中心接口
urls.py
router.register('user/center', views.UserCenterViewSet, 'center')
views.py
class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializers.UserCenterSerializer
serializers.py
class UserCenterSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ('username', 'mobile', 'icon', 'email')
图书资源接口
urls.py
router.register('books', views.BookViewSet, 'book')
views.py
class BookViewSet(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = serializers.BookSerializer
serializers.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ('name', )
认证组件
重点
"""
1)认证规则
2)如何自定义认证类
3)我们一般不需要自定义认证类,在settings中全局配置第三方 jwt 认证组件提供的认证类即可
"""
自定义认证类
"""
1)自定义认证类,继承 BaseAuthentication 类
2)必须重写 authenticate(self, request) 方法
没有认证信息,返回None:匿名用户(游客) => 匿名用户request.user也有值,就是"匿名对象(Anonymous)"
有认证信息,且通过,返回(user, token):合法用户 => user对象会存到request.user中
有认证信息,不通过,抛异常:非法用户
"""
权限组件
重点
"""
1)权限规则
2)如何自定义权限类
3)我们一般在视图类中局部配置drf提供的权限类,但是也会自定义权限类完成局部配置
"""
自定义权限类
"""
1)自定义权限类,继承 BasePermission 类
2)必须重写 has_permission(self, request, view): 方法
设置权限条件,条件通过,返回True:有权限
设置权限条件,条件失败,返回False:有权限
3)drf提供的权限类:
AllowAny:匿名与合法用户都可以
IsAuthenticated:必须登录,只有合法用户可以
IsAdminUser:必须是admin后台用户
IsAuthenticatedOrReadOnly:匿名只读,合法用户无限制
"""
jwt认证示意图
""" jwt优势
1)没有数据库写操作,高效
2)服务器不存token,低耗
3)签发校验都是算法,集群
"""
jwt认证算法:签发与校验
"""
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
"user_id": 1,
...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
"head": "头的加密字符串",
"payload": "体的加密字符串",
"secret_key": "安全码"
}
"""
签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
"""
1)用基本信息存储json字典,采用base64算法加密得到 头字符串
2)用关键信息存储json字典,采用base64算法加密得到 体字符串
3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串
账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
"""
校验:根据客户端带token的请求 反解出 user 对象
"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""
drf项目的jwt认证开发流程(重点)
"""
1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
注:登录接口需要做 认证 + 权限 两个局部禁用
"""
drf-jwt框架基本使用
安装(终端)
>: pip install djangorestframework-jwt
签发token(登录接口):视图类已经写好了,配置一下路由就行(urls.py)
# api/urls.py
urlpatterns = [
# ...
url('^login/$', ObtainJSONWebToken.as_view()),
]
# Postman请求:/api/login/,提供username和password即可
校验token(认证组件):认证类已经写好了,全局配置一下认证组件就行了(settings.py)
# drf-jwt的配置
import datetime
JWT_AUTH = {
# 配置过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
# drf配置(把配置放在最下方)
REST_FRAMEWORK = {
# 自定义三大认证配置类们
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_jwt.authentication.JSONWebTokenAuthentication'],
# 'DEFAULT_PERMISSION_CLASSES': [],
# 'DEFAULT_THROTTLE_CLASSES': [],
}
设置需要登录才能访问的接口进行测试(views.py)
from rest_framework.permissions import IsAuthenticated
class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
# 设置必须登录才能访问的权限类
permission_classes = [IsAuthenticated, ]
queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializers.UserCenterSerializer
测试访问登录认证接口(Postman)
"""
1)用 {"username": "你的用户", "password": "你的密码"} 访问 /api/login/ 接口等到 token 字符串
2)在请求头用 Authorization 携带 "jwt 登录得到的token" 访问 /api/user/center/1/ 接口访问个人中心
"""
今日小结
"""
1)注册接口、个人中心接口、图书接口
2)认证组件
i)如何自定义认证类:继承谁、实现什么方法、方法体逻辑就是认证规则
ii)认证规则:游客、合法用户、方法用户
3)权限组件
i)如何自定义权限类:继承谁、实现什么方法、方法体逻辑就是权限规则
ii)权限规则:有权限、无权限
4)认证权限配置:局部 > 全局 > 默认
认证一般都是全局配置,所有登录注册接口要局部禁用
权限一般做局部配置 => 电商类项目,90%以上接口游客可以访问
权限一般做全局配置 => 邮箱项目,几乎所有接口都需要登录才能访问
5)jwt签发校验规则
6)drf-jwt框架的简单使用
"""
八、复习
"""
1)APIView的请求生命周期
as_view():路由配置,禁用csrf
dispatch():请求分发,请求解析、三大认证、异常处理、响应渲染
request._request、request.query_params、request.data、request.META(请求头等诸多配置)
2)序列化组件
i)视图类中使用序列化
serializer = UserModelSerializer(
instance="对象(们)",
data="数据(们)",
many=False|True, # 与数据或对象配套,代表操作的是否是多个
partial=False|True, # 运用在局部修改中,所以校验可以选填(required=False)
context={'request': request} # 视图、序列化传参
)
serializer.is_valid(raise_exception=False|True) # 校验
serializer.save() # 入库
serializer.data # 序列化后的数据
serializer.errors # 校验失败的信息
ii)Meta配置类中的配置
model:关联的Model类
fields:所以序列化与反序列化字段
extra_kwargs:简单的校验规则
exclude:除某些字段
depth:连表深度
iii)自定义校验规则
validate_字段名:局部校验钩子
validate:全局校验钩子
iv)入库方法
create:增数据入库
update:改数据入库
后期这两个方法可能会被重写:涉及一些字段加密解密处理、不仅仅是单表入库操作
user = User.create()
UserDetail.create(user_id=user.id)
v)自定义字段
@property:在model类中自定义序列化字段
自定义字段 = serializers.字段类型(write_only=True, 其他规则):在serializer类中的自定义反序列化字段
系统字段 = serializers.字段类型(规则):覆盖字段,可以设置为只读、只写、可读可写
外键字段的字段类型:PrimaryKeyRalatedField
vi)如果有群改操作
自定义ListSerializer子类,重写update方法
在相关ModelSerializer中用list_serializer_class配置进行关联
重(难)点:
i)在视图类中初始化序列化类对象是的参数选择
ii)指定好所以的序列化与反序列化字段
iii)全局校验钩子的逻辑(难点)
iv)是否要重写入库方法(难点)
3)视图家族
两个基类:APIView、GenericAPIVAPIView
APIView:禁用csrf、解析、认证、渲染...
GenericAPIVAPIView:禁用csrf、解析、认证、渲染... + 三个属性三个方法
工具类:mixin包下的五个类,使用GenericAPIVAPIView的三个属性三个方法(过渡)
工具视图类:mixin包下的类 + GenericAPIVAPIView 的组合形成的类
配置三个属性 + 是否需要重写get、post等方法
视图集:ViewSetMixin重写as_view方法,可以自定义 请求方式 - 响应函数 的映射关系
配置三个属性 + mixin包下的类 + GenericViewSet
ViewSet
4)路由组件:SimpleRouter
5)权限认证
i)RBAC:Role-Based Access Control - 基于角色权限的认证规则
权限六表:用户表、分组表、权限表 + 三个关系表
ii)auth六表是否需要重写或断关联
不需要
iii)admin|xadmin|自定义 提供的后台管理项目
iv)前台接口权限:三大认证
"""
项目初始化
settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 自定义用户表
AUTH_USER_MODEL = 'api.User'
主urls.py
from django.conf.urls import url, include
from django.contrib import admin
from django.views.static import serve
from django.conf import settings
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 路由分发
url(r'^api/', include('api.urls')),
url(r'^media/(?P<path>).*', serve, {'document_root': settings.MEDIA_ROOT}),
]
子urls.py
from django.conf.urls import url, include
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
from . import views
# 路由注册
# router.register('register', views.RegisterViewSet, 'register')
urlpatterns = [
url('', include(router.urls))
]
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
mobile = models.CharField(max_length=11, unique=True, verbose_name='移动电话')
icon = models.ImageField(upload_to='icon', default='icon/default.png', verbose_name='头像')
class Meta:
db_table = 'o_user'
verbose_name_plural = '用户表'
def __str__(self):
return self.username
class Book(models.Model):
name = models.CharField(max_length=64, verbose_name='书名')
class Meta:
db_table = 'o_book'
verbose_name_plural = '书表'
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=64, verbose_name='车名')
class Meta:
db_table = 'o_car'
verbose_name_plural = '车表'
def __str__(self):
return self.name
注册接口
urls.py
router.register('register', views.RegisterViewSet, 'register')
views.py
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework import mixins
from . import models, serializers
class RegisterViewSet(GenericViewSet, mixins.CreateModelMixin):
queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializers.RegisterSerializer
serializers.py
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from . import models
class RegisterSerializer(serializers.ModelSerializer):
re_password = serializers.CharField(write_only=True, min_length=8, max_length=18)
class Meta:
model = models.User
fields = ('username', 'password', 're_password', 'mobile')
extra_kwargs = {
'password': {
'write_only': True,
'min_length': 8,
'max_length': 18
}
}
# username和mobile可以自定义局部钩子校验(省略)
def validate(self, attrs):
password = attrs.get('password')
re_password = attrs.pop('re_password')
if password != re_password:
raise ValidationError({'re_password': 'password confirm error'})
return attrs
# 需要重写create,创建用户需要密文
def create(self, validated_data):
return models.User.objects.create_user(**validated_data)
用户中心接口
urls.py
router.register('user/center', views.UserCenterViewSet, 'center')
views.py
class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializers.UserCenterSerializer
serializers.py
class UserCenterSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ('username', 'mobile', 'icon', 'email')
图书资源接口
urls.py
router.register('books', views.BookViewSet, 'book')
views.py
class BookViewSet(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = serializers.BookSerializer
serializers.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ('name', )
认证组件
重点
"""
1)认证规则
2)如何自定义认证类
3)我们一般不需要自定义认证类,在settings中全局配置第三方 jwt 认证组件提供的认证类即可
"""
自定义认证类
"""
1)自定义认证类,继承 BaseAuthentication 类
2)必须重写 authenticate(self, request) 方法
没有认证信息,返回None:匿名用户(游客) => 匿名用户request.user也有值,就是"匿名对象(Anonymous)"
有认证信息,且通过,返回(user, token):合法用户 => user对象会存到request.user中
有认证信息,不通过,抛异常:非法用户
"""
权限组件
重点
"""
1)权限规则
2)如何自定义权限类
3)我们一般在视图类中局部配置drf提供的权限类,但是也会自定义权限类完成局部配置
"""
自定义权限类
"""
1)自定义权限类,继承 BasePermission 类
2)必须重写 has_permission(self, request, view): 方法
设置权限条件,条件通过,返回True:有权限
设置权限条件,条件失败,返回False:有权限
3)drf提供的权限类:
AllowAny:匿名与合法用户都可以
IsAuthenticated:必须登录,只有合法用户可以
IsAdminUser:必须是admin后台用户
IsAuthenticatedOrReadOnly:匿名只读,合法用户无限制
"""
jwt认证示意图
""" jwt优势
1)没有数据库写操作,高效
2)服务器不存token,低耗
3)签发校验都是算法,集群
"""
jwt认证算法:签发与校验
"""
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
"user_id": 1,
...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
"head": "头的加密字符串",
"payload": "体的加密字符串",
"secret_key": "安全码"
}
"""
签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
"""
1)用基本信息存储json字典,采用base64算法加密得到 头字符串
2)用关键信息存储json字典,采用base64算法加密得到 体字符串
3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串
账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
"""
校验:根据客户端带token的请求 反解出 user 对象
"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""
drf项目的jwt认证开发流程(重点)
"""
1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
注:登录接口需要做 认证 + 权限 两个局部禁用
"""
drf-jwt框架基本使用
安装(终端)
>: pip install djangorestframework-jwt
签发token(登录接口):视图类已经写好了,配置一下路由就行(urls.py)
# api/urls.py
urlpatterns = [
# ...
url('^login/$', ObtainJSONWebToken.as_view()),
]
# Postman请求:/api/login/,提供username和password即可
校验token(认证组件):认证类已经写好了,全局配置一下认证组件就行了(settings.py)
# drf-jwt的配置
import datetime
JWT_AUTH = {
# 配置过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
# drf配置(把配置放在最下方)
REST_FRAMEWORK = {
# 自定义三大认证配置类们
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_jwt.authentication.JSONWebTokenAuthentication'],
# 'DEFAULT_PERMISSION_CLASSES': [],
# 'DEFAULT_THROTTLE_CLASSES': [],
}
设置需要登录才能访问的接口进行测试(views.py)
from rest_framework.permissions import IsAuthenticated
class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
# 设置必须登录才能访问的权限类
permission_classes = [IsAuthenticated, ]
queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializers.UserCenterSerializer
测试访问登录认证接口(Postman)
"""
1)用 {"username": "你的用户", "password": "你的密码"} 访问 /api/login/ 接口等到 token 字符串
2)在请求头用 Authorization 携带 "jwt 登录得到的token" 访问 /api/user/center/1/ 接口访问个人中心
"""
小结
"""
1)注册接口、个人中心接口、图书接口
2)认证组件
i)如何自定义认证类:继承谁、实现什么方法、方法体逻辑就是认证规则
ii)认证规则:游客、合法用户、方法用户
3)权限组件
i)如何自定义权限类:继承谁、实现什么方法、方法体逻辑就是权限规则
ii)权限规则:有权限、无权限
4)认证权限配置:局部 > 全局 > 默认
认证一般都是全局配置,所有登录注册接口要局部禁用
权限一般做局部配置 => 电商类项目,90%以上接口游客可以访问
权限一般做全局配置 => 邮箱项目,几乎所有接口都需要登录才能访问
5)jwt签发校验规则
6)drf-jwt框架的简单使用
"""
复习
"""
1)前后台权限管理:
后台管理:基于RBAC(auth模块的六表),用admin|xadmin来管理
前台管理:基于三大认证权限管理,认证采用的是jwt认证,jwt一般也是依赖auth模块的六表
2)认证模块:
继承BaseAuthentication,实现authenticate方法,规则:
没认证信息,返回None:游客
有认证信息,认证成功,返回(user, token):合法用户
有认证信息,认证失败,抛异常:非法用户
3)权限模块:
继承BasePermission,实现has_permission方法,规则:
权限条件判断成立,返回True:有权限
权限条件判断失败,返回Fasle:无权限
4)jwt认证规则:
json web token
组成:header.payload.sign
header:基础信息
payload:核心信息 - 用户主键、客户端设备信息、过期时间(token过期时间,token可刷新过期时间)
sign:安全信息 - 前两段加密结果 + 服务器安全码
优势:
没有数据库写:高效
数据库不存:低耗
只是算法:集群
5)三大认证使用:
i)是否要自定义三大认证类:
认证类采用第三方jwt,所以自己就不写了
权限类drf通过了一些,如果够用,直接使用,不够用会自定义
频率类系统也提供了一些,一般也会自定义
ii)配置:
局部配置:在视图类中初始化类属性:permission_classes
全局配置:在settings中自定义drf的配置:DEFAULT_AUTHENTICATION_CLASSES
"""
"""
1)三大认证
2)异常、日志
3)群查接口:搜索、排序、分页、分类、区间
"""
系统权限类使用
图书接口:游客只读,用户可增删改查权限使用
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class BookViewSet(ModelViewSet):
# 游客只读,用户可增删改查
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = models.Book.objects.all()
serializer_class = serializers.BookSerializer
特殊路由映射的请求
实现用户中心信息自查,不带主键的get请求,走单查逻辑
urls.py
# 用路由组件配置,形成的映射关系是 /user/center/ => list | user/center/(pk)/ => retrieve
# router.register('user/center', views.UserCenterViewSet, 'center')
urlpatterns = [
# ...
# /user/center/ => 单查,不能走路由组件,只能自定义配置映射关系
url('^user/center/$', views.UserCenterViewSet.as_view({'get': 'user_center'})),
]
views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class UserCenterViewSet(GenericViewSet):
permission_classes = [IsAuthenticated, ]
queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializers.UserCenterSerializer
def user_center(self, request, *args, **kwargs):
# request.user就是前台带token,在经过认证组件解析出来的,
# 再经过权限组件IsAuthenticated的校验,所以request.user一定有值,就是当前登录用户
serializer = self.get_serializer(request.user)
return Response(serializer.data)
token刷新机制(了解)
drf-jwt直接提供刷新功能
"""
1)运用在像12306这样极少数安全性要求高的网站
2)第一个token由登录签发
3)之后的所有正常逻辑,都需要发送两次请求,第一次是刷新token的请求,第二次是正常逻辑的请求
"""
settings.py
import datetime
JWT_AUTH = {
# 配置过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=5),
# 是否可刷新
'JWT_ALLOW_REFRESH': True,
# 刷新过期时间
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
urls.py
from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken
urlpatterns = [
url('^login/$', ObtainJSONWebToken.as_view()), # 登录签发token接口
url('^refresh/$', RefreshJSONWebToken.as_view()), # 刷新toekn接口
]
Postman
# 接口:/api/refresh/
# 方法:post
# 数据:{"token": "登录签发的token"}
认证组件项目使用:多方式登录
urls.py
# 自定义登录(重点):post请求 => 查操作(签发token返回给前台) - 自定义路由映射
url('^user/login/$', views.LoginViewSet.as_view({'post': 'login'})),
views.py
# 重点:自定义login,完成多方式登录
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class LoginViewSet(ViewSet):
# 登录接口,要取消所有的认证与权限规则,也就是要做局部禁用操作(空配置)
authentication_classes = []
permission_classes = []
# 需要和mixins结合使用,继承GenericViewSet,不需要则继承ViewSet
# 为什么继承视图集,不去继承工具视图或视图基类,因为视图集可以自定义路由映射:
# 可以做到get映射get,get映射list,还可以做到自定义(灵活)
def login(self, request, *args, **kwargs):
serializer = serializers.LoginSerializer(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
token = serializer.context.get('token')
return Response({"token": token})
serializers.py
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
# 重点:自定义login,完成多方式登录
class LoginSerializer(serializers.ModelSerializer):
# 登录请求,走的是post方法,默认post方法完成的是create入库校验,所以唯一约束的字段,会进行数据库唯一校验,导致逻辑相悖
# 需要覆盖系统字段,自定义校验规则,就可以避免完成多余的不必要校验,如唯一字段校验
username = serializers.CharField()
class Meta:
model = models.User
# 结合前台登录布局:采用账号密码登录,或手机密码登录,布局一致,所以不管账号还是手机号,都用username字段提交的
fields = ('username', 'password')
def validate(self, attrs):
# 在全局钩子中,才能提供提供的所需数据,整体校验得到user
# 再就可以调用签发token算法(drf-jwt框架提供的),将user信息转换为token
# 将token存放到context属性中,传给外键视图类使用
user = self._get_user(attrs)
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
self.context['token'] = token
return attrs
# 多方式登录
def _get_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
import re
if re.match(r'^1[3-9][0-9]{9}$', username):
# 手机登录
user = models.User.objects.filter(mobile=username, is_active=True).first()
elif re.match(r'^.+@.+$', username):
# 邮箱登录
user = models.User.objects.filter(email=username, is_active=True).first()
else:
# 账号登录
user = models.User.objects.filter(username=username, is_active=True).first()
if user and user.check_password(password):
return user
raise ValidationError({'user': 'user error'})
权限组件项目使用:vip用户权限
数据准备
"""
1)User表创建两条数据
2)Group表创建一条数据,name叫vip
3)操作User和Group的关系表,让1号用户属于1号vip组
"""
permissions.py
from rest_framework.permissions import BasePermission
from django.contrib.auth.models import Group
class IsVipUser(BasePermission):
def has_permission(self, request, view):
if request.user and request.user.is_authenticated: # 必须是合法用户
try:
vip_group = Group.objects.get(name='vip')
if vip_group in request.user.groups.all(): # 用户可能不属于任何分组
return True # 必须是vip分组用户
except:
pass
return False
views.py
from .permissions import IsVipUser
class CarViewSet(ModelViewSet):
permission_classes = [IsVipUser]
queryset = models.Car.objects.all()
serializer_class = serializers.CarSerializer
serializers.py
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = models.Car
fields = ('name', )
urls.py
router.register('cars', views.CarViewSet, 'car')
频率组件
重点
"""
1)如何自定义频率类
2)频率校验的规则
3)自定义频率类是最常见的:短信接口一分钟只能发生一条短信
"""
自定义频率类
"""
1)自定义类继承SimpleRateThrottle
2)设置类实现scope,值就是一个字符串,与settings中的DEFAULT_THROTTLE_RATES进行对应
DEFAULT_THROTTLE_RATES就是设置scope绑定的类的频率规则:1/min 就代表一分钟只能访问一次
3)重写 get_cache_key(self, request, view) 方法,指定限制条件
不满足限制条件,返回None:代表对这类请求不进行频率限制
满足限制条件,返回一个字符串(是动态的):代表对这类请求进行频率限制
短信频率限制类,返回 "throttling_%(mobile)s" % {"mobile": 实际请求来的电话}
"""
系统频率类
#1)UserRateThrottle: 限制所有用户访问频率
#2)AnonRateThrottle:只限制匿名用户访问频率
频率组件项目使用:请求方式频率限制
throttles.py
from rest_framework.throttling import SimpleRateThrottle
# 只限制查接口的频率,不限制增删改的频率
class MethodRateThrottle(SimpleRateThrottle):
scope = 'method'
def get_cache_key(self, request, view):
# 只有对get请求进行频率限制
if request.method.lower() not in ('get', 'head', 'option'):
return None
# 区别不同的访问用户,之间的限制是不冲突的
if request.user.is_authenticated:
ident = request.user.pk
else:
# get_ident是BaseThrottle提供的方法,会根据请求头,区别匿名用户,
# 保证不同客户端的请求都是代表一个独立的匿名用户
ident = self.get_ident(request)
return self.cache_format % {'scope': self.scope, 'ident': ident}
settings.py
REST_FRAMEWORK = {
# ...
# 频率规则配置
'DEFAULT_THROTTLE_RATES': {
# 只能设置 s,m,h,d,且只需要第一个字母匹配就ok,m = min = maaa 就代表分钟
'user': '3/min', # 配合drf提供的 UserRateThrottle 使用,限制所有用户访问频率
'anon': '3/min', # 配合drf提供的 AnonRateThrottle 使用,只限制匿名用户访问频率
'method': '3/min',
},
}
views.py
from .permissions import IsVipUser
from .throttles import MethodRateThrottle
class CarViewSet(ModelViewSet):
permission_classes = [IsVipUser]
throttle_classes = [MethodRateThrottle]
queryset = models.Car.objects.all()
serializer_class = serializers.CarSerializer
异常组件项目使用:记录异常信息到日志文件
exception.py
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
def exception_handler(exc, context):
# 只处理客户端异常,不处理服务器异常,
# 如果是客户端异常,response就是可以直接返回给前台的Response对象
response = drf_exception_handler(exc, context)
if response is None:
# 没有处理的服务器异常,处理一下
# 其实给前台返回 服务器异常 几个字就行了
# 那我们处理异常模块的目的是 不管任何错误,都有必要进行日志记录(线上项目只能通过记录的日志查看出现过的错误)
response = Response({'detail': '%s' % exc})
# 需要结合日志模块进行日志记录的:项目中讲
return response
settings.py
REST_FRAMEWORK = {
# ...
# 异常模块
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', # 原来的,只处理客户端异常
'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}
课程小结
"""
1)认证模块:多方式登录
2)权限组件:vip权限认证
3)频率组件:请求方式的频率限制
4)异常组件:目的是记录异常日志
drf整体总结:
1)APIView生命周期:请求解析、响应渲染、异常响应(日志)
2)序列化组件
3)视图家族
4)三大认证
5)群查:搜索、排序、分页、分类、区间
"""