drf框架
web应用模式
在开发web应用中有两种应用模式:
- 前后端不分离 ,客户端看到的内容和所有的页面效果都是服务端提供出来的,这种情况下,前端页面中会出现很多涉及到服务端的模板语法
- 前后端分离,把前端页面的效果(html,css,js分离到一个项目中,python服务端只需要返回数据即可)前端形成一个独立的网站,服务端形成一个独立的网站
api接口
服务端对接客户端的接口,市面上常用的接口服务框架有,restful、rpc、soap。
- rpc:远程过程调用,远程服务调用
服务端提供统一的请求数据的api地址:http://api.xxx.cn
客户端需要使用post请求
api地址加参数,可以调用后端的函数如:action=get_all_student&class=301&sex=1
优点:不需要关心当前操作是什么请求,也不需要操作url地址的编写,对接简单
缺点:接口多了,对应的函数名和参数就多了,前端在请求api接口的时候,就会比较难找容易出现重复的接口 - restful:资源状态转换
把后端所有的数据/文件都看作是资源
接口请求数据本质上来说就是对资源的操作了
web项目中操作资源,无非就是增删改查,所以要求在地址栏中声明要操作的资源是什么,然后通过http请求方法来说明对资源进行哪一种操作。
POST 添加 http://www.xxx.cn/api/students/
GET 获取 http://www.xxx.cn/api/students/
DELETE 删除某条信息 http://www.xxx.cn/api/students//
PUT 修改一个学生全部信息 http://www.xxx.cn/api/students//
PATCH 修改一个学生部分信息 http://www.xxx.cn/api/students//
优点:维护开发简单,可以保证后期的开发不会出现太多重复的接口
缺点:1.有部分接口不会有明确的增删改查的区分,会导致后期维护成本上升
2.因为restful把对于资源的操作都理解成了增删改查,建议使用http,所以restful接口天生局限于web开发。
restful api接口规范
REST全程Representational State Transfer
restful是一种定义web api接口的设计风格,尤其适用于前后端分离的应用模式中
这种风格是理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的url路径就表示这种要操作的数据资源
对于数据资源分别使用POST、DELETE、GET、UPDATE、等请求动作来表达对数据的增删改查
请求方法 | 请求地址 | 后端操作 |
---|---|---|
GET | /students | 获取所有学生 |
POST | /students | 增加学生 |
GET | /students/ | 获取主键为pk的学生 |
PUT/PATCH | /students/ | 修改主键为pk的学生 |
DELETE | /students/ | 删除主键为pk的学生 |
文档链接:http://www.runoob.com/w3cnote/restful-architecture.html
接口实施过程中,会存在幂等性,所谓幂等性是指客户端发起多次请求是否对于服务端里面的资源产生不同结果,如果多次请求,服务端返回的结果还是一样,则属于幂等接口,如果多次请求,服务端产生的结果不一样,则属于非幂等接口,在http请求中,get/put/patch/delete都属于幂等性接口,post属于非幂等接口
考虑幂等性,主要是接口操作的安全性问题
具体规范举例:
- 域名
应该尽量将API部署在专用域名下面
https://api.example.com
如果确定API很简单,不会有进一步的扩展,可以考虑放在主域名下面
https://www.example.org/api/ - 版本 Versioning
项目开发过程中都会对项目进行版本迭代,所以每一个版本的api接口肯定都有可能存在差异,因此应该将API本本号放入URL.
http://www.example.com/api/1.0/
http://www.example.com/api/1.1/
http://www.example.com/api/2.0/
另一种做法是,将版本号放在HTTP头部信息中,但是不如放在url中方便和直观
因为是不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URL,版本号可以在HTTP请求头信息的Accept字段进行区分
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0 - 路径 Endpoint
路径又称终点,表示API的具体网址,每个网闸代表一种资源(resource)
资源作为网址,只能有名词不能有动词,而且所用的名词往往于数据库的表明对应
不好的例子:
/getProducts
/listOrders
/retreiveClientByOrder?orderId=1
对于一个简介的结构,应该使用名词,此外利用HTTP方法可以分离网址中的资源名称的操作
GET /products : 将返回所有产品清单
POST /products : 将产品新建到集合
GET /products/4 : 将获取产品 4
PATCH(或)PUT /products/4 :将更新产品 4
API中的名词应该使用复数,无论是子资源还是所有资源
如:获取产品的API可以这样定义:
获取单个产品:http://127.0.0.1:8080/AppName/rest/products/1
获取所有产品: http://127.0.0.1:8080/AppName/rest/products - HTTP动词
对于资源的具体操作类型,有HTTP动词表示
常见的HTTP动词有下面四个(括号里对相应sql命令)- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- DELETE(DELETE):从服务器删除资源。
三个不常用的HTTP动词
- PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
举例:
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园(上传文件)
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
过滤信息 Filtering
如果记录数量很多,服务器不可能都将他们返回给用户,API应提供参数,过滤返回结果
下面是一些常见的参数,query_string 查询字符串,地址栏后面问号后面的数据,格式: name=xx&sss=xxx完整的URL地址格式:
协议://域名(IP):端口号/路径?查询字符串#锚点
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoos/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。状态码 Status Codes
http所有的请求 必有回应的协议1xx 表示当前本次请求还是持续,没结束
2xx 表示当前本次请求成功
3xx 表示当前本次请求成功,但是服务器进行代理操作/重定向
4xx 表示当前本次请求失败,主要是客户端发生了错误
5xx 表示当前本次请求失败,主要是服务器发生了错误
服务器向用户返回的状态码和提示信息,常见的有以下(括号中是该状态码对应HTTP动词)- 200 OK - [GET]:服务器成功返回用户请求的数据
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST - [GET/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 - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
- 507 数据存储出错,往往数据库操作错误出错,服务器就返回这个
- 错误处理 Error handling
如果状态码是4xx,服务器就应该向用户返回错误信息,一般来说,返回的信息中将error作为键名,出错信息作为值{
error: "Invalid API key"
}
- 返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。- GET /collection:返回资源对象的列表(数组)
- GET /collection/ID:返回单个资源对象(json)
- POST /collection:返回新生成的资源对象(json)
- PUT /collection/ID:返回完整的资源对象(json)
- DELETE /collection/ID:返回一个空文档(空字符串)
超媒体 Hypermedia API
RESTful API最好做到Hypermedia ,就是返回结果中提供链接,连向其他API方法,这样可以让用户不查文档也知道下一步应该做什么
比如,Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。
上面代码表示,服务器给出了提示信息,以及文档的网址。其他
服务器返回的数据格式,应该尽量使用JSON,避免使用XML
序列化
api接口开发中,最常见核心的就是序列化,所谓序列化就是把数据格式进行转换,序列化可以分为两段
序列化:把我们识别的数据转换成指定的格式提供给别人
如:在django中获取到的数据默认是模型对象,但是模型对象无法直接提供给前端或者别的平台使用,所以我们要把数据序列化,变成字符串或者json,提供给前端或者别的平台
反序列化:把别人提供的数据还原成我们需要的格式
如 :前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化成模型对象,这样我们才能把数据保存到数据库中
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的基础上进行的功能的扩展,根据请求的方式不同,直接定位到不同的函数中去执行。
如果是基于django编写restful API,很显然使用CBV的方式会更加简洁,因为restful规范中就是根据method不同来执行不同操作。
drf 对请求数据的再次封装
以前我们通过django开发项目时,视图中的request是 django.core.handlers.wsgi.WSGIRequest
类的对象,其中包含了请求相关的所有数据。
# Django FBV
def index(request):
request.method
request.POST
request.GET
request.body
# Django CBV
from django.views import View
class UserView(View):
def get(self,request):
request.method
request.POST
request.GET
request.body
而在使用drf框架时,视图中的request是rest_framework.request.Request
类的对象,其是又对django的request进行了一次封装,包含了除django原request对象以外,还包含其他后期会使用的其他对象。
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
def get(self, request, *args, **kwargs):
# request,不再是django中的request,而是又被封装了一层,内部包含:django的request、认证、解析器等。
return Response({"code": 1000, "data": "xxx"})
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
对象 = (request, 其他数据)
# rest_framework.request.Request 类
class Request:
"""
Wrapper allowing to enhance a standard `HttpRequest` instance.
Kwargs:
- request(HttpRequest). The original request instance. (django中的request)
- parsers(list/tuple). The parsers to use for parsing the
request content.
- authenticators(list/tuple). The authenticators used to try
authenticating the request's user.
"""
def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):
self._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
...
@property
def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data
def __getattr__(self, attr):
try:
return getattr(self._request, attr) # self._request.method
except AttributeError:
return self.__getattribute__(attr)
所以,在使用drf框架开发时,视图中的request对象与原来的有些不同,例如:
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views import View
from rest_framework.request import Request
class UserView(APIView):
def get(self, request, *args, **kwargs):
# 通过对象的嵌套直接找到原request,读取相关值
request._request.method
request._request.GET
request._request.POST
request._request.body
# 举例:
content-type: url-form-encoded
v1=123&v2=456&v3=999
django一旦读取到这个请求头之后,就会按照 {"v1":123,"v2":456,"v3":999}
content-type: application/json
{"v1":123,"v2":456}
request._request.POST
request._request.body
# 直接读取新request对象中的值,一般此处会对原始的数据进行一些处理,方便开发者在视图中使用。
request.query_params # 内部本质上就是 request._request.GET
request.data # 内部读取请求体中的数据,并进行处理,例如:请求者发来JSON格式,他的内部会对json字符串进行反序列化。
# 通过 __getattr__ 去访问 request._request 中的值
request.method
源码:
drf版本管理
在restful规范中要去,后端的API中需要体现版本。
drf框架中支持5种版本的设置。
URL的GET参数传递(*)
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": "v",
"DEFAULT_VERSION": "v1",
"ALLOWED_VERSIONS": ["v1", "v2", "v3"],
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.QueryParameterVersioning"
}
源码执行流程:
URL路径传递(*)
请求头传递
二级域名传递
在使用二级域名这种模式时需要先做两个配置:
- 域名需解析至IP,本地可以在hosts文件中添加
127.0.0.1 v1.wupeiqi.com
127.0.0.1 v2.wupeiqi.com
- 在django的settings.py配置文件中添加允许域名访问
ALLOWED_HOSTS = ["*"]
路由的namespace传递
以上就是drf中支持的5种版本管理的类的使用和配置。
全局配置
上述示例中,如果想要应用某种 版本 的形式,需要在每个视图类中定义类变量:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning
class UserView(APIView):
versioning_class = QueryParameterVersioning
...
如果你项目比较大,需要些很多的视图类,在每一个类中都写一遍会比较麻烦,所有drf中也支持了全局配置。
# settings.py
REST_FRAMEWORK = {
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning", # 处理版本的类的路径
"VERSION_PARAM": "version", # URL参数传参时的key,例如:xxxx?version=v1
"ALLOWED_VERSIONS": ["v1", "v2", "v3"], # 限制支持的版本,None表示无限制
"DEFAULT_VERSION": "v1", # 默认版本
}
访问URL:
http://127.0.0.1:8000/api/users/?version=v1
http://127.0.0.1:8000/api/users/?version=v2
http://127.0.0.1:8000/api/users/?version=v3
http://127.0.0.1:8000/api/admin/?version=v1
http://127.0.0.1:8000/api/admin/?version=v2
http://127.0.0.1:8000/api/admin/?version=v3
http://127.0.0.1:8000/api/v1/order/
http://127.0.0.1:8000/api/v2/order/
http://127.0.0.1:8000/api/v3/order/
底层源码实现
反向生成URL
在每个版本处理的类中还定义了reverse
方法,他是用来反向生成URL并携带相关的的版本信息用的,例如:
drf认证组件
对于认证功能的使用,可以定义一个类,继承BaseAuthentication,当继承之后就可以写两个方法authenticate和authentication_header,在视图类中应用认证功能的时候,只需要在类中定义一个类变量 authentication_classes = [写的认证类名,],那么这个类就可以使用认证功能了,authentication_classes里可以放多个认证类,drf会根据列表顺序逐一去执行authenticate方法,如果返回元组或抛出异常则终止后面认证类的执行,返回None,表示跳过该认证类,继续执行后面的认证类。
抛出异常AuthenticationFailed,表示认证失败内部还会执行 authenticate_header
将返回值设置给响应头 WWW-Authenticate
class Auth(BaseAuthentication):
def authenticate(self,request):
#写认证的逻辑处理代码
def authentication_header(self,request):
#当认证失败的时候会执行这里面的代码
return '404'
-----------------------------------------------------------------------
class OrderView(APIView):
authentication_classes=[Auth,] # 此试图类应用上认证功能,只有认证成功后,才能访问视图函数
def get(self,request,*args,**kwargs)
...
对于认证成功后此方法的return值的第一个值会赋值给 request.user,第二个值会赋值给request.auth
后面在视图类中需要用的时候直接通过request取值就可以
认证中返回None
对于认证类中的authenticate,只返回一个None的时候,表示跳过该认证类,执行下一个认证类,因在视图类中,authentication_classes这个类变量里面可以放多个认证类,如果所有的认证类都跳过了表示都没认证成功,会将request.user赋值为”AnonymousUser”也就是匿名对象,request.auth赋值为None,如果要修改AnonymousUser 和None的默认值可以在配置文件中进行配置
REST_FARMEWORK = {
"UNAUTHENTICATED_USER":lambda :None,
"UNAUTHENTICATED_TOKEN":lambda :None,
}
#此时request.user赋值为"None"也就是匿名对象,request.auth赋值为None
认证中返回None和None
对于认证类中返回了None None 表示认证通过,就不会再执行后面的认证类.
使用场景:
对于某个API,已认证和未认证的用户都可以用时,比如:
- 已认证用户,访问API返回该用户的视频播放记录的列表
- 未认证用户,访问API返回最新的视频列表
多个认证类
一般情况下,编写一个认证类就可以
当项目中可能存在多种认证方式时,就可以写多个认证类,比如项目认证支持:
- 在请求中传递token进行验证
- 请求携带cookie进行验证
- 请求携带jwt进行验证
- 请求携带加密的数据需要用特定算法解密,一般为app开发的接口都是有加密算法的
此时就可以写多个认证类
认证全局配置
在每个试图类中的类变量authentication_classes中可以定义认证类,如果不想在每个视图类中都定义,让他们使用一样的认证功能,其实在配置文件中也可以进行全局配置,这样所有的视图类的认证功能都会用配置文件中的。
如果在试图类中定义了authentication_classes则优先使用authentication_classes的认证,也就是类中的会覆盖配置文件中的
REST_FARMEWORK = {
"UNAUTHENTICATED_USER":lambda :None,
"UNAUTHENTICATED_TOKEN":lambda :None,
"DEFAULT_AUTHENTICATION_CLASSES":["xx.XX.类名","xx.XX.类名"],#默认使用的认证类
}
认证组件源码分析
drf权限组件
对于权限组件的使用,可以定义一个类,继承BasePermission,当继承之后就可以写两个方法has_permission和has_object_permission,在视图类中应用权限功能的时候,只需要在类中定义一个类变量 permission_classes = [写的权限类名,],那么这个类就可以使用认证功能了,permission_classes里可以放多个认证类,drf会根据列表顺序逐一去执行has_permission方法,如果返回True,则继续执行后续的权限类;如果有一个返回None或False,则抛出权限异常并停止后续权限类的执行。
认证,根据用户携带的token或者其他 获取当前用户信息
权限,读取认证中获取的用户信息,判断当前用户是否有权限访问,如:普通用户,管理员,超级用户,都是有不同权限的
class UserInfo(models.Model):
role_choices = ((1, "普通用户"), (2, "管理员"), (3, "超级管理员"),)
role = models.IntegerField(verbose_name="角色", choices=role_choices, default=1)
username = models.CharField(verbose_name="用户名", max_length=32)
password = models.CharField(verbose_name="密码", max_length=64)
token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)
多个权限类
当开发过程中需要用户同时具备多个权限(缺一不可)时,可以用多个权限类来实现。
权限组件内部处理机制:按照列表的顺序逐一执行 has_permission
方法,如果返回True,则继续执行后续的权限类;如果有一个返回None或False,则抛出权限异常并停止后续权限类的执行。
关于 has_object_permission
在视图中调用 get_object()时候就会执行has_object_permission方法,has_object_permission就是判断当前用户是否有权限访问当前对象。
当我们使用drf来编写 视图类时,如果是继承 APIView
,则 has_object_permission
不会被执行(没用),例如:
get_object源码如下:
权限全局配置
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES":["xxxx.xxxx.xx.类名","xxxx.xxxx.xx.类名",]
}
权限组件源码
drf限流组件
限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。
- 对于匿名用户,使用用户IP作为唯一标识。
- 对于登录用户,使用用户ID或名称作为唯一标识。
为每一个用户创建一个唯一标识,用标识为键,值为列表,放当前访问的时间戳,也就是时间。当请求过来时 会读取限制的配置,比如是1小时/5次,把当前请求的时间比如12:30减去1小时,然后去列表里从右边往左找,当有11:30的则踢除掉,因为过了限制时间了,然后把当前请求的时间放到列表最左边,剔除完成后看当前列表有几条记录,如果列表没有大于5,则表示还可以访问
缓存={
用户标识:[12:33,12:32,12:31,12:30,12,] 1小时/5次 12:34 11:34
}
当前的访问记录是放到缓存里面的,如果要用限流功能需要配置django的缓存配置
pip3 install django-redis
# settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache", #缓存放到redis
"LOCATION": "redis://127.0.0.1:6379", #redis ip 端口
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "qwe123", #redis 密码
}
}
}
在任意位置写一个限流类,继承 SimpleRateThrottle类,类中可以定义两个方法,get_cache_key,此方法的返回值就是唯一标识,throttle_failure,此方法是用来超出访问限制的时候抛出的异常,异常前端是可以看到的。
限流应用视图类的时候,可以在类中定义一个类变量 throttle_classes = [限流类的类名,]
from django.urls import path, re_path
from app01 import views
urlpatterns = [
path('api/order/', views.OrderView.as_view()),
]
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import exceptions
from rest_framework import status
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import cache as default_cache
class ThrottledException(exceptions.APIException):
#自定义的异常
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_code = 'throttled'
class MyRateThrottle(SimpleRateThrottle):
cache = default_cache # 访问记录存放在django的缓存中(需设置缓存)
scope = "user" # 构造缓存中的key
cache_format = 'throttle_%(scope)s_%(ident)s'
# 设置访问频率,例如:1分钟允许访问10次
# 其他:'s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day'
THROTTLE_RATES = {"user": "10/m"}
def get_cache_key(self, request, view):
if request.user:
ident = request.user.pk # 用户ID
else:
ident = self.get_ident(request) # 获取请求用户IP(去request中找请求头)
# throttle_u # throttle_user_11.11.11.11ser_2
return self.cache_format % {'scope': self.scope, 'ident': ident}
def throttle_failure(self):
wait = self.wait()
detail = {
"code": 1005,
"data": "访问频率限制",
'detail': "需等待{}s才能访问".format(int(wait))
}
raise ThrottledException(detail)
class OrderView(APIView):
throttle_classes = [MyRateThrottle, ]
def get(self, request):
return Response({"code": 0, "data": "数据..."})
多个限流类
本质,每个限流的类中都有一个 allow_request
方法,此方法内部可以有三种情况:
- 返回True,表示当前限流类允许访问,继续执行后续的限流类。
- 返回False,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
- 抛出异常,表示当前限流类不允许访问,后续限流类不再执行。
allow_request方法是执行的父类中的
限流全局配置
在配置文件中配置,如果在视图类中定义了则会覆盖掉配置文件中的
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES":["xxx.xxx.xx.限流类", ],
"DEFAULT_THROTTLE_RATES": {
"user": "10/m", #user 是表示那个限流类,也就是在限流类中定义的scope
"xx":"100/h"
}
}
限流源码
Django REST_Framework
Django REST_Framework是建立在Django基础之上的web应用开发框架,本质就是一个内置在django里面的子应用,可以快速的开发rest api接口应用
在REST_Framework中,提供了序列化器 对象Serialzier的定义,可以帮助我们简化序列化与反序列化的操作,还提供丰富的类视图,扩展类,视图集来简化视图的编写工作,REST_Framework还提供了认证,权限,限流,过滤,分页,接口文档等功能支持,REST_Framework提供了一个用于测试api接口的可视化web页面,可以游览器直接访问接口
restful就是一个接口开发的规范,不局限于django或者drf,即使不使用drf,也能实现符合restful规范的api接口
特点:
- 提供了定义序列化器serializer的方法可以快速根据 Django ORM或者其他库自动序列化或反序列化
- 提供了丰富的类视图、Mixin扩展类,简化视图的编写
- 丰富的定制层级,函数视图、类视图、视图集合到自动生成API,满足各种需求
- 多种身份认证和权限认证方式的支持 jwt json web token
- 内置限流系统
- 直观的API web 界面,方便调试
- 可扩展性,插件丰富
文档链接:
https://q1mi.github.io/Django-REST-framework-documentation/#django-rest-framework
https://github.com/encode/django-rest-framework/tree/master
drf框架执行流程:
环境安装依赖:
drf是以django扩展应用的方式提供的,所以可以直接利用已有的django环境无需重新创建,如果没有django环境就要先创建django
python (2.7,3.2以上)
Django (1.10,1.11,2.0以上)
# mkvirtualenv drfdemo -p python3
#创建虚拟环境
#pip install django==2.2.0 -i https://pypi.douban.com/simple
# 安装django
pip install djangorestframework -i https://pypi.douban.com/simple
#安装djangorestframework
pip install pymysql -i https://pypi.douban.com/simple
添加rest_framework应用
在setting.py中的INSTALLED_APPS中注册”rest_framework”,把drf框架注册到Django项目中
接下来就可以使用DRF提供的功能进行API接口的开发了,在项目中如果使用rest_framework框架实现API接口,主要是3步骤:
- 将请求的数据 如json格式,转换为模型类对象
- 通过模型类对象进行数据库操作,完成客户端请求的增删改查
- 将模型类对象转换为响应的数据 如json格式
体验drf完全简写代码过程
- 在django下创建子应用
python manage.py startapp students - 创建模型操作类
子应用的models.py文件中创建模型对象 ```python from django.db import models
创建一个模型类
class Student(models.Model):
#表字段声明
#字段名 = models.数据类型(字段约束)
name = models.CharField(null=False,max_length=32,verbose_name="姓名")
sex = models.BooleanField(default=True,verbose_name="性别")
age = models.IntegerField(verbose_name="年龄")
class_num = models.CharField(max_length=5,verbose_name="班级编号")
description = models.TextField(max_length=1000,verbose_name="个性签名")
#表信息
class Meta:
#设置表名
db_table="tb_students"
verbose_name="学生"
verbose_name_plural = verbose_name
#模型的操作方法
def __str__(self):
return self.name
<br />执行数据迁移(注意准备步骤,注册app,设置操作数据库驱动pymysql,setting中配置数据库信息)
- 创建序列化器<br />在students应用目录中新建serializers.py用于保存该应用的序列化器<br />创建一个StudentModelSerializer用于序列化和反序列化
```python
#创建序列化器,在视图中调用该类
from rest_framework import serializers
from .models import Student
class StudentModelSerializer(serializers.ModelSerializer):
#创建一个学生信息的序列化器 类
#模型序列化器相关声明
class Meta:
model = Student
fields ="all"
# model 指明该序列化器处理的数据字段从模型类studnet生成
# fields 指明该序列化器包含模型类中的哪些字段,all代表包含所有
- 编写视图
在students应用的views.py中创建视图 StudentsViewSet,这是一个视图集合 ```python from rest_framework.viewsets import ModelViewSet from .models import Student from .serializers import StudentModelSerializer
class StudentViewSet(ModelViewSet): queryset = Student.object.all() serializer_class = StudentModelSerializer
queryset 指明该视图集在查询数据时使用的数据集
serializer_class 指明该视图在进行序列化或者反序列化时使用的序列化器
- 定义路由<br />在students应用的urls.py中定义路由信息
```python
from . import views
from django.urls import path.re_path
from rest_framework.routers import DefaultRouter
#路由列表
urlpatterns = []
#可以处理视图的路由器
router = DefaultRouter()
#向路由器中注册视图集
router.register('studnets',views.StudnetviewSet)
#将路由器中的所有路由信息追加到django的路由列表中
urlpatterns += router.urls
- 网页访问应用
序列化器-Serializer
- 序列化,序列化器会把模型对象转换成字典,将来提供给视图经过response以后变成json字符串
- 反序列化,把客户端发送过来的数据,经过视图调用request以后变成python字典,序列化器可以把字典转换成模型
- 反序列化,完成数据校验功能和操作数据库
- serializer不是只能为数据库模型定义类,也可以为非数据库模型类的数据定义,serializer是独立于数据库之外的存在
序列化器使用阶段:
- 在客户端请求的时候,使用序列化器可以完成对数据的反序列化
- 在服务端响应时,使用序列化器可以完成对数据的序列化
序列化器支持的数据格式:
数据格式 | 字段构造方式 |
---|---|
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) |
RegeField | 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) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
选项参数:
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
max_value | 最小值 |
min_value | 最大值 |
通用参数:
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
定义序列化器
django REST framework 中的Serializer使用类来定义,必须继承rest_framework.serializers.Serializer
1.在应用中新建 serializers.py文件 用来存放序列化器类
import rest_framework import serializers
class StudentSerializer(serializers.Serializer):
#需要进行转换的字段,都写在该类中,接收来自模型的数据进行转换,模型中的name字段转换成CharField格式
name = serializers.CharField()
2.在视图中创建类
from django.views import View
from serializers import StudentSerializer #引入写的序列化器类
from django.http.response import HttpResponse
from students.models import Student #引入写的模型类,用于查询数据,传给序列化器
from django.http.response import JsonResponse
class StudentView(View):
def get(self,request):
#根据模型类获取所有的数据,也可以获取某条数据
student_list = Student.objects.all()
#实例化序列化器,创建序列化器对象
serializer = StudentSerializer(instance=student_list,data=,context=,many=True)
#instance 模型对象或者模型列表
#data 进行反序列化阶段使用的数据,数据来自于客户端的提交
#context 当需要从视图中转发数据到序列化器时,可以使用
#many 当instance参数为模型列表时,则many值必须为True,表示传递的参数是多个
print(serializer.data)
转换后的数据放在序列化器对象.data中
#【0rderedDict([(name:XXX)]),0rderedDict([(name:ccc)])】
#0rderedDict python内置的高级数据类型,有序字典
# from collection import 0rderedDict
print(student_list)
#<QuerySet [<studnet:xxx>,<Studnet:ccc>]>
return JsonResponse(serializer.data,safe=False)
#JsonResponse不识别列表,所以加上safe=False
创建Serializer对象
定义好Serializer类后,就可以创建Serializer对象了。
Serializer的构造方法为:
Serializer(instance=None, data=empty, **kwarg)
说明:
- 用于序列化时,将模型类对象传入instance参数
- 用于反序列化时,将要被反序列化的数据传入data参数
- 除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如
serializer = AccountSerializer(account, context={'request': request})
通过context参数附加的数据,可以通过Serializer对象的context属性获取。
- 使用序列化器的时候一定要注意,序列化器声明了以后,不会自动执行,需要我们在视图中进行调用才可以。
- 序列化器无法直接接收数据,需要我们在视图中创建序列化器对象时把使用的数据传递过来。
- 序列化器的字段声明类似于我们前面使用过的表单系统。
- 开发restful api时,序列化器会帮我们把模型数据转换成字典.
- drf提供的视图会帮我们把字典转换成json,或者把客户端发送过来的数据转换字典.
序列化-基本使用
1) 先查询出一个学生对象
from students.models import Student
student = Student.objects.get(id=3)
2) 构造序列化器对象
from .serializers import StudentSerializer
#如果是queryset就要加many=true 。StudentSerializer(instance=student,many=True)
serializer = StudentSerializer(instance=student)
3)获取序列化数据
通过data属性可以获取序列化后的数据
serializer.data
# {'id': 4, 'name': '小张', 'age': 18, 'sex': True, 'description': '猴赛雷'}
序列化器进行反序列化验证数据
数据验证
开发中,用户的数据是不可信的
使用序列化器进行反序列化时,需要对数据进行验证后,才能获取成功的数据或保存成模型类对象
在获取反序列化的客户端数据前,必须在视图中调用序列化对象的is_valid()方法,序列化器内部是在is_valid方法内部调用验证选项和验证方法进行验证,验证成功返回True,否在返回False
验证失败,可以通过序列化器对象的errors属性获取错误信息,返回字典,包含了字段和字段的错误提示,如果是非字段错误,可以通过修改REST Farmework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名
验证成功,可以通过序列化器对象的validated_data属性获取数据
在定义序列化器时,指明每个字段的序列化类型和选项参数,本身就是一种验证行为
反序列化验证数据案例
客户端传数据过来使用反序列化对数据进行验证
- 创建新的子应用
- 创建反序列化器 ```python 在子应用的目录下新建:serializers.py文件
from rest_framework import serializers
字段验证函数 只能验证一个字段数据,验证成功要返回验证成功后的数据
def check_class_num(data): if data ==”404”: raise serializers.ValidationError(“没有404的班级”) return data
class StudentSerializer(serializers.Serializer): name = serializers.CharField(max_length=20,required=True)
#定义的规则检测name是否是字符串类型,并且最大长度20,是必填项,字段内置验证
age = serializers.IntegerField(max_value=100,)
sex = serializers.BooleanField(allow_null=True,default=1)
#validators=[check_class_num],是使用字段验证函数对该字段进行验证
class_num = serializers.CharField(required=True,validators=[check_class_num])
description = serializers.CharField(allow_null=True,allow_blank=True)
#字段验证方法 :validate() 验证所有字段,validate_字段名()验证单个字段,字段名必须是上面定义的名字
def validate_name(self,attrs):
if attrs =="王":
#抛出错误
raise serializers.ValidationError("错误信息")
#每次验证完成以后,必须要返回数据,返回的就是传过来的数据,当然可以修改后返回,不返回会数据丢失
return attrs
def validate(self,attrs):
#attrs接收的是,视图中实例化序列化器对象的时候传入的数据,此案例中是data
if attrs.get("age") >60 and attrs.get('sex')==1:
raise serializers.ValidationError("错误信息")
return attrs
单个字段的校验优先级比多个字段的优先级高
“”” 视图中的序列化器对象在传递数据的时候 ,必填字段是必须传递的也就是我们定义的规则中有required=True的必须传,否则会抛出验证异常,我们可以使用partial参数来允许部分字段的更新,
如: 在视图中实例化序列化器对象的时候添加该参数: serializer = StudentSerializer(studnet,data,partial=True) partial=True 是设置序列化器只针对客户端提交的字段进行验证,没有提交的字段即使有验证选项和方法也不进行验证,客户端没提交的那些就默认通过,当然数据库中的字段规则设置也要是允许该字段为空否则会报错
“””
在视图文件中编写类:
from django.Views import View from django.http.response import HttpResponse
class StudentView(View): def post(self,request): print(request.body) import json data = json.loads(request.body)
#反序列化
#1.实例化序列化器对象
serialiser = StudentSerializer(data = data)
#2.调用验证方法,验证客户端传来的数据和序列化器类中的定义规则是否符合,通过返回True,失败返回False,raise_exception=True是指如果验证失败,以后的代码就不执行了,直接抛出错误
"""
is_valid实际上内部执行了三种不同的验证方式
1.先执行字段内置的验证选项
2.再执行字段验证函数
3.最后执行validate自定义验证方法,包括validate_字段,validate
"""
ret = serializer.is_valid(raise_exception=True)
#验证成功后,将通过验证的信息存放在validated_data中
serializer.validated_data
#验证失败的错误信息存在
serializer.errors
return HttpResponse("OK")
<a name="9f7e9f84"></a>
### 使用序列化器进行反序列化阶段操作数据库
数据保存
通过序列化器完成数据的更新或者添加,把视图中对于模型中的操作代码移出视图,放入序列化器
前面的验证数据成功后,可以使用序列化器完成数据反序列化的过程,这个过程可以把数据转成模型类对象
可以通过create()和update()两个方法来实现,是定义在序列化器类中的,必须是create和update名
```python
''#序列化器文件中
from .models import Student
class StudentSerializer(serializers.Serializer):
def create(self,validate_data):
#添加数据
#validate_data接收的是数据验证成功后传过来的数据,自动传的
#models语法进行数据库的操作
ret = Studnet.objects.create(
**validated_data
)
return ret
#把添加的结果返回
#接下来就可以在视图中使用 serializer.save(),会自动调用序列化器内的create()方法或者update()方法
def update(self,instance,validated_data):
#更新数据
#instances是更新的模型是哪个,也就是models类是哪个,validated_data是更新的数据]
#validated_data传递过来几个参数就要更新几个参数
#validated_data是验证成后的数据,都是自动传参的
instance.name = validated_data.get("name")
instance.age = validated_data.get("age")
#这是模型的save和序列化中的save是不一样的
instance.save()
return ret
#视图文件中
from django.Views import View
from django.http.response import HttpResponse
class StudentView(View):
def put(self,request):
"""修改数据"""
import json
data = json.loads(request.body)
id = data.get('id')
student = Student.objects.get(pk=id)
#创建序列化器对象,此时数据是还没有更新的
serializer = SudentSerializer(instance=student,data=data)
serializer.is_valid(raise_exception=True)
#会调用序列器的update方法,具体的更新在update方法中写
#serializer.save()会根据我们是否传入instance来判断是调用create还是update,传入了instance则是updata,没传则是create,返回值是新增的数据的对象
serializer.save()
# 新增之后的一个对象(内部调用serializer进行序列化)
"""
在对序列化器进行save保存时,可以传递额外的数据,这些数据可以在create()和update()中的validated_data中获取到
如:
request.user是django中记录当前登陆用户的模型对象
serializer.save(owner=request.user)
"""
序列化器操作数据库附加参数说明
- 对序列化器进行save()保存时,可以额外传递数据,这些数据可以在create()和update()中的validated_data中获取到
可以传递任意参数到数据保存方法中
如:request.user 是django中记录当前登录用户的模型对象
serializer.save(owner=request.user)
- 默认序列化器必须传递所有必填字段[required=True],否则会抛出异常验证,但是可以使用partial参数来允许部分字段更新
#update BookInfo with partial data
partial =True 设置序列化器只是针对客户端提交的字段进行验证,没有提交的字段,即使有验证选项或方法也不进行验证,设置此字段的时候要在模型类中定义的字段要允许字段为空,也就是定义的字段要有null=True
serializer = BookInfoSerializer(book,data=data,partial=True)
模型类的序列化器
如果我们想要使用的序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类
ModelSerializer与常规的Serializer相同,但是提供了
- 基于模型类自动生成一系列序列化器字段
- 基于模型类自动为Serializer生成validators,如unique_together
- 包含默认默认的create()和update()的实现
定义模型类序列化器
class BookInfoSerializer(serializers.ModelSerializer):
#图书数据序列化器
#也可以定义字段声明
token = serializers.CharField(read_only=True,default='abc')
#source就是获取数据库的对象然后执行 对象.get_level_display()
level_text = serializers.CharField(source='get_level_display')
#source就是获取数据库的对象然后执行 对象.depart.title
depart = serializers.CharField(source='depart.title')
#一些字段想自定义展示的样式可以使用serializers.SerialzerMethodField()
#对于展示的具体样式要定义一个钩子方法格式是 get_字段名 也就是 get_roles
#只有SerialzerMethodField有这个钩子方法
roles = serializers.SerialzerMethodField()
extra = serializers.SerialzerMethodField()
"""
source原理,如果有点的话内部通过反射进行 对象.xx.xx
对于source='depart.title'来说,我们从数据库中取到每一个对象后都会执行
对象.depart.title,最后drf会校验是否是可以加()执行的,如果可以执行就执行,不能执行 就不执行
"""
#模型声明
class Meta:
model=BookInfo
#调用模型所有字段
fields = '__all__'
#fields = ["id","name","age"]
#read_only_fields表示设置模型中那些字段属于只读类型,对于不在模型中的字段无效
read_only_fields=["id",'name']
#添加额外的属性,相当于定义的字段声明
extra_kwargs={
'token':{'read_only':True},
'age':{"min_value":1."max_value":100}
}
def get_roles(self,obj):
#obj就是当前要序列化的那个对象
data_list = obj.roles.all()
return [model_to_dict(item,['id','title']) for item in data_list]
def get_extra(self,obj):
return 666
"""
model 指明参考那个模型类
fields 指明为模型类的那些字段生成
"""
序列化器类的嵌套
序列化器存在嵌套的情况需要将嵌套的序列化器字段增加read_only=True属性,如果不增加read_only=True属性,就必须要重写create方法和update方法,这是因为对于多对多的情况下需要存数据到关联表中,drf的save()只能对单表或外键关系进行保存,create方式是指在post视图下也就是新增加数据的情况下我们用save()的时候会去调用cerate方法进行数据的保存,update方法是指当更新数据的时候save()就会调用update方法,不仅要增加这两个方法,对于嵌套序列化器的里面的id字段要增加read_only=False,因为需要用对象的id来进行关联数据的增加。
指定字段
- 使用fields来明确字段,
__all__
表名包含所有字段,也可以写明具体哪些字段,如
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
fields = ('id', 'btitle', 'bpub_date')
- 使用exclude可以明确排除掉哪些字段
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
exclude = ('image',)
- 显示指明字段,如:
class HeroInfoSerializer(serializers.ModelSerializer):
hbook = BookInfoSerializer()
class Meta:
model = HeroInfo
fields = ('id', 'hname', 'hgender', 'hcomment', 'hbook')
- 指明只读字段
可以通过read_only_fields指明只读字段,只有在序列化的时候才给用户返回回去。即仅用于序列化输出的字段
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
read_only_fields = ('id', 'bread', 'bcomment')
添加额外参数
我们可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数
class BookInfoSerializer(serializers.ModelSerializer):
"""图书数据序列化器"""
class Meta:
model = BookInfo
fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
extra_kwargs = {
'bread': {'min_value': 0, 'required': True},
'bcomment': {'min_value': 0, 'required': True},
}
# BookInfoSerializer():
# id = IntegerField(label='ID', read_only=True)
# btitle = CharField(label='名称', max_length=20)
# bpub_date = DateField(allow_null=True, label='发布日期', required=False)
# bread = IntegerField(label='阅读量', max_value=2147483647, min_value=0, required=True)
# bcomment = IntegerField(label='评论量', max_value=2147483647, min_value=0, required=True)
对于使用postman提交数据的时候如果只是列表或者字典等只能使用json格式进行提交,无法使用form-data这种格式
序列化器源码分析
序列化的底层源码实现有别于上述其他的组件,序列化器相关类的定义和执行都是在视图中被调用的,所以源码的分析过程可以分为:定义类、序列化、数据校验。
源码1:序列化过程
源码2:数据校验过程
http请求与响应
drf除了在数据序列化部分简写代码以外,还在试图中提供了简写操作,所以在django原有的django.views.View类基础上,drf封装了多个视图子类提供使用
Django REST Framework提供的试图的主要作用:
- 控制序列化器的执行(检验,保存,转换数据)
- 控制数据库查询的执行
- 调用请求类和响应类,这两个类也是由drf帮我们再次扩展了一些功能类。
Request
rest_framework.request.Request
REST Framework传入视图的request对象不再是Django默认的HTTP Request对象,而是在REST Framework提供的扩展了HTTP Request类的Request类的对象,在drf中原生的django的http请求对象,通过request._request调用
REST Framework提供了Parser解析器,在接受请求后会自动根据Content-Type指明的请求数据类型如json 表单等将请求数据进行parse解析,解析为Query Dict对象保存到Request对象中
Request对象的数据是自动根据前端发送数据的格式进行解析之后的结构
无论前端发生的是哪一种数据格式,我们都可以统一的方式读取数据
常用属性
- .data
request.data返回解析之后的请求体数据,类似于django中标准的request.POST和request.FIELS属性,但是提供如下特性。
- 包含了解析之后的文件和非文件数据
- 包含了对POST PUT PATCH请求方式解析后的数据
- 利用了REST Framework的parse解析器,不仅支持表单类型数据,也支持json数据
- 相当于drf的request.data替代了 django的request.POST,request.FILES,request.body、
- .query_params
request.query_params返回解析之后的查询字符串数据,是字典形式的 query dict,字典形式的我们可以使用.get()和.getlist()进行取值,如:一个键名重复出现多次的时候 get只能查出最后一个值 getlist可以获取所有的值是一个列表
request.query_params.get(“字典中的键”)
request.query_params.getlist(“字典中的键”)
request.query_params
与Django原生的request.GET
相同,只是更换了更正确的名称而已。
Response
rest_framework.response.Response
REST framework 提供了一个响应类Response,使用该类构造响应对象时,响应的具体数据内容会被转换(renderer渲染器)成符合前端需求的类型
REST framework 提供了Renderer渲染器,用来根据请求头中的Accept(接受数据类型声明)来自动转换响应数据到对应格式,如果前端请求中未生名Accept,则会采用默认的方式处理响应数据,可以通过配置来修改默认响应格式[就是Renderer能通过请求中的Accept查询出客户端支持和希望的数据类型,把视图的结果以客户端能识别的格式返回]
可以在rest_framework.settings.py查找所有的drf默认配置项
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器API渲染器
)
}
构造方式
Response(data, status=None, template_name=None, headers=None, content_type=None)
"""
data数据不是要renderer处理后的数据,只传递python的内置类型数据就可以,REST Framework 会使用renderer渲染器处理data
data不能是复杂的数据结构,如Django的模型类对象,对于这样的数据可以使用Serializer序列化器进行序列化后,再传递给data
参数说明
data 为响应准备的序列化处理后的数据
status 状态码默认是200
template_name 模板名称 如果使用HTMLRenderer 时需指明
headers 存放响应头信息的字典
content_type 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数。
"""
from rest_framework .Views import APIView
from rest_framework .response import Response
class StudentAPIView(APIView):
def get(self,request):
response = Response("ok")
#设置cookie
response.set_cookie("username","li")
#设置响应头信息
response['comm']='oldhy'
return response
#使用 rest_framework .response.Response的视图必须继承APIView,否则无法实现相应的效果
常用属性
- .data
传给response对象的序列化后,但尚未render处理的数据
- .status_code
状态码的数字
- .content
经过renderer处理后的响应数据
状态码
方便设置状态码,REST framewrok在rest_framework.status
模块中提供了常用状态码常量。
- 信息告知 - 1xx
HTTP_100_CONTINUE
HTTP_101_SWITCHING_PROTOCOLS
- 成功 - 2xx
HTTP_200_OK
HTTP_201_CREATED
HTTP_202_ACCEPTED
HTTP_203_NON_AUTHORITATIVE_INFORMATION
HTTP_204_NO_CONTENT
HTTP_205_RESET_CONTENT
HTTP_206_PARTIAL_CONTENT
HTTP_207_MULTI_STATUS
- 重定向 - 3xx
HTTP_300_MULTIPLE_CHOICES
HTTP_301_MOVED_PERMANENTLY
HTTP_302_FOUND
HTTP_303_SEE_OTHER
HTTP_304_NOT_MODIFIED
HTTP_305_USE_PROXY
HTTP_306_RESERVED
HTTP_307_TEMPORARY_REDIRECT
- 客户端错误 - 4xx
HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
HTTP_402_PAYMENT_REQUIRED
HTTP_403_FORBIDDEN
HTTP_404_NOT_FOUND
HTTP_405_METHOD_NOT_ALLOWED
HTTP_406_NOT_ACCEPTABLE
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
HTTP_408_REQUEST_TIMEOUT
HTTP_409_CONFLICT
HTTP_410_GONE
HTTP_411_LENGTH_REQUIRED
HTTP_412_PRECONDITION_FAILED
HTTP_413_REQUEST_ENTITY_TOO_LARGE
HTTP_414_REQUEST_URI_TOO_LONG
HTTP_415_UNSUPPORTED_MEDIA_TYPE
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
HTTP_417_EXPECTATION_FAILED
HTTP_422_UNPROCESSABLE_ENTITY
HTTP_423_LOCKED
HTTP_424_FAILED_DEPENDENCY
HTTP_428_PRECONDITION_REQUIRED
HTTP_429_TOO_MANY_REQUESTS
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS
- 服务器错误 - 5xx
HTTP_500_INTERNAL_SERVER_ERROR
HTTP_501_NOT_IMPLEMENTED
HTTP_502_BAD_GATEWAY
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
HTTP_507_INSUFFICIENT_STORAGE
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED
http请求响应状态码使用案例
1.新建Django项目创建app
2.注册路由 注册应用
3. 在试图文件中编写代码
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class Student1APIView(APIView):
def get(self,request):
#使用Response进行返回数据
response = Response("ok")
#设置cookie
response.set_cookie("username","li")
#设置响应头信息
response['comm']='oldhy'
return response
def get(self,request):
#使用request提取数据
data = request.data
new_data = request.query_params.get("用户传过来的数据字典形式的键")
#使用Response返回状态码
return Response(status=status.HTTP_201_CREATED)
drf 视图
Django REST Framework 提供的视图的主要作用有:
- 控制序列化器的执行 (检验 保存 转换数据)
- 控制数据库查询的执行(数据库的删除 查询代码写在视图中 更新和添加写在序列化器中)
REST Framework 提供了众多的通用视图基类与扩展类,用来简化视图的编写
基类
APIView
rest_framework.views.APIView
APIView是REST Framework提供的所有视图的基类,继承自Django的View父类
drf的APIView在继承django view的基础上新增了,在请求到来时,新增了:免除csrf、请求封装、版本、认证、权限、限流的功能。
- 传入到视图方法中的是REST Framework的Request对象,而不是Django的HttpRequest对象
- 视图方法可以返回REST Framework 的Response对象,视图会为响应数据设置(render)符合前端要求的格式
- 任何APIException异常都会被捕获到,并且处理成合适的响应信息
- 重写了as_view(),在进行dispatch()路由分发前,会对http请求进行身份认证 权限检查 访问流量控制
支持定义的类属性
- authentication_classes 列表或者元组 ,身份认证类
- permissoin_classes 列表或元组 权限检查类
- throttle_classes 列表或元组 流量控制类
在APIView中仍以常规的类视图定义方法实现get() post()或者其他请求的方法
举例:
# Create your views here.
"""APIView是drf里面提供的所有视图类的父类
APIView提供的功能/属性/方法是最少的,所以使用APIView基本类似我们使用django的View
"""
"""
GET /students/ 获取多个学生信息
POST /students/ 添加一个学生信息
GET /students/<pk>/ 获取一个学生信息
PUT /students/<pk>/ 修改一个学生信息
DELETE /students/<pk>/ 删除一个学生信息
"""
from rest_framework.views import APIView
from students.models import Student
from .serializers import StudentModelSerializer
from rest_framework.response import Response
from rest_framework import status
class Student1APIView(APIView):
def get(self,request):
"""获取所有学生信息"""
# 1. 获取学生信息的数据模型
student_list = Student.objects.all()
# 2. 调用序列化器
serializer = StudentModelSerializer(instance=student_list, many=True)
# 3. 返回数据
return Response(serializer.data)
def post(self,request):
"""添加一个学生信息"""
# 1. 调用序列化器对用户提交的数据进行验证
serializer = StudentModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# 2. 调用序列化器进行数据库操作
instance = serializer.save() # save()方法返回的是添加成功以后的模型对象
serializer = StudentModelSerializer(instance=instance)
# 3. 返回新增数据
return Response(serializer.data, status=status.HTTP_201_CREATED)
class Student2APIView(APIView):
def get(self,request,pk):
"""获取一个学生信息"""
# 1. 根据pk获取模型对象
student = Student.objects.get(pk=pk)
# 2. 序列化器转换数据
serializer = StudentModelSerializer(instance=student)
# 3. 响应数据
return Response(serializer.data)
def put(self,request,pk):
"""修改一个学生信息"""
# 1. 通过pk查询学生信息
student = Student.objects.get(pk=pk)
# 3. 调用序列化器对客户端发送过来的数据进行验证
serializer = StudentModelSerializer(instance=student, data=request.data)
serializer.is_valid(raise_exception=True)
# 4. 保存数据
instance = serializer.save() #?这里实际上调用的序列化器里面的update方法
serializer = StudentModelSerializer(instance=instance)
# 5. 返回结果
return Response(serializer.data, status=status.HTTP_201_CREATED)
def delete(self, request, pk):
# 1. 通过pk查询学生信息
Student.objects.get(pk=pk).delete()
return Response({"message":"ok"}, status=status.HTTP_204_NO_CONTENT)
#路由分发
urlpatterns = [
path('asview', 视图中的类名.as_view())
]
as_view()可以根据客户端的请求方法自动去视图中的类去匹配相应的方法名,并执行该方法,如客户端使用的get方法进行请求,则匹配到的是视图类中的get方法
GenericAPIView
通过视图类主要作用就是把视图中独特的代码抽取出来,让视图方法中的代码更加通用,方便把通用代码进行简写
rest_framework.generics.GenericAPIView
继承自APIView,主要增加操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持,通常在使用时可以搭配一个或多个Minxin扩展类
GenericAPIView类提供的关于序列化器使用的属性与方法
- serializer_class 属性
指明视图使用的序列化器是哪一个
- get_serializer_class(self)方法
当出现一个类视图中调用多个序列化器时候,可以通过条件判断在get_serializer_class方法中返回不同序列化器的类名,就可以让视图方法执行不同的序列化器对象了
返回序列化器类,默认返回的是serializer_class可以重写get_serializer_class(self)方法
如:
def get_serializer_class(self):
if self.request.method == 'get':
return getSerializer
return postSerializer
- get_serializer(self,args,*kwargs)方法
返回值是序列化器对象,就是返回get_serializer_class中返回的那个序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,可以直接调用该方法
注意,该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request,format,view这个三个数据对象可以在定义序列化器时使用
requets: 当前视图的请求对象
view:当前请求的类视图对象
format:当前请求期望返回的数据格式
GenericAPIView类提供的关于数据库查询的属性与方法
- query_set属性
指明使用的数据查询集合
- get_queryset(self)方法
返回视图使用的查询集,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回queryset属性,可以重写。
如:
def get_queryset(self):
user = self.request.user
return user.accounts.all()
- get_object(self)方法
返回单个视图模型类对象,主要是用来给Mixin扩展类使用
在视图中可以调用该方法获取单条详情信息的模型类对象
如详情信息的模型类对象不在会返回404
该方法会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问
如:
# url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()),
class BookDetailView(GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
book = self.get_object() # get_object()方法根据pk参数查找queryset中的数据对象
serializer = self.get_serializer(book)
return Response(serializer.data)
- pagination_class属性
指明分页控制类
- fiter_backends属性
指明过滤控制后端
GenericAPIView属性方法案例
案例1:
from rest_framework.response import Response
from rest_framework import status
from rest_framework.generics import GenericAPIView
from students.models import Student
from students.serializers import StudentModelSerializer,Student2ModelSerializer
class Student1APIView(GenericAPIView):
# 用于指定当前视图类中使用的默认序列化器
serializer_class = StudentModelSerializer
# 用于指定当前视图类中使用的数据集
queryset = Student.objects.all()
# get_serializer_class 这个方法主要的作用就是提供给开发者需要在同一个视图类中调用多个序列化器时进行重写时使用的.
def get_serializer_class(self):
if self.request.method.lower() == "get":
return Student2ModelSerializer
else:
return StudentModelSerializer
def get(self,request):
"""获取多条"""
# serializer = self.serializer_class(instance=self.queryset.all(),many=True)
serializer = self.get_serializer(instance=self.get_queryset(),many=True)
return Response(serializer.data)
def post(self,request):
"""添加信息"""
serialzier = self.get_serializer(data=request.data,context={})
serialzier.is_valid(raise_exception=True)
serialzier.save()
return Response(serialzier.data)
class Student2APIView(GenericAPIView):
serializer_class = StudentModelSerializer
# 面向对象中, 类属性的值不能出现变量赋值的情况,一定要具体的代码逻辑或者具体值
queryset = Student.objects.all()
def get(self,request,pk):
"""获取一条数据"""
# serializer = self.serializer_class(instance=self.queryset.get(pk=pk))
serializer = self.get_serializer(instance=self.get_object())
return Response(serializer.data)
def put(self,request,pk):
serializer = self.get_serializer(instance=self.get_object(), data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def delete(self,request,pk):
self.get_object().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
案例2:
from rest_framework.generics import GenericAPIView
from students.models import Student
from .serializers import StudentModelSerializer, StudentModel2Serializer
from rest_framework.response import Response
class StudentsGenericAPIView(GenericAPIView):
# 本次视图类中要操作的数据[必填],就是模型类查询出的数据
queryset = Student.objects.all()
# 本次视图类中要调用的默认序列化器[选填],当类继承GenericAPIView的时候必须声明serializer_class
serializer_class = StudentModelSerializer
def get(self, request):
"""获取所有学生信息"""
serializer = self.get_serializer(instance=self.get_queryset(), many=True)
return Response(serializer.data)
def post(self,request):
data = request.data
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
serializer = self.get_serializer(instance=instance)
return Response(serializer.data)
class StudentGenericAPIView(GenericAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get_serializer_class(self):
"""重写获取序列化器类的方法"""
if self.request.method == "GET":
return StudentModel2Serializer
else:
return StudentModelSerializer
# 在使用GenericAPIView视图获取或操作单个数据时,视图方法中的代表主键的参数最好是pk
def get(self,request,pk):
"""获取一条数据"""
serializer = self.get_serializer(instance=self.get_object())
return Response(serializer.data)
def put(self,request,pk):
data = request.data
serializer = self.get_serializer(instance=self.get_object(),data=data)
serializer.is_valid(raise_exception=True)
serializer.save()
serializer = self.get_serializer(instance=self.get_object())
return Response(serializer.data)
序列化器类:
from rest_framework import serializers
from students.models import Student
class StudentModelSerializer(serializers.ModelSerializer):
class Meta:
model= Student
fields = "__all__"
class StudentModel2Serializer(serializers.ModelSerializer):
class Meta:
model= Student
fields = ("name","class_null")
扩展类
ListModeMixin
列表视图扩展类,提供了list(request,args,*kwargs)方法快速实现列表视图,返回200状态码
该Mixin的list方法会对数据进行过滤和分页
源代码:
class ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
# 过滤
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)
案例:
from rest_framework.mixins import ListModelMixin
class BookListView(ListModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request):
#提供的list方法
return self.list(request)
CreateModelMixin
创建视图扩展类,提供create(request,args,*kwargs)方法快速实现创建资源的视图,成功返回201状态码
如果序列化器对前端发送的数据验证失败,返回400错误
源码:
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
# 获取序列化器
serializer = self.get_serializer(data=request.data)
# 验证
serializer.is_valid(raise_exception=True)
# 保存
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
案例:
class BookDetailView(RetrieveModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def post(self, request):
return self.create(request)
RetrieveModelMixin
详情视图扩展类,提供retrieve(request,args,*kwargs)方法可以快速实现返回一个存在的数据对象
如果存在,返回200,否则返回404
源码:
class RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
# 获取对象,会检查对象的权限
instance = self.get_object()
# 序列化
serializer = self.get_serializer(instance)
return Response(serializer.data)
案例:
class BookDetailView(RetrieveModelMixin, GenericAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
def get(self, request, pk):
return self.retrieve(request)
UpdateModelMixin
更新视图扩展类,提供update(request,args,*kwargs)方法,可以快速实现更新一个存在的数据对象
同时也提供partial_update(request,args,*kwargs)方法,可以实现局部更新
成功返回200,序列化器校验数据失败时,返回400错误
源码:
class UpdateModelMixin(object):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
案例:
class Student4APIView(UpdateModelMixin,GenericAPIView):
serializer_class = StudentModelSerializer
queryset = Student.objects.all()
def put(self,request,pk):
return self.update(request,pk)
DestroyModelMixin
删除视图扩展类,提供destroy(request,args,*kwargs)方法,可以快速实现删除一个存在的数据对象
成功返回204 不存在返回404
源码:
class DestroyModelMixin(object):
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
案例:
class Student4APIView(DestroyModelMixin,GenericAPIView):
serializer_class = StudentModelSerializer
queryset = Student.objects.all()
def delete(self,request,pk):
return self.destroy(request,pk)
扩展类综合案例:
"""GenericAPIView结合视图扩展类实现api接口"""
from rest_framework.mixins import ListModelMixin,CreateModelMixin
class Students2GenericAPIView(GenericAPIView,ListModelMixin,CreateModelMixin):
# 本次视图类中要操作的数据[必填]
queryset = Student.objects.all()
# 本次视图类中要调用的默认序列化器[选填]
serializer_class = StudentModelSerializer
def get(self, request):
"""获取多个学生信息"""
return self.list(request)
def post(self,request):
"""添加学生信息"""
return self.create(request)
from rest_framework.mixins import RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin
class Student2GenericAPIView
(GenericAPIView,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# 在使用GenericAPIView视图获取或操作单个数据时,视图方法中的代表主键的参数最好是pk
def get(self,request,pk):
"""获取一条数据"""
return self.retrieve(request,pk)
def put(self,request,pk):
"""更新一条数据"""
return self.update(request,pk)
def delete(self,request,pk):
"""删除一条数据"""
return self.destroy(request,pk)
GenericAPIView的视图子类
由视图扩展类和通用视图类还可以进行组合,产生新的视图这些视图子类,可以让我们开发基本的API接口变得更加简单
CreateAPIView
提供post方法
继承自 GenericAPIView、CreateModelMixin
ListAPIView
提供 get 方法
继承自:GenericAPIView、ListModelMixin
RetrieveAPIView
提供 get 方法
继承自: GenericAPIView、RetrieveModelMixin
DestoryAPIView
提供 delete 方法
继承自:GenericAPIView、DestoryModelMixin
UpdateAPIView
提供 put 和 patch 方法
继承自:GenericAPIView、UpdateModelMixin
RetrieveUpdateAPIView
提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
RetrieveUpdateDestoryAPIView
提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin
案例 :
视图文件中:
class Student6APIView(RetrieveUpdateDestroyAPIView):
serializer_class = StudentModelSerializer
queryset = Student.objects.all()
#在想使用哪些方法的时候直接继承该类就可以了,该类的基类中帮我们提供了处理代码,我们是需要使用就可以了,但是要指明使用的序列化器和处理的数据集
视图集VIewSet
为了让视图代码变得更加简短,drf提供了视图集,视图集允许开发者自定义类视图的方法名,还允许为一个类分配多个不同的路由,从而让操作同一个模型的视图方法写在一个视图类中
使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中
- list()提供一组数据
- retrieve()提供单个数据
- create()创建数据
- update()保存数据
- destory()删除数据
ViewSet视图集类不再是get(),post()等方法,而是实现动作action 如:list(),create()等
视图集只在使用as_view()方法进行路由分发的时候,才会将action动作与具体请求方式对应上 ,就是将客户端的请求方法与视图类中的方法进行对应
如:
class BookInfoViewSet(viewsets.ViewSet):
def list(self, request):
books = BookInfo.objects.all()
serializer = BookInfoSerializer(books, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
books = BookInfo.objects.get(id=pk)
except BookInfo.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = BookInfoSerializer(books)
return Response(serializer.data)
在设置路由时,可以进行如下操作:
urlpatterns = [
url(r'^books/$', BookInfoViewSet.as_view({'get':'list'}),#当客户端请求是get方法且路由可以匹配上,就去执行BookInfoViewSet视图类的list方法
url(r'^books/(?P<pk>\d+)/$', BookInfoViewSet.as_view({'get': 'retrieve'})
]
常见的视图集父类
ViewSet
继承自APIView 和 ViewSetMixin 作用也与APIView基本类似,提供了身份认证,权限校验,流量管理等
ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典的映射来处理工作
在VIewSet中,没有提供任何动作 action方法,需要我们自己实现action方法,就是需要自己在视图类中写处理逻辑
GenericViewSet
GenericViewSet
类中没有定义任何代码,他就是继承 ViewSetMixin
和 GenericAPIView
,也就说他的功能就是将继承的两个类的功能继承到一起。
GenericAPIView
,将数据库查询、序列化类的定义提取到类变量中,便于后期处理。ViewSetMixin
,将 get/post/put/delete 等方法映射到 list、create、retrieve、update、partial_update、destroy方法中,让视图不再需要两个类
使用ViewSet通常并不方便,因为list、retrieve、create、update、destory等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView
,所以还需要继承GenericAPIView
。
GenericViewSet就帮助我们完成了这样的继承工作,继承自GenericAPIView
与ViewSetMixin
,在实现了调用as_view()时传入字典(如{'get':'list'}
)的映射处理工作的同时,还提供了GenericAPIView
提供的基础方法,可以直接搭配Mixin扩展类使用。
如:
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin
class Student4ViewSet
(GenericViewSet,ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
url配置
urlpatterns = [
path("students7/", views.Student4ViewSet.as_view({"get": "list", "post": "create"})),
re_path("students7/(?P<pk>\d+)/", views.Student4ViewSet.as_view({"get": "retrieve","put":"update","delete":"destroy"})),
]
ModelViewSet
继承自GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。
ReadOnlyModelViewSet
继承自GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin。
视图集中定义附加action动作
在视图集中,处理上述默认的方法动作外还可以添加自定义动作
如:
from rest_framework.viewsets import ModelViewSet,ReadOnlyModelViewSet
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def login(self,request):
"""学生登录功能"""
return Response({"message":"登录成功"})
url配置
urlpatterns = [
path("students8/", views.StudentModelViewSet.as_view({"get": "list", "post": "create"})),
re_path("students8/(?P<pk>\d+)/",
views.StudentModelViewSet.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
path("stu/login/",views.StudentModelViewSet.as_view({"get":"login"}))
]
action属性
在视图集中,我们可以通过action对象属性来获取当前请求视图集时的action动作是哪个。
如:
from rest_framework.viewsets import ModelViewSet
from students.models import Student
from .serializers import StudentModelSerializer
from rest_framework.response import Response
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get_new_5(self,request):
"""获取最近添加的5个学生信息"""
# 操作数据库
print(self.action) # 获取本次请求的视图方法名
通过路由访问到当前方法中.可以看到本次的action就是请求的方法名
在开发过程中使用 五大类
或 ModelViewSet
是比较常见的,并且如果他们内部的某些功能不够用,还可以进行重新某些方法进行扩展。
问题:drf中提供了这么多视图,以后那个用的比较多?
- 接口与数据库操作无关,直接继承APIView
- 接口背后需要对数据库进行操作,一般:
ModelViewSet
或CreateModelMixin、ListModelMixin...
``` - 利用钩子自定义功能。
- 重写某个写方法,实现更加完善的功能。 ```
- 根据自己公司的习惯,自定义 :
ModelViewSet
或 `CreateModelMixin、ListModelMixin…
路由
对于视图集ViewSet我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息
REST Framework提供了两个router
- SimpleRouter
- DefaultRouter
使用方法
- 创建router对象 并注册视图集
URLS 文件中:
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'router_stu', StudentModelViewSet, base_name='student')
#
如上述代码会形成的路由如下
^router_stu/$ name: student-list
^router_stu/{pk}/$ name: student-detail
register(prefix, viewset, base_name)
prefix 该视图集的路由前缀
viewset 视图集
base_name 路由别名的前缀
- 添加路由数据
就是添加到路由列表中‘
urlpatterns = [
...
]
urlpatterns += router.urls
或
urlpatterns = [
...
url(r'^', include(router.urls))
]
两种都可以
路由使用案例:
视图文件:
from rest_framework.viewsets import ModelViewSet,ReadOnlyModelViewSet
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def login(self,request):
"""学生登录功能"""
print(self.action)
return Response({"message":"登录成功"})
路由文件:
from django.urls import path, re_path
from . import views
urlpatterns = [
...
]
"""使用drf提供路由类router给视图集生成路由列表"""
# 实例化路由类
# drf提供一共提供了两个路由类给我们使用,他们用法一致,功能几乎一样
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
# 注册视图集
# router.register("路由前缀",视图集类)
router.register("router_stu",views.StudentModelViewSet)
# 把生成的路由列表追加到urlpatterns
print( router.urls )
urlpatterns += router.urls
上面的代码就成功生成了路由地址[增/删/改/查一条/查多条的功能],但是不会自动我们在视图集自定义方法的路由。
所以我们如果也要给自定义方法生成路由,则需要进行action动作的声明
视图集中附加action的声明
在视图集中,如果想要让Router自动帮助我们为自定义的动作生成路由信息,需要使用 rest_framework.decorators.action装饰器
以action装饰器装饰的方法名会作为action动作名,与list、retrieve等同。
action装饰器可以接收两个参数
- methods 声明该action对应的请求方式,列表传递
- detail
声明该action的路径是否与单一资源对应,就是是否可以携带pk值
xxx/<pk>/action方法名/
True 表示路径格式是xxx/<pk>/action方法名/
False 表示路径格式是xxx/action方法名/
如:
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# methods 设置当前方法允许哪些http请求访问当前视图方法
# detail 设置当前视图方法是否是操作一个数据
# detail为True,表示路径名格式应该为 router_stu/{pk}/login/
@action(methods=['get'], detail=True)
def login(self, request,pk):
"""登录"""
...
# detail为False 表示路径名格式应该为 router_stu/get_new_5/
@action(methods=['put'], detail=False)
def get_new_5(self, request):
"""获取最新添加的5个学生信息"""
#路由生成的信息如下:
^router_stu/get_new_5/$ name: router_stu-get_new_5
^router_stu/{pk}/login/$ name: router_stu-login
路由router形成url的方式
SimpleRouter
DefaultRouter
DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据。
DRF框架常用的组件
认证Authentication
可以在配置文件中配置全局默认的认证方案
site-packages/rest_framework/settings.py
# 可以在项目的主应用的settings.py配置文件中加入以下配置覆盖全局默认的配置方案。
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication', # session认证
'rest_framework.authentication.BasicAuthentication', # 基本认证
)
}
也可以在每个视图中通过设置authentication_classess类属性来设置
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView
class ExampleView(APIView):
# 类属性
authentication_classes = [SessionAuthentication, BasicAuthentication]
def get(self,request):
pass
认证失败会有两种可能的返回值从,这个需要我们配合权限组件来使用
- 401 Unauthorized 未认证
- 403 Permission Denied 权限被禁止
权限 Permissions
权限控制可以根据用户对于视图的访问和对于具体数据对象的访问
- 在执行视图的as_view()方法的dispatch()方法前,会先进行视图访问权限的判断
- 在通过get_object()获取具体模型对象时,会进行模型对象访问权限的判断
提供的权限
AllowAny 允许所有用户
IsAuthenticates 仅通过登录认证的用户
IsAdminUser 仅管理员用户
IsAuthenticatedOrReadOnly 已经登录认证的用户可以对数据进行增删改查操作,没有登录认证的只能查看数据
可以在配置文件settings.py中全局设置默认的权限管理类:如:
# drf的配置信息,需要卸载django的配置文件,而且必须写在REST_FRAMEWORK的字典中,才能被drf识别
REST_FRAMEWORK = {
....
# 权限[全局配置,所有的视图都会被影响到]
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated', # 已经登录认证的用户才能访问
)
}
如果没有设置权限组件,默认使用的配置如下:
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
)
也可以在具体的视图中通过permission_classes属性来设置,如
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = (IsAuthenticated,)
认证和权限使用案例:
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import RetrieveAPIView
class StudentAPIView(RetrieveAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
authentication_classes = [SessionAuthentication]
permission_classes = [IsAuthenticated]
自定义权限
如果需要自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现一下两个中的任何一个方法或全部方法。
- .has_permission(self,request,view)
是否可以访问视图,view表示当前视图对象
- .has_object_permission(self,request,view,obj)
是否可以访问数据对象,view表示当前视图,obj为模型类数据对象
如:
在当前子应用下,创建一个权限文件permissions.py中声明自定义权限类:
from rest_framework.permissions import BasePermission
class IsXiaoMingPermission(BasePermission):
def has_permission(self, request, view):
if request.user and request.user.username == "xiaoming":
return True
视图文件:
from .permissions import IsXiaoMingPermission
class StudentViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
permission_classes = [IsXiaoMingPermission]
可选限流类
- AnonRateThrottle
限制所有匿名未认证用户,使用ip区分用户
使用 DEFAULT_THROTTLE_RATES[‘anon’]来设置频次
- UserRateThrottle
限制认证用户,使用user id来区分
使用 DEFAULT_THROTTLE_RATES[‘user’]来设置频次
- ScopedRateThrottle
限制用户对于每个视图的访问频次,使用ip或user id。
案例:
class ContactListView(APIView):
throttle_scope = 'contacts'
...
class ContactDetailView(APIView):
throttle_scope = 'contacts'
...
class UploadView(APIView):
throttle_scope = 'uploads'
...
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.ScopedRateThrottle',
),
'DEFAULT_THROTTLE_RATES': {
'contacts': '1000/day',
'uploads': '20/day'
}
}
使用:
可以在配置文件中,使用DEFAULT_THROTTLE_CLASSES
和 DEFAULT_THROTTLE_RATES
进行全局配置,
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': ( # 启用的限制类
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': { # 限制频率
'anon': '100/day',
'user': '1000/day'
}
}
DEFAULT_THROTTLE_RATES
可以使用 second
, minute
, hour
或day
来指明周期。
也可以在具体视图中通过throttle_classess属性来配置,如
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView
class ExampleView(APIView):
throttle_classes = (UserRateThrottle,)
综合案例:
全局配置中设置访问频率,settings.py代码:
REST_FRAMEWORK = {
# 权限[全局配置,会被局部配置覆盖]
# 'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated',
# ),
# 限流
# 'DEFAULT_THROTTLE_CLASSES': ( # 全局启用的限制类
# 'rest_framework.throttling.AnonRateThrottle', # 匿名用户,游客
# 'rest_framework.throttling.UserRateThrottle' # 登录用户
# ),
'DEFAULT_THROTTLE_RATES': { # 限制频率
'anon': '3/minute',
'user': '10/minute',
'access': '5/minute', # 这个是自定义限流的频率配置
}
}
视图代码:
from students.models import Student
from students.serializers import StudentModelSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import AllowAny,IsAuthenticated,IsAuthenticatedOrReadOnly,IsAdminUser
from .permission import ISMingGe
from rest_framework.throttling import UserRateThrottle,AnonRateThrottle,ScopedRateThrottle
class Students8APIView(ModelViewSet):
serializer_class = StudentModelSerializer
queryset = Student.objects.all()
# 权限配置
permission_classes = [AllowAny]
# 限流配置
# throttle_classes = [AnonRateThrottle,UserRateThrottle]
# 自定义限流配置
throttle_classes = [ScopedRateThrottle]
throttle_scope = 'access'
过滤 Filtering
自定义filter
自己定义类要继承BaseFilterBackend,类中定义一个filter_queryset方法,接收request,queryset,view,在里面写过滤的逻辑,在视图类中用的时候需要定义类变量filter_backends = [Filter1, Filter2],filter_backends里放的是自己定义的过滤类的类名,当请求进入list视图类的时候会去数据库中查询出queryset,并将queryset传给自己定义的过滤方法中的queryset,当Filter1过滤完数据后,执行Filter2的过滤方法,并且Filter2中的queryset接收的是Filter1过滤完数据后的queryset。
# urls.py
from django.urls import path
from app01 import views
urlpatterns = [
path('api/users/', views.UserView.as_view(
{"get": "list", "post": "create"}
)),
path('api/users/<int:pk>/', views.UserView.as_view(
{"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"}
)),
]
# views.py
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import BaseFilterBackend
from app01 import models
class UserModelSerializer(serializers.ModelSerializer):
level_text = serializers.CharField(
source="get_level_display",
read_only=True
)
extra = serializers.SerializerMethodField(read_only=True)
class Meta:
model = models.UserInfo
fields = ["username", "age", "email", "level_text", "extra"]
def get_extra(self, obj):
return 666
class Filter1(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
age = request.query_params.get('age')
if not age:
return queryset
return queryset.filter(age=age)
class Filter2(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
user_id = request.query_params.get('id')
if not user_id:
return queryset
return queryset.filter(id__gt=user_id)
class UserView(ModelViewSet):
filter_backends = [Filter1, Filter2]
queryset = models.UserInfo.objects.all()
serializer_class = UserModelSerializer
def perform_create(self, serializer):
""" 序列化:对请求的数据校验成功后,执行保存。"""
serializer.save(depart_id=1, password="123")
第三方filter
对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展开增强支持
pip3.9 install django-filter
在配置文件setting.py中增加过滤组件的设置:
INSTALLED_APPS = [
...
'django_filters', # 需要注册应用,
]
REST_FRAMEWORK = {
...
# 全局配置,也可以使用局部配置
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
在视图类中添加类属性filter_fields ,指定可以过滤的字段
class StudentListView(ListAPIView):
queryset = Student.objects.all()
FILTER_BACKENDS
filter_backends=[DjangoFilterBackend,]#局部配置filter
serializer_class = StudentSerializer
filter_fields = ('age', 'sex')
# 127.0.0.1:8000/opt/students/?sex=true #单个过滤条件
# http://127.0.0.1:8000/opt/students/?sex=false&age=27 # 多个并列的过滤条件
第三方过滤处理更复杂的条件
from django_filters import FilterSet,filters
#自定义的类,写过滤的条件
class MyFilterSet(FilterSet):
#自定义的字段
#field_name对应数据库中的字段,lookup_expr就是搜索条件,gte就是大于等于,exact是等于
#depart__title是跨表的意思
depart = filters.CharFilter(field_name='depart__title',lookup_exper='exact')
min_id = filters.NumberFilter(field_name='id',lookup_expr='gte')
class Meta:
#针对UserInfo表做过滤
model = models.UserInfo
#支持对'username','min_id','depart'这个几个字段在url上传参数,?username=‘123’
fields = ['username','min_id','depart']
"""
当url传过来的是 ?min_id = 3 ,此时的过程是,先取min_id = 3然后再把min_id字段对应的数据库字段取过来,根据lookup_expr进行过滤 也就是 [id>=3]
"""
class UserView(ModelViewSet):
filter_backends = [DjangoFilterBackend,]
#MyFilterSet是自己写的过滤类,filterset_class是专门配合filter_backends进行过滤的
filterset_class = MyFilterSet
def get(self,request):
pass
lookup_exper的配置项:
'exact': _(''),
'iexact': _(''),
'contains': _('contains'),
'icontains': _('contains'),
'startswith': _('starts with'),
'istartswith': _('starts with'),
'endswith': _('ends with'),
'iendswith': _('ends with'),
'gt': _('is greater than'),
'gte': _('is greater than or equal to'),
'lt': _('is less than'),
'lte': _('is less than or equal to'),
'in': _('is in'),
'range': _('is in range'),
'isnull': _(''),
'regex': _('matches regex'),
'iregex': _('matches regex'),
第三方过滤处理更加复杂的条件
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from django_filters.rest_framework import DjangoFilterBackend, OrderingFilter
from django_filters import FilterSet, filters
from app01 import models
class UserModelSerializer(serializers.ModelSerializer):
level_text = serializers.CharField(
source="get_level_display",
read_only=True
)
depart_title = serializers.CharField(
source="depart.title",
read_only=True
)
extra = serializers.SerializerMethodField(read_only=True)
class Meta:
model = models.UserInfo
fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"]
def get_extra(self, obj):
return 666
class MyFilterSet(FilterSet):
# /api/users/?min_id=2 -> id>=2
min_id = filters.NumberFilter(field_name='id', lookup_expr='gte')
# /api/users/?name=wupeiqi -> not ( username=wupeiqi )
name = filters.CharFilter(field_name="username", lookup_expr="exact", exclude=True)
# /api/users/?depart=xx -> depart__title like %xx%
depart = filters.CharFilter(field_name="depart__title", lookup_expr="contains")
# /api/users/?token=true -> "token" IS NULL
# /api/users/?token=false -> "token" IS NOT NULL
token = filters.BooleanFilter(field_name="token", lookup_expr="isnull")
# /api/users/?email=xx -> email like xx%
email = filters.CharFilter(field_name="email", lookup_expr="startswith")
# /api/users/?level=2&level=1 -> "level" = 1 OR "level" = 2(必须的是存在的数据,否则报错-->内部有校验机制)
# level = filters.AllValuesMultipleFilter(field_name="level", lookup_expr="exact")
level = filters.MultipleChoiceFilter(field_name="level", lookup_expr="exact", choices=models.UserInfo.level_choices)
# /api/users/?age=18,20 -> age in [18,20]
age = filters.BaseInFilter(field_name='age', lookup_expr="in")
# /api/users/?range_id_max=10&range_id_min=1 -> id BETWEEN 1 AND 10
range_id = filters.NumericRangeFilter(field_name='id', lookup_expr='range')
# /api/users/?ordering=id -> order by id asc
# /api/users/?ordering=-id -> order by id desc
# /api/users/?ordering=age -> order by age asc
# /api/users/?ordering=-age -> order by age desc
ordering = filters.OrderingFilter(fields=["id", "age"])
# /api/users/?size=1 -> limit 1(自定义搜索)
# 当数据过来时候回去执行filter_size方法并将值传递给value ,required=False是不是必填项
size = filters.CharFilter(method='filter_size', distinct=False, required=False)
class Meta:
model = models.UserInfo
fields = ["id", "min_id", "name", "depart", "email", "level", "age", 'range_id', "size", "ordering"]
def filter_size(self, queryset, name, value):
int_value = int(value)
return queryset[0:int_value]
class UserView(ModelViewSet):
filter_backends = [DjangoFilterBackend, ]
filterset_class = MyFilterSet
queryset = models.UserInfo.objects.all()
serializer_class = UserModelSerializer
def perform_create(self, serializer):
""" 序列化:对请求的数据校验成功后,执行保存。"""
serializer.save(depart_id=1, password="123")
内置filter
排序Ordering Filter
对于列表数据,REST Framework 提供了Ordering Filter过滤器来帮助我们快速指明数据按照字段进行排序
使用方法
在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器,REST Framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含,则按照ordering参数指明的字段对数据集进行排序
全段可以传递的ordering参数的可选字段值需要在ordering_fields中指明、
案例:
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
filter_backends = [OrderingFilter]
ordering_fields = ('id', 'age')
# 127.0.0.1:8000/books/?ordering=-age
# -id 表示针对id字段进行倒序排序
# id 表示针对id字段进行升序排序
如果需要在过滤以后再次进行排序,则需要两者进行同步,要么都写在全局配置中,要么都写在视图类中
from rest_framework.generics import ListAPIView
from students.models import Student
from .serializers import StudentModelSerializer
from django_filters.rest_framework import DjangoFilterBackend
class Student3ListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
filter_fields = ('age', 'sex')
# 因为排序配置和过滤配置使用同一个类属性,所以当视图中需要使用排序和过滤时,
# 要么大家一起在视图类中局部配置,要么大家一起在全局中配置,否则会出现过滤组件使用无效的情况
# filter_backends = [DjangoFilterBackend,OrderingFilter]
ordering_fields = ('id', 'age')
#配置文件
# 过滤组件[全局引入]
# 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend','rest_framework.filters.OrderingFilter')
SearchFilter,支持模糊搜索。
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from app01 import models
from rest_framework.filters import SearchFilter
class UserModelSerializer(serializers.ModelSerializer):
level_text = serializers.CharField(
source="get_level_display",
read_only=True
)
depart_title = serializers.CharField(
source="depart.title",
read_only=True
)
extra = serializers.SerializerMethodField(read_only=True)
class Meta:
model = models.UserInfo
fields = ["id", "username", "age", "email", "level_text", "extra", "depart_title"]
def get_extra(self, obj):
return 666
class UserView(ModelViewSet):
# ?search=武沛%齐
filter_backends = [SearchFilter, ]
search_fields = ["id", "username", "age"]
queryset = models.UserInfo.objects.all()
serializer_class = UserModelSerializer
def perform_create(self, serializer):
""" 序列化:对请求的数据校验成功后,执行保存。"""
serializer.save(depart_id=1, password="123")
"app01_userinfo"."id" LIKE %武沛齐% ESCAPE '\'
OR
"app01_userinfo"."username" LIKE %武沛齐% ESCAPE '\'
OR
"app01_userinfo"."age" LIKE %武沛齐% ESCAPE '\'
分页 Pagination
REST Framework 提供了分页支持
可以在配置文件中设置全局的分页方式。如:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 100 # 每页数目
}
如果在配置settings.py文件中, 设置了全局分页,那么在drf中凡是调用了ListModelMixin的list(),都会自动分页。如果项目中出现大量需要分页的数据,只有少数部分的分页,则可以在少部分的视图类中关闭分页功能。
class 视图类(ListAPIView):
pagination_class = None
也可通过自定义Pagination类,来为视图添加不同分页行为。在视图中通过pagination_clas
属性来指明。
class LargeResultsSetPagination(PageNumberPagination):
page_size = 1000
page_size_query_param = 'page_size'
max_page_size = 10000
class BookDetailView(RetrieveAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
pagination_class = LargeResultsSetPagination
可选分页器
- PageNumberPagination
前端访问网址形式
get http://127.0.0.1:8000/students/?page=4
可以在子类中定义的属性有:
page_size 每页的数目
page_query_param 前端发送的页数关键字名,默认是page
page_size_query_param 前端发送的每页数目关键字名 默认是None
max_page_size 前端最多能设置的每页数量
# 声明分页的配置类
from rest_framework.pagination import PageNumberPagination
class StandardPageNumberPagination(PageNumberPagination):
# 默认每一页显示的数据量
page_size = 2
# 允许客户端通过get参数来控制每一页的数据量
page_size_query_param = "size"
max_page_size = 10
# 自定义页码的参数名
page_query_param = "p"
class StudentAPIView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
pagination_class = StandardPageNumberPagination
# 127.0.0.1/four/students/?p=1&size=5
- LimitOffsetPagination
前端访问网址形式
GET http://127.0.0.1/four/students/?limit=100&offset=100
可以在子类中定义的属性
default_limit 默认限制 ,默认值与page_size设置一致
limit_query_param limit参数名 默认是limit
offset_query_param offset参数名 默认是offset
max_limit 最大limit限制 默认None
from rest_framework.pagination import LimitOffsetPagination
class StandardLimitOffsetPagination(LimitOffsetPagination):
# 默认每一页查询的数据量,类似上面的page_size
default_limit = 2
limit_query_param = "size"
offset_query_param = "start"
class StudentAPIView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# 调用页码分页类
# pagination_class = StandardPageNumberPagination
# 调用查询偏移分页类
pagination_class = StandardLimitOffsetPagination
异常处理 Exceptions
REST Framework 提供了自定义异常处理,我们可以自定义的当时来编写异常处理函数,如 我们想要创建一个自定义异常函数
这个函数,我们保存到当前主应用中 ,在实际工作中,我们可以设置一个单独的独立的公共目录来保存这种公共的函数/工具/类库
from rest_framework.views import exception_handler as drf_exception_handler
from django.db import DatabaseError
from rest_framework.response import Response
from rest_framework import status
def custom_exception_handler(exc, context):
"""
自定义异常处理函数
:param exc: 异常对象,本次发生的异常对象
:param context: 字典,异常出现时的执行上下文环境
:return:
"""
# 先让drf进行异常判断
response = drf_exception_handler(exc, context)
# 判断response对象是否为None
if response is None:
"""出现drf不能处理的异常"""
if isinstance(exc, DatabaseError):
view = context.get("view")
print('数据库报错,[%s]: %s' % (view, exc))
return Response({"detail":"服务器内部错误!"}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
if isinstance(exc, ZeroDivisionError):
view = context.get("view")
print("0不能作为除数! [%s]: %s" % (view, exc) )
return Response({"detail":"服务器内部错误!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return response
在主应用的配置文件settings.py中声明自定义的异常处理
REST_FRAMEWORK = {
# 异常处理
'EXCEPTION_HANDLER': 'drfdemo.exceptions.custom_exception_handler',
}
如果未声明,会采用默认的方式,如下
rest_frame/settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
REST framework定义的异常
APIException使用drf中所有异常的父类,他的子类有以下:
- ParseError 解析错误
- AuthenticationFailed 认证失败
- NotAuthenticated 尚未认证
- PermissionDenied 权限受限
- NotFound 未找到
- MethodNotAllowed 请求方式不支持
- NotAcceptable 要获取的数据格式不支持
- Throttled 超过限流次数
- ValidationError 校验失败
也就是说,很多的没有在上面列出来的异常,就需要我们在自定义异常中自己处理了。
自动生成接口文档
官方文档:http://core-api.github.io/python-client/
REST Framework 可以帮助我们自动生成接口文档
接口文档 以网页的形式呈现
自动接口文档能生成的是继承自API View 及其子类的视图
安装依赖
pip install coreapi
REST framewrok生成接口文档需要coreapi库的支持。
设置接口文档访问路径
在settings.py中配置接口文档。
REST_FRAMEWORK = {
# 。。。 其他选项
# 接口文档
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
}
在总路由中添加接口文档路径。
文档路由对应的视图配置为rest_framework.documentation.include_docs_urls
,
参数title
为接口文档网站的标题。
from rest_framework.documentation import include_docs_urls
urlpatterns = [
...
path('docs/', include_docs_urls(title='站点页面标题'))
]
文档描述说明的定义位置
- 单一方法的视图,可直接使用类视图的文档字符串,如
class BookListView(generics.ListAPIView):
"""
返回所有图书信息.
"""
- 包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
class BookListCreateView(generics.ListCreateAPIView):
"""
get:
返回所有图书信息.
post:
新建图书.
"""
- 对于视图集ViewSet,仍在类视图的文档字符串中分开定义,但是应使用action名称区分,如
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
"""
list:
返回图书列表数据
retrieve:
返回图书详情数据
latest:
返回最新的图书数据
read:
修改图书的阅读量
"""
访问接口文档
浏览器访问 127.0.0.1:8000/docs/,即可看到自动生成的接口文档。
两点说明
1) 视图集ViewSet中的retrieve名称,在接口文档网站中叫做read
2)参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:
class Student(models.Model):
...
age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄')
或
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
extra_kwargs = {
'age': {
'required': True,
'help_text': '年龄'
}
}
DRF解析器
之前使用request.data获取请求体中的数据
这个request.data的数据是怎么来的?其实内部在drf内部是由解析器,根据请求者传入的数据格式+请求头来进行处理的。
JSONParser
对于在视图类中定义了parser_classes = [JSONParser],那么发送过来的数据请求体必须是json格式,并且必须携带application-json的请求头,drf才能解析数据
FormParser
对于在视图类中定义了parser_classes = [FormParser],那么发送过来的数据请求体必须是form-data格式,并且请求头content-type必须携带application/x-www-form-urlencoded的请求头,drf才能解析数据这种请求头application/x-www-form-urlencoded的数据,或者 name=123&age=123这种格式的数据
MultiPartParser
适用于数据即有文件又有数据的情况,
FileUploadParser
解析器可以设置多个,默认的是 parser_classes = [JSONParser,FormParser,MultiPartParser]
django Admin和XAdmin介绍
Admin
django 内置了一个强大的组件 叫Admin 提供给网站管理员快速开发运营后台的管理站点。
站点文档: https://docs.djangoproject.com/zh-hans/2.2/ref/contrib/admin/
辅助文档:https://www.runoob.com/django/django-admin-manage-tool.html
要使用Admin,必须先创建超级管理员. python manage.py createsuperuser
访问地址:http://127.0.0.1:8000/admin
admin站点默认并没有提供其他的操作给我们,所以一切功能都需要我们进行配置,在项目中,我们每次创建子应用的时候都会存在一个admin.py文件,这个文件就是用于配置admin站点功能的文件。
admin.py里面允许我们编写的代码一共可以分成2部分:
列表页配置
主要针对与项目中各个子应用里面的models.py里面的模型,根据这些模型自动生成后台运营站点的管理功能
from django.contrib import admin
from .models import Student
class StudentModelAdmin(admin.ModelAdmin):
"""学生模型管理类"""
pass
# admin.site.register(模型类, 模型管理类)
admin.site.register(Student, StudentModelAdmin)
关于列表页的配置 如下:
from django.contrib import admin
from .models import Student
class StudentModelAdmin(admin.ModelAdmin):
"""学生模型管理类"""
date_hierarchy = 'born' # 按指定时间字段的不同值来进行选项排列
list_display = ['id', "name", "sex", "age", "class_num","born","my_born"] # 设置列表页的展示字段
ordering = ['-id'] # 设置默认排序字段,字段前面加上-号表示倒叙排列
actions_on_bottom = True # 下方控制栏是否显示,默认False表示隐藏
actions_on_top = True # 上方控制栏是否显示,默认False表示隐藏
list_filter = ["class_num"] # 过滤器,按指定字段的不同值来进行展示
search_fields = ["name"] # 搜索内容
def my_born(self,obj):
return str(obj.born).split(" ")[0]
my_born.short_description = "出生日期" # 自定义字段的描述信息
my_born.admin_order_field = "born" # 自定义字段点击时使用哪个字段作为排序条件
# admin.site.register(模型类, 模型管理类)
admin.site.register(Student, StudentModelAdmin)
详情页的配置
from django.contrib import admin
from .models import Student
class StudentModelAdmin(admin.ModelAdmin):
"""学生模型管理类"""
date_hierarchy = 'born' # 按指定时间字段的不同值来进行选项排列
list_display = ['id', "name", "sex", "age", "class_null","born","my_born"] # 设置列表页的展示字段
ordering = ['-id'] # 设置默认排序字段,字段前面加上-号表示倒叙排列
actions_on_bottom = True # 下方控制栏是否显示,默认False表示隐藏
actions_on_top = True # 上方控制栏是否显示,默认False表示隐藏
list_filter = ["class_null"] # 过滤器,按指定字段的不同值来进行展示
search_fields = ["name"] # 搜索内容
def my_born(self,obj):#相当于在模型类中新加了一个字段
return str(obj.born).split(" ")[0]
my_born.short_description = "出生日期" # 自定义字段的描述信息
my_born.admin_order_field = "born" # 自定义字段点击时使用哪个字段作为排序条件
def delete_model(self, request, obj):
"""当站点删除当前模型时执行的钩子方法"""
print("有人删除了模型信息[添加/修改]")
# raise Exception("无法删除") # 可以阻止删除
return super().delete_model(request, obj) # 继续删除
def save_model(self, request, obj, form, change):
(self,客户的请求对象,模型对象,客户提交的表单,修改后的数据)
"""
当站点保存当前模型时
"""
print("有人修改了模型信息[添加/修改]")
# 区分添加和修改? obj是否有id
print(obj.id)
return super().save_model(request, obj, form, change)
#配置添加页的编辑页 显示的表单字段
# fields = ('name', 'age', 'class_null', "description") # exclude 作用与fields相反
# readonly_fields = ["name"] # 设置只读字段
# 字段集,fieldsets和fields只能使用其中之一,字段分组显示
fieldsets = (
("必填项", {
'fields': ('name', 'age', 'sex')
}),
('可选项', {
'classes': ('collapse',), # 折叠样式
'fields': ('class_null', 'description'),
}),
)
# admin.site.register(模型类, 模型管理类)
admin.site.register(Student, StudentModelAdmin)
XAdmin
xadmin是Django的第三方扩展,是一个比Django的admin站点使用更方便的后台站点。构建于admin站点之上。
文档:http://sshwsfc.github.io/xadmin/
[https://xadmin.readthedocs.io/en/latest/index.html](https://xadmin.readthedocs.io/en/latest/index.html)
安装
pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2
在配置文件中 setting.py中注册应用
INSTALLED_APPS = [
...
'xadmin',
'crispy_forms',
'reversion',
...
]
# 修改使用中文界面
LANGUAGE_CODE = 'zh-Hans'
# 修改时区
TIME_ZONE = 'Asia/Shanghai'
xadmin有建立自己的数据库模型类,需要进行数据库迁移
python manage.py makemigrations
python manage.py migrate
在总路由中添加xadmin的路由信息
import xadmin
xadmin.autodiscover()
# version模块自动注册需要版本控制的 Model
from xadmin.plugins import xversion
xversion.register_models()
urlpatterns = [
path('xadmin/', xadmin.site.urls),
]
创建超级用户
python manage.py createsuperuser
使用
- xadmin不再使用Django的admin.py进行功能配置了,而是需要编写代码在adminx.py文件中。
xadmin的配置文件需要我们在每个子应用中使用时先创建adminx.py - xadmin的站点管理类不用继承
admin.ModelAdmin
,而是直接继承object
即可。
例如:在子应用students中创建adminx.py文件。
站点的全局配置
import xadmin
from xadmin import views
class BaseSetting(object):
"""xadmin的基本配置"""
enable_themes = True # 开启主题切换功能
use_bootswatch = True
xadmin.site.register(views.BaseAdminView, BaseSetting)
class GlobalSettings(object):
"""xadmin的全局配置"""
site_title = "路飞学城" # 设置站点标题
site_footer = "路飞学城有限公司" # 设置站点的页脚
menu_style = "accordion" # 设置菜单折叠
xadmin.site.register(views.CommAdminView, GlobalSettings)
站点Model管理
xadmin可以使用的页面样式控制基本与Django原生的admin一致。
- list_display 控制列表展示的字段
list_display = ['id', 'name', 'sex', 'age']
- search_fields 控制可以通过搜索框搜索的字段名称,xadmin使用的是模糊查询
search_fields = ['id','name']
- list_filter 可以进行过滤操作的列,对于分类、性别、状态
list_filter = ['is_delete']
- ordering 默认排序的字段
- readonly_fields 在编辑页面的只读字段
- exclude 在编辑页面隐藏的字段
- list_editable 在列表页可以快速直接编辑的字段
- show_detail_fields 在列表页提供快速显示详情信息
- refresh_times 指定列表页的定时刷新
refresh_times = [5, 10,30,60] # 设置允许后端管理人员按多长时间(秒)刷新页面
- list_export 控制列表页导出数据的可选格式
list_export = ('xls', 'xml', 'json') list_export设置为None来禁用数据导出功能
list_export_fields = ('id', 'title', 'pub_date') # 允许导出的字段
- show_bookmarks 控制是否显示书签功能
show_bookmarks = True
- data_charts 控制显示图表的样式
data_charts = {
"order_amount": {
'title': '图书发布日期表',
"x-field": "pub_date",
"y-field": ('title',),
"order": ('id',)
},
# 支持生成多个不同的图表
# "order_amount": {
# 'title': '图书发布日期表',
# "x-field": "pub_date",
# "y-field": ('title',),
# "order": ('id',)
# },
}
- title 控制图标名称
- x-field 控制x轴字段
- y-field 控制y轴字段,可以是多个值
- order 控制默认排序
- model_icon 控制菜单的图标
这里使用的图标是来自bootstrap3的图标。https://v3.bootcss.com/components/ ``` class BookInfoAdmin(object): model_icon = ‘fa fa-gift’
- model_icon 控制菜单的图标
xadmin.site.register(models.BookInfo, BookInfodmin)
修改admin或者xadmin站点下的子应用成中文内容。
```python
# 在子应用的apps.py下面的配置中,新增一个属性verbose_name
from django.apps import AppConfig
class StudentsConfig(AppConfig):
name = 'students'
verbose_name = "学生管理"
# 然后在当前子应用的__init__.py里面新增一下代码:
default_app_config = "students.apps.StudentsConfig"