Skip to content

关于前后端分离

接下来,你将进入 前后端分离项目开发 模块。 这也是现在企业中比较常见的开发模式。

疑问:

  • 什么是前后端分离?与之前的开发模式有什么区别?
  • 企业为什么要用前后端分离?

什么是前后端分离?

  • 前后端不分离,像咱们之前学习django、案例、crm项目、bug管理 时的那些模块。

    特点:
    	- 用户访问URL
    	- 执行视图函数,视图进行业务处理
    	- 视图render,读取HTML模块+数据渲染,将渲染完成的HTML/CSS/JS返回并呈现在用户浏览器上。
    	
    配合开发:
    	- 前端,写HTML、CSS、JS
    	- 后端,前端代码给我后端,后端代码 + 前端代码 集成到项目中。

    image-20210828135148077

    image-20210819121547705

  • 前后端分离

    特点:
    	- 一般基于 vue.js、react.js、angular.js 框架来编写前端页面(本质上是HTML、CSS、JS)。
    	- 页面上如果需要呈现数据,则需要则需要通过 ajax 的形式向后端发送请求(URL)并获取数据。
    	- 后端接收到请求后,执行视图函数并进行业务处理
    	- 后端的视图执行完毕后,给前端返回JSON格式数据。
    	- 前端接收到JSON格式数据后呈现在浏览器上即可。
    	
    配合开发:
    	- 前端,写HTML、CSS、JS(数据都是通过调用后端API获得)
    	- 后端,写API接口
    	- 前后端约定好接口的规则。

    image-20210819122103573

    流程:部署前端服务器url -> 静态页面  ->  点击页面按钮之类的  ->  想后端发请求要数据 -> 前端得到返回的数据后,组装页面。
    
    后端只负责提供数据
    前端只负责可视化的部分
    image-20210807125206445

image-20210807124425671

为什么要使用前后端分离?

目前企业一般都会采用前后端分离的形式来进行项目开发,这种模式:

  • 前后端职责清晰,前端开发者只vue.js、react.js、angular.js等框架编写页面;后端开发者只用Python编写后端代码;(两者通过json格式请求数据的传输)。
  • 开发高效,前后端做自己擅长的领域且使用vue.js等前端框架比用传统的HTML、CSS、JS、jQuery等开发速度快很多。
  • 有利于项目的扩展(开发APP、微信小程序等)。

image-20240301121759852

**注意:**前后端不分离的项目,现在一般用于开发用户量少、简单的项目。

关于项目安排

image-20240301124024252

此项目最终是基于前后端分离来进行开发,所以对于你们来讲需要学会前端开发必备技能、后端开发必备技能后,再进行业务功能开发,所以我们的项目讲解的安排如下:

  • 第一部分:作为后端开发者,学会基于Django编写后端API(给前端提供URL并返回相应格式数据)。
  • 第二部分:作为前端开发者,学会基于vue.js编写前端页面并调用后端API获取数据。
  • 第三部分:结合前端和后端技术,开发本项目的业务功能。

第一部分:后端开发

image-20240301124128838

RESTful规范

image-20210819123735927

对于后端开发者,本质上就是提供URL给前端开发者调用并返回相应的数据。例如:

image-20210807125206445

现在咱们大家知道前端后端分离的项目是需要:前端、后端 双方来进行合作开发,既然合作进行开发就必须要提前约定一些规范,以防止双方”打架“,例如:

  • 数据传输用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.HTTPS协议

建议使用https协议替代http协议,让接口数据更加安全。

这一条其实与开发无关,在最后的项目部署时只要使用https部署即可。

image-20210807135928170

如果是基于HTTP协议,则意味着用户浏览器再向服务器发送数据时,都是以明文的形式传输,如果你在某咖啡厅上网,他就可以把你网络传输的数据明文都获取到。

如果是基于HTTPS协议,则意味着用户浏览器再向服务器发送数据时,都是以密文的形式传输,中途即使有非法用户获取到网络数据,也可以密文的,无法破译。

HTTPS保证了数据安全,但由数据传输存在着加密和解密的过程,所以会比HTTP协议慢一些。

注意:此处大家先了解https和http的不同,至于底层原理和部署,在项目部署时再细讲。

参考文档:https://www.cnblogs.com/wupeiqi/p/11647089.html

2. 域名

对于后端API接口中要体现API标识,例如:

image-20210807141747844

image-20210807141524804

image-20210807142204010

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

image-20240301130104281

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

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   =>   局部更新

6. 搜索条件

在URL中通过参数的形式来传递搜索条件。

python
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		指定筛选条件

7. 返回数据

针对不同操作,服务器向用户返回的结果结构应该不同。

python
https://api.example.com/v1/users
https://api.example.com/v1/users/2/
URL方法描述返回数据
/users/GET列表返回资源对象的列表
[ {id:1,name:"武沛齐"}, {id:1,name:"日天"} ]
/users/POST添加返回新生成的资源对象
{id:1,name:"武沛齐"}
/users/(\d+)/GET获取单条数据返回单个资源对象
{id:1,name:"武沛齐"}
/users/(\d+)/DELETE删除数据返回一个空文档
null
/users/(\d+)/PUT更新数据返回完整的资源对象
{id:1,name:"武沛齐"}
/users/(\d+)/PATCH局部更新返回完整的资源对象
{id:1,name:"武沛齐"}

一般在实际的开发过程中会对上述返回数据进行补充和完善,例如:每次请求都返回一个字典,其中包含:

  • code,表示返回码,用于表示请求执行请求,例如:0表示请求成功,1003表示参数非法,40009数据量太大等。
  • data,表示数据
  • error,错误信息
python
{
    code:0,
    data:[ {id:1,name:"武沛齐"},   {id:1,name:"日天"}  ]
}
python
{
    code:41007,
    error:"xxxxx"
}

8. 状态码

后端API在对请求进行响应时,除了返回数据意外还可以返回状态码,来表示请求状况。

python
from django.urls import path, include
from app01 import views

urlpatterns = [
    path('users/', views.users),
]
python
from django.http import JsonResponse

def users(request):
    info = {
        "code": 1000,
        "data": {"id":1,"name":"武沛齐"}
    }
    return JsonResponse(info, status=200)

image-20210829161449149

200 OK - [GET]:服务器成功返回用户请求的数据
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
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

9. 错误处理

错误处理,状态码是4xx时,应返回错误信息,error当做key。

{
    error: "Invalid API key"
}

在 1.1.7 中中已包含。

了解常用resful规范,那么以后在开发后端API接口时,就要根据上述要求遵循restful规范(非必须,视公司情况灵活变通)。

Django的FBV和CBV

基于django开发项目时,对于视图可以使用 FBV 和 CBV 两种模式编写。

  • FBV,function base views,其实就是编写函数来处理业务请求。

    python
    from django.contrib import admin
    from django.urls import path
    from app01 import views
    urlpatterns = [
        path('users/', views.users),
    ]
    python
    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,其实就是编写类来处理业务请求。

    python
    from django.contrib import admin
    from django.urls import path
    from app01 import views
    urlpatterns = [
        path('users/', views.UserView.as_view()),
    ]
    python
    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的底层实现本质上相同的。

image-20210819114755157

python
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
    path('users/', views.UserView.as_view()),   # as_view()方法会判断来的请求的具体方法
]

as_view()方法会判断来的请求的具体方法,原理如下:

image-20210819115517407

CBV,其实就是在FBV的基础上进行的功能的扩展,根据请求的方式不同,直接定位到不同的函数中去执行。

如果是基于django编写restful API,很显然使用CBV的方式会更加简洁,因为restful规范中就是根据method不同来执行不同操作。

基于django的CBV和restful规范开发实战案例:

python
# 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()),

]
python
# 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

    image-20240301130319269

  • 基于django + django restframework框架

    image-20240301130403050

django restframework(简称drf)本质上其实就是一个别人编写好的app,里面集成了很多编写restful API的功能功能,接下里咱们就来学习drf并用他来开发restful API。

drf内置了很多便捷的功能,在接下来的课程中会给大家依次讲解下面的内容:

  • 快速上手
  • 请求的封装
  • 版本管理
  • 认证
  • 权限
  • 限流
  • 序列化
  • 视图
  • 条件搜索
  • 分页
  • 路由
  • 解析器

快速上手

  • 安装

    pip install djangorestframework==3.12.4
    python
    版本要求: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中添加配置

    python
    INSTALLED_APPS = [
        ...
        # 注册rest_framework(drf)
        'rest_framework',
    ]
    
    # drf相关配置以后编写在这里 
    REST_FRAMEWORK = {
       
    }
  • URL和视图

    python
    # urls.py
    
    from django.urls import path
    from app01 import views
    
    urlpatterns = [
        path('users/', views.UserView.as_view()),
    ]
    python
    # views.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    
    class UserView(APIView):
        def get(self, request, *args, **kwargs):
            return Response({"code": 1000, "data": "xxx"})
    
        def post(self, request, *args, **kwargs):
            return Response({"code": 1000, "data": "xxx"})

其实drf框架是在django基础进行的扩展,所以上述执行过得底层实现流程(同django的CBV):

image-20210819141447988

drf中重写了 as_viewdispatch方法,其实就是在原来django的功能基础上添加了一些功能,例如:

  • as_view,免除了csrf 验证,一般前后端分离不会使用csrf token认证(后期会使用jwt认证)。
  • dispatch,内部添加了 版本处理、认证、权限、访问频率限制等诸多功能(后期逐一讲解)。

请求数据的封装

以前我们通过django开发项目时,视图中的request是 django.core.handlers.wsgi.WSGIRequest 类的对象,其中包含了请求相关的所有数据。

python
# 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对象以外,还包含其他后期会使用的其他对象。

python
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"})
python
对象 = (request, 其他数据)
python
# 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对象与原来的有些不同,例如:

python
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

底层源码实现:

image-20240301140154560

版本管理

在restful规范中要去,后端的API中需要体现版本。

drf框架中支持5种版本的设置。

1.5.1 URL的GET参数传递(*)

image-20210819154455680

python
# settings.py

REST_FRAMEWORK = {
    "VERSION_PARAM": "v",
    "DEFAULT_VERSION": "v1",
    "ALLOWED_VERSIONS": ["v1", "v2", "v3"],
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.QueryParameterVersioning"
}

源码执行流程:

image-20210820105543193

1.5.2 URL路径传递(*)

image-20210819154955480

1.5.3 请求头传递

image-20210819155617845

1.5.4 二级域名传递

image-20210819160202013

在使用二级域名这种模式时需要先做两个配置:

  • 域名需解析至IP,本地可以在hosts文件中添加

  • image-20240301144652928

    127.0.0.1       v1.wupeiqi.com
    127.0.0.1       v2.wupeiqi.com
  • 在django的settings.py配置文件中添加允许域名访问

    ALLOWED_HOSTS = ["*"]

1.5.5 路由的namespace传递

image-20210819161839318

以上就是drf中支持的5种版本管理的类的使用和配置。

全局配置

上述示例中,如果想要应用某种 版本 的形式,需要在每个视图类中定义类变量:

python
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中也支持了全局配置。

python
# 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",  # 默认版本
}

image-20210820113538002

访问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/

底层源码实现

image-20210820105543193

反向生成URL

在每个版本处理的类中还定义了reverse方法,他是用来反向生成URL并携带相关的的版本信息用的,例如:

image-20210820105543193

image-20240301144613434

1.5.6 小结

以后使用drf开发后端API接口时:

  1. 创建django程序
  2. 安装drf框架
  3. 创建一个app专门来处理用户的请求
  4. 注册APP
  5. 设置版本
  6. 编写视图类

1.5.7 配置版本

全局配置

python
# 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",  # 默认版本
}

局部配置

python
versioning_class = QueryParameterVersioning 
versioning_class = URLPathVersioning
versioning_class = NamespaceVersioning         #后三个几乎用不到
versioning_class = HostNameVersioning
versioning_class = AcceptHeaderVersioning

认证

image-20210819123735927

在开发后端的API时,不同的功能会有不同的限制,例如:

  • 无需认证,就可以访问并获取数据。
  • 需认证,用户需先登录,后续发送请求需携带登录时发放的凭证(后期会讲jwt)

在drf中也给我们提供了 认证组件 ,帮助我们快速实现认证相关的功能,例如:

python
# models.py

from django.db import models

class UserInfo(models.Model):
    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)

image-20210821170610618

image-20210821171152853

认证通过后,用户名和token存储在request对象中!!!!!

python
class OrderView(APIView):
  authentication_classes=[MyAuthentication,]
  def get(self,request,*args,**kwargs):
    print(request.user)               #登陆用户的用户名
    print(request.auth)               #登陆用户的token !!!!!!
    return Response({"code":0,"data":"……"})

在视图类中设置类变量 authentication_classes的值为 认证类 MyAuthentication,表示此视图在执行内部功能之前需要先经过 认证。

认证类的内部就是去执行:authenticate方法,根据返回值来表示认证结果。

  • 抛出异常AuthenticationFailed,表示认证失败。内部还会执行 authenticate_header将返回值设置给响应头 WWW-Authenticate

  • 返回含有两个元素的元组,表示认证成功,并且会将元素的第1个元素赋值给 request.user、第2个值赋值给request.auth

    第1个值,一般是用户对象。
    第2个值,一般是token
  • 返回None,表示继续调用 后续的认证类 进行认证(上述案例未涉及)

**关于 ”返回None“ **

接下来说说 “返回None” 是咋回事。

在视图类的 authentication_classes 中定义认证类时,传入的是一个列表,支持定义多个认证类。

当出现多个认证类时,drf内部会按照列表的顺序,逐一执行认证类的 authenticate 方法,如果 返回元组 或 抛出异常 则会终止后续认证类的执行;如果返回None,则意味着继续执行后续的认证类。

如果所有的认证类authenticate都返回了None,则默认 request.user="AnonymousUser" 和 request.auth=None,也可以通过修改配置文件来修改默认值。

python
REST_FRAMEWORK = {
 "UNAUTHENTICATED_USER": lambda: None,
 "UNAUTHENTICATED_TOKEN": lambda: None,
}

”返回None“的应用场景:

当某个API,已认证 和 未认证 的用户都可以方法时,比如:

  • 已认证用户,访问API返回该用户的视频播放记录列表。
  • 未认证用户,访问API返回最新的的视频列表。

注意:不同于之前的案例,之前案例是:必须认证成功后才能访问,而此案例则是已认证和未认证均可访问。

image-20210821214440034

1.6.1关于多个认证类

多个认证类时,满足其中任意一个即可

一般情况下,编写一个认证类足矣。

当项目中可能存在多种认证方式时,就可以写多个认证类。例如,项目认证支持:

  • 在请求中传递token进行验证。

  • 请求携带cookie进行验证。

  • 请求携带jwt进行验证(后期讲)。

  • 请求携带的加密的数据,需用特定算法解密(一般为app开发的接口都是有加密算法)

此时,就可以编写多个认证类,并按照需要应用在相应的视图中,例如:

image-20210821220439103

注意:此示例后续在视图中读取的 request.user 的值为None时,表示未认证成功;不为None时,则表示认证成功。

自定义认证类

python
import datetime
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

from api.extension import return_code
from api import models


# 必须认证成功之后才能访问
class TokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            raise AuthenticationFailed({"code": 0, "error": "认证失败"})
        user_object = models.UserInfo.objects.filter(token=token).first()
        if not user_object:
            raise AuthenticationFailed({"code": 0, "error": "认证失败"})

        if datetime.datetime.now() > user_object.token_expiry_date:
            raise AuthenticationFailed({"code": 2, "error": "认证过期"})

        return user_object, token

    def authenticate_header(self, request):
        return 'Bearer realm="API"'    #生成一个http头部

#    登录,可以访问  request.user
# 不登录,也可以访问  request.user=None
class UserAnonTokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            return None
        user_object = models.UserInfo.objects.filter(token=token).first()
        if not user_object:
            return None

        if datetime.datetime.now() > user_object.token_expiry_date:
            return None

        return user_object, token

    def authenticate_header(self, request):
        return 'Bearer realm="API"'

全局配置

在每个视图类的类变量 authentication_classes 中可以定义,其实在配置文件中也可以进行全局配置,例如:

python
REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": lambda: None,
    "UNAUTHENTICATED_TOKEN": lambda: None,
    "DEFAULT_AUTHENTICATION_CLASSES":["xxxx.xxxx.xx.类名","xxxx.xxxx.xx.类名",]
}

#例如
REST_FRAMEWORK = {
    # 配置认证方式的选项
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication', # session认证
        'rest_framework.authentication.BasicAuthentication',   # basic认证[基于账号密码]
    ),
  "UNAUTHENTICATED_USER": lambda: None,
"UNAUTHENTICATED_TOKEN": lambda: None,
}

局部配置

python
from rest_framework.authentication import SessionAuthentication

class PayView(APIView):
    authentication_classes=[MyAuthentication,SessionAuthentication] #认证类可以自定义,也可以用drf的
    permission_classes = [PermissionA, IsAuthenticated] 

    def get(self, request, *args, **kwargs):
        pass

底层源码实现:

image-20240301153240734

权限

认证,根据用户携带的 token/其他 获取当前用户信息。

权限,读取认证中获取的用户信息,判断当前用户是否有权限访问,例如:普通用户、管理员、超级用户,不同用户具有不同的权限。

python
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)

image-20210822100012603

python
import uuid
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import AuthenticationFailed

from app01 import models


class AuthView(APIView):
    """ 用户登录认证 """
    authentication_classes = []
    permission_classes = []

    def post(self, request, *args, **kwargs):
        print(request.data)  # {"username": "wupeiqi", "password": "123"}
        username = request.data.get('username')
        password = request.data.get('password')

        user_object = models.UserInfo.objects.filter(username=username, password=password).first()
        if not user_object:
            return Response({"code": 1000, "data": "用户名或密码错误"})

        token = str(uuid.uuid4())

        user_object.token = token
        user_object.save()

        return Response({"code": 0, "data": {"token": token, "name": username}})

# 自定义认证类,继承BaseAuthentication
class TokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            raise AuthenticationFailed({"code": 1002, "data": "认证失败"})
        user_object = models.UserInfo.objects.filter(token=token).first()
        if not user_object:
            raise AuthenticationFailed({"code": 1002, "data": "认证失败"})
        return user_object, token

    def authenticate_header(self, request):
        return 'Bearer realm="API"'

#自定义权限类,继承BasePermission
class PermissionA(BasePermission):
    message = {"code": 1003, 'data': "无权访问"}

    def has_permission(self, request, view):
        if request.user.role == 2:
            return True
        return False
	
    # 暂时先这么写
    def has_object_permission(self, request, view, obj):
        return True


class OrderView(APIView):
    authentication_classes = [TokenAuthentication, ]

    permission_classes = [PermissionA,]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return Response({"code": 0, "data": {"user": None, 'list': [1, 2, 3]}})


class PayView(APIView):
    authentication_classes = [TokenAuthentication, ]
    permission_classes = [PermissionA, ]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return Response({"code": 0, "data": "数据..."})

1.7.1 关于多个权限类

当开发过程中需要用户同时具备多个权限(缺一不可)时,可以用多个权限类来实现。

权限组件内部处理机制:按照列表的顺序逐一执行 has_permission 方法,如果返回True,则继续执行后续的权限类;如果返回None或False,则抛出权限异常并停止后续权限类的执行。

python
# models.py

from django.db import models


class Role(models.Model):
    """ 角色表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class UserInfo(models.Model):
    """ 用户表 """
    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)

    roles = models.ManyToManyField(verbose_name="角色", to="Role")

image-20210822103254687

python
# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/auth/', views.AuthView.as_view()),
    path('api/order/', views.OrderView.as_view()),
]
python
# views.py

import uuid
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import AuthenticationFailed

from app01 import models


class AuthView(APIView):
    """ 用户登录认证 """

    def post(self, request, *args, **kwargs):
        print(request.data)  # {"username": "wupeiqi", "password": "123"}
        username = request.data.get('username')
        password = request.data.get('password')

        user_object = models.UserInfo.objects.filter(username=username, password=password).first()
        if not user_object:
            return Response({"code": 1000, "data": "用户名或密码错误"})

        token = str(uuid.uuid4())

        user_object.token = token
        user_object.save()

        return Response({"code": 0, "data": {"token": token, "name": username}})


class TokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            raise AuthenticationFailed({"code": 1002, "data": "认证失败"})
        user_object = models.UserInfo.objects.filter(token=token).first()
        if not user_object:
            raise AuthenticationFailed({"code": 1002, "data": "认证失败"})
        return user_object, token

    def authenticate_header(self, request):
        return 'Bearer realm="API"'


class PermissionA(BasePermission):
    message = {"code": 1003, 'data': "无权访问"}

    def has_permission(self, request, view):
        exists = request.user.roles.filter(title="员工").exists()
        if exists:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        return True


class PermissionB(BasePermission):
    message = {"code": 1003, 'data': "无权访问"}

    def has_permission(self, request, view):
        exists = request.user.roles.filter(title="主管").exists()
        if exists:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        return True

class OrderView(APIView):
    authentication_classes = [TokenAuthentication, ]
    permission_classes = [PermissionA, PermissionA]

    def get(self, request, *args, **kwargs):
        return Response({"code": 0, "data": {"user": None, 'list': [1, 2, 3]}})


class PayView(APIView):
    authentication_classes = [TokenAuthentication, ]
    permission_classes = [PermissionA, ]

    def get(self, request, *args, **kwargs):
        return Response({"code": 0, "data": "数据..."})

关于 has_object_permission【欠】

当我们使用drf来编写 视图类时,如果是继承 APIView,则 has_object_permission不会被执行(没用),例如:

python
from rest_framework.views import APIView

class PayView(APIView):
    authentication_classes = [TokenAuthentication, ]
    permission_classes = [PermissionA, ]

    def get(self, request, *args, **kwargs):
        return Response({"code": 0, "data": "数据..."})

但是,当我们后期学习了 视图类的各种骚操作之后,发现视图也可以继承 GenericAPIView,此时 有可能 会执行 has_object_permission 用于判断是否有权限访问某个特定ID的对象(学完视图后,再细讲)。

image-20210822105606505

调用 self.get_object 方法时,会按照 permission_classes中权限组件的顺序,依次执行他们的 has_object_permission 方法。

self.get_object其实就根据用户传入的 pk,搜索并获取某个对象的过程。

全局配置

python
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES":["xxxx.xxxx.xx.类名","xxxx.xxxx.xx.类名",]
}

#例如:
REST_FRAMEWORK = {
    ....
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',  # AllowAny 表示允许任何用户访问站点视图
    ],
}

局部配置

python
from rest_framework.permissions import IsAuthenticated

class PayView(APIView):
    permission_classes = [PermissionA, IsAuthenticated] #权限类可以自定义,也可以用drf的

    def get(self, request, *args, **kwargs):
        pass

底层源码实现

image-20210822110404096

补充:权限

在之前定义权限类时,类中可以定义两个方法:has_permissionhas_object_permission

  • has_permission ,在请求进入视图之前就会执行。
  • has_object_permission,当视图中调用 self.get_object时就会被调用(删除、更新、查看某个对象时都会调用),一般用于检查对某个对象是否具有权限进行操作。
python
class PermissionA(BasePermission):
    message = {"code": 1003, 'data': "无权访问"}

    def has_permission(self, request, view):
        exists = request.user.roles.filter(title="员工").exists()
        if exists:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        return True

所以,让我们在编写视图类时,如果是直接获取间接继承了 GenericAPIView,同时内部调用 get_object方法,这样在权限中通过 has_object_permission 就可以进行权限的处理。

小结

  • 请求的封装
  • 版本的处理
    • 过程:选择版本处理类,获取用户传入的版本信息
    • 结果:在 request.version = 版本request.versioning_scheme=版本处理类的对象
  • 认证组件,在视图执行之前判断用户是否认证成功。
    • 过程:执行所有的认证类中的 authenticate 方法
      • 返回None,继续执行后续的认证类(都未认证成功,request.user 和 auth有默认值,也可以全局配置)
      • 返回2个元素的元组,中断
      • 抛出 AuthenticationFailed,中断
    • 结果:在 request.userrequest.auth 赋值(后续代码可以使用)
  • 权限
    • 过程:执行所有的权限类的has_permission方法,只有所有都返回True时,才表示具有权限
    • 结果:有权限则可以执行后续的视图,无权限则直接返回 自定义的错误信息

限流

限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。

  • 对于匿名用户,使用用户IP作为唯一标识。
  • 对于登录用户,使用用户ID或名称作为唯一标识。
python
缓存={
	用户标识:[12:33,12:32,12:31,12:30,12,]    1小时/5次   12:34   11:34
{
pip3 install django-redis
python
# settings.py
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "qwe123",
        }
    }
}

image-20210822115201724

python
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "qwe123",
        }
    }
}
python
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    path('api/order/', views.OrderView.as_view()),
]
python
# 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,配置频率时的key eg:{"user":"100/h"}
    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,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
  • 抛出异常,表示当前限流类不允许访问,后续限流类不再执行。(自定义限流类推荐这个)

全局配置

python


REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES":[
      "xxx.xxx.xx.限流类",   #自定义限流类
       'rest_framework.throttling.UserRateThrottle' ],  # rest_framework的限流类
    "DEFAULT_THROTTLE_RATES": {
        "user": "10/m",       # user,xx 是限流类中的scope
        "xx":"100/h"
    }
}

局部配置

python
from rest_framework.throttling import UserRateThrottle
class Student2ModelViewSet(ModelViewSet):
    queryset = Student.objects
    # 限流局部配置[这里需要配合在全局配置中的DEFAULT_THROTTLE_RATES来设置频率]
    throttle_classes = [UserRateThrottle]

底层源码实现:

image-20210822121259284

image-20210822120127336

python
# settings.py

...
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "qwe123",
        }
    }
}
python
# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/order/', views.OrderView.as_view()),
]
python
# 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
        return self.cache_format % {'scope': self.scope, 'ident': ident}


class OrderView(APIView):
    throttle_classes = [MyRateThrottle, ]

    def get(self, request):
        return Response({"code": 0, "data": "数据..."})

    def throttled(self, request, wait):
        detail = {
            "code": 1005,
            "data": "访问频率",
            'detail': "需等待{}s才能访问".format(int(wait))
        }
        raise ThrottledException(detail)

自定义限流类,更改频率,解决请求失败问题

更改限流频率为1/5m,并解决请求不成功后仍计算限流次数的问题

python
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import cache as default_cache
from api.utils import return_code
from rest_framework import exceptions
from rest_framework import status


class ThrottledException(exceptions.APIException):
    status_code = status.HTTP_429_TOO_MANY_REQUESTS
    default_code = 'throttled'


class MySimpleRateThrottle(SimpleRateThrottle):
    cache = default_cache  # 访问记录存放在django的缓存中(需设置缓存)
    scope = "user"  # 构造缓存中的key
    cache_format = 'throttle_%(scope)s_%(ident)s'

    # 设置访问频率,例如:1/5m
    THROTTLE_RATES = {"user": "1/5m"}

    def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        if rate is None:
            return (None, None)
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[-1]]
        count = int(period[0])
        return (num_requests, duration * count)

    def get_cache_key(self, request, view):
        ident = request.user.pk
        return self.cache_format % {'scope': self.scope, 'ident': ident}

    def throttle_success(self):
        return True

    def throttle_failure(self):
        wait = self.wait()
        detail = {
            "code": return_code.TOO_MANY_REQUESTS,
            "data": "访问频率限制",
            'detail': "需等待{}秒后才能访问".format(int(wait))
        }
        raise ThrottledException(detail)  # 自定义的exception,在上面

    def done(self):
        self.history.insert(0, self.now)
        self.cache.set(self.key, self.history, self.duration)

Serializer(*)

drf中为我们提供了Serializer,他主要有两大功能:

  • 对请求数据校验(底层调用Django的Form和ModelForm)
  • 对数据库查询到的对象进行序列化

1.7.1.数据校验1

示例1:基于Serializer:

image-20240301121304381

示例2:基于ModelSerializer:

python
# models.py

from django.db import models


class Role(models.Model):
    """ 角色表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class Department(models.Model):
    """ 部门表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class UserInfo(models.Model):
    """ 用户表 """
    level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
    level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)

    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    age = models.IntegerField(verbose_name="年龄", default=0)
    email = models.CharField(verbose_name="邮箱", max_length=64)
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)

    # 外键
    depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE)
    
    # 多对多
    roles = models.ManyToManyField(verbose_name="角色", to="Role")

image-20240301121315706

提示1:save方法可以添加新的字段信息,如上图。

提示2:Serializer序列化器里新定义的字段,要在视图中pop掉,不然会ser.save()会报错Got a TypeError when calling UserInfo.objects.create(). !

提示3:ser.save()的返回值就是新增的一条数据对象

提示4:要修改表字段的属性用 extra_kwargs,如上图

示例3:基于ModelSerializer(含FK+M2M):

image-20240301121419727

*提示:save方法会返回新生成的数据

7.2 序列化

通过ORM从数据库获取到的 QuerySet 或 对象 均可以被序列化为 json 格式数据。

python
# models.py

from django.db import models


class Role(models.Model):
    """ 角色表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class Department(models.Model):
    """ 部门表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class UserInfo(models.Model):
    """ 用户表 """
    level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
    level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)

    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    age = models.IntegerField(verbose_name="年龄", default=0)
    email = models.CharField(verbose_name="邮箱", max_length=64, null=True, blank=True)
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)

    depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE, null=True, blank=True)
    roles = models.ManyToManyField(verbose_name="角色", to="Role")

示例1:序列化基本字段

image-20240301121436420

python
# 切记, 如果从数据库获取的不是QuerySet对象,而是单一对象,例如:
data_object = modes.UserInfo.objects.filter(id=2).first()
ser = UserModelSerializer(instance=data_object,many=False)
print(ser.data)

示例2:自定义字段

image-20240301121446338

python
class UserModelSerializer(serializers.ModelSerializer):"ce="get_level_display")level_text = serializers.CharField(soudepart = serializers.CharField(source="depart .title"
roles = serializers.SerializerMethodField()extra = serializers.SerializerMethodField()
class Meta:model = models.UserInfofields = ["username", "age","email","Level_text","depart", "roles","extra"]
def get_roles(self, 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

示例3:序列化类的嵌套

image-20240301121456611

提示1:Serializer嵌套,当表结构为多对多时,序列化器要加many=True,如下:

python
from rest_framework import serializers
from api.models import UserInfo, Department, Role


class DepartSerializer(serializers.ModelSerializer):
    class Meta:
        model = Department
        fields = '__all__'


class RolesSerializer(serializers.ModelSerializer):
    class Meta:
        model = Role
        fields = '__all__'


class UserInfoSerializer(serializers.ModelSerializer):
    depart = DepartSerializer()
    roles = RolesSerializer(many=True)  # !!!!!!!!!! 要加many=True

    class Meta:
        model = UserInfo
        fields = '__all__'

7.3 数据校验&序列化

上述示例均属于单一功能(要么校验,要么序列化),其实当我们编写一个序列化类既可以做数据校验,也可以做序列化,例如:

image-20210823210822789

image-20210823211016050

image-20210823211041662

提示: Serializer嵌套,字段不是read_only,一定要自定义create和update,自定义新增和更新的逻辑。否则报错如下:

html
The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `api.serializers.UserInfoSerializer`, or set `read_only=True` on nested serializer fields.
python
# models.py

from django.db import models


class Role(models.Model):
    """ 角色表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class Department(models.Model):
    """ 部门表 """
    title = models.CharField(verbose_name="名称", max_length=32)


class UserInfo(models.Model):
    """ 用户表 """
    level_choices = ((1, "普通会员"), (2, "VIP"), (3, "SVIP"),)
    level = models.IntegerField(verbose_name="级别", choices=level_choices, default=1)

    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    age = models.IntegerField(verbose_name="年龄", default=0)
    email = models.CharField(verbose_name="邮箱", max_length=64, null=True, blank=True)
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)

    depart = models.ForeignKey(verbose_name="部门", to="Department", on_delete=models.CASCADE, null=True, blank=True)
    roles = models.ManyToManyField(verbose_name="角色", to="Role")
python
# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view()),
]
python
# views.py

from django.core.validators import EmailValidator
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers

from app01 import models


class DepartModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Department
        fields = ['id', "title"]
        extra_kwargs = {
            "id": {"read_only": False},  # 数据验证
            "title": {"read_only": True}  # 序列化
        }


class RoleModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = ['id', "title"]
        extra_kwargs = {
            "id": {"read_only": False},  # 数据验证
            "title": {"read_only": True}  # 序列化
        }


class UserModelSerializer(serializers.ModelSerializer):
    level_text = serializers.CharField(source="get_level_display", read_only=True)

    # Serializer嵌套,不是read_only,一定要自定义create和update,自定义新增和更新的逻辑。
    depart = DepartModelSerializer(many=False)
    roles = RoleModelSerializer(many=True)

    extra = serializers.SerializerMethodField(read_only=True)
    email2 = serializers.EmailField(write_only=True)

    # 数据校验:username、email、email2、部门、角色信息
    class Meta:
        model = models.UserInfo
        fields = [
            "username", "age", "email", "level_text", "depart", "roles", "extra", "email2"
        ]
        extra_kwargs = {
            "age": {"read_only": True},
            "email": {"validators": [EmailValidator, ]},
        }

    def get_extra(self, obj):
        return 666

    def validate_username(self, value):
        return value

    # 新增加数据时
    def create(self, validated_data):
        """ 如果有嵌套的Serializer,在进行数据校验时,只有两种选择:
              1. 将嵌套的序列化设置成 read_only
              2. 自定义create和update方法,自定义新建和更新的逻辑
            注意:用户端提交数据的格式。
        """
        depart_id = validated_data.pop('depart')['id']

        role_id_list = [ele['id'] for ele in validated_data.pop('roles')]

        # 新增用户表
        validated_data['depart_id'] = depart_id
        user_object = models.UserInfo.objects.create(**validated_data)

        # 在用户表和角色表的关联表中添加对应关系
        user_object.roles.add(*role_id_list)      # 多对多的添加语法!!!!!!!!

        return user_object          # !!!!!!!!! return user_obj !!!!!!!!!


class UserView(APIView):
    """ 用户管理 """

    def get(self, request):
        """ 添加用户 """
        queryset = models.UserInfo.objects.all()
        ser = UserModelSerializer(instance=queryset, many=True)
        return Response({"code": 0, 'data': ser.data})

    def post(self, request):
        """ 添加用户 """
        ser = UserModelSerializer(data=request.data)
        if not ser.is_valid():
            return Response({'code': 1006, 'data': ser.errors})

        ser.validated_data.pop('email2')

        instance = ser.save(age=18, password="123", depart_id=1)

        # 新增之后的一个对象(内部调用UserModelSerializer进行序列化)
        print(instance)
        # ser = UserModelSerializer(instance=instance, many=False)
        # ser.data

        return Response({'code': 0, 'data': ser.data})

底层源码实现:

序列化的底层源码实现有别于上述其他的组件,序列化器相关类的定义和执行都是在视图中被调用的,所以源码的分析过程可以分为:定义类、序列化、数据校验。

源码1:序列化过程

image-20210823235237512

image-20210823235752483

源码2:数据校验过程

image-20210824001814091

image-20240302142219010

视图

1.8.1 APIView

  • View,django
  • APIView,drf,在请求到来时,新增了:免除csrf、请求封装、版本、认证、权限、限流的功能。
python
class GenericAPIView(APIView):
    pass # 10功能

class GenericViewSet(xxxx.View-2个功能, GenericAPIView):
    pass # 5功能能

class UserView(GenericViewSet):
    def get(self,request):
        pass

APIView是drf中 “顶层” 的视图类,在他的内部主要实现drf基础的组件的使用,例如:版本、认证、权限、限流等。

python
# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view()),
    path('api/users/<int:pk>/', views.UserDetailView.as_view()),
]
python
# views.py

from rest_framework.views import APIView
from rest_framework.response import Response

class UserView(APIView):
    
    # 认证、权限、限流等
    
    def get(self, request):
		# 业务逻辑:查看列表
        return Response({"code": 0, 'data': "..."})

    def post(self, request):
        # 业务逻辑:新建
        return Response({'code': 0, 'data': "..."})
    
class UserDetailView(APIView):
    
	# 认证、权限、限流等
        
    def get(self, request,pk):
		# 业务逻辑:查看某个数据的详细
        return Response({"code": 0, 'data': "..."})

    def put(self, request,pk):
        # 业务逻辑:全部修改
        return Response({'code': 0, 'data': "..."})
    
    def patch(self, request,pk):
        # 业务逻辑:局部修改
        return Response({'code': 0, 'data': "..."})
    
    def delete(self, request,pk):
        # 业务逻辑:删除
        return Response({'code': 0, 'data': "..."})

1.8.2 GenericAPIView

GenericAPIView 继承APIView,在APIView的基础上又增加了一些功能。例如:get_querysetget_object等。

实际在开发中一般不会直接继承它,他更多的是担任 中间人的角色,为子类提供公共功能。

python
# urls.py

from django.urls import path, re_path, include
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view()),
    path('api/users/<int:pk>/', views.UserDetailView.as_view()),
]
python
# views.py

from rest_framework.generics import GenericAPIView
from rest_framework.response import Response

class UserView(GenericAPIView):
    queryset = models.UserInfo.objects.filter(status=True) #在类中通过self.get_queryset获得
    serializer_class = 序列化类
    
    def get(self, request):
        queryset = self.get_queryset()          # 就是上边的类变量queryset
        ser = self.get_serializer(intance=queryset,many=True)
        print(ser.data)
        return Response({"code": 0, 'data': "..."})

注意:最大的意义,将数据库查询、序列化类提取到类变量中,后期再提供公共的get/post/put/delete等方法,让开发者只定义类变量,自动实现增删改查。

1.8.3 GenericViewSet

image-20210824092131703

GenericViewSet类中没有定义任何代码,他就是继承 ViewSetMixinGenericAPIView,也就说他的功能就是将继承的两个类的功能继承到一起。

  • GenericAPIView,将数据库查询、序列化类的定义提取到类变量中,便于后期处理。
  • ViewSetMixin,将 get/post/put/delete 等方法映射到 list、create、retrieve、update、partial_update、destroy方法中,让视图不再需要两个类。
python
# urls.py

from django.urls import path, re_path, include
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":"destory"})),
]
python
# views.py

from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response

    
class UserView(GenericViewSet):
    
	# 认证、权限、限流等
    queryset = models.UserInfo.objects.filter(status=True)
    serializer_class = 序列化类
    
    def list(self, request):
		# 业务逻辑:查看列表
        queryset = self.get_queryset()
        ser = self.get_serializer(intance=queryset,many=True)
        print(ser.data)
        return Response({"code": 0, 'data': "..."})

    def create(self, request):
        # 业务逻辑:新建
        return Response({'code': 0, 'data': "..."})
    
    def retrieve(self, request,pk):
		# 业务逻辑:查看某个数据的详细
        return Response({"code": 0, 'data': "..."})

    def update(self, request,pk):
        # 业务逻辑:全部修改
        return Response({'code': 0, 'data': "..."})
    
    def partial_update(self, request,pk):
        # 业务逻辑:局部修改
        return Response({'code': 0, 'data': "..."})
    
    def destory(self, request,pk):
        # 业务逻辑:删除
        return Response({'code': 0, 'data': "..."})

注意:开发中一般也很少直接去继承他,因为他也属于是 中间人类,在原来 GenericAPIView 基础上又增加了一个映射而已。

1.8.4 五大类

在drf的为我们提供好了5个用于做 增、删、改(含局部修改)、查列表、查单个数据的5个类(需结合 GenericViewSet 使用)。

python
# urls.py

from django.urls import path, re_path, include
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"})),
]
python
# views.py

from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import (
    ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin,
    DestroyModelMixin, ListModelMixin
)

class UserView(CreateModelMixin,RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin,ListModelMixin,GenericViewSet):
    
	# 认证、权限、限流等
    queryset = models.UserInfo.objects.filter(status=True)
    serializer_class = 序列化类

在这个5个类中已帮我们写好了 listcreateretrieveupdatepartial_updatedestory 方法,我们只需要在根据写 类变量:queryset、serializer_class即可。

示例1:

image-20210824230441249

python
# urls.py

from django.urls import path
from app01 import views

urlpatterns = [
    path('api/users/', views.UserView.as_view({"get": "list"})),
    path('api/users/<int:pk>/', views.UserView.as_view({"get": "retrieve"})),
]
python
# views.py

from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
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 UserView(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

示例2:

image-20210824231043061

python
# 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"})),
]
python
# views.py

from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
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 UserView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, GenericViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

示例3:

python
# 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"}
    )),
]
python
# views.py

from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
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 UserView(mixins.ListModelMixin,
               mixins.RetrieveModelMixin,
               mixins.CreateModelMixin,
               mixins.UpdateModelMixin,
               mixins.DestroyModelMixin,
               GenericViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")
	
	def perform_update(self, serializer):
        serializer.save()
        
    def perform_destroy(self, instance):
        instance.delete()

示例4:

python
# 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"}
    )),
]
python
# views.py
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
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 UserView(ModelViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

在开发过程中使用 五大类ModelViewSet 是比较常见的,并且如果他们内部的某些功能不够用,还可以进行重新某些方法进行扩展。

问题:drf中提供了这么多视图,以后那个用的比较多?

  • 接口与数据库操作无关,直接继承APIView

  • 接口背后需要对数据库进行操作,一般:ModelViewSetCreateModelMixin、ListModelMixin...

    - 利用钩子自定义功能。
    - 重写某个写方法,实现更加完善的功能。
  • 根据自己公司的习惯,自定义 :ModelViewSetCreateModelMixin、ListModelMixin...

各种视图类的关系

  1. 两个视图基类:

    APIView:继承自django的View,需要重新自定义各种方法的时候用。

    GenericAPIView: 继承自APIView,作为链接APIView和视图子类的中间人,为了提供queryset和serializer,几乎不直接使用,各种get_queryset,get_serializer,get_object……的可重写方法都在这里面!!!!!!!

  2. 五个视图扩展类MixMin:

​ 具体的功能都写在这五个里面了,list,retrieve,create,update,destory。关于具体方法的实现都在这里面,需要的话可重写!!!!!!

注:update分为patrial_update,和perform_update(全部更新),使用方法:序列化器的partial参数,如下:

python
 serializer = self.get_serializer(instance=instance, data=data, partial=True)
  1. 视图集:

​ 提供了url的匹配。eg:

python
from django.urls import path
from course.views import CourseDirectionViews, CourseCategoryViews, TestView

urlpatterns = [
    path('test/', TestView.as_view({
        'get': 'list',
        'put': 'update',
        'post': 'create',
    })),
   path('test/<int:pk>/', TestView.as_view({
        'get': 'retrieve',
        'delete': 'destroy',
        'patch': 'partial_update'
    }))
]
  1. **9个视图子类:**GenericAPIView的功能+Mixin的功能。看名字就知道了。

条件搜索

如果某个API需要传递一些条件进行搜索,其实就在是URL后面通过GET传参即可,例如:

/api/users?age=19&category=12

在drf中也有相应组件可以支持条件搜索。

自定义Filter

image-20210825200814769

python
# 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"}
    )),
]
python
# 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

在drf开发中有一个常用的第三方过滤器:DjangoFilterBackend。

pip install django-filter

注册app:

python
INSTALLED_APPS = [
    ...
    'django_filters',
    ...
]

局部配置示例:

视图配置和应用(示例1):

python
# views.py
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
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 UserView(ModelViewSet):
    filter_backends = [DjangoFilterBackend, ]
    filterset_fields = ["id", "age", "email"]

    queryset = models.UserInfo.objects.all()
    serializer_class = UserModelSerializer

    def perform_create(self, serializer):
        """ 序列化:对请求的数据校验成功后,执行保存。"""
        serializer.save(depart_id=1, password="123")

视图配置和应用(示例2):

python
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
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):
    depart = filters.CharFilter(field_name="depart__title", lookup_expr="exact")
    min_id = filters.NumberFilter(field_name='id', lookup_expr='gte')

    class Meta:
        model = models.UserInfo
        fields = ["min_id", "depart"]


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")

视图配置和应用(示例3):

python
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(自定义搜索)
    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")

lookup_expr有很多常见选择:

python
'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'),

全局配置和应用:

python
# settings.py 全局配置

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend',]
}

内置Filter

drf源码中内置了2个filter,分别是:

  • OrderingFilter,支持排序。

    python
    from rest_framework import serializers
    from rest_framework.viewsets import ModelViewSet
    from app01 import models
    from rest_framework.filters import OrderingFilter
    
    
    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):
        filter_backends = [OrderingFilter, ]
        # ?order=id
        # ?order=-id
        # ?order=age
        ordering_fields = ["id", "age"]
    
        queryset = models.UserInfo.objects.all()
        serializer_class = UserModelSerializer
    
        def perform_create(self, serializer):
            """ 序列化:对请求的数据校验成功后,执行保存。"""
            serializer.save(depart_id=1, password="123")
  • SearchFilter,支持模糊搜索。

    python
    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")
    python
    "app01_userinfo"."id" LIKE %武沛齐% ESCAPE '\' 
    OR 
    "app01_userinfo"."username" LIKE %武沛齐% ESCAPE '\' 
    OR 
    "app01_userinfo"."age" LIKE %武沛齐% ESCAPE '\'