第二十六章:drf
一、drf 框架介绍
1.关于前后端分离
接下来,你将进入 前后端分离项目开发 模块。 这也是现在企业中比较常见的开发模式。
疑问:
- 什么是前后端分离?与之前的开发模式有什么区别?
- 企业为什么要用前后端分离?
1.1 什么是前后端分离?
-
前后端不分离,像咱们之前学习django、案例、crm项目、bug管理 时的那些模块。
特点: - 用户访问URL - 执行视图函数,视图进行业务处理 - 视图render,读取HTML模块+数据渲染,将渲染完成的HTML/CSS/JS返回并呈现在用户浏览器上。 配合开发: - 前端,写HTML、CSS、JS - 后端,前端代码给我后端,后端代码 + 前端代码 集成到项目中。
-
前后端分离
特点: - 一般基于 vue.js、react.js、angular.js 框架来编写前端页面(本质上是HTML、CSS、JS)。 - 页面上如果需要呈现数据,则需要则需要通过 ajax 的形式向后端发送请求(URL)并获取数据。 - 后端接收到请求后,执行视图函数并进行业务处理 - 后端的视图执行完毕后,给前端返回JSON格式数据。 - 前端接收到JSON格式数据后呈现在浏览器上即可。 配合开发: - 前端,写HTML、CSS、JS(数据都是通过调用后端API获得) - 后端,写API接口 - 前后端约定好接口的规则。
1.2 为什么要使用前后端分离?
目前企业一般都会采用前后端分离的形式来进行项目开发,这种模式:
- 前后端职责清晰,前端开发者只vue.js、react.js、angular.js等框架编写页面;后端开发者只用Python编写后端代码;(两者通过json格式请求数据的传输)。
- 开发高效,前后端做自己擅长的领域且使用vue.js等前端框架比用传统的HTML、CSS、JS、jQuery等开发速度快很多。
- 有利于项目的扩展(开发APP、微信小程序等)。
注意:前后端不分离的项目,现在一般用于开发用户量少、简单的项目。
2. 关于项目安排
大多数项目最终是基于前后端分离来进行开发,所以需要学会前端开发必备技能、后端开发必备技能后,再进行业务功能开发,项目讲解的安排如下:
- 第一部分:作为后端开发者,学会基于 Django 编写后端API(给前端提供URL并返回相应格式数据)。
- 第二部分:作为前端开发者,学会基于 vue.js 编写前端页面并调用后端API获取数据。
- 第三部分:结合前端和后端技术,开发本项目的业务功能。
二、RESTful与Django视图模式
1. RESTful 规范
对于后端开发者,本质上就是提供URL给前端开发者调用并返回相应的数据。例如:
现在咱们大家知道前端后端分离的项目是需要:前端、后端 双方来进行合作开发,既然合作进行开发就必须要提前约定一些规范,以防止双方”打架“,例如:
-
数据传输用XML格式?JSON格式?
-
出现错误时,错误信息有谁来提供?
方案1:错误时后端返回错误信息,前端只做呈现即可。 { code:40001, error:"xxx错误" } 方案2:错误时后端返回错误码,前端根据错误码的对应关系呈现。 { code:40001 }
-
等等等...
所以,我们需要先来学习下规范,然后再来进行后续开发。
restful是主流的一套API规范,企业进行前后端分离开发一般都会遵循它,他定义了很多规范的条款。
例如:如果现在让大家来开发一个对 用户表 进行增删改查的接口,在不了解restful规范前, 大家一定会这样来搞:
接口:/users/list/ 用户列表
接口:/users/add/ 添加用户
接口:/users/detail/(\d+) 用户详细信息
接口:/users/edit/(\d+) 更新用户
接口:/users/del/(\d+) 删除用户
很早之前开发者们也确实都是这么干的,直到后来有人提出了 restful API规范(包含了很多规定),如果按照这个规范来开发上述的功能的话:
接口:/users/ 方法:GET => 用户列表
接口:/users/ 方法:POST => 添加列表
接口:/users/(\d+)/ 方法:GET => 获取单条数据
接口:/users/(\d+)/ 方法:DELETE => 删除数据
接口:/users/(\d+)/ 方法:PUT => 更新数据
暂且不说restful规范有多好,对于前端和后端只要能统一了规范,对大家的开发效率都会有很大的帮助,不用再做很多无效的沟通了。
所以,接下来咱们就是学习最常见的restful规范。
1.1.HTTPS协议
建议使用https协议替代http协议,让接口数据更加安全。
这一条其实与开发无关,在最后的项目部署时只要使用https部署即可。
如果是基于HTTP协议,则意味着用户浏览器再向服务器发送数据时,都是以明文的形式传输,如果你在某咖啡厅上网,他就可以把你网络传输的数据明文都获取到。
如果是基于HTTPS协议,则意味着用户浏览器再向服务器发送数据时,都是以密文的形式传输,中途即使有非法用户获取到网络数据,也可以密文的,无法破译。
HTTPS保证了数据安全,但由数据传输存在着加密和解密的过程,所以会比HTTP协议慢一些。
注意:此处大家先了解https和http的不同,至于底层原理和部署,在项目部署时再细讲。
参考文档:https://www.cnblogs.com/wupeiqi/p/11647089.html
1.2. 域名
对于后端API接口中要体现API标识,例如:
- https://api.example.com
- https://www.example.com/api/
1.3. 版本
对于后端API接口中要体现版本,例如:
- http://api.example.com/v1/
- http://api.example.com/?version=v1
- http://v1.example.com/
- http://api.example.com/
请求头:Accept: application/json; version=v1
1.4. 路径
restful API这种风格中认为网络上的一切都称是资源,围绕着资源可以进行 增删改查等操作。
这些资源,在URL中要使用名词表示(可复数),围绕着资源进行的操作就用Method不同进行区分。
https://api.example.com/v1/person
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
1.5. 请求方法
根据请求方法不同进行不同的操作。
GET 在服务器取出资源(一项或多项)
POST 在服务器新建一个资源
PUT 在服务器更新资源(客户端提供改变后的完整资源)
PATCH 在服务器更新资源(客户端提供改变的属性)
DELETE 在服务器删除资源
例如:
https://api.example.com/v1/users
https://api.example.com/v1/users/1/
接口:/users/ 方法:GET => 用户列表
接口:/users/ 方法:POST => 添加用户
接口:/users/(\d+)/ 方法:GET => 获取单条数据
接口:/users/(\d+)/ 方法:DELETE => 删除数据
接口:/users/(\d+)/ 方法:PUT => 更新数据
接口:/users/(\d+)/ 方法:PATCH => 局部更新
1.6. 搜索条件
在URL中通过参数的形式来传递搜索条件。
https://api.example.com/v1/users
https://api.example.com/v1/zoos?limit=10 指定返回记录的数量
https://api.example.com/v1/zoos?offset=10 指定返回记录的开始位置
https://api.example.com/v1/zoos?page=2&per_page=100 指定第几页,以及每页的记录数
https://api.example.com/v1/zoos?sortby=name&order=asc 指定返回结果按照哪个属性排序,以及排序顺序
https://api.example.com/v1/zoos?animal_type_id=1 指定筛选条件
1.7. 返回数据
针对不同操作,服务器向用户返回的结果结构应该不同。
https://api.example.com/v1/users
https://api.example.com/v1/users/2/
URL | 方法 | 描述 | 返回数据 |
---|---|---|---|
/users/ | GET | 列表 | 返回资源对象的列表 [ {id:1,name:"武沛齐"}, {id:1,name:"日天"} ] |
/users/ | POST | 添加 | 返回新生成的资源对象 |
/users/(\d+)/ | GET | 获取单条数据 | 返回单个资源对象 |
/users/(\d+)/ | DELETE | 删除数据 | 返回一个空文档 null |
/users/(\d+)/ | PUT | 更新数据 | 返回完整的资源对象 |
/users/(\d+)/ | PATCH | 局部更新 | 返回完整的资源对象 |
一般在实际的开发过程中会对上述返回数据进行补充和完善,例如:每次请求都返回一个字典,其中包含:
- code,表示返回码,用于表示请求执行请求,例如:0表示请求成功,1003表示参数非法,40009数据量太大等。
- data,表示数据
- error,错误信息
{
code:0,
data:[ {id:1,name:"武沛齐"}, {id:1,name:"日天"} ]
}
{
code:41007,
error:"xxxxx"
}
1.8. 状态码
后端API在对请求进行响应时,除了返回数据意外还可以返回状态码,来表示请求状况。
from django.urls import path, include
from app01 import views
urlpatterns = [
path('users/', views.users),
]
from django.http import JsonResponse
def users(request):
info = {
"code": 1000,
"data": {"id":1,"name":"武沛齐"}
}
return JsonResponse(info, status=200)
200 OK - [GET]:服务器成功返回用户请求的数据
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
301 Moved Permanently 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一
302 Found 请求的资源现在临时从不同的 URI 响应请求
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作。
401 Unauthorized - [*]:表示用户未认证(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
更多看这里:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
状态码可以表示一部分的服务端的处理请求,但特别细致的信息无法全都都包括,所以一般在开发中会配合code返回码来进行。
例如:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Global_Return_Code.html
1.9. 错误处理
错误处理,状态码是4xx时,应返回错误信息,error当做key。
{
error: "Invalid API key"
}
在 1.1.7 中中已包含。
了解常用 resful 规范,那么以后在开发后端API接口时,就要根据上述要求遵循 restful 规范(非必须,视公司情况灵活变通)。
案例展示
基于 django + restful 规范来开发一个后台接口示例(FBV)。
# urls.py
from django.urls import path
from app01 import views
# http://www.xxx.com/api/v1/users/
urlpatterns = [
path('api/<str:version>/users/', views.users),
path('api/<str:version>/users/<int:pk>/', views.users),
]
# views.py
from django.http import JsonResponse
def users(request, version, pk=None):
print("版本:", version)
if not pk:
if request.method == "GET":
# 请求用户列表
info = {
"code": 0,
"data": [{"id": 1, "name": "武沛齐"}]
}
return JsonResponse(info)
elif request.method == "POST":
# 新增用户,读取 request.POST 中提交的数据并添加到数据库中
info = {
"code": 0,
"data": {"id": 1, "name": "武沛齐"}
}
return JsonResponse(info)
else:
info = {
"code": 1000,
"error": "请求错误"
}
return JsonResponse(info)
if request.method == "GET":
# 获取ID=pk的用户信息,并返回
info = {
"code": 0,
"data": {"id": 1, "name": "武沛齐"}
}
return JsonResponse(info)
elif request.method == "DELETE":
# 删除id=pk的用户
info = {
"code": 0,
"data": {}
}
return JsonResponse(info)
elif request.method == "PUT":
# 读取request.POST中的数据 + pk,更新数据库中的用户信息
info = {
"code": 0,
"data": {"id": 1, "name": "武沛齐"}
}
return JsonResponse(info)
elif request.method == "PATCH":
# 读取request.POST中的数据 + pk,更新数据库中的用户信息
info = {
"code": 0,
"data": {"id": 1, "name": "武沛齐"}
}
return JsonResponse(info)
else:
info = {
"code": 1000,
"error": "请求错误"
}
return JsonResponse(info)
2. Django的FBV和CBV
基于 django 开发项目时,对于视图可以使用 FBV 和 CBV 两种模式编写。
-
FBV,function base views,其实就是编写函数来处理业务请求。
from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path('users/', views.users), ]
from django.http import JsonResponse def users(request,*args, **kwargs): if request.method == "GET": return JsonResponse({"code":1000,"data":"xxx"}) elif request.method == 'POST': return JsonResponse({"code":1000,"data":"xxx"}) ...
-
CBV,class base views,其实就是编写类来处理业务请求。
from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path('users/', views.UserView.as_view()), ]
from django.views import View class UserView(View): def get(self, request, *args, **kwargs): return JsonResponse({"code": 1000, "data": "xxx"}) def post(self, request, *args, **kwargs): return JsonResponse({"code": 1000, "data": "xxx"})
其实,CBV和FBV的底层实现本质上相同的。
CBV,其实就是在FBV的基础上进行的功能的扩展,根据请求的方式不同,直接定位到不同的函数中去执行。
如果是基于 django 编写 restful API,很显然使用CBV的方式会更加简洁,因为 restful 规范中就是根据 method 不同来执行不同操作。
基于 django CBV和 restful 规范开发实战案例:
# urls.py
from django.urls import path
from app01 import views
urlpatterns = [
# http://www.xxx.com/api/v1/users/
path('api/<str:version>/users/', views.UserView.as_view()),
# http://www.xxx.com/api/v1/users/2/
path('api/<str:version>/users/<int:pk>/', views.UserView.as_view()),
]
# views.py
from django.views import View
from django.http import JsonResponse
class UserView(View):
def get(self, request, version, pk=None):
if not pk:
# 请求用户列表
info = {
"code": 0,
"data": [
{"id": 1, "name": "武沛齐"},
{"id": 1, "name": "武沛齐"},
]
}
return JsonResponse(info)
else:
# 获取ID=pk的用户信息,并返回
info = {
"code": 0,
"data": {"id": 1, "name": "武沛齐"}
}
return JsonResponse(info)
def post(self, request, version):
# 新增用户,读取 request.POST 中提交的数据并添加到数据库中
info = {
"code": 0,
"data": {"id": 1, "name": "武沛齐"}
}
return JsonResponse(info)
def delete(self, request, version, pk):
# 删除id=pk的用户
info = {
"code": 0,
"data": {}
}
return JsonResponse(info)
def put(self, request, version, pk):
# 读取request.POST中的数据 + pk,更新数据库中的用户信息
info = {
"code": 0,
"data": {"id": 1, "name": "武沛齐"}
}
return JsonResponse(info)
def patch(self, request, version, pk):
# 读取request.POST中的数据 + pk,更新数据库中的用户信息
info = {
"code": 0,
"data": {"id": 1, "name": "武沛齐"}
}
return JsonResponse(info)
从上面的示例看来,基于 django 框架完全可以开发 restful API。
django restframework 框架是在 django 的基础上又给我们提供了很多方便的功能,让我们可以更便捷基于 django 开发 restful API,来一个简单的实例,快速了解下:
-
基于django
-
基于 django + django restframework 框架
三、接口测试工具
# API接口写好,后端人员要测试,不可能在浏览器里测试
# 使用postman软件,来做接口测试
-浏览器只能发送 get 请求,不能自动发送 post,delete 请求
-postman ---》开源软件,只是谷歌浏览器的插件,越做好好,后来可以按装到操作系统上,再后来,收费
-很多很多其他的,不同公司用的也可能不一样,你只需要明白一个点,这个工具只是用来发送http请求
# 接口测试软件有很多,postwoman
# 官网下载:https://www.postman.com/downloads/
# 双击安装
四、djangorestframework 快速使用
1.rest_framework 使用
-
安装
pip install djangorestframework==3.12.4
版本要求:djangorestframework==3.12.4 Python (3.5, 3.6, 3.7, 3.8, 3.9) Django (2.2, 3.0, 3.1) 版本要求:djangorestframework==3.11.2 Python (3.5, 3.6, 3.7, 3.8) Django (1.11, 2.0, 2.1, 2.2, 3.0)
-
配置,在 settings.py 中添加配置
INSTALLED_APPS = [ ... # 注册rest_framework(drf) 'rest_framework', ] # drf相关配置以后编写在这里 REST_FRAMEWORK = { }
-
代码实现
# urls.py
from django.contrib import admin
from django.urls import path
from rest_framework.routers import SimpleRouter
from app01 import views
router = SimpleRouter()
router.register('books', views.BookView)
urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += router.urls
# views.py
from .models import Book
from rest_framework.viewsets import ModelViewSet
from .serializer import BookSerializer
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
publish = models.CharField(max_length=32)
# serializer.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
2. cbv 源码分析
# cbv 写好后,路由配置如下
-第一个参数是路径,第二个参数是视图函数的内存地址(视图类执行as_view这个类方法,把它执行完,结果放在第二个参数上:我们猜执行完的结果是个函数内存地址)
路由中写的:path('api/v1/books/', views.BookView.as_view()),第二个参数无论是fbv还是cbv放的都是函数内存地址
-当请求来了,匹配成功会执行,views.BookView.as_view()(request)
-views.BookView.as_view()执行结果是View的类方法as_view返回的结果是内层函数view,是个函数内层地址
-本身请求来了,匹配成功,会执行view(request)
def view(request, *args, **kwargs):
return self.dispatch(request, *args, **kwargs)
-self.dispatch View类的方法
def dispatch(self, request, *args, **kwargs):
# request.method请求方式转成小写,必须在列表中才能往下走
if request.method.lower() in self.http_method_names:
# 反射,去self【视图类的对象:BookView】,去通过get字符串,反射出属性或方法
# BookView的get方法
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# BookView的get方法,加括号,传入request
return handler(request, *args, **kwargs)
drf 中重写了 as_view
和dispatch
方法,其实就是在原来 django 的功能基础上添加了一些功能,例如:
as_view
,免除了 csrf 验证,一般前后端分离不会使用 csrf token 认证(后期会使用 jwt 认证)。dispatch
,内部添加了版本处理、认证、权限、访问频率限制等诸多功能(后期逐一讲解)。
五、APIView + drf 的使用
使用 APIView 类后,重写了 as_view() 方法,重新封装了 request 参数。请求都没有 csrf 的校验了,且执行了认证,频率,权限功能。
1.代码编写
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
class BookView(APIView): # APIView 继承自 django 的 View
def get(self, request):
# print(type(self.request)) # 新的 request
# print(type(request))
print(request._request)
print(type(request._request)) # django.core.handlers.wsgi.WSGIRequest
# print(request.method) # get
# print(request.path) # /books/
# print(request.GET) # 原来的 get 请求提交的参数
# print(request.POST) # 原来 post 请求提交的参数
book_list = Book.objects.all()
# book_list 是 queryset 对象不能直接序列化,只能通过 for 循环一个个拼成列表套字典的形式
res_list = []
for book in book_list:
res_list.append({'name': book.name, 'price': book.price, 'publish': book.publish})
return Response(res_list)
2.APIView 源码分析
# 视图类继承 APIView 后,执行流程就发生了变化,这个变化就是整个的 drf 的执行流程
# 一旦继承了 APIView 入口
-路由配置跟之前继承View是一样的----》找视图类的as_view---》【APIView的as_view】
@classmethod
def as_view(cls, **initkwargs):
# 又调用了父类(View)的as_view
view = super().as_view(**initkwargs)
'''
# 从此以后,所有的请求都没有 csrf 的校验了
# 在函数上加装饰器
@csrf_exempt
def index(request):
pass
本质等同于 index=csrf_exempt(index)
'''
return csrf_exempt(view)
-请求来了,路由匹配成功会执行 View 类的 as_view 类方法内的 view 闭包函数(但是没有了 csrf 认证)
-真正的执行,执行 self.dispatch---->APIView 的 dispatch 【这是重点】
def dispatch(self, request, *args, **kwargs):
# 参数的request是原来的django原生的request
# 下面的request,变成了drf提供的Request类的对象---》return Request(。。。)
request = self.initialize_request(request, *args, **kwargs)
# self 是视图类的对象,视图类对象.request=request 新的request
self.request = request
try:
# 执行了认证,频率,权限 [不读]
self.initial(request, *args, **kwargs)
# 原来的 View 的 dispatch 的东西
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
# 如果出了异常,捕获异常,处理异常,正常返回
# 在执行三大认证和视图类中方法过程中,如果出了异常,是能被捕获并处理的 ---》全局异常的处理
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# 总结:APIView的执行流程
1 去除了所有的 csrf
2 包装了新的 request,以后在视图类中用的 request 是新的,Request 类的对象,不是原生的了
- 原生的在:新的 requets._request
3 在执行视图类的方法之前,执行了 3 大认证【认证,频率,权限】
4 如果在 3 大认证或视图函数方法执行过程中出了错,会有异常捕获 ----》全局异常捕获
3.Request 类源码分析
# 1 视图类中使用的 request 对象,已经变成了 drf 提供的 Request 类的对象了
-原生 djagno 的 request 是这个类的对象:django.core.handlers.wsgi.WSGIRequest
-drf 的 request 是这个类的对象:rest_framework.request.Request
# 2 request 已经不是原来的 request 了,还能像原来的 request 一样使用吗?
-用起来,像之前一样
print(request.method) # get
print(request.path) # /books/
print(request.GET) # 原来的get请求提交的参数
print(request.POST) # 原来post请求提交的参数
# 3 Request 源码
-方法 __getattr__
-在视图类的方法中,执行 request.method,新的 request 是没有 method,就触发了新的 Request 的 __getattr__ 方法的执行
def __getattr__(self, attr):
try:
# 从老的 request 中反射出 要取得属性
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
-request.data--->这是个方法,包装成了数据属性
-以后无论post,put。。放在body中提交的数据,都从request.data中取,取出来就是字典
-无论是那种编码格式
-request.query_params--->这是个方法,包装成了数据属性
-get请求携带的参数,以后从这里面取
-query_params:查询参数--->restful规范请求地址中带查询参数
-request.FILES--->这是个方法,包装成了数据属性
-前端提交过来的文件,从这里取
# 4 Request 类总结
-1 新的 request 用起来,跟之前一模一样,因为新的取不到,会取老的 __getattr__
-2 request.data 无论什么编码,什么请求方式,只要是 body 中的数据,就从这里取,字典
-3 request.query_params 就是原来的 request._request.GET
-4 上传的文件从 request.FILES
# 5 python 中有很多魔法方法,在类中,某种情况下触发,会自动执行
-__str__: 打印对象会调用
-__init__: 类() 会调用
-__call__: 对象() 会调用
-__new__: 实例化一个对象时,首先调用 __new__() 方法构造一个类的实例,并为其分配对应类型的内存空间
-__getattr__: 对象.属性,如果属性不存在,会触发它的执行
六、序列化组件的使用
api接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把【数据转换格式】,序列化可以分两个阶段:
# 序列化: 把我们识别的数据转换成指定的格式提供给别人
-字典,列表------》json格式存到文件中了
-例如:我们在django中获取到的数据默认是模型对象,但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人
-read
# 反序列化:把别人提供的数据转换/还原成我们需要的格式
-例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中。
-write
1.序列化的基本使用
1.1 模型类
# models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
publish = models.CharField(max_length=32)
1.2 定义序列化类
# myserializer.py
# 写序列化类:给 book 进行序列化
# from rest_framework.serializers import Serializer
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
# 要序列化的字段 有很多字段类,字段类有很多字段属性
name = serializers.CharField() # 字段类
# price = serializers.CharField()
publish = serializers.CharField()
1.3 查询多条数据 [序列化]
class BookView(APIView): # APIView继承自django的View
def get(self, request):
book_list = Book.objects.all()
# instance表示要序列化的数据,many=True表示序列化多条(instance是qs对象,一定要传many=True)
ser = BookSerializer(instance=book_list, many=True)
return Response(ser.data)
1.4 查询单条数据 [序列化]
class BookDetailView(APIView):
def get(self, request, pk):
book = Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book)
return Response(ser.data)
1.5 新增 [反序列化]
视图类
class BookView(APIView): # APIView 继承自 django 的 View
def post(self, request):
# 前端传递数据,从 request.data 取出来
ser = BookSerializer(data=request.data)
if ser.is_valid(): # 表示校验前端传入的数据 没有写校验规则,现在等于没校验
ser.save() # 再写东西,这里会报错 调用 save 会触发 BookSerialize r的 save 方法,判断了,如果 instance 有值执行 update,没有值执行 create
return Response(ser.data)
else:
return Response(ser.errors)
序列化类
class BookSerializer(serializers.Serializer):
# 要序列化的字段 有很多字段类,字段类有很多字段属性
name = serializers.CharField() # 字段类
price = serializers.CharField()
publish = serializers.CharField()
# 重写create方法,
def create(self, validated_data):
res = Book.objects.create(**validated_data)
return res
1.6 修改 [反序列化]
视图类
class BookDetailView(APIView):
def put(self, request, pk):
book = Book.objects.filter(pk=pk).first()
# 前端传递数据,从request.data取出来
ser = BookSerializer(instance=book, data=request.data)
if ser.is_valid(): # 表示校验前端传入的数据 没有写校验规则,现在等于没校验
ser.save() # 再写东西,这里会报错 调用save会触发BookSerializer的save方法,判断了,如果instance有值执行update,没有值执行create
return Response(ser.data)
else:
return Response(ser.errors)
序列化类
class BookSerializer(serializers.Serializer):
# 要序列化的字段 有很多字段类,字段类有很多字段属性
name = serializers.CharField() # 字段类
price = serializers.CharField()
publish = serializers.CharField()
# 重写 update
def update(self, instance, validated_data):
# instance 要修改的对象
# validated_data 校验过后的数据
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.publish = validated_data.get('publish')
instance.save()
return instance
1.7 删除
视图类
class BookDetailView(APIView):
def delete(self, request, pk):
models.Book.objects.filter(pk=pk).delete()
return Response()
2.序列化常用字段
# BooleanField BooleanField()
# NullBooleanField NullBooleanField()
# CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
# EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
# RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
# SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+
# URLField URLField(max_length=200, min_length=None, allow_blank=False)
# UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
# IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
# IntegerField IntegerField(max_value=None, min_value=None)
# FloatField FloatField(max_value=None, min_value=None)
# DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置
# DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
# DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
# TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
# DurationField DurationField()
# ChoiceField ChoiceField(choices) choices与Django的用法相同
# MultipleChoiceField MultipleChoiceField(choices)
# FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
# ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
---------记住一下几个-----------
CharField
BooleanField
IntegerField
DecimalField
# ListField: {name:'lqz',age:19,hobby:['篮球','足球']}
# DictField:{name:'lqz',age:19,wife:{'name':'刘亦菲','age':33}}
3.常用字段参数
# 选项参数:
参数名称 作用
# 给 CharField 字段类使用的参数
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
# 给 IntegerField 字段类使用的参数
max_value 最小值
min_value 最大值
# 通用参数:放在哪个字段类上都可以的
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
validators 该字段使用的验证器【不需要了解】
error_messages 包含错误编号与错误信息的字典
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息
# 重点
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
4.序列化类高级用法
4.1 source
# 获取所有图书接口 APIView+Response+序列化类
# name 字段在前端显示的时候叫 book_name
-使用 source,字段参数,可以指定序列化表中得哪个字段
book_title = serializers.CharField(min_length=1, max_length=20, source='title')
- source 指定的可以是字段,也可以是方法,用于重命名
- source 可以做跨表查询
示例:
# model.py
from django.db import models
# Create your models here.
class Book(models.Model):
title = models.CharField(max_length=20, verbose_name='名称')
price = models.IntegerField(verbose_name='价格')
press = models.CharField(max_length=20, verbose_name='出版社')
def prefix_name(self):
return 'ysg_' + self.title
# myserializer.py
from rest_framework import serializers
from app01.models import Book
class BookSerializer(serializers.Serializer):
# book_title = serializers.CharField(min_length=1, max_length=20, source='title')
book_title = serializers.CharField(min_length=1, max_length=20, source='prefix_name')
price = serializers.IntegerField(min_value=1, max_value=99)
press= serializers.CharField(min_length=3, max_length=20)
def create(self, validated_data):
print(validated_data)
book_obj = Book.objects.create(**validated_data)
return book_obj
# views.py
from django.shortcuts import render
# Create your views here.
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.myseria import myserializer
from app01 import models
class GetData(APIView):
def post(self, request):
ser = myserializer.BookSerializer(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response({'code': 1001, 'msg': ser.errors})
def get(self, request):
books = models.Book.objects.all()
ser = myserializer.BookSerializer(instance=books, many=True)
return Response(ser.data)
# 请求数据 post
# book_title = serializers.CharField(min_length=1, max_length=20, source='title')
url:http://127.0.0.1:8000/drf/books/
{
"title": "梦的解析2",
"price": "89",
"press": "上海出版社"
}
# 验证错误的消息
{
"code": 1001,
"msg": {
"book_title": [
"这个字段是必填项。"
]
}
}
# 请求数据 get
# book_title = serializers.CharField(min_length=1, max_length=20, source='prefix_name')
url:http://127.0.0.1:8000/drf/books/
[
{
"book_title": "ysg_梦的解析",
"price": 89,
"press": "上海出版社"
},
{
"book_title": "ysg_梦的解析2",
"price": 89,
"press": "上海出版社"
}
]
4.2 定制序列化字段的两种方式
# urls.py
from django.urls import path, include
from .views import Format, Format2, FormatBook, FormatBook2
urlpatterns = [
# path('format/', Format.as_view()),
# path('format/<int:pk>/', Format2.as_view()),
path('format/', FormatBook.as_view()),
path('format/<int:pk>/', FormatBook2.as_view()),
]
# models.py
from django.db import models
# 图书跟作者:多对多,需要建立中间表,但是我们可以通过ManyToManyField自动生成,写在哪里都行
# 图书跟出版社:一对多,一个出版社,出版多本书,关联字段写在多的一方,写在Book
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE) # 留住,还有很多
authors = models.ManyToManyField(to='Author')
# 在表模型中设置
def publish_detail(self):
return {'name': self.publish.name, 'addr': self.publish.addr}
def author_list(self):
l = []
for author in self.authors.all():
l.append({'name': author.name, 'phone': author.phone})
return l
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
# 指定在book中现实publish的名字方案一,用的不多
# def __str__(self):
# return self.name
class Author(models.Model):
name = models.CharField(max_length=32)
phone = models.CharField(max_length=11)
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializer import FormatSerializer, FormatModelSerializer
from .models import Book, Publish, Author
class Format(APIView):
def get(self, request):
books = Book.objects.all()
print(books)
ser = FormatSerializer(instance=books, many=True)
return Response({'code': 0, 'data': ser.data})
def post(self, reuqest):
ser = FormatSerializer(data=reuqest.data)
if ser.is_valid():
ser.save()
return Response({'code': 0, 'data': ser.data})
else:
return Response({'code': 0, 'msg': '格式错误'})
class Format2(APIView):
def put(self, request, pk):
info = Book.objects.filter(pk=pk).first()
ser = FormatSerializer(instance=info, data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 0, 'data': ser.data})
else:
return Response({'code': 0, 'msg': ser.errors})
class FormatBook(APIView):
def get(self, request):
books = Book.objects.all()
ser = FormatModelSerializer(instance=books, many=True)
return Response({'code': 0, 'data': ser.data})
def post(self, reuqest):
ser = FormatModelSerializer(data=reuqest.data)
if ser.is_valid():
ser.save()
return Response({'code': 0, 'data': ser.data})
else:
return Response({'code': 0, 'msg': '格式错误'})
class FormatBook2(APIView):
def put(self, request, pk):
info = Book.objects.filter(pk=pk).first()
ser = FormatModelSerializer(instance=info, data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 0, 'data': ser.data})
else:
return Response({'code': 0, 'msg': ser.errors})
4.2.1 使用Serializer
4.2.1.1 在 Serializer 中
# serializer.py
from rest_framework import serializers
from .models import Book, Publish, Author
class FormatSerializer(serializers.Serializer):
name = serializers.CharField(max_length=32)
price = serializers.CharField(max_length=32)
author = serializers.ListField(write_only=True)
publish = serializers.CharField(write_only=True)
# 在表模型中设置
# author_list = serializers.ListField(read_only=True)
# publish_dict = serializers.DictField(read_only=True)
# 在 Serializer 中
author_list = serializers.SerializerMethodField()
def get_author_list(self, obj):
lit = []
for aut in obj.author.all():
lit.append({'name': aut.name, 'phone': aut.phone, 'gender': aut.get_gender_display()})
return lit
publish_dict = serializers.SerializerMethodField()
def get_publish_dict(self, obj):
dit = {'name': obj.publish.name, 'addr': obj.publish.addr}
return dit
def create(self, validated_data):
book = Book.objects.create(
name=validated_data.get('name'),
price=validated_data.get('price'),
publish_id=validated_data.get('publish')
)
book.author.add(*validated_data.get('author'))
return book
def update(self, instance, validated_data):
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.publish_id = validated_data.get('publish')
author = validated_data.get('author')
instance.author.clear()
instance.author.add(*author)
instance.save()
return instance
4.2.1.1 在表模型中设置
# serializer.py
from rest_framework import serializers
from .models import Book, Publish, Author
class FormatSerializer(serializers.Serializer):
name = serializers.CharField(max_length=32)
price = serializers.CharField(max_length=32)
author_list = serializers.ListField(read_only=True)
publish_dict = serializers.DictField(read_only=True)
# 在表模型中设置
author = serializers.ListField(write_only=True)
publish = serializers.CharField(write_only=True)
def create(self, validated_data):
book = Book.objects.create(
name=validated_data.get('name'),
price=validated_data.get('price'),
publish_id=validated_data.get('publish')
)
book.author.add(*validated_data.get('author'))
return book
def update(self, instance, validated_data):
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.publish_id = validated_data.get('publish')
author = validated_data.get('author')
instance.author.clear()
instance.author.add(*author)
instance.save()
return instance
4.2.2 使用ModelSerializer
4.2.2.1 在 Serializer 中
# myserializer.py
class FormatModelSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['name', 'price', 'author_list', 'publish_dict', 'author', 'publish']
extra_kwargs = {
'author_list': {'read_only': True},
'publish_dict': {'read_only': True},
'author': {'write_only': True},
'publish': {'write_only': True},
}
# 在 Serializer 中
author_list = serializers.SerializerMethodField()
def get_author_list(self, obj):
lit = []
for aut in obj.author.all():
lit.append({'name': aut.name, 'phone': aut.phone, 'gender': aut.get_gender_display()})
return lit
publish_dict = serializers.SerializerMethodField()
def get_publish_dict(self, obj):
dit = {'name': obj.publish.name, 'addr': obj.publish.addr}
return dit
4.2.2.1 在表模型中设置
# myserializer.py
class FormatModelSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['name', 'price', 'author_list', 'publish_dict', 'author', 'publish']
extra_kwargs = {
'author_list': {'read_only': True},
'publish_dict': {'read_only': True},
'author': {'write_only': True},
'publish': {'write_only': True},
}
4.2.3 定义子序列化
在 Serializer 中使用,有一个大前提:Course
表中要存在与 teacher
一致的字段
子序列化
class TeacherModelSerializer(serializers.ModelSerializer):
class Meta:
model = Teacher
fields = ['id', 'name', 'role', 'title', 'signature', 'image']
序列化
class CourseModelSerializer(serializers.ModelSerializer):
teacher = TeacherModelSerializer() # 引用子序列化
class Meta:
model = Course
fields = ['id',
'name',
'course_img',
'brief',
'pub_date',
'period',
'attachment_path',
'students',
'sections',
'pub_sections',
'price',
'course_type_name',
'level_choices_name',
'status_choicese_name',
'teacher', # Course 表中要存在与 teacher 一致的字段
'course_category_dit'
]
4.3 使用ModelSerializer步骤
# 在模型类中写逻辑代码,称之为 ddd,领域驱动模型
### 使用继承 Serializer 的序列化类保存需要重写 create 方法
# 缺点
1 在序列化中每个字段都要写,无论是序列化还是反序列化
2 如果新增或者修改,在序列化类中都需要重写 create 或 update
# 解决这个缺点,使用ModelSerializer来做
1 定义一个类继承 ModelSerializer
2 类内部写内部内 class Meta:
3 在内部类中指定 model (要序列化的表)
4 在内部类中指定 fields (要序列化的字段,写__all__表示所有,不包含方法,写[一个个字段])
5 在内部类中指定 extra_kwargs,给字段添加字段参数的
6 在序列化类中,可以重写某个字段,优先使用你重写的
name = serializers.SerializerMethodField()
def get_name(self, obj):
return 'sb---' + obj.name
7 以后不需要重写 create 和 update
-ModelSerializer写好了,兼容性更好,任意表都可以直接存
4.4 反序列化数据校验源码分析
# 先校验字段自己的规则(最大,最小),走局部钩子校验,走全局钩子
# 局部:validate_name,全局叫:validate 为什么?
# 入口:从哪开始看,哪个操作,执行了字段校验ser.is_valid()
-BaseSerializer内的is_valid()方法
def is_valid(self, *, raise_exception=False):
if not hasattr(self, '_validated_data'):
try:
# 真正的走校验,如果成功,返回校验过后的数据
self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
return not bool(self._errors)
-内部执行了:self.run_validation(self.initial_data)---》本质执行的 Serializer 的
-如果你按住 ctrl 键,鼠标点击,会从当前类中找 run_validation,找不到会去父类找
-这不是代码的执行,代码执行要从头开始找,从自己身上再往上找
def run_validation(self, data=empty):
#局部钩子的执行
value = self.to_internal_value(data)
try:
# 全局钩子的执行,从根上开始找着执行,优先执行自己定义的序列化类中得全局钩子
value = self.validate(value)
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
-全局钩子看完了,局部钩子---》 self.to_internal_value---》从根上找----》本质执行的Serializer的
def to_internal_value(self, data):
for field in fields: # fields:序列化类中所有的字段,for循环每次取一个字段对象
# 反射:去self:序列化类的对象中,反射 validate_字段名 的方法
validate_method = getattr(self, 'validate_' + field.field_name, None)
try:
# 这句话是字段自己的校验规则(最大最小长度)
validated_value = field.run_validation(primitive_value)
# 局部钩子
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
return ret
# 你自己写的序列化类---》继承了ModelSerializer---》继承了Serializer---》BaseSerializer---》Field
4.5 assert 断言
# 框架的源码中,大量使用断言
# assert :断言,作用的判断,断定一个变量必须是xx,如果不是就报错
# 你的土鳖写法
# name = 'lqz1'
# if not name == 'lqz':
# raise Exception('name不等于lqz')
#
# print('程序执行完了')
# assert 的断言写法
name = 'lqz1'
assert name == 'lqz', 'name不等于lqz'
print('程序执行完了')
4.6 钩子
# 跟 forms 很像
-字段自己的校验规则
-局部钩子
-全局钩子
# 字段自己的校验规则
-如果继承的是Serializer,写法如下
name=serializers.CharField(max_length=8,min_length=3,error_messages={'min_length': "太短了"})
-如果继承的是ModelSerializer,写法如下
extra_kwargs = {
'name': {'max_length': 8, 'min_length': 3, 'error_messages': {'min_length': "太短了"}},
}
# 局部钩子
-如果继承的是Serializer,写法一样
-如果继承的是ModelSerializer,写法一样
def validate_name(self, name):
if name.startswith('sb'):
# 校验不通过,抛异常
raise ValidationError('不能以sb卡头')
else:
return name
# 全局钩子
-如果继承的是Serializer,写法一样
-如果继承的是ModelSerializer,写法一样
def validate(self, attrs):
if attrs.get('name') == attrs.get('publish_date'):
raise ValidationError('名字不能等于日期')
else:
return attrs
七:请求与响应
1.Request 与 Response 类
1.1 Request
1.1.1 Request 前端编码格式
需求是该接口只能接收 json 格式,不能接收其他格式
# 总共有三个
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
# 方式一:在继承自 APIView 及其子类的的视图类中配置(局部配置)
class BookView(APIView):
parser_classes = [JSONParser,]
# 方法二:在配置文件中配置(影响所有,全局配置)
-django 有套默认配置,每个项目有个配置
-drf 有套默认配置,每个项目也有个配置 ---》就在 django 的配置文件中
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
# 'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
],
}
1.1.2 Request 属性和方法
request 类,在继承 APIView 后,请求对象时,每次都是一个重新封装后新的 request
# Request 类:属性与方法
- data:POST、PUT、PATCH、META 请求方式解析后的数据
-- 原生 diango,put 数据提交后,在 request.POST 中取不到的(在 request.body 中)
- __getattr__
- query_params
其它的用法与之前的一样(FILES、method、path。。。)--->底层原理(__getattr__)
1.2 Response
1.2.1 Response的编码格式
drf 是 djagno 的一个 app,所以要注册
drf 做了个判断,如果是浏览器,默认使用模板美化界面,如果是 postman 只要 json 数据
# 方式一:在视图类中写(局部配置)
-两个响应类 ---》找 ---》drf 的配置文件中找 --》两个类
-from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer class BookView(APIView):
renderer_classes=[JSONRenderer,]
# 方式二:在项目配置文件中写(全局配置)
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
1.2.2 Resposne 属性或方法
# drf 的Response 源码分析
-from rest_framework.response import Response
-视图类的方法返回时,retrun Response ,走它的__init__, init 中可以传什么参数
# Response init 可以传的参数
def __init__(self,
data=None,
status=None,
template_name=None,
headers=None,
exception=False,
content_type=None)
-data:之前咱们写的 ser.data 可以是字典或列表,字符串 ---》序列化后返回给前端 ---》前端在响应体中看到的就是这个
-status:http 响应的状态码,默认是 200,你可以改
-drf 在 status 包下,把所有 http 响应状态码都写了一遍,常量
-from rest_framework.status import HTTP_200_OK
-Response('dddd',status=status.HTTP_200_OK)
-template_name:了解即可,修改响应模板的样子,BrowsableAPIRenderer 定死的样子,后期公司可以自己定制
-headers:响应头,http 响应的响应头
-原生 djagno,如何像响应头中加东西
# 四件套 render,redirect,HttpResponse,JsonResponse
obj = HttpResponse('dddd')
obj['xxc'] = 'yyc'
return obj
-content_type :响应编码格式,一般不动
# 重点:data,status,headers
2 执行小结
-总结:
-解析类的使用顺序:优先用视图类自己的,然后用项目配置文件,最后用内置的配置文件
-实际项目如何配置
-基本上都运行 JSONParser, FormParser
-如果上传文件只允许 MultiPartParser
八:视图基类
视图类中:
通过重写 get_serializer,达到不同方法使用的序列化类不一样
通过重写 get_queryset,达到不同方法使用的数据不一样
通过重写 perform_destroy,达到控制某些能删,某些不能删除的目的
1. 2 个视图基类
from rest_framework.views import APIView
from rest_framework.generics import GenericAPIView
# APIView
# GenericAPIView
# GenericAPIView:属性和方法
-- 属性
queryset = None # 要序列化的数据
serializer_class = None # 序列化类
如:
queryset = User.objects.all()
serializer_class = UserSerializer
----- 了解 -----
lookup_field # 通过 get_object 获取单个对象的查询 key 值,value 值是路由中传进来的
filter_backends # 过滤类
pagination_class # 分页类
-- 方法
self.get_queryser() # 获取要序列化的数据
self.get_object() # 根据 lookup_field 配置的参数获取单个对象
self.get_serializer() # 获取序列化类
self.get_serializer_class() # 获取序列化类,get_serializer 调用了它
----- 了解 -----
self.filter_queryset # 用于过滤
self.paginate_xxx # 跟分页有关
2. 5 个视图扩展类
必须配合 GenericAPIView 使用,不能配合 APIView 使用
from rest_framework.mixins import RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, ListModelMixin
# RetrieveModelMixin:写了一个 Create 方法,就是原来咱们 post 中的代码
# RetrieveModelMixin:retrieve,就是咱们原来的 get
# UpdateModelMixin:update 方法,就是咱们原来的 put
# ListModelMixin:list 方法,就是原来咱们的 get
# DestroyModelMixin:destroy 方法,就是原来咱们的 delete
class UserView(GenericAPIView, ListModelMixin, CreateModelMixin):
# 配置两个 类属性
queryset = User.objects.all()
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class UserDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = User.objects.all()
serializer_class = UserSerializer
def get(self,request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args,**kwargs):
return self.update(request,*args,**kwargs)
def delete(self, request, *args,**kwargs):
return self.destroy(request,*args,**kwargs)
3. 9 个视图子类
继承 GenericAPIView+ 某个或某几个视图扩展类
from rest_framework.generics import ListAPIView,CreateAPIView, RetrieveAPIView,DestroyAPIView,UpdateAPIView
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView,RetrieveUpdateAPIView,RetrieveDestroyAPIView
# 正常来讲 Destroy+Update 应该有一个,作者没加
class UserView(ListCreateAPIView):
# 配置两个 类属性
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetailView(RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
4. 视图集
4.1 继承ModelViewSet编写5个接口
# views.py
from rest_framework.viewsets import ModelViewSet,ReadOnlyModelViewSet
class UserView(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
# urls.py
path('user/', views.UserView.as_view({'get': 'list', 'post': 'create'})),
path('user/<int:pk>', views.UserView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
4.2 继承ReadOnlyModelViewSet编写2个接口
# views.py
from rest_framework.viewsets import ModelViewSet,ReadOnlyModelViewSet
class UserView(ReadOnlyModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
# urls.py
path('user/', views.UserView.as_view({'get': 'list', 'post': 'create'})),
4.3 源码分析 ViewSetMixin
# 请求来了,路由匹配成功---》get请求,匹配成功books,会执行 views.BookView.as_view({'get': 'list', 'post': 'create'})()------>读as_view【这个as_view是ViewSetMixin的as_view】
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
# 如果没有传actions,直接抛异常,路由写法变了后,as_view中不传字典,直接报错
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
# 。。。。其他代码不用看
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if 'get' in actions and 'head' not in actions:
actions['head'] = actions['get']
self.action_map = actions
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
return self.dispatch(request, *args, **kwargs)
# 去除了csrf校验
return csrf_exempt(view)
# 路由匹配成功执行views.BookView.as_view({'get': 'list', 'post': 'create'})()----》本质执
行ViewSetMixin----》as_view----》内的view()---》代码贴过来
def view(request, *args, **kwargs):
#actions 是传入的字典--->{'get': 'list', 'post': 'create'}
self.action_map = actions
# 第一次循环:method:get,action:list
# 第一次循环:method:post,action:create
for method, action in actions.items():
# 反射:去视图类中反射,action对应的方法,action第一次是list,去视图类中反射list方法
# handler就是视图类中的list方法
handler = getattr(self, action)
# 反射修改:把method:get请求方法,handler:list
# 视图类的对象的get方法,变成了list
setattr(self, method, handler)
return self.dispatch(request, *args, **kwargs) #dispatch是APIView的
# 总结:
-1 只要继承ViewSetMixin的视图类,路由写法就变了(重写了as_veiw)
-2 变成需要需要传入字典映射方法:{'get': 'list', 'post': 'create'}
-只要传入actions,以后访问get就是访问list,访问post,就是访问create
-3 其他执行跟之前一样
-4 以后视图类类中的方法名,可以任意命名,只要在路由中做好映射即可【重要】
4.4 小结
'''
from rest_framework.viewsets下有这几个类
ModelViewSet:5个试图扩展类+ViewSetMixin+GenericAPIView
ReadOnlyModelViewSet::2个试图扩展类+ViewSetMixin+GenericAPIView 只读的两个
ViewSetMixin:魔法,重新了as_view,只要继承他,以后路由写法变成了映射方法
ViewSet:ViewSetMixin+ APIView
GenericViewSet:ViewSetMixin+ GenericAPIView
'''
#重点:
以后,你想继承APIView,但是想变路由写法【视图类中方法名任意命名】,要继承ViewSet
以后,你想继承GenericAPIView,但是想变路由写法【视图类中方法名任意命名】,要继承GenericViewSet
5.视图类继承关系
Processon 链接:https://www.processon.com/view/link/63e0cff310f39607276b731f
九:路由
1.自动生成路由
# 导入
from rest_framework.routers import SimpleRouter, DefaultRouter
# 实例化
router = SimpleRouter()
# 注册 register(prefix, viewset, basename) 前缀、视图集、基名
router.register('user', views.UserView, 'user')
urlpatterns = [
# 加入到 urlpatterns 中(两种方式:二)
path('', include(router.urls)),
]
# 加入到 urlpatterns 中(两种方式:一)
urlpatterns += router.urls
1 自动生成的路由映射关系其实定死了
-本质是自动做映射,能够自动成的前提是,视图类中要有 5个方法的某要给或多个
get--->list
get---->retrieve
put---->update
post---->create
delete---->destory
-ModelViewSet,ReadOnlyModelViewSet可以自动生成
-9个试图子类+配合ViewSetMixin 才可以自动生成
-GenericAPIView+5个试图扩展类+配合ViewSetMixin 才能自动生成
2 以后写的视图类不需要写action装饰器的话,视图类中必须要有
-list,destroy,retrieve,create,update方法之一
-其实是必须是 5 个视图扩展类之一+GenericAPIView 9个视图子类,ModelViewSet
3 SimpleRouter 和 DefaultRouter
-DefaultRoute r比SimpleRouter多一个根路径,显示所有注册过的路由
2.action 装饰器的使用
# 在视图函数中,会有一些其它名字的方法,必须要使用 action 装饰器做映射
# methods:支持的请求方式,列表
# detail:默认是False
-False,不带id的路径:send/send_sms/
-True,带id的路径:send/2/send_sms/
# url_path: 控制生成的/user/后的路径是什么,如果不写,默认以方法名命名 /user/login/,一般跟函数名同名即可
# url_name:别名,用于反向解析
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from app01 import models
import uuid
class UserNameView(ViewSet)
@action(methods=['GET', 'POST'], detail=False, url_path='login', url_name='login')
def login(self, request):
if request.method == 'POST':
token = str(uuid.uuid4())
username = request.data.get('username')
password = request.data.get('password')
models.User.objects.update_or_create(defaults={'token': token}, username=username, password=password)
return Response(username)
users = models.User.objects.all().values('username')
return Response(users)
# 以后看到的 drf 路由写法
后期,都是自动生成,一般不在urlpatterns 加入路由了
# 补充:
-1 不同请求方式可以使用不同序列化类
-2 不同action使用不同序列化类
class SendView(GenericViewSet):
queryset = None
serializer_class = '序列化类'
def get_serializer(self, *args, **kwargs):
if self.action=='lqz':
return '某个序列化类'
else:
return '另一个序列化列'
@action(methods=['GET'], detail=True)
def send_sms(self, request,pk):
print(pk)
# 手机号,从哪去,假设get请求,携带了参数
phone = request.query_params.get('phone')
print('发送成功,%s' % phone)
return Response({'code': 100, 'msg': '发送成功'})
@action(methods=['GET'], detail=True)
def lqz(self,request): # get
# 序列化类
pass
@action(methods=['GET'], detail=True)
def login(self,request): # get
# 序列化类
pass
3.路由小结
# 1 drf路由的使用
-路由有三种写法
-视图类.as_view()
-视图类.as_view({'get':'list'})
-自动生成路由
from rest_framework.routers import SimpleRouter, DefaultRouter
-导入--实例化--注册--加入到 urlpatterns
-类有两个:SimpleRouter, DefaultRouter(可以显示根目录)
-加入到 urlpatterns 的两种方式:列表加操作,include
-路由的第二种和第三种写法,视图类必须继承 ViewSetMixin ---》重写了类方法as_view
-要自动生成:其实自动生成的是定死的{'get':'list','post':'create','delete':destroy,put:update,get:retrieve}
-如果你完全自动生成路由,视图类必须有:list,create....
-可以继承 ViewSetMixin + GenericAPIView + 5个视图扩展类
-继承 ViewSetMixin + 9 个视图子类
-视图类中得其他方法必须使用 action 装饰器来装饰,才能自动生成路由
/user/login
@action(methods=['post','get'],detail=False,url_path='login')
十:认证
1.实现步骤
# 通过认证类完成,使用步骤
1 写一个认证类,继承 BaseAuthentication
2 重写 authenticate 方法,在内部做认证
3 如果认证通过,返回两个值【返回 None 或两个值】
4 认证不通过抛 AuthenticationFailed 异常
5 只要返回了两个值,在后续的 request.user 就是当前登录用户
6 局部使用和全局使用
-方式一:局部配置
class BookView(ModelViewSet):
authentication_classes = [LoginAuth,]
-方式二:全局配置
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth',]
}
-局部禁用:
authentication_classes = []
2.代码实例
# auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
"""
Authenticate the request and return a two-tuple of (user, token).
"""
# 在这里实现认证,如果是登录的,继续往后走返回两个值,如果不是抛异常
# 请求中是否携带 token,判断是否登录,放在地址栏中
token = request.GET.get('token')
if token:
user_token = models.User.objects.filter(token=token).first()
if user_token:
return user_token.username, token
else:
raise AuthenticationFailed('token 认证失败')
else:
raise AuthenticationFailed('token 没传')
# views.py
from app01.auth import LoginAuth
# 查询所有
class BookView(ViewSetMixin, ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 查询单个
class BookDetailView(ViewSetMixin, RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [LoginAuth, ] # 需要写一个认证类,需要咱们写
# urls.py
router.register('books', views.BookView, 'books')
router.register('books', views.BookDetailView, 'books')
3.认证配置
# 局部配置
authentication_classes = [LoginAuth,]
# 局部禁用
authentication_classes = []
# 全局配置
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.LoginAuth',]
}
4.源码分析
# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
-497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
# APIView类的399行左右:
def initial(self, request, *args, **kwargs):
# 能够解析的编码,版本控制。。。。
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置【读它】
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
# APIView的316行左右
def perform_authentication(self, request):
request.user #咱们觉得它是个属性,其实它是个方法,包装成了数据属性
# Request类的user方法 219行左右
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
# self 是Request的对象,找Request类的self._authenticate() 373 行
def _authenticate(self):
# self.authenticators 我们配置在视图类上认证类的一个个对象,放到列表中
# Request类初始化的时候,传入的
for authenticator in self.authenticators:
try:
# 返回了两个值,第一个是当前登录用户,第二个的token,只走这一个认证类,后面的不再走了
# 可以返回None,会继续执行下一个认证类
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
# 解压赋值:
#self.user=当前登录用户,self是当次请求的新的Request的对象
#self.auth=token
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
# self.authenticators 去Request类的init中找 152行左右
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
.....
self.authenticators = authenticators or ()
.....
# 什么时候调用Reqeust的__init__?---》APIVIew的dispatch上面的492行的:request = self.initialize_request(request, *args, **kwargs)-----》385行----》
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
-APIView 的 --get_authenticators()----》return [auth() for auth in self.authentication_classes]
-self.authentication_classes:视图类中配置的一个个的认证类的列表,如果没配,配置文件中,内置配置文件中
# 总结:
1 配置在视图类上的认证类,会在执行视图类方法之前执行,在权限认证之前执行
2 自己写的认证类,可以返回两个值或 None
3 后续可以从 request.user 取出当前登录用户(前提是你要在认证类中返回)
十一:权限
1.权限步骤
# 使用步骤
-第一步:写一个类,继承BasePermission
-第二步:重写 has_permission 方法,在该方法在中实现权限认证,在这方法中,request.user 就是当前登录用户
-第三步:在方法中校验用户是否有权限(request.user就是当前登录用户)
-第四步:如果有权限,返回True,没有权限,返回False
-第五步:self.message 是给前端的提示信息
-第六步:局部使用,全局使用,局部禁用
2.实例代码
# Permission.py
from rest_framework.permissions import BasePermission
class UserBasePermission(BasePermission):
# self 中没有找类属性,类属性中没有找父类
# message = f'用户没有该权限'
def has_permission(self, request, view):
if request.user.user_type == 1:
return True
else:
self.message = f'{request.user.get_user_type_display()},没有该权限'
return False
3.配置权限
# 全局配置
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ['app01.permission.UserBasePermission', ]
}
# 局部配置
from app01.permission import UserBasePermission
permission_classes = [UserBasePermission, ]
4.源码分析
# 权限的源码执行流程
-写一个权限类,局部使用,配置在视图类的,就会执行权限类的has_permission方法,完成权限校验
# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
-497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
# APIView类的399行左右:
def initial(self, request, *args, **kwargs):
# 能够解析的编码,版本控制。。。。
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置
self.perform_authentication(request)
# 权限组件 [读它]
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
# APIView的326 左右
def check_permissions(self, request):
# self.get_permissions()----》[CommonPermission(),]
# permission 是我们配置在视图类上权限类的对象
for permission in self.get_permissions():
# 权限类的对象,执行has_permission,这就是为什么我们写的权限类要重写has_permission方法
# self 是视图类的对象,就是咱们自己的的权限类的has_permission的view参数
if not permission.has_permission(request, self):
# 如果return 的是False,就会走这里,走这里是,没有权限
# 如果配了多个权限类,第一个没过,直接不会再执行下一个权限类了
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
# APIView的274行左右 get_permissions
def get_permissions(self):
# self.permission_classes 是咱们配置在视图类上的列表,里面是一个个的权限类,没加括号
# permission_classes = [CommonPermission]
# [CommonPermission(),] 本质返回了权限类的对象,放到列表中
return [permission() for permission in self.permission_classes]
# 总结:
-APIView---dispatch----》initial---》倒数第二行---》self.check_permissions(request)
里面取出配置在视图类上的权限类,实例化得到对象,一个个执行对象的has_permission方法,如果返回False,就直接结束,不再继续往下执行,权限就认证通过
-如果视图类上不配做权限类:permission_classes = [CommonPermission],会使用配置文件的api_settings.DEFAULT_PERMISSION_CLASSES
优先使用项目配置文件,其次使用drf内置配置文件
十二:频率
1.频率步骤
# 使用步骤:
-第一步:写一个类:继承SimpleRateThrottle
-第二步:重写 get_cache_key,返回唯一的字符串,会以这个字符串做频率限制
-第三步:写一个类属性 scope='随意写',必须要跟配置文件对象
-第四步:配置文件中写
'DEFAULT_THROTTLE_RATES': {
'随意写': '3/m' # 3/h 3/s 3/d
}
-第五步:局部配置,全局配置,局部禁用
2.实例代码
# rates.py
from rest_framework.throttling import SimpleRateThrottle, BaseThrottle
class Login_Rate(SimpleRateThrottle):
scope = 'sumber'
def get_cache_key(self, request, view):
# 返回什么,频率就以什么做限制
# 可以通过用户 id 限制
# 可以通过 ip 地址限制
return request.META.get('REMOTE_ADDR')
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'sumber': '3/m'
}
}
3.配置频率
# 全局配置
'DEFAULT_THROTTLE_CLASSES': [app01.rates.Login_Rate, ]
# 局部配置
from app01.rates import Login_Rate
throttle_classes = [Login_Rate, ]
4.自定义频率类
class SuperThrottle(BaseThrottle):
VISIT_RECORD = {}
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 自己写逻辑,判断是否超频
# (1)取出访问者ip
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
# (1)取出访问者ip
ip = request.META.get('REMOTE_ADDR')
import time
ctime = time.time()
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip] = [ctime, ]
return True
# self.history = [时间1]
self.history = self.VISIT_RECORD.get(ip,[])
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
while self.history and ctime - self.history[-1] > 60:
self.history.pop()
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
if len(self.history) < 3:
self.history.insert(0, ctime)
return True
else:
return False
def wait(self):
import time
ctime = time.time()
return 60 - (ctime - self.history[-1])
5.源码分析
5.1 check_throttles 源码
# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
-497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
# APIView类的399行左右:
def initial(self, request, *args, **kwargs):
# 能够解析的编码,版本控制。。。。
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件【读它】
self.check_throttles(request)
# APIView 的352行
def check_throttles(self, request):
throttle_durations = []
#self.get_throttles() 配置在视图类上的频率类的对象,放到列表中
# 每次取出一个频率类的对象,执行allow_request方法,如果是False,频率超了,不能再走了
# 如果是True,没有超频率,可以直接往后
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
if throttle_durations:
# Filter out `None` values which may happen in case of config / rate
# changes, see #1438
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
# 总结:
-我们写的频率类:继承BaseThrottle,重写allow_request,在内部判断,如果超频了,就返回False,如果没超频率,就返回True
5.2 SimpleRateThrottle 源码
# SimpleRateThrottle
-源码里执行的频率类的allow_request,读SimpleRateThrottle的allow_request
class SimpleRateThrottle(BaseThrottle):
cache = default_cache
timer = time.time
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
def __init__(self): # 只要类实例化得到对象就会执行,一执行,self.rate就有值了,而且self.num_requests和self.duration
if not getattr(self, 'rate', None): # 去频率类中反射rate属性或方法,发现没有,返回了None,这个if判断就符合,执行下面的代码
self.rate = self.get_rate() #返回了 '3/m'
# self.num_requests=3
# self.duration=60
self.num_requests, self.duration = self.parse_rate(self.rate)
def get_rate(self):
return self.THROTTLE_RATES[self.scope] # 字典取值,配置文件中咱们配置的字典{'ss': '3/m',},根据ss取到了 '3/m'
def parse_rate(self, rate):
if rate is None:
return (None, None)
# rate:字符串'3/m' 根据 / 切分,切成了 ['3','m']
# num=3,period=m
num, period = rate.split('/')
# num_requests=3 数字3
num_requests = int(num)
# period='m' ---->period[0]--->'m'
# {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
# duration=60
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
# 3 60
return (num_requests, duration)
def allow_request(self, request, view):
if self.rate is None:
return True
# 咱们自己写的,返回什么就以什么做限制 咱们返回的是ip地址
# self.key=当前访问者的ip地址
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
# self.history 访问者的时间列表,从缓存中拿到,如果拿不到就是空列表,如果之前有 [时间2,时间1]
self.history = self.cache.get(self.key, [])
# 当前时间
self.now = self.timer()
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
# 总结:以后要再写频率类,只需要继承SimpleRateThrottle,重写get_cache_key,配置类属性scope,配置文件中配置一下就可以了
十三:分页
查询所有的接口才需要分页。分页后端写法是固定的,前端展现形式是不一样的。
-
pc 端的下一页的点击
-
app 中,翻页是下拉加载更多
1.分页步骤
# drf 中分页的使用:
- 写一个类,继承 drf 提供的三个分页类之一
- PageNumberPagination, CursorPagination, LimitOffsetPagination
- 重写某几个类属性
- 把它配置在继承自 GenericAPIView + ListModelMixin 的子视图类上
- 如果继承的是 APIView,需要自己写
page = MyPageNumberPagination()
res = page.paginate_queryset(qs, request)
2.实例代码
# pagination.py
from rest_framework.pagination import PageNumberPagination, CursorPagination, LimitOffsetPagination
# PC端 常用
class BookPageNumberPagination(PageNumberPagination):
page_size = 2 # 每页显示的条数
# Client can control the page using this query parameter.
page_query_param = 'page' # /books/?page=3 查询第几页的参数
# Client can control the page size using this query parameter.
# Default is 'None'. Set to eg 'page_size' to enable usage.
page_size_query_param = 'size' # /books/?page=3&size=4 # 查询第三页,每页显示4条
# Set to an integer to limit the maximum page size the client may request.
# Only relevant if 'page_size_query_param' has also been set.
max_page_size = 10 # 限制通过size查询,最大的条数
# 偏移分页
class BookLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3 # 每页显示的条数
limit_query_param = 'limit' # /books/?limit=4 这一页显示4条数据
offset_query_param = 'offset' # /books/?offset=3&limit=4 # 从第3条开始,取4条数据
max_limit = 5 # 限制最多显示多少条
# APP 大数据量 常用 游标分页
class BookCursorPagination(CursorPagination): # 只能上一页和下一页,它的分页效率是最高的,高于上面所有的分页方式,大数据量的分页,建议使用这种
cursor_query_param = 'cursor'
page_size = 3 #每页显示条数
ordering = 'id' # 排序,必须是表中得字段
3.配置分页
from rest_framework.viewsets import ViewSet, ModelViewSet
from app01 import pagination
class BookView(ModelViewSet):
# 局部配置
pagination_class = pagination.BookLimitOffsetPagination
queryset = models.Book.objects.all()
serializer_class = serializr.BookSerializer
# 全局配置
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':['app01.drf.page.ListLimitOffsetPagination', ],
}
4.基于APIView编写分页
# page.py
from rest_framework.pagination import LimitOffsetPagination
class ListLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3 # 每页显示的条数
limit_query_param = 'limit' # /books/?limit=4 这一页显示4条数据
offset_query_param = 'offset' # /books/?offset=3&limit=4 # 从第3条开始,取4条数据
max_limit = 5 # 限制最多显示多少条
# views.py
# 分页功能,只有查询所有才有
from rest_framework.viewsets import ViewSet
from .drf.page import ListLimitOffsetPagination
class CarModelView2(ViewSet):
def list(self, request):
carmodel = CarModel.objects.all()
paginator = ListLimitOffsetPagination()
page = paginator.paginate_queryset(carmodel, request, self)
if page is not None:
serializer = CarModelSerializers(instance=page, many=True)
return Response({
'total': paginator.count,
'next': paginator.get_next_link(),
'previous': paginator.get_previous_link(),
'results': serializer.data
})
5.源码分析
十四:排序
查询所有才涉及到排序,其它接口不需要
# 必须是继承 GenericAPIView + ListModelMixin 的子视图类上
-配置排序类:
filter_backends=[OrderingFilter,]
-配置排序的字段
ordering_fields=['id','price']
-支持前端的访问形式
http://127.0.0.1:8000/books/?ordering=-price,id # 先按价格的降序排,如果价格一样再按id的升序排
# 纯自己写的,继承了APIView的,需要自己从请求地址中取出排序规则,自己排序
-'price','-id'=reqeust.query_params.get('ordering').split(',')
-qs = Book.objects.all().order_by('price','-id')
# 分页和排序能一起用,但是是先排序后分页的
# 请求方式
http://127.0.0.1:8000/drf/book/?page=1&size=5&ordering=price,id&token=a5e72741-8e49-4a5e-90be-7333d005a01a
# views.py
from rest_framework.viewsets import ViewSet, ModelViewSet
from rest_framework.filters import OrderingFilter, SearchFilter
class BookView(ModelViewSet):
# 分页
pagination_class = pagination.BookPageNumberPagination
# 排序
filter_backends = [OrderingFilter, ]
ordering_fields = ['id', 'price']
queryset = models.Book.objects.all()
serializer_class = serializr.BookSerializer
十五:过滤
1.内置过滤
# 查询所有才涉及到过滤,其它接口都不需要
# restful 规范中有一条,请求地址中带过滤条件:分页,排序,过滤统称为过滤
# 使用内置过滤类使用步骤 查询所有才涉及到排序,其它接口都不需要
必须是继承GenericAPIView+ListModelMixin的子视图类上
-配置过滤类:
filter_backends=[SearchFilter,]
-配置过滤的字段
ordering_fields=['name','publish']
-支持前端的访问形式
http://127.0.0.1:8000/books/?search=三 # 只要 name 中或 publish 中有三都能搜出来
# 内置过滤类只能通过 search 写条件,如果配置了多个过滤字段,是或者的条件
# 不够用:
-第三方:过滤类
-自己写:自己写过滤类
# 请求方式
http://127.0.0.1:8000/drf/book/?page=1&size=5&search=爱&token=a5e72741-8e49-4a5e-90be-7333d005a01a
# views.py
from rest_framework.filters import OrderingFilter, SearchFilter
class BookView(ModelViewSet):
# 分页
pagination_class = pagination.BookPageNumberPagination
# 指定过滤类
filter_backends=[SearchFilter,]
# 指定过滤字段
search_fields=['name']
queryset = models.Book.objects.all()
serializer_class = serializr.BookSerializer
2.三方过滤(重点)
内置的过滤类 ,只能通过 ?search= 搜索条件,不能指定 name 或 publish 来搜索。
多个过滤类和排序类可以共用,filter_backends=[],可以配置多个,执行顺序是从做往右,所以,放在最左侧的尽量先过滤掉大部分数据
# 面试官问你,你在工作中遇到的问题及如何解决的?
-我在写 xx 接口的时候,因为我们过滤条件很多,搜索数据库很慢,写了很多搜索类,之前时候搜索类的排序是随意的,搜索效率比较低,后来,读了 drf 搜索类的源码,发现执行顺序是从左往右,我就想到了,最左侧的搜索类,尽量搜索速度快,并且过滤掉的数据多,后续再搜索就会更快,所有我调整了一下搜索类的配置顺序,发现该接口的效率有所提升
2.1 精确过滤步骤
安装:pip3 install django-filter
from django_filters.rest_framework import DjangoFilterBackend
# 1 配置在视图类中配置:filter_backends = [DjangoFilterBackend, ]
# 2 配置搜索的字段:filterset_fields = ['name', 'publish']
# 3 支持的搜索方式:http://127.0.0.1:8008/books/?name=三国演义&publish=南京出版社
- 精准搜索,条件是并且的关系
from rest_framework.viewsets import ViewSet, ModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
class BookView(ModelViewSet):
# 过滤
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'price']
queryset = models.Book.objects.all()
serializer_class = serializr.BookSerializer
# 支持的查询方式
http://127.0.0.1:8000/api/v1/books/?price=939
http://127.0.0.1:8000/api/v1/books/?price=939&name=细雨中的呼喊
3.自定义过滤类(重点)
3.1 自定义内置模糊过滤类
# 1 写一个类,继承 BaseFilterBackend
# 2 重写方法:filter_queryset,在该方法中实现过滤,返回 qs 对象
def filter_queryset(self, request, queryset, view):
# 在这里面实现过滤,一定要返回 qs 对象,过滤后的数据
name = request.query_params.get('name', None)
publish = request.query_params.get('publish', None)
qs = queryset.filter(name__icontains=name, publish__icontains=publish)
return qs
# 3 配置在视图类上
filter_backends = [MyFilter, ]
# filters.py
from rest_framework.filters import BaseFilterBackend
class BookFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
name_val = request.query_params.get('name', None)
qs = queryset.filter(name__icontains=name_val)
return qs
# views.py
from rest_framework.viewsets import ViewSet, ModelViewSet
from .filters import BookFilter
class BookView(ModelViewSet):
# 自定义过滤
filter_backends = [BookFilter, ]
queryset = models.Book.objects.all()
serializer_class = serializr.BookSerializer
3.2 自定义三方模糊过滤类
4.排序和过滤的源码分析
# 继承了 GenericAPIView+ListModelMixin,只要在视图类中配置 filter_backends 它就能实现过滤和排序
-drf内置的过滤类(SearchFilter),排序类(OrderingFiler)
-django-filter
-自定义:写一个类,继承BaseFilterBackend,重写filter_queryset,返回的qs对象,就是过滤或排序后的
# 只有获取多有才涉及到排序
-list方法
def list(self, request, *args, **kwargs):
# self.get_queryset()所有数据,经过了self.filter_queryset返回了qs
# self.filter_queryset完成的过滤
queryset = self.filter_queryset(self.get_queryset())
# 如果有分页,走的分页----》视图类中配置了分页类
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
# 如果没有分页,走正常的序列化,返回
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
-self.filter_queryset完成了过滤,当前在视图类中,self是视图类的对象,去视图类中找没找到,去父类---》GenericAPIView---》filter_queryset
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
# 总结:
-写的过滤类要重写 filter_queryset,返回 qs(过滤或排序后)对象
-后期如果不写过滤类,只要在视图类中重写 filter_queryset,在里面实现过滤也可以
十六:全局异常处理
drf 中无论在三大认证还是视图类的方法中执行,只要报错(主动抛异常),都能执行一个函数(异常处理的函数),只要除了异常在 APIView 的 dispatch 中捕获了
1.实现步骤
# drf 中分页的使用:
- 写一个方法,传入 (exc, context) 两个方法
- 调用异常处理函数 exception_handler(exc, context) 得到返回值
-- from rest_framework.views import exception_handler
- 有值则为 drf 的报错,无值则为 django 的报错
- 返回固定的 json 返回值格式
- 将错误信息记录日志
2.实例代码
# 自己写一个函数,配置在配置文件中,以后出了异常,它就走咱们写的函数了
-1 统一返回格式 {'code':10001,'msg':错误信息}
-2 记录日志
# exception.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
import datetime
def drf_exception_handler(exc, context):
"""
Returns the response that should be used for any given exception.
By default we handle the REST framework `APIException`, and also
Django's built-in `Http404` and `PermissionDenied` exceptions.
Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
# 记录日志:只要进入到这里面,一定是出了异常,只要出异常就要记录日志
request = context.get('request') # 当此请求的 request
username = request.user.username
new_time = datetime.datetime.now
msg = f'''
时间: {new_time()}
登录用户id: {request.user.username}
用户ip: {request.META.get('REMOTE_ADDR')}
请求方式: {request.method}
请求地址: {request.path}
执行的视图类: {context.get('view')}
错误原因: {exc}'''
print(msg)
# 后期以上的代码要写到日志中
# 统一返回格式
response = exception_handler(exc, context) # 内置的这个异常处理函数,只处理了 drf 自己的异常(继承了了APIException的异常)
if response: # 如果 response 有值,说明错误被处理了(Http404,PermissionDenied,APIException)
return Response({'code': 10001, 'msg': f'drf 报错了{response.data.get("detail", "位置报错")}'})
else: # 如果是 None,这个错误没有被处理,说明这个错误不是 drf 的错误,而是 django 的或代码的错误
return Response({'code': 10002, 'msg': f'系统报错了{str(exc)}'})
3.配置处理
# settings.py
# 配置在配置文件中
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.exctptions.common_exception_handler'
}
十七:jwt 原理
1.cookie,session,token介绍
# https://www.cnblogs.com/liuqingzheng/p/8990027.html
token: 三段式
第一段:头:公司信息,加密方式。。。 {}
第二段:荷载,真正的数据 {name:lqz,id:1}
第三段:签名,通过第一段和第二段,通过某种加密方式加密得到的 aafasdfas
token 的使用分两个阶段
-登录成功后的【签发 token 阶段】---》生成三段
-登录成功访问某个接口的 【验证阶段】---》验证 token 是否合法
cookie是:存在客户端浏览器的键值对
session是:存在于服务端的键值对
token是:三段式,服务端生成的,存放在客户端(浏览器就放在 cookie 中,移动端:存在移动端中)
使用 token 的认证机制,服务端还要存数据吗? token 是服务的生成,客户端保存,服务的不存储 token
2.base64编码和解码
# base64 可以把字符串编码成 base64 的编码格式:(大小写字母,数字和 =)eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9
# base64 可以把 base64 编码的字符串,解码回原来的格式
# 应用场景:
- jwt 中使用
- 网络中传输字符串就可以使用 base64 编码
- 网络中传输图片,也可能使用 base64 的编码
# 编码解码
# 把图片保存起来看看
import json
import base64
d = {'name': 'lqz', 'userid': 6, 'age': 19}
info = json.dumps(d)
print(info)
# 把字符串使用base64编码
res=base64.b64encode(info.encode('utf-8'))
print(res) # eyJuYW1lIjogImxxeiIsICJ1c2VyaWQiOiA2LCAiYWdlIjogMTl9
res=base64.b64decode(s)
with open('code.png','wb') as f:
f.write(res)
3.jwt 原理介绍
# Json web token (JWT), token 的应用于 web 方向的称之为 jwt
# 构成和工作原理
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了 Jwt 字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
# header:头
-声明类型,这里是 jwt
-声明加密的算法 通常直接使用 HMAC SHA256
-公司信息
由
{
'typ': 'JWT',
'alg': 'HS256'
}
变成了(base64 的编码)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
# payload:荷载
exp: jwt的过期时间,这个过期时间必须要大于签发时间
iat: jwt的签发时间
用户信息: 用户信息
由
{
"exp": "1234567890",
"name": "John Doe",
"userid": 3
}
变成了(base64 的编码)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
# signature:签名
把头和荷载加密后得到的:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
#注意:secret 是保存在服务器端的(加密方式+盐),jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和验证的,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret, 那就意味着客户端是可以自我签发 jwt 了
# jwt 使用流程最核心的是:
-签发:登录接口签发
-认证:认证类认证
4.drf-jwt 快速使用
4.1 使用步骤
# jwt:签发(登录接口) 认证(认证类)
# django中使用jwt
-可以自己写
-https://github.com/jpadilla/django-rest-framework-jwt (比较老)
-https://github.com/jazzband/djangorestframework-simplejwt (比较新)
# 安装
pip3 install djangorestframework-jwt
# 快速使用
-迁移表,因为它默认使用 auth 的 user 表签发 token
-创建超级用户(auth 的 user 表中要有记录)
-咱们不需要写登录接口了,如果是使用 auth 的 user 表作为用户表,它可以快速签发
-签发(登录):只需要在路由中配置(因为它帮咱们写好了登录接口)
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
-认证(认证类):导入,配置在视图类上
class TestView(APIView):
authentication_classes = [JSONWebTokenAuthentication,]
permission_classes = [IsAuthenticated,]
-前端访问时,token 需要放在请求头中
Authorization:jwt token 串
4.2 代码示例
# urls.py
# 签发
from django.urls import path, include
from app01 import views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token), # 签发路由
path('shop/', views.Shop.as_view()), # 认证路由
]
# views.py
# 认证
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class Shop(APIView):
authentication_classes = [JSONWebTokenAuthentication, ]
permission_classes = [IsAuthenticated, ]
def get(self, request):
return Response('OK')
5.drf-jwt 修改返回格式
5.1 使用步骤
# 登录成功后,前端看到的格式,太固定了,只有token,我们想做成
{code:100,msg:'登录成功',token:adfasdfasdf}
# 固定写法:写一个函数,函数返回什么,前端就看到什么,配置在配置文件中
# 使用步骤:
-1 写一个函数
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code':100,
'msg':'登录成功',
'username':user.username,
'token':token
}
-2 把函数配置在配置文件中
JWT_AUTH={
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.response.jwt_response_payload_handler',
}
# 以后登录接口返回的格式就是咱们写的函数的返回值
5.2 代码示例
# respons.py
def Login_jwt_response_payload_handler(token, user=None, request=None):
"""
Returns the response data for both the login and refresh views.
Override to return a custom response such as including the
serialized representation of the User.
Example:
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token,
'user': UserSerializer(user, context={'request': request}).data
}
"""
return {
'code': 0,
'msg':'登录成功',
'user': user.username,
'token': token
}
# settings.py
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.response.Login_jwt_response_payload_handler'
}
6.自定义 user 表,签发 token
6.1 使用步骤
# 1、models.py 中定义UserInfo表
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
# 2、views.py 写一个登录接口
from rest_framework.exceptions import APIException
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from .models import UserInfo
class UserView(APIView):
def post(self, request):
try:
username = request.data.get('username')
password = request.data.get('password')
user=UserInfo.objects.get(username=username,password=password)
# 根据user,签发token---》三部分:头,荷载,签名
# 使用djagnorestframework-jwt模块提供的签发token的函数,生成token
payload = jwt_payload_handler(user) # 通过user对象---》{username:lqz,id:1,过期时间}
token=jwt_encode_handler(payload) # 根据payload---》得到token:头.荷载.签名
print(payload)
print(token)
return Response({'code':100,'msg':'登录成功','token':token})
except Exception as e:
raise APIException('用户名或密码错误')
6.2 代码示例
# models.py
class Username(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
def __str__(self):
return self.username
# PS: Username 模型类中的用户字段必须为 username,因为 get_username() 方法中通过 user.username 来获取用户名
from rest_framework_jwt.utils import jwt_payload_handler
def jwt_payload_handler(user):
username_field = get_username_field()
username = get_username(user)
def get_username(user):
try:
username = user.get_username()
except AttributeError:
username = user.username
return username
# views.py
class UserView(APIView):
def post(self, request):
try:
username = request.data.get('username')
password = request.data.get('password')
user_obj = Username.objects.get(username=username, password=password)
# 根据 user,签发 token ---》三部分:头,荷载,签名
# 使用 djagnorestframework-jwt 模块提供的签发 token 的函数,生成 token
payload = jwt_payload_handler(user_obj) # 通过user对象 ---》{username:lqz,id:1,过期时间}
token = jwt_encode_handler(payload) # 根据 payload---》得到 token:头.荷载.签名
return Response({'code': 0, 'user': username, 'msg': '登录成功', 'token': token})
except Exception as e:
raise APIException('用户名或密码错误 %s' %e)
7 自定义 jwt 的认证类
# auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import jwt_decode_handler
import jwt
from .models import UserName
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
# 验证 token
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('签名过期')
except jwt.DecodeError:
raise AuthenticationFailed('解析出错')
except jwt.InvalidTokenError:
raise AuthenticationFailed('无效令牌错误')
print(payload)
user = UserName.objects.filter(username=payload.get('username')).first()
if user:
return (user, token)
else:
return {'code': 10001, 'msg': '认证失败'}
# views.py
class PublishView(APIView):
authentication_classes = [JWTAuthentication, ]
def get(self, request):
info = Publish.objects.all()
pser = PublishSerializer(instance=info, many=True)
return Response({'code': 0, 'number': len(pser.data), 'data': pser.data})
8.jwt源码分析
8.1 双token认证
用户正在 app 或者应用中操作 token 突然过期,此时用户不得不返回登陆界面,重新进行一次登录,这种体验性不好,于是引入双 token 校验机制
实现原理:首次登陆时服务端返回两个 token ,accessToken 和 refreshToken,accessToken 过期时间比较短,refreshToken 时间较长,且每次使用后会刷新,每次刷新后的 refreshToken 都是不同
refreshToken 假设7天,accessToken 过期时间5分钟
正常使用 accessToken 即可,如果 accessToken 过期了,重新发请求,携带 refreshToken 过来,能正常返回,并且这次响应中又带了 acessToken
8.2 签发
# 签发的 token,有过期时间,默认过期时间是 300s,配置一般设为 7 天
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
# django 中顶格写的代码,都会执行
# 登录接口,路由匹配成功,执行obtain_jwt_token---》post请求---》ObtainJSONWebToken的post方法
path('login/', obtain_jwt_token),
# ObtainJSONWebToken的post方法 继承APIView
def post(self, request, *args, **kwargs):
# 实例化得到序列化类
serializer = self.get_serializer(data=request.data)
# 做校验:字段自己,局部钩子,全局钩子
if serializer.is_valid():
# user:当前登录用户
user = serializer.object.get('user') or request.user
# 签发的token
token = serializer.object.get('token')
# 构造返回格式,咱们可以自定制---》讲过了
response_data = jwt_response_payload_handler(token, user, request)
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
#最终返回了咱们定制的返回格式
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 如何得到user,如何签发的token----》在序列化类的全局钩子中得到的user和签发的token
-JSONWebTokenSerializer---全局钩子---validate
#前端传入,校验过后的数据---》{"username":"lqz","password":"lqz1e2345"}
def validate(self, attrs):
credentials = {
# self.username_field: attrs.get(self.username_field),
'username':attrs.get('username')
'password': attrs.get('password')
}
if all(credentials.values()):
# auth 模块,authenticate 可以传入用户名,密码如果用户存在,就返回用户对象,如果不存就是None
# 正确的用户
user = authenticate(**credentials)
if user:
# 校验用户是否是活跃用户,如果禁用了,不能登录成功
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
# 荷载----》通过user得到荷载 {id,name,email,exp}
payload = jwt_payload_handler(user)
return {
# jwt_encode_handler通过荷载得到token串
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
### 重点:
1 通过user得到荷载:payload = jwt_payload_handler(user)
2 通过荷载签发token:jwt_encode_handler(payload)
## 了解:
# 翻译函数,只要做了国际化,放的英文,会翻译成该国语言(配置文件配置的)
from django.utils.translation import ugettext as _
msg = _('Unable to log in with provided credentials.')
8.3 签发
# JSONWebTokenAuthentication---->父类BaseJSONWebTokenAuthentication----》authenticate方法
def authenticate(self, request):
# 前端带在请求头中的 token 值
jwt_value = self.get_jwt_value(request)
# 如果没有携带 token,就不校验了
if jwt_value is None:
return None
try:
# jwt_value 就是 token
# 通过 token,得到荷载,中途会出错
# 出错的原因:
-篡改token
-过期了
-未知错误
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
# 如果能顺利解开,没有被异常捕获,说明 token 是可以信任的
# payload 就可以使用,通过 payload 得到当前登录用户
user = self.authenticate_credentials(payload)
# 返回当前登录用户,token
return (user, jwt_value)
# jwt_value = self.get_jwt_value(request)
def get_jwt_value(self, request):
# 拿到了前端请求头中传入的 jwt dasdfasdfasdfa
# auth=[jwt,asdfasdfasdf]
auth = get_authorization_header(request).split()
# 'jwt'
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
# 请求头中如果没带,去 cookie 中取
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1]
# 认证类配置了,如果不传 jwt,不会校验,一定配合权限类使用
# 它这个认证类:只要带了 token,request.user 就有只,如果没带 token,不管了,继续往后走
十八:权限管理模式
1.RBAC的介绍和使用
# RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,
-权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。
这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便
# RBAC权限管理的模式,最适合公司内部的管理系统,不适合对外互联网用户的系统
-用户:用户表
-角色(部门):角色表(部门表)
-权限:权限表
-所有权限都是存在权限表中一条条的记录(发工资,招聘员工,开员工,发布新版本,开董事会)
-权限是授予角色的(部门的),一个个角色,就是一条条记录(开发角色,hr角色,股东角色)
-用户:一个个用户是用户表中一条条记录,用户属于某个部门
-三个表之间的关系
-用户和角色关系:多对多,中间表
-角色和权限关系:多对多,中间表
-5张表了
-用户表
-角色表
-权限表
-用户和角色关联表
-角色和权限关联表
-举例子:
用户表:
id 姓名
1 张三
2 里斯
角色表
id 角色名称
1 hr角色
2 股东角色
3 开发角色
权限表
id 权限名词
1 发工资
2 招人
3 提交代码
#####张三要有提交代码的权限
用户和角色中间表:
id 角色id 用户id
1 3 1
权限和角色的中间表
id 角色id 权限id
1 3 3
# django的后台管理admin就自带了rbac的权限,通过auth模块实现的,比普通rbac更高级一些
-本来5张表
-django是6张表,用户和权限的多对多关系表(一个用户可以分配多个权限,一个权限可以给多个用户)
-6张表了
-用户表
-角色表
-权限表
-用户和角色关联表
-角色和权限关联表
-用户和权限的多对多关系表
-启用了admin和auth,这6张表就迁移进去了
auth_user # 用户表
auth_group # 角色,组,部门表
auth_permission # 权限表
auth_user_groups # 用户和角色中间表
auth_group_permissions # 角色跟权限中间表
auth_user_user_permissions#用户和权限的中间表
-之前很多公司写后台管理使用dajngo,使用django的admin二次开发,不用写权限了,快速加功能即可
-体验django admin的rbac的权限控制
2.其它权限控制的介绍
# ACL(Access Control List,访问控制列表)
将用户或组等使用者直接与对象的权限对接。
-用户表,权限表,中间 给用户授予某些权限即可
# RBAC(Role-Based Access Control,基于角色的访问控制)
将用户与角色对接,然后角色与对象的权限对接
# RBAC+ACL django,公司用的比较多啊
# ABAC(Attribute-Based Access Control,基于属性的访问控制)
ABAC(Attribute-Based Access Control,基于属性的访问控制)
又称为PBAC(Policy-Based Access Control,基于策略的访问控制)
CBAC(Claims-Based Access Control,基于声明的访问控制)
传统的ACL、RBAC的架构是
{subject,action,object},
而ABAC的架构是
{subject,action,object,contextual}且为他们添加了parameter(参数)。
subject属性:比如用户的年龄、部门、角色、威望、积分等主题属性。
action属性:比如查看、读取、编辑、删除等行为属性。
object属性:比如银行账户、文章、评论等对象或资源属性。
contextual属性:比如时段、IP位置、天气等环境属性。
权限表
id 权限名
1 开除员工
id 权限名 属性
1 开除员工 女
1 开除员工 男
3.casbin的使用
安装:pip install casbin
3.1 mycasbin.py
import casbin
e = casbin.Enforcer("./model.conf", "./policy.csv")
sub = "lqz" # 想要访问资源的用户
obj = "book" # 将要被访问的资源
act = "get" # 用户对资源进行的操作
# 自己写acl的控制
# 当前用户id,去权限和用户表查询即可,有记录就是有权限
# 自己写rbac
# 当前用户id,找到他的角色,根据角色拿出权限,判断当前访问有没有
if e.enforce(sub, obj, act):
# 允许alice读取data1
print('有权限')
else:
# 拒绝请求,抛出异常
print('没有权限')
3.2 model.conf
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
3.3 policy.csv
p,alice,data1,read
p,bob,data2,write
p,lqz,book,get
4.simpleui的介绍和使用
# django admin自带了权限控制,但是是前后端混合的,我们可以二次开发,开发出公司内部的自动化运行,自动化测试,人事管理系统,订单系统。。。。样子不好看
# 对 django admin 进行美化
-xadmin(不用了,过时了)
-simpleui(正红)
# 基于 drf+vue 自己写前后端分离的权限管理
# go-vue-admin
# 使用 simpleui:https://simpleui.72wo.com/simpleui/
# 安装 pip install django-simpleui
# 现在阶段,一般前后端分离比较多:django+vue
-带权限的前端端分离的快速开发框架
-django-vue-admin
-自己写
4.1 使用步骤
# 1 安装
pip install django-simpleui
# 2 在 app 中注册
# 3 调整左侧导航栏----》
- menu_display对应menus name
- 如果是项目的app,就menus写app
- 菜单可以多级,一般咱们内部app,都是一级
- 可以增加除咱们app外的其它链接---》如果是外部链接,直接写地址,如果是内部链接,跟之前前后端混合项目一样的写法:咱们的案例---》show 的路由
SIMPLEUI_CONFIG = {
'system_keep': False,
'menu_display': ['图书管理', '权限认证', '张红测试'], # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
'dynamic': True, # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
'menus': [
{
'name': '图书管理',
'app': 'app01',
'icon': 'fas fa-code',
'models': [
{
'name': '图书',
'icon': 'fa fa-user',
'url': 'app01/book/'
},
{
'name': '出版社',
'icon': 'fa fa-user',
'url': 'app01/publisssh/'
},
{
'name': '作者',
'icon': 'fa fa-user',
'url': 'app01/author/'
},
{
'name': '作者详情',
'icon': 'fa fa-user',
'url': 'app01/authordetail/'
},
]
},
{
'app': 'auth',
'name': '权限认证',
'icon': 'fas fa-user-shield',
'models': [
{
'name': '用户',
'icon': 'fa fa-user',
'url': 'auth/user/'
},
{
'name': '组',
'icon': 'fa fa-user',
'url': 'auth/group/'
},
]
},
{
'name': '张红测试',
'icon': 'fa fa-file',
'models': [
{
'name': 'Baidu',
'icon': 'far fa-surprise',
# 第三级菜单 ,
'models': [
{
'name': '爱奇艺',
'url': 'https://www.iqiyi.com/dianshiju/'
# 第四级就不支持了,element只支持了3级
}, {
'name': '百度问答',
'icon': 'far fa-surprise',
'url': 'https://zhidao.baidu.com/'
}
]
},
{
'name': '大屏展示',
'url': '/show/',
'icon': 'fab fa-github'
}]
}
]
}
# 4 内部 app,图书管理系统某个链接要展示的字段---》在admin.py 中----》自定义按钮
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ('nid', 'name', 'price', 'publish_date', 'publish')
# 增加自定义按钮
actions = ['custom_button']
def custom_button(self, request, queryset):
print(queryset)
custom_button.confirm = '你是否执意要点击这个按钮?'
# 显示的文本,与django admin一致
custom_button.short_description = '测试按钮'
# icon,参考element-ui icon与https://fontawesome.com
# custom_button.icon = 'fas fa-audio-description'
# # 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
custom_button.type = 'danger'
# # 给按钮追加自定义的颜色
# custom_button.style = 'color:black;'
# 5 app 名字显示中文,字段名字显示中文
-新增,查看修改展示中文,在表模型的字段上加:verbose_name='图书名字',help_text='这里填图书名'
-app名字中文:apps.py---》verbose_name = '图书管理系统'
# 6 其它配置项
SIMPLEUI_LOGIN_PARTICLES = False #登录页面动态效果
SIMPLEUI_LOGO = 'https://avatars2.githubusercontent.com/u/13655483?s=60&v=4'# 图标替换
SIMPLEUI_HOME_INFO = False # 首页右侧 github 提示
SIMPLEUI_HOME_QUICK = False # 快捷操作
SIMPLEUI_HOME_ACTION = False # 动作
4.2 settings.py
import time
SIMPLEUI_CONFIG = {
'system_keep': False,
'menu_display': ['我的首页', '图书管理','权限认证', '多级菜单测试', '动态菜单测试'], # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
'dynamic': True, # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
'menus': [
{
'name': '我的首页',
'icon': 'fas fa-code',
'url': '/index/',
},
{
'app': 'app01',
'name': '图书管理',
'icon': 'fas fa-code',
'models': [
{
'name': '用户',
'icon': 'fa fa-user',
'url': 'app01/userinfo/'
},
{
'name': '图书',
'icon': 'fa fa-user',
'url': 'app01/book/'
},
{
'name': '出版社',
'icon': 'fa fa-user',
'url': 'app01/publish/'
},
]
},
{
'app': 'auth',
'name': '权限认证',
'icon': 'fas fa-user-shield',
'models': [{
'name': '用户',
'icon': 'fa fa-user',
'url': 'auth/user/'
}]
},
{
# 自2021.02.01+ 支持多级菜单,models 为子菜单名
'name': '多级菜单测试',
'icon': 'fa fa-file',
# 二级菜单
'models': [
{
'name': '百度',
'icon': 'far fa-surprise',
# 第三级菜单 ,
'models': [
{
'name': '爱奇艺',
'url': 'https://www.iqiyi.com/dianshiju/'
# 第四级就不支持了,element只支持了3级
},
{
'name': '百度问答',
'icon': 'far fa-surprise',
'url': 'https://zhidao.baidu.com/'
}
]
},
{
'name': 'lqz',
'url': 'https://www.wezoz.com',
'icon': 'fab fa-github'
}]
},
{
'name': '动态菜单测试',
'icon': 'fa fa-desktop',
'models': [{
'name': time.time(),
'url': 'http://baidu.com',
'icon': 'far fa-surprise'
}]
}]
}
SIMPLEUI_LOGIN_PARTICLES = False
SIMPLEUI_HOME_INFO = False
4.3 admin.py
from django.contrib import admin
# Register your models here.
from .models import *
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'price')
# 增加自定义按钮
actions = ['make_copy', ]
def make_copy(self, request, queryset):
print('adsfasdf')
make_copy.short_description = '自定义按钮'
# # icon,参考element-ui icon与https://fontawesome.com
make_copy.icon = 'fas fa-audio-description'
# # 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
make_copy.type = 'danger'
make_copy.confirm = '你是否执意要点击这个按钮?'
admin.site.register(UserInfo)
admin.site.register(Publish)
5.simpleui集成监控大屏
# 从 gitee 上找到开源的前端页面 ---》集成到项目中即可 ---》 https://gitee.com/lvyeyou/DaShuJuZhiDaPingZhanShi?_from=gitee_search
# 本质就是前后端混合的项目
十九:drf回顾
# djangorestframework
-视图:执行流程多了东西
-两个视图基类:
-5个视图扩展类
-9个视图子类
-ViewSetMinxin,ModelViewSet,ReadOnlyModelViewSet
-路由
-三种写法
-自动生成(两个类)
-action装饰器
-序列化类
-字段类,字段属性(read_only,write_only)
-Serializer:每个字段都要写
-ModelSerializer:跟表有映射关系
-model,fields,extra_kwargs
-局部钩子,全局钩子
-save和update
-自定义序列化的字段:两种方式
-认证,权限,频率
-认证类,
-权限类:acl,rabc
-频率类
-局部使用和全局使用
-过滤,排序
-配置排序和过滤即可
-内置的
-第三方
-自己写的
-全局异常
-写个函数,配置一下
-接口文档
-md,word,第三方。。。。
-jwt:
-签发和认证
二十:考试
附件1:鸭子类型
# 指的是面向对向中,子类不需要显式的继承某个类,只要有某个的方法和属性,那就属于这个类
# 假设有个鸭子类 Duck 类,有两个方法,run,speak 方法
# 假设又有一个普通鸭子类 PDuck,如果它也是鸭子,它需要继承 Duck 类,只要继承了鸭子类,什么都不需要写,普通鸭子类的对象就是鸭子这种类型;如果不继承,普通鸭子类的对象就不是鸭子这种类型
#假设又有一个唐老鸭子类 TDuck,如果它也是鸭子,它需要继承 Duck 类,只要继承了鸭子类,什么都不需要写,唐老鸭子类的对象就是鸭子这种类型;如果不继承,唐老鸭子类的对象就不是鸭子这种类型
# python 不推崇这个,它推崇鸭子类型,指的是
不需要显式的继承某个类,只要我的类中有 run 和 speak 方法,我就是鸭子这个类
# 有小问题:如果使用 python 鸭子类型的写法,如果方法写错了,它就不是这个类型了,会有问题
# python为了解决这个问题:
-方式一:abc 模块,装饰后,必须重写方法,不重写就报错
-方式二:drf 源码中使用的:父类中写这个方法,但没有具体实现,直接抛异常
附件二:django的配置文件每个配置项的作用
# 必须大写才是配置项
# django 项目要启动,要先加载配置文件,如果配置文件报错,项目运行不起来
from pathlib import Path
# 项目根路径
BASE_DIR = Path(__file__).resolve().parent.parent
# 密钥,自动生成的,很复杂,django 中涉及到加密的都使用它,没有不行
SECRET_KEY = 'django-insecure-4i$6azd399fl$vh9%*7#oald(i&ik(5#i_-y)v&z3g+cxsd$@-'
# 如果是 True,项目是调试模式,好处是抛异常在浏览器直接能看到,如果路径不存在,也会提示有哪些路径
DEBUG = True
# 允许项目部署的地址(后期项目上线,这里写服务器的地址),debug 是 False,这个必须加,不加就报错
ALLOWED_HOSTS = ['*']
# 所有的app,django大而全,就是因为它提供了很多内置的 app
INSTALLED_APPS = [
'django.contrib.admin', # 后台管理 admin
'django.contrib.auth', # 权限,6 个表
'django.contrib.contenttypes',
'django.contrib.sessions',# session 认证相关 ---》django_session 表
'django.contrib.messages', # 消息框架(不了解)
'django.contrib.staticfiles',# 静态文件
'app01.apps.App01Config', # app01
'rest_framework',
]
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', #
'django.contrib.sessions.middleware.SessionMiddleware', # session 相关
'django.middleware.common.CommonMiddleware', # 把路径跟路由匹配,如果匹配不成功把路径加/ 再匹配,如果匹配成功,让该请求重定向到带/的地址
'django.middleware.csrf.CsrfViewMiddleware', # csrf相关
'django.contrib.auth.middleware.AuthenticationMiddleware', # 认证相关
'django.contrib.messages.middleware.MessageMiddleware', # 消息框架,学到 flask,闪现相同的功能
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# 根路由,所有请求进来,先去根路由匹配---》内部可能有路由分发
ROOT_URLCONF = 'drf_day08.urls'
# 模板相关
TEMPLATES = [
]
# 后期项目项目上线,uwsgi运行这个application,测试阶段使用manage.py 运行项目
WSGI_APPLICATION = 'drf_day08.wsgi.application'
# 数据库配置 【可以使用多数据库】
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# auth的认证相关
AUTH_PASSWORD_VALIDATORS = [
]
# 国际化
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# 静态文件相关
STATIC_URL = '/static/'
# 所有的自增主键都用 BigAutoField
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# drf 中的各项自定义配置
REST_FRAMEWORK = {
}
附件三:内置认证类,权限类,频率类
# 内置的认证类:
-BasicAuthentication
-RemoteUserAuthentication
-SessionAuthentication:session认证,建议自己写
-如果前端带着cookie过来,经过session的中间件,如果登录了,在request.user中就有当前登录用户
-drf没有限制是否登录
-加了这个认证,如果没有登录,就不允许往后访问了
-TokenAuthentication:jwt认证,建议自己写
-建议自己写
# 内置权限类
-BasePermission:所有的基类
-AllowAny:允许所有
-IsAuthenticated:是否登录,登录了才有权限
-认证类,如果登录了,返回(当前登录用户,None), 如果没有登录返回 None
-IsAdminUser:auth的user表中is_staff字段是否为True,对后台管理有没有权限
# 内置的频率类:
-BaseThrottle:所有的基类
-SimpleRateThrottle:咱们写频率类都继承它,少写代码
-AnonRateThrottle:如果你想按 ip 地址限制,只需要在配置文件中配置
'DEFAULT_THROTTLE_RATES': {
'anon': '3/m',
}
-UserRateThrottle:如果你想按用户 id 限制,只需要在配置文件中配置
'DEFAULT_THROTTLE_RATES': {
'user': '3/m',
}
附件四:接口文档
1.直接使用 word 或 md 编写
2.搭建本地接口文档管理平台、使用第三方平台(收费)
3.使用 django 自动生成接口文档(了解即可)
# 接口文档,需要有的东西
-描述
-地址
-请求方式
-请求编码格式
-请求数据详解(必填,类型)
-返回格式案例
-返回数据字段解释
-错误码
# 在公司里的写法
-1 直接使用 word 或者 md 编写
-2 使用接口文档平台,在接口文档平台录入(Yapi(百度开源的自己搭建),第三方平台(收费),自己开发接口文档平台)
-https://www.showdoc.com.cn/item/index
- 不想花钱,没有能力开发,就使用开源的YAPI,https://zhuanlan.zhihu.com/p/366025001
- https://www.liuqingzheng.top:3000/
输入邮箱:306334678@qq.com
输入密码:admin
-3 项目自动生成:
-swagger---》drf-yasg---》官方推荐使用
-coreapi----》咱们讲
-1 下载:pip3 install coreapi
-2 路由中配置:
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站点页面标题'))
]
-3 在视图类中加注释
-在类上加注释
-在类的方法上加注释
-在序列化类或表模型的字段上加 help_text,required。。。。
-4 在配置文件中配置
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
-5 访问地址:http://127.0.0.1:8000/docs
# 注意:
-直接在视图类中写注释
-在序列化类上写 help_text
-注释要写在
附件五:断点调试使用
# pycharm 软件
# 程序是 debug 模式运行,可以在任意位置停下,查看当前情况下变量数据的变化情况
# pycharm 来调试程序
-以 debug 形式运行
-在左侧空白处,点击加入断电 (红圈)
-step over 单步调试
-step into 进入到函数内部运行
-快速调到下一个断电,绿色箭头