用于反射属性和方法
xclass Foo(object): def __init__(self, name, age): self.name = name self.age = age
def show(self): return "这是 show 方法的返回值"
obj = Foo("小明", 21)
v1 = getattr(obj, 'name')print(v1)
v2 = getattr(obj, 'show')()print(v2)结果:
xxxxxxxxxx小明这是 show 方法的返回值在面向对象当你获取不存在的属性时执行
xxxxxxxxxxclass Foo(object): def __init__(self, name, age): self.name = name self.age = age
def __getattr__(self, item): print("不存在的属性----->",item) return "这是 getattr 的返回值"
obj = Foo("小明", 21)
print(obj.xxxxx)结果:
xxxxxxxxxx不存在的属性-----> xxxxx这是 getattr 的返回值如果没有getattr方法的话打印不存在的属性时会报错
在面向对象中访问属性时执行无论是否存在都会执行
xxxxxxxxxxclass Foo(object): def __init__(self, name, age): self.name = name self.age = age
def __getattribute__(self, item): print("无论是否存在的属性----->",item) return "这是 getattribute 的返回值"
obj = Foo("小明", 21)
print(obj.name)print(obj.age)print(obj.xxxxx)结果:
xxxxxxxxxx无论是否存在的属性-----> name这是 getattribute 的返回值无论是否存在的属性-----> age这是 getattribute 的返回值无论是否存在的属性-----> xxxxx这是 getattribute 的返回值xxxxxxxxxxdef user(self): return 123此时可以直接使用属性的调用方式调用
xxxxxxxxxxobj.user
settings.py基本配置,可参考
xxxxxxxxxxfrom pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-6t452+ox7@$=!#-jgywyo*7$fy3&2*x^0xaqtg25k74k51j8bf'
DEBUG = True
ALLOWED_HOSTS = []
INSTALLED_APPS = [ # 'django.contrib.admin', # 'django.contrib.auth', # 'django.contrib.contenttypes', # 'django.contrib.sessions', # 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'api.apps.ApiConfig']
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', # 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', # 'django.contrib.auth.middleware.AuthenticationMiddleware', # 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',]
ROOT_URLCONF = 'blog.urls'
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', # 'django.contrib.auth.context_processors.auth', # 'django.contrib.messages.context_processors.messages', ], }, },]
WSGI_APPLICATION = 'blog.wsgi.application'
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', }}
AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', },]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
STATIC_URL = 'static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
REST_FRAMEWORK = { "UNAUTHENTICATED_USER": None}
源码入口

在settings.py中添加
xxxxxxxxxxREST_FRAMEWORK = { "UNAUTHENTICATED_USER": None,}
这样的话就从源码从分析,会走下面的这个,如果走上面那个会调用drf的默认值,使页面报错
基础实现
xxxxxxxxxxfrom rest_framework.response import Responsefrom rest_framework.views import APIViewfrom rest_framework.authentication import BaseAuthenticationfrom rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if token: return "JELEE", token # raise AuthenticationFailed("认证失败") raise AuthenticationFailed({"code": 2000, "message": "认证失败"})
class loginView(APIView): def get(self, request): print(request.user,request.auth) # None None return Response("loginView")
class userView(APIView): authentication_classes = [MyAuthentication, ] # 添加验证的类,可以填写多个 def get(self, request): print(request.user,request.auth) # JELEE xxxx 这里打印的是上面返回的两个值 return Response("userView")
class orderView(APIView): authentication_classes = [MyAuthentication, ] def get(self, request): return Response("orderView")解释代码
raise AuthenticationFailed()如果里面填写的是字符串,那他会自动添加detail拼接成字典
如果里面直接填写的字典,那他就会返回字典在页面上显示
return "JELEE", token这里面的两个分别会赋值给
request.user和request.auth
就可以在方法里面获取这两个的值
添加配置
xxxxxxxxxxREST_FRAMEWORK = { "UNAUTHENTICATED_USER": None, "DEFAULT_AUTHENTICATION_CLASSES": ["位置", ]}
"DEFAULT_AUTHENTICATION_CLASSES": ["位置", ]的意思是添加全局验证配置,列表中填写验证的规则位置
如果两个地方都写了配置,会执行视图的配置
注意:
理解方式:优先执行全局的配置后执行视图的配置后会覆盖全局的配置
真实实现原理(面向对象的继承):
xxxxxxxxxxclass APIView(object):authentication_classes = 读取配置文件中的列表def dispatch(self):self.authentication_classesclass UserView(APIView):authentication_classes = []obj = UserView()obj.dispatch()源码解析:
此图较大,看不清的话就下载下来看
验证的位置
如果是全局的验证,不能编写在视图中
如果是在视图中编写全局的应用类,会出现循环引用的问题

如果有多个认证组件,认证组件中如果没有抛出异常的话,那视图函数依然会执行
如果抛出了异常,那视图函数就不会执行
最佳实践:
将所有的验证类都编写在一个统一的文件中
在AuthenticationFailed抛出异常以后此处状态码应该是401的

但是实际上的状态码

这个现象需要在源码中解释

需要让自己的子类里面必须有某个方法的时候
使用
raise NotImplementedError
xxxxxxxxxxclass Foo(object): def f1(self): raise NotImplementedError("")
class New(Foo): pass此时的New里面必须有f1方法,否则就会报错
在drf中的体现:

request.data可以获取用户发过来的请求体中的数据
xxxxxxxxxxdef post(self, request): print(request.data) print(request.query_params) return Response("loginView")# 通过这两个可以把请求体中的数据和用户在url中携带的数据都拿到

案例代码
auth.py
xxxxxxxxxxfrom rest_framework.authentication import BaseAuthenticationfrom rest_framework.exceptions import AuthenticationFailedfrom app01 import models
class QueryParamsAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if not token: return user_object = models.UserInfo.objects.filter(token=token).first() if user_object: return user_object, token
def authenticate_header(self, request): return "token API"
class MetaAuthentication(BaseAuthentication): def authenticate(self, request): token = request.META.get("HTTP_AUTHORIZATION") # 获取请求头中的 Authorization if not token: return user_object = models.UserInfo.objects.filter(token=token).first() if user_object: return user_object, token
def authenticate_header(self, request): return "token API"
class NoAuthentication(BaseAuthentication): def authenticate(self, request): raise AuthenticationFailed({"code": 10001, "msg": "认证失败"})
def authenticate_header(self, request): return "token API"views.py
xxxxxxxxxxfrom rest_framework.response import Responsefrom rest_framework.views import APIViewfrom app01 import modelsimport uuidclass loginView(APIView): authentication_classes = [] def post(self, request): user = request.data.get("username") pwd = request.data.get("password") user_object = models.UserInfo.objects.get(username=user, password=pwd) if user_object: token = str(uuid.uuid4()) user_object.token = token user_object.save() return Response({"code": 0, "token": token}) return Response({"code": 1001, "msg": "用户名或密码错误"})
class userView(APIView): def get(self, request): return Response("userView")
class orderView(APIView): def get(self, request): return Response("orderView")
概述:

代码:
编写一个权限类
xxxxxxxxxxfrom rest_framework.permissions import BasePermissionfrom random import randintclass MyPermissions(BasePermission): def has_permission(self, request, view): v = randint(1,3) if v == 2: return False return True全局应用
xxxxxxxxxxREST_FRAMEWORK = { "UNAUTHENTICATED_USER": None, # ------------------------------------- "DEFAULT_PERMISSION_CLASSES": [ "ext.per.MyPermissions" ]}
局部应用
xxxxxxxxxxclass loginView(APIView): authentication_classes = [] permission_classes = [] 应用的场景和生效的场景和认证组件一样
默认情况下的错误信息

在定义权限类的时候添加message类变量
xxxxxxxxxxfrom rest_framework.permissions import BasePermissionfrom random import randintclass MyPermissions(BasePermission): message = {"status": False, "msg": "无权访问"} def has_permission(self, request, view): v = randint(1,3) if v == 2: return False return True此时的错误信息


从源码也可以看出,当进行权限认证时
遇到一个报错了后面的就不会再执行了
当有一个返回了False就会执行permission_denied报错拦截
我们可以通过更改这个逻辑来实现不一样的效果:

在执行的过程中会优先在自己的方法里面寻找check_permissoins
如果自己的方法里面没有的话才会去父类中找
所以可以在自己的类里面定义一个check_permissoins方法
达到重写逻辑的目的
xxxxxxxxxxclass orderView(APIView): authentication_classes = [] permission_classes = [MyPermissions, MyPermissions1, MyPermissions2]
def check_permissions(self, request): for permission in self.get_permissions(): if permission.has_permission(request, self): return else: self.permission_denied( request, message=getattr(permission, 'message', None), code=getattr(permission, 'code', None) )在类里面添加方法修改
改为只要有一个返回为True就通过验证
都返回False才会报错
错误信息也可以自定义
xxxxxxxxxxdef check_permissions(self, request): no_permissions_list = [] for permission in self.get_permissions(): if permission.has_permission(request, self): return else: no_permissions_list.append(permission) else: self.permission_denied( request, message=getattr(no_permissions_list[0], 'message', None), code=getattr(no_permissions_list[0], 'code', None) )甚至可以自己写
xxxxxxxxxxmessage = {"code": 1000, "message": "这是自己写的错误信息"}
如果想对多个视图函数使用的话可以编写一个类,让其他的类继承他,这样就可以实现多个类共用
xxxxxxxxxxclass NbAPIView(APIView): def check_permissions(self, request): no_permissions_list = [] for permission in self.get_permissions(): if permission.has_permission(request, self): return else: no_permissions_list.append(permission) else: self.permission_denied( request, message=getattr(no_permissions_list[0], 'message', None), code=getattr(no_permissions_list[0], 'code', None) )让其他的类继承NbAPIView
认证处理
xxxxxxxxxxfrom rest_framework.authentication import BaseAuthenticationfrom rest_framework.exceptions import AuthenticationFailedfrom app01 import models
class QueryParamsAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if not token: return user_object = models.UserInfo.objects.filter(token=token).first() if user_object: return user_object, token
def authenticate_header(self, request): return "token API"
class MetaAuthentication(BaseAuthentication): def authenticate(self, request): token = request.META.get("HTTP_AUTHORIZATION") # 获取请求头中的 Authorization if not token: return user_object = models.UserInfo.objects.filter(token=token).first() if user_object: return user_object, token
def authenticate_header(self, request): return "token API"
class NoAuthentication(BaseAuthentication): def authenticate(self, request):
raise AuthenticationFailed({"code": 10001, "msg": "认证失败"}) def authenticate_header(self, request): return "token API"
权限处理
xxxxxxxxxxfrom rest_framework.permissions import BasePermissionfrom random import randint
class UserPermissions(BasePermission): message = {"status": False, "msg": "无权访问"}
def has_permission(self, request, view): if request.user.role == 3: return True return False
class ManagePermissions1(BasePermission): message = {"status": False, "msg": "无权访问"}
def has_permission(self, request, view): if request.user.role == 2: return True return False
class BossPermissions2(BasePermission): message = {"status": False, "msg": "无权访问"}
def has_permission(self, request, view): if request.user.role == 1: return True return False
视图
xxxxxxxxxxfrom ext.view import NbAPIView # 上文中提到过
class userView(NbAPIView): permission_classes = [ManagePermissions1, BossPermissions2] def get(self, request): print("访问成功,只有管理员或老板可以访问") return Response("userView")
class orderView(NbAPIView): permission_classes = [UserPermissions, ManagePermissions1, BossPermissions2] def get(self, request): print("访问成功,都可以访问") return Response("orderView")这样就可以实现识别用户的权限,并且根据权限拦截访问
多个权限组件与多个认证组件的区分
| 组件类型 | 进行下一个判断的条件 | 退出判断的条件 | 逻辑 |
|---|---|---|---|
| 认证组件 | 返回None | 返回user和auth或者raise AuthenticationFailed | 接收到值或者报错就退出校验 |
| 权限组件(默认逻辑) | 返回True | 返回False | 默认全部为True才通过,返回一个False即结束 |
| 权限组件(修改为或逻辑) | 返回False | 返回True | 返回False即说明不符合这个权限,进行下一个权限验证,返回True说明权限校验成功,退出验证 |
或逻辑权限校验
xxxxxxxxxxfrom rest_framework.views import APIView
class NbAPIView(APIView): def check_permissions(self, request): no_permissions_list = [] for permission in self.get_permissions(): if permission.has_permission(request, self): return else: no_permissions_list.append(permission) else: self.permission_denied( request, message=getattr(no_permissions_list[0], 'message', None), code=getattr(no_permissions_list[0], 'code', None) )django中的中间件执行时机在认证组件,权限组件之前
限制访问的次数频率(一分钟多少次?)
使用示例:
先安装模块
xxxxxxxxxxpip install django-redis
xxxxxxxxxxfrom rest_framework.throttling import SimpleRateThrottlefrom django.core.cache import cache as default_cache
class MyThrottle(SimpleRateThrottle): scope = "XXX" # 配置节流器的名称 THROTTLE_RATES = {"XXX": "5/m"} # 键是节流器名称 值是限制的频率 cache = default_cache # 配置redis作为cache将访问记录缓存起来 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}
from django.core.cache import cache as default_cache这里引入的cache默认读取的是settings文件中CACHES中的 default ,如果没没有配置default,这个cache默认是用不了的
xxxxxxxxxx# 导入组件class loginView(APIView): throttle_classes = [MyThrottle, ] # 编写 throttle_classes 应用类
配置redis作为默认缓存(settings.py)
xxxxxxxxxxCACHES = { # 默认缓存 "default": { "BACKEND": "django_redis.cache.RedisCache", # 项目上线时,需要调整这里的路径 # "LOCATION": "redis://:密码@IP地址:端口/库编号", "LOCATION": "redis://:@192.168.207.100:6379/0", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 10} } }}配置了以后在redis中会记录访问的频率
前五次正常访问
超过频率后
提示超过频率限制
获取时间间隔、访问次数

此时得到
self.num_requests和self.duration
源码执行流程

xxxxxxxxxxfrom rest_framework.throttling import SimpleRateThrottlefrom django.core.cache import cache as default_cachefrom rest_framework.exceptions import Throttled
class IPThrottle(SimpleRateThrottle): scope = "ip" cache = default_cache def get_cache_key(self, request, view): ident = self.get_ident(request) return self.cache_format % {'scope': self.scope, 'ident': ident}
class UserThrottle(SimpleRateThrottle): scope = "user" cache = default_cache def get_cache_key(self, request, view): ident = request.user.pk return self.cache_format % {'scope': self.scope, 'ident': ident}
def wait(self): """ 重写wait方法,返回自定义信息 """ if self.history: remaining_duration = self.duration - (self.now - self.history[-1]) else: remaining_duration = self.duration raise Throttled( detail={ "message": "请求过于频繁,请稍后再试", "retry_after": int(remaining_duration), "code": 429 } )在分析源码执行流程时可以看出请求被限制时会进入wait方法
如果想要自定义异常信息的话只需要重写wait方法即可
xxxxxxxxxxclass UserThrottle(SimpleRateThrottle): scope = "user" cache = default_cache def get_cache_key(self, request, view): ident = request.user.pk return self.cache_format % {'scope': self.scope, 'ident': ident} # --------------------- 在throttle类里面重写wait方法 def wait(self): """ 重写wait方法,返回自定义信息 """ if self.history: remaining_duration = self.duration - (self.now - self.history[-1]) else: remaining_duration = self.duration raise Throttled( detail={ "message": "请求过于频繁,请稍后再试", "retry_after": int(remaining_duration), "code": 429 } )关注源码的流程,只有当请求收到限制的时候,也就是超过了限流类允许的最大次数时,wait方法才会被调用,所以当需要自定义抛出的异常信息时,只需要在wait方法里面抛出自定义的错误信息就可以了
xxxxxxxxxxfrom rest_framework.versioning import QueryParameterVersioning
class homeView(APIView): versioning_class = QueryParameterVersioning def get(self, request): # http://127.0.0.1:8000/home/?version=v2 print(request.version) # v2 return Response({"code": 0, "message": "成功"})此时会自动的读取query请求头中携带的版本信息
比如此时
?name=vhsj&age=20&version=v2此时会自动读取请求头中的version信息
并赋值给request.version

从源码中可以看出,获取的请求头中的名字取决于配置文件中的VERSION_PARAM
可以通过配置这个信息来自定义获取的参数名
xxxxxxxxxxREST_FRAMEWORK = { "UNAUTHENTICATED_USER": None, "VERSION_PARAM": "v", # ----------- 如果获取不到的话就会获取DEFAULT_VERSION "DEFAULT_VERSION": "v", # ----------- 填写已有的版本号信息 如果用户发送了不存在的版本号则报错 "ALLOWED_VERSIONS": ['v1']}此时就可以使用v来传递版本参数
最佳实践:
使用默认的version来传递参数名称
####
首先分析源码流程

了解源码流程以后就可以执行以下操作反向生成url
xxxxxxxxxxfrom django.urls import pathfrom api import viewsurlpatterns = [ path("home/", views.homeView.as_view(), name="hh")]xxxxxxxxxxclass homeView(APIView): versioning_class = QueryParameterVersioning def get(self, request): # http://127.0.0.1:8000/home/?version=v1 print(request.version) # v1 url = request.versioning_scheme.reverse("hh", request=request) print("反向生成的url:", url) return Response({"code": 0, "message": "成功"})
注意此时反向生成的url是只会携带你的版本号参数,如果有其他参数他不会自动携带
xxxxxxxxxxfrom django.urls import pathfrom api import viewsurlpatterns = [ path("api/<str:version>/home/", views.homeView.as_view(), name="hh")]xxxxxxxxxxfrom rest_framework.versioning import QueryParameterVersioning, URLPathVersioning
class homeView(APIView): versioning_class = URLPathVersioning def get(self, request, *args, **kwargs): print(request.version) return Response({"code": 0, "message": "成功"})"""class homeView(APIView): versioning_class = URLPathVersioning def get(self, request, version): print(version) 直接获取 return Response({"code": 0, "message": "成功"})"""xxxxxxxxxxfrom django.urls import pathfrom api import viewsurlpatterns = [ path("api/home/", views.homeView.as_view(), name="hh")]xxxxxxxxxxfrom rest_framework.response import Responsefrom rest_framework.views import APIViewfrom rest_framework.versioning import AcceptHeaderVersioning
class homeView(APIView): versioning_class = AcceptHeaderVersioning def get(self, request, *args, **kwargs): print(request.version) print(request.versioning_scheme.reverse("hh", request=request)) return Response({"code": 0, "message": "成功"})

这样也可以获取到版本信息,且同样可以反向生成URL
以上三种传递参数的方式
最常用的是方式二
其次是方式一
方式三几乎不使用
为了方便可以这样写
xxxxxxxxxxREST_FRAMEWORK = { "UNAUTHENTICATED_USER": None, "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning"}在定义类的时候就不用每次都写一遍版本信息的相关配置了
解析器:解析请求者发送过来的数据,根据请求头的类型匹配类,然后在类里面对不同的数据进行解析之后赋值给request.data
xxxxxxxxxxfrom rest_framework.parsers import JSONParser, FormParserfrom rest_framework.negotiation import DefaultContentNegotiation
class homeView(APIView): # 所有的解析器 parser_classes = [JSONParser, FormParser]
# 根据请求 匹配对应的解析器 content_negotiation_class = DefaultContentNegotiation def get(self, request, *args, **kwargs): print(request.version) print(request.versioning_scheme.reverse("hh", request=request)) return Response({"code": 0, "message": "成功"})
def post(self, request, *args, **kwargs): return Response("OK")


此时可以通过request.data打印获取到的数据
最佳实践:
使用JSONParser
因为JSON对于多层嵌套的格式易于处理

在执行request.data时才会去触发解析器的动作
如果调用多次
xxxxxxxxxxrequest.datarequest.data那只会解析一次

FileUploadParser
此方法为手动写入文件且写入的路径是项目的根路径
xxxxxxxxxxfrom rest_framework.parsers import FileUploadParser
class homeView(APIView): parser_classes = [FileUploadParser]
content_negotiation_class = DefaultContentNegotiation def post(self, request, *args, **kwargs): print(request.content_type) print(request.data) file_object = request.data.get('file') with open(file_object.name, mode='wb') as target_file_object: for chunk in file_object: target_file_object.write(chunk) file_object.close() return Response("OK")FileUploadParser最佳实践
default_storage.save会将文件保存到media目录下
default_storage.url会返回文件的相对路径(相对根目录)
缺点:
此方法只能传输文件,不能同时传输其他类型的数据,导致了在传输时必须携带Header
(前端一般默认会携带)
在某些场景下会显得不方便
xxxxxxxxxxfrom rest_framework.parsers import FileUploadParserfrom django.core.files.storage import default_storageclass homeView(APIView): parser_classes = [FileUploadParser]
content_negotiation_class = DefaultContentNegotiation def post(self, request, *args, **kwargs): file_object = request.data.get('file') file_name = default_storage.save(file_object.name, file_object) file_url = default_storage.url(file_name) print(file_url) return Response("OK")MultiPartParser最佳实践
这种解析可以允许多种数据的携带
可以在上传文件的同时上传其他的东西

xxxxxxxxxxfrom rest_framework.parsers import MultiPartParserfrom django.core.files.storage import default_storageclass homeView(APIView): parser_classes = [MultiPartParser] content_negotiation_class = DefaultContentNegotiation def post(self, request, *args, **kwargs): print(request.data) file_object = request.data.get('file') default_storage.save(file_object.name, file_object) return Response("OK")许多开发者一般都不写解析器的配置,drf内部有默认值
在学习阶段建议手动编写配置
xxxxxxxxxxclass homeView(APIView): # 所有的解析器 content_negotiation_class = DefaultContentNegotiation def post(self, request, *args, **kwargs): print(self.parser_classes) return Response("OK")输出结果
xxxxxxxxxx[<class 'rest_framework.parsers.JSONParser'>, <class 'rest_framework.parsers.FormParser'>, <class 'rest_framework.parsers.MultiPartParser'>]可以通过在settings.py中通过配置来修改这一项
xxxxxxxxxxREST_FRAMEWORK = { "DEFAULT_PARSER_CLASSES": ['rest_framework.parsers.JSONParser']}输出结果
xxxxxxxxxx[<class 'rest_framework.parsers.JSONParser'>]
以下两部分的创建类的效果完全一样
都是使用的type创建类,只是表现的形式不一样
xxxxxxxxxx# class Foo(object):# v1 = 123# def func(self):# return 999
Foo = type("Foo", (object,), {"v1": 123, "func": lambda self: 999})
obj = Foo()print(obj.v1)print(obj.func())
type("Foo", (object,), {"v1": 123, "func": lambda self: 999})会去执行这个类里面的
__init__方法所有的类在执行
__init__方法之前会先执行一个__new__方法
__new__方法会先创建一个空值,然后再让__init__方法去这个空值里面去初始化数据所以创建类本质上是在
__new__方法中执行的
演示:
xxxxxxxxxxclass MyType(type): def __new__(cls, *args, **kwargs): xx = super().__new__(cls, *args, **kwargs) print(xx) return xx
Foo = MyType("Foo", (object,), {"v1": 123, "func": lambda self: 999})print(Foo)
print("--------------- 下面是测试类产生的")obj = Foo()print(obj.v1)print(obj.func())new方法的应用
xxxxxxxxxxclass MyType(type):def __new__(cls, name, bases, attrs):del attrs['v1']xx = super().__new__(cls, name, bases, attrs)print(name, bases, attrs)return xxclass Foo(object, metaclass=MyType):v1 = 123def __init__(self):self.age = 20def func(self):passprint(Foo.v1)可以在创建之前就把属性删除
注意此时删除的不能是init方法里面创建的属性
要在参数传递到super里面之前删除否则传递到super里面以后删除也没用了
可以删除属性东西同样也可以往里面添加东西
继承的子类也由父类的决定
xxxxxxxxxxclass MyType(type):def __new__(cls, name, bases, attrs):xx = super().__new__(cls, name, bases, attrs)return xxclass Base(object, metaclass=MyType):passclass Foo(Base):pass此时Foo也由MyType创建
类或父类中如果指定了metaclass则当前类和子类也由指定的metaclass创建
在类加载的时候可以通过元类对类进行一些操作
xxxxxxxxxx# 利用attrs获取里面的数据def __new__(cls, name, bases, attrs):for k, v in list(attrs.items()):if isinstance(v, int):del attrs[k]像这样的操作
序列化的本质是把数据库中获取到的数据转换成JSON格式
并返回给用户, 已有的json.dumps()方法由于使用类型受限,只能转换原生的python中的数据类型
所以需要用到drf的序列化

这是用于测试的数据库表
xxxxxxxxxxclass DepartSerializers(serializers.Serializer): title = serializers.CharField() count = serializers.IntegerField()
class DepartView(APIView): def get(self, request): depart_obj = models.Depart.objects.all().first() ser = DepartSerializers(instance=depart_obj) print(ser.data) return Response("这是成功的消息")想要使用序列化首先要定义一个类
这个类里面的字段使用serializers.字段类型(定义时的数据类型)
这里面有哪些字段,后面的对象就可以获取到哪些字段
上面的演示代码会输出
xxxxxxxxxx{'title': '开发部', 'count': 10}
在上一节点中使用的depart_obj是一个字典类型的对象
在实际开发中我们遇到的不一定是这样简简单单的对象,可能是queryset类型的
xxxxxxxxxxclass DepartSerializers(serializers.Serializer): title = serializers.CharField() count = serializers.IntegerField()
class DepartView(APIView): def get(self, request): queryset = models.Depart.objects.all() ser = DepartSerializers(instance=queryset, many=True) print(ser.data) return Response(ser.data)通过指定many=True就可以让他支持queryset类型的数据

注意此处的名称类型需要对应
此时输出的内容:
xxxxxxxxxx[{'title': '开发部', 'count': 10}, {'title': '运营部', 'count': 20}]每次使用Serializer都需要把需要的字段都重新写一遍显得有点麻烦
我们可以使用ModelSerializer来简化写法
就像这样:
xxxxxxxxxxfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom api.auth import MyAuthenticationfrom api import modelsfrom rest_framework import serializers
class DepartSerializers(serializers.ModelSerializer): class Meta: model = models.Depart fields = "__all__"
class DepartView(APIView): def get(self, request): queryset = models.Depart.objects.all() ser = DepartSerializers(instance=queryset, many=True) print(ser.data) return Response(ser.data)输出示例:
xxxxxxxxxx[{'id': 1, 'title': '开发部', 'order': 1, 'count': 10}, {'id': 2, 'title': '运营部', 'order': 2, 'count': 20}]首先创建一个模型
xxxxxxxxxxclass UserInfo(models.Model):name = models.CharField(verbose_name="姓名", max_length=32)age = models.IntegerField(verbose_name="年龄")gender = models.SmallIntegerField(verbose_name="性别", choices=((1, '男'), (2, '女')))depart = models.ForeignKey(verbose_name="部门", to="Depart", on_delete=models.CASCADE)ctime = models.DateTimeField(verbose_name="时间", auto_now_add=True)插入两条数据
现在
xxxxxxxxxxclass UserSerializers(serializers.ModelSerializer):class Meta:model = models.UserInfofields = "__all__"class UserView(APIView):def get(self, request):models.UserInfo.objects.all().update(ctime=datetime.datetime.now())queryset = models.UserInfo.objects.all()ser = UserSerializers(instance=queryset, many=True)return Response(ser.data)发送请求后得到的返回的数据是这样的:
xxxxxxxxxx[{"id": 1,"name": "a","age": 21,"gender": 1,"ctime": "2026-03-07T07:08:12.571546Z","depart": 1},{"id": 2,"name": "b","age": 21,"gender": 2,"ctime": "2026-03-07T07:08:12.571546Z","depart": 2}]
利用source指定,利用Django中的知识
xxxxxxxxxxclass UserSerializers(serializers.ModelSerializer): gender = serializers.CharField(source="get_gender_display") class Meta: model = models.UserInfo fields = ["name", "age", "gender"]指定
source="get_gender_display"注意此时获取到的“男”/“女”是CharField,所以需要改成CharField
如果说同字段类型重名,则会报错,比如如果我model中定义的是CharField,我这里再自定义一个CharFIeld,那就会报错,不同类型重名则会覆盖
xxxxxxxxxx[ { "name": "a", "age": 21, "gender": "男" }, { "name": "b", "age": 21, "gender": "女" }]xxxxxxxxxxclass UserSerializers(serializers.ModelSerializer): gender = serializers.CharField(source="get_gender_display") depart = serializers.CharField(source="depart.title") class Meta: model = models.UserInfo fields = ["name", "age", "gender", "depart"]指定
source="depart.title"本质上:
xxxxxxxxxxobj.get_gender_display()obj.depart.title
xxxxxxxxxxclass UserSerializers(serializers.ModelSerializer): gender = serializers.CharField(source="get_gender_display") depart = serializers.CharField(source="depart.title") ctime = serializers.DateTimeField(format="%Y年%m月%d日") class Meta: model = models.UserInfo fields = ["name", "age", "gender", "depart", "ctime"]利用
format=""将时间格式格式化
输出:
xxxxxxxxxx[{"name": "a","age": 21,"gender": "男","depart": "开发部","ctime": "2026年03月07日"},{"name": "b","age": 21,"gender": "女","depart": "运营部","ctime": "2026年03月07日"}]
xxxxxxxxxxclass UserSerializers(serializers.ModelSerializer): gender = serializers.CharField(source="get_gender_display") depart = serializers.CharField(source="depart.title") ctime = serializers.DateTimeField(format="%Y年%m月%d日") xxx = serializers.SerializerMethodField() class Meta: model = models.UserInfo fields = ["name", "age", "gender", "depart", "ctime", "xxx"]
def get_xxx(self, obj): return f'{obj.name}-{obj.age}-{obj.gender}'自定义字段名称是
xxx的话方法就是get_xxx方法里面的obj就是当前的字段对象
示例返回:
xxxxxxxxxx[{"name": "a","age": 21,"gender": "男","depart": "开发部","ctime": "2026年03月07日","xxx": "a-21-1"},{"name": "b","age": 21,"gender": "女","depart": "运营部","ctime": "2026年03月07日","xxx": "b-21-2"}]
示例model:
xxxxxxxxxxclass Depart(models.Model): title = models.CharField(verbose_name="部门", max_length=64) order = models.IntegerField(verbose_name="顺序") count = models.IntegerField(verbose_name="人数")
class UserInfo(models.Model): name = models.CharField(verbose_name="姓名", max_length=32) age = models.IntegerField(verbose_name="年龄") gender = models.SmallIntegerField(verbose_name="性别", choices=((1, '男'), (2, '女'))) depart = models.ForeignKey(verbose_name="部门", to="Depart", on_delete=models.CASCADE) ctime = models.DateTimeField(verbose_name="时间", auto_now_add=True) tags = models.ManyToManyField(verbose_name="标签", to="Tag")
class Tag(models.Model): caption = models.CharField(verbose_name="标签", max_length=32)此时由于UserInfo中返回的tags字段是一个queryset类型的数据,无法利用source解决
可以使用自定义方法处理
处理实例:
xxxxxxxxxxclass UserSerializers(serializers.ModelSerializer): gender = serializers.CharField(source="get_gender_display") depart = serializers.CharField(source="depart.title") ctime = serializers.DateTimeField(format="%Y年%m月%d日") tags = serializers.SerializerMethodField() class Meta: model = models.UserInfo fields = ["name", "age", "gender", "depart", "ctime", "tags"]
def get_tags(self, obj): result = [{"id": item.id, "tag": item.caption} for item in obj.tags.all()] return resultxxxxxxxxxxclass D1(serializers.ModelSerializer): class Meta: model = models.Depart() fields = "__all__"
class D2(serializers.ModelSerializer): class Meta: model = models.Tag() fields = "__all__"
class UserSerializers(serializers.ModelSerializer): gender = serializers.CharField(source="get_gender_display") depart = D1() ctime = serializers.DateTimeField(format="%Y年%m月%d日") tags = D2(many=True) class Meta: model = models.UserInfo fields = ["name", "age", "gender", "depart", "ctime", "tags"]利用类的嵌套
在新创建的类里面对于这种类型的数据添加
many=True实现获取到所有对应的数据返回示例:
xxxxxxxxxx[{"name": "a","age": 21,"gender": "男","depart": {"id": 1,"title": "开发部","order": 1,"count": 10},"ctime": "2026年03月08日","tags": [{"id": 1,"caption": "暖男"},{"id": 2,"caption": "渣男"}]},...]注意上述的D1和D2类里面的字段也可以自定义,和普通的类里面自定义的方法一致
对于两个类里面都需要使用同一个字段的情况,可以使用继承
示例:
xxxxxxxxxxclass Base(serializers.ModelSerializer): xx = ... class D(serializers.ModelSerializer, Base): ...这样在D类里面就自带了xx这个字段,就不用再自己重新写了

上一步中创建了字段了,按照执行的流程,这一步到了创建类


xxxxxxxxxxclass DepartSerializers(serializers.Serializer): username = serializers.CharField(required=True) password = serializers.CharField(required=True)
class DepartView(APIView): def post(self, request, *args, **kwargs): # 1. 获取原始数据 # print(request.data) # 2. 校验 ser = DepartSerializers(data=request.data) if ser.is_valid(): print(ser.validated_data) else: print(ser.errors) return Response("...")xxxxxxxxxxclass DepartSerializers(serializers.Serializer): username = serializers.CharField(required=True) password = serializers.CharField(required=True)
class DepartView(APIView): def post(self, request, *args, **kwargs): ser = DepartSerializers(data=request.data) ser.is_valid(raise_exception=True) return Response("成功")第二种写法关键在于传递了
raise_exception=True这样的话如果校验成功他就会继续往下执行
如果校验失败就会抛出异常,并且让异常信息返回,drf会自动捕获这个异常,不会中断程序
演示:
校验成功:
xxxxxxxxxx"成功"校验失败:
xxxxxxxxxx{"password": ["该字段是必填项。"]}这种写法的坏处在于抛出的异常信息不方便控制
xxxxxxxxxxclass DepartSerializers(serializers.Serializer): title = serializers.CharField(required=True, max_length=20, min_length=6) order = serializers.IntegerField(required=False, max_value=100, min_value=10) level = serializers.ChoiceField(choices=[("1", "高级"), (2, "中级")])
class DepartView(APIView): def post(self, request, *args, **kwargs): # 1. 获取原始数据 # print(request.data) # 2. 校验
ser = DepartSerializers(data=request.data) if ser.is_valid(): print(ser.validated_data) else: print(ser.errors)
return Response("...")在创建类的时候添加校验规则
上面的level里面定义的choices中数字的类型程序会自动帮我们处理,实际开发中建议保持规则
比如在上述实例中:
输入
xxxxxxxxxx{"title": "afsdfsd", "order": "11", "level": 1}程序打印
xxxxxxxxxx{'title': 'afsdfsd', 'order': 11, 'level': '1'}会自动处理类型
上述中有一种特殊的
xxxxxxxxxxclass DepartSerializers(serializers.Serializer): email = serializers.EmailField()这种写法几乎等价于:
xxxxxxxxxxfrom django.core.validators import EmailValidatorclass DepartSerializers(serializers.Serializer): email = serializers.CharField(validators=[EmailValidator(message="邮箱格式错误")])邮箱可以用内置的校验规则,也可以自己编写校验规则
xxxxxxxxxxfrom django.core.validators import RegexValidatorclass DepartSerializers(serializers.Serializer): email = serializers.CharField(validators=[RegexValidator(r'\d+', message="格式错误")])在定义字段的时候,可以在下面定义
validate_字段名的钩子方法,会进行相应的校验
xxxxxxxxxxfrom django.core.validators import RegexValidatorfrom rest_framework import exceptionsclass DepartSerializers(serializers.Serializer): title = serializers.CharField(required=True, max_length=20, min_length=6) order = serializers.IntegerField(required=False, max_value=100, min_value=10) level = serializers.ChoiceField(choices=[("1", "高级"), (2, "中级")]) email = serializers.CharField(validators=[RegexValidator(r'\d+', message="邮箱格式错误")]) def validate_email(self, value): if len(value) > 6: raise exceptions.ValidationError("钩子校验失败") return valuevalue是传进去的旧值,返回的值是校验后的,如果在最后修改了返回的值,他会反映到validated_data里面
上面说的是局部的字段的钩子,还有全局的钩子,全局的钩子会在所有局部的钩子校验通过后执行
xxxxxxxxxxfrom django.core.validators import RegexValidatorfrom rest_framework import exceptionsclass DepartSerializers(serializers.Serializer): title = serializers.CharField(required=True, max_length=20, min_length=6) order = serializers.IntegerField(required=False, max_value=100, min_value=10) level = serializers.ChoiceField(choices=[("1", "高级"), (2, "中级")]) email = serializers.CharField(validators=[RegexValidator(r'\d+', message="邮箱格式错误")]) def validate_email(self, value): if len(value) > 6: raise exceptions.ValidationError("钩子校验失败") return value def validate(self, attrs): print(attrs) return attrs # 在这里面同样可以像上面一样抛出异常在这里面和Django一样,attrs和validated_data名字不同但是所指向的内存地址是相同的,在执行完了所有的校验方法后,attrs会直接引用到validated_data
如果抛出了异常,全局的钩子的异常和局部的钩子的异常的信息不一样
xxxxxxxxxx{'non_field_errors': [ErrorDetail(string='全局钩子校验失败', code='invalid')]}xxxxxxxxxx{'email': [ErrorDetail(string='钩子校验失败', code='invalid')]}一个是字段名,一个是non_field_errors
后者可以在配置中修改(settings.py)
xxxxxxxxxxREST_FRAMEWORK = { "NON_FIELD_ERRORS_KEY": "ALL_ERROR"}此时:
xxxxxxxxxx{'ALL_ERROR': [ErrorDetail(string='全局钩子校验失败', code='invalid')]}一般不用
xxxxxxxxxxclass DepartModelSerializer(serializers.ModelSerializer): class Meta: model = models.Depart fields = ["title", "order", "count"] extra_kwargs = { "title": {"max_length": 5, "min_length": 1}, "order": {"min_value": 5}, # "count": {"validators": [RegexValidator(r"\d+", message="格式错误")]}, }这里面同样可以添加钩子方法,主要区别在于校验的参数需要通过字典的形式放进去
对于Serializer
可以把数据打散放进去
xxxxxxxxxxser = DepartSerializer(data=request.data)ser.validated_datamodels.Depart.objects.create(**ser.validated_data)对于ModelSerializer
可以直接调用save方法
xxxxxxxxxxser = DepartModelSerializer(data=request.data)ser.validated_dataser.save()调用save方法直接存储会面临两个小问题
问题一:
如果数据库中有三个字段,但是用户只传了两个字段
就需要通过save方法传参自定义字段
ser.save(count=100)像这样问题二:
如果数据库中只有三个字段,但是用户传了四个字段
就需要在最后删除字段
ser.validated_data.pop('more')像这样,在保存的时候就不会提交多余的字段
对于ForeignKey,直接传入对应的关联的id即可,如果传入的id不存在,会报错,存在则自动创建
xxxxxxxxxx{'depart': [ErrorDetail(string='无效主键 “10” - 对象不存在。', code='does_not_exist')]}如果有更多的校验规则,可以通过添加钩子函数来完成,此时的钩子函数里面的value是depart中的一个对象
对于ManyToManyField,传入一个列表,列表里面是需要关联的表中的id即可
xxxxxxxxxx{"name": "test1", "age": "21", "gender": "1", "depart": "1", "tags": [1, 2]}像这样传入,会自动关联相关的信息以及映射关系
如果添加了钩子方法,钩子方法里面的value是一个列表,列表里面是符合规则的对象
如果对于ManyToManyField存储希望进行自定义,那接受数据类型的时候可以选择serializers.ListField()
示例:
xxxxxxxxxxclass UsSerializer(serializers.ModelSerializer): tags = serializers.ListField()
class Meta: model = models.UserInfo fields = ['name', 'age', 'gender', 'depart', 'tags']
def validate_tags(self, value): queryset = models.Tag.objects.filter(id__in=value) print(queryset) return queryset像这样也可以和之前一样实现功能,还可以在里面加入自己想要自定义的内容
唯一的区别是原生的是传递的list类型的,现在传递的是QuerySet类型的,效果一样
xxxxxxxxxxclass DpModelSerializer(serializers.ModelSerializer): class Meta: model = models.Depart fields = "__all__"
class Dp2ModelSerializer(serializers.ModelSerializer): class Meta: model = models.Depart fields = ["id", "title", "order"]
class DpView(APIView): def post(self, request, *args, **kwargs): ser = DpModelSerializer(data=request.data) if ser.is_valid():
instance = ser.save() xx = Dp2ModelSerializer(instance=instance) return Response(xx.data) else: print(ser.errors) return Response("...")
从而拿到返回的数据,也可以不单独写一个Serializer,共用一个Serializer,只不过这样的话返回的东西就不可以自定制了,显得很局限,但是每次都写两个类就显得很麻烦,此时就可以使用两个参数来控制
xxxxxxxxxxclass DpModelSerializer(serializers.ModelSerializer): class Meta: model = models.Depart fields = ["id", "order", "title", "count"] extra_kwargs = { "id": {"read_only": True}, "count": {"write_only": True} }
read_only看字面意思就是只读的,用这里的场景来解释就是只能在序列化(读取查看)的时候使用,写的时候不能写(此处的原因是因为他是自动生成的)
write_only看字面意思就是只写的,用这里的场景来解释的话就是只能在校验(写入)的时候使用,读的时候不能读
实际使用场景:
给用户返回性别信息时返回性别的信息而不是返回数字
xxxxxxxxxxclass UserModelSerializer(serializers.ModelSerializer): gender_info = serializers.CharField(source="get_gender_display", read_only=True) class Meta: model = models.UserInfo fields = ["id", "name", "age", "gender", "gender_info"] extra_kwargs = { "id": {"read_only": True}, "gender": {"write_only": True} }
class UserView(APIView): def post(self, request, *args, **kwargs): ser = UserModelSerializer(data=request.data) if ser.is_valid():
instance = ser.save(depart_id=1) xx = UserModelSerializer(instance=instance) return Response(xx.data) else: print(ser.errors) return Response("...")还可以这样实现:
xxxxxxxxxxclass UserModelSerializer(serializers.ModelSerializer): gender_info = serializers.SerializerMethodField()
class Meta: model = models.UserInfo fields = ["id", "name", "age", "gender", "gender_info"] extra_kwargs = { "id": {"read_only": True}, "gender": {"write_only": True} }
def get_gender_info(self, obj): return obj.get_gender_display()对于外键类型的可以通过嵌套实现数据的显示,但是如果要让名字的对应关系不出问题可以使用source参数
xxxxxxxxxxclass UserModelSerializer(serializers.ModelSerializer): gender_info = serializers.SerializerMethodField() v1 = P1Serializer(read_only=True, source="depart")
class Meta: model = models.UserInfo fields = ["id", "name", "age", "gender", "gender_info", "v1"] extra_kwargs = { "id": {"read_only": True}, "gender": {"write_only": True} }
def get_gender_info(self, obj): return obj.get_gender_display()注意
当只写了一个类的时候,就可以不这样写了
xxxxxxxxxxclass UserView(APIView):def post(self, request, *args, **kwargs):ser = UserModelSerializer(data=request.data)if ser.is_valid():instance = ser.save(depart_id=1)xx = UserModelSerializer(instance=instance)return Response(xx.data)else:print(ser.errors)return Response("...")直接这样写:
xxxxxxxxxxclass UserView(APIView):def post(self, request, *args, **kwargs):ser = UserModelSerializer(data=request.data)if ser.is_valid():ser.save()return Response(ser.data)else:print(ser.errors)return Response("...")当只写一个的时候内部会自动处理,两个的效果都是一样的
在上述校验的同时序列化的时候,会遇到一个问题,当编写一个类的时候,以gender举例,怎么实现一个类的情况下,让gender在写的时候用choices,读的时候为“男”/“女”呢?
首先将Serializer类中to_representation方法源码复制过来准备修改
xxxxxxxxxxfrom rest_framework.fields import SkipFieldfrom rest_framework.relations import PKOnlyObject from rest_framework import serializers
class UserModelSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = ["id", "name", "age", 'gender'] extra_kwargs = { "id": {"read_only": True} }
def to_representation(self, instance): """ Object instance -> Dict of primitive datatypes. """ ret = {} fields = self._readable_fields
for field in fields: try: attribute = field.get_attribute(instance) except SkipField: continue
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute if check_for_none is None: ret[field.field_name] = None else: ret[field.field_name] = field.to_representation(attribute)
return ret修改后:
xxxxxxxxxxfrom rest_framework.fields import SkipFieldfrom rest_framework.relations import PKOnlyObject from rest_framework import serializers
class UserModelSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = ["id", "name", "age", 'gender'] extra_kwargs = { "id": {"read_only": True} }
def to_representation(self, instance): """ Object instance -> Dict of primitive datatypes. """ ret = {} fields = self._readable_fields
for field in fields: # ---------------- 修改部分 if hasattr(self, 'nb_%s' % field.field_name): value = getattr(self, 'nb_%s' % field.field_name)(instance) ret[field.field_name] = value else: # ---------------- try: attribute = field.get_attribute(instance) except SkipField: continue
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute if check_for_none is None: ret[field.field_name] = None else: ret[field.field_name] = field.to_representation(attribute) return ret
def nb_gender(self, obj): return obj.get_gender_display()上述的里面是自定义的
nb_方法名,实际开发中可以自己自定义也可以自己自定义一个基类,避免每次都重新编写
to_representation方法
xxxxxxxxxxpath('api/blog/<int:pk>/', views.BlogDetailView.as_view())获取参数的方法:(两种,分情况使用)
xxxxxxxxxxdef get(self, request, *args, **kwargs): pk = kwargs.get('pk') xxxxxxxxxxdef get(self, request, pk): pk = pkinitial_data未经处理的原始数据(从前端接收到的)【钩子校验里面使用】
validated_data经过了所有验证方法后的数据【钩子钩子校验流程之后使用】
attrs在全局验证函数中的经过了字段级校验的所有参数【全局钩子校验函数中使用】
request.data前端传递过来的body中的数据(请求体参数)
request.query_params.get('')获取url链接中携带的参数(类似?name=jelee)
我们在编写确认密码字段的时候,由于数据库中并没有这个字段
所以在保存的时候需要先把
validated_data里面的这个字段pop掉且需要添加
write_only=True
代码示例:
xxxxxxxxxxclass UserSerializer(serializers.ModelSerializer): confirm_password = serializers.CharField(write_only=True) class Meta: model = models.UserInfo fields = ["username", "password", "confirm_password"] extra_kwargs = { "password": {"write_only": True} } def validate(self, attrs): if attrs['password'] == attrs['confirm_password']: attrs.pop('confirm_password') return attrs raise ValidationError("两次输入密码不一致")添加
write_only的原因是如果不添加的话,虽然在写入的时候已经删除了,但是在后面查看的时候会默认去数据库中读取这个字段,但是这个字段在数据库中是没有的,就会报错
案例场景:
xxxxxxxxxxclass LoginSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = ["username", "password"]
class LoginView(APIView): def post(self, request): ser = LoginSerializer(data=request.data)此处如果错误的使用了instance,会导致问题,容易疑惑的点是:
登陆不是比较已有的信息吗,为什么要用data
校验也要用data,因为此时你不知道用户输入的账号密码是否正确,如果不正确,那怎么找到已有信息
利用这一点可以实现一些功能
比如:如果需要外键用户名输入到数据库里面的时候是id,序列后期输出的时候是用户名
xxxxxxxxxxclass BlogCommentSerializer(serializers.ModelSerializer): user = serializers.CharField(source="user.username", read_only=True) class Meta: model = models.Comment fields = ["id", "user", "content"]然后在保存的时候
xxxxxxxxxxser.save(user=request.user, blog=instance)这样指定保存的时候的某些字段就可以避免序列化器的影响
防止数据过多时一次性请求量过大
首先需要在配置中配置每页显示几条数据
settings.py
xxxxxxxxxxREST_FRAMEWORK = { "UNAUTHENTICATED_USER": None, "PAGE_SIZE": 3 # 添加配置}然后处理数据
处理示例:
xxxxxxxxxxfrom rest_framework.pagination import PageNumberPagination导入类以后:
xxxxxxxxxxdef get(self, request, *args, **kwargs): """ 博客列表 """ flag = models.Blog.objects.all().order_by('id') # -------- 处理代码 pager = PageNumberPagination() queryset = pager.paginate_queryset(flag, request, self) # -------- ser = BlogSerializer(instance=queryset, many=True) return Response(ser.data)利用这个类实例化一个方法,然后调用这个方法的paginate_queryset方法传入参数
待分页的queryset、request对象、self
演示:
xxxxxxxxxxhttp://127.0.0.1:8000/api/blog/?page=1传入需要第几页就会返回第几页的数据,所以数据必须是有序的,也就是数据必须经过order_by排序
默认情况不支持传入一页多少条数据(page_size)不过可以自己配置
配置示例:
xxxxxxxxxxclass MyPageNumberPagination(PageNumberPagination): page_size_query_param = "size" page_size = 2 max_page_size = 5
class BlogView(APIView): def get(self, request, *args, **kwargs): """ 博客列表 """ flag = models.Blog.objects.all().order_by('id') # -------- 写成自己编写的类 pager = MyPageNumberPagination() # -------- queryset = pager.paginate_queryset(flag, request, self) ser = BlogSerializer(instance=queryset, many=True) return Response(ser.data)注意,如果这里写了page_size配置,全局也写了page_size配置,drf内部会先读取全局的配置,然后再读取局部的配置,在读取局部的配置时会把全局的配置覆盖,所以最终生效的是局部的配置
这种分页方式与PageNumberPagination最大的区别是:
PageNumberPagination是告诉他需要第几页,每页几条数据
LimitOffsetPagination是告诉他前面有多少条数据,我需要多少条
limit和offset的默认值都是0
xxxxxxxxxxfrom rest_framework.pagination import LimitOffsetPaginationxxxxxxxxxxclass BlogView(APIView): def get(self, request, *args, **kwargs): """ 博客列表 """ flag = models.Blog.objects.all().order_by('id') # ---------- 配置区域 pager = LimitOffsetPagination() queryset = pager.paginate_queryset(flag, request, self) # ---------- ser = BlogSerializer(instance=queryset, many=True) return Response(ser.data)然后就可以使用了
xxxxxxxxxxhttp://127.0.0.1:8000/api/blog/?limit=2&offset=4就会返回两条数据,这两条数据的前面有四条数据,也就是返回第五条和第六条数据
xxxxxxxxxxclass CustomLimitOffsetPagination(LimitOffsetPagination): # 默认要多少条数据 default_limit = 3 # URL 中指定条数的参数名(默认就是 limit,可自定义) limit_query_param = 'limit' # URL 中指定偏移量的参数名(默认就是 offset,可自定义) offset_query_param = 'offset' # 限制最大条数 max_limit = 20使用:
xxxxxxxxxxclass CustomLimitOffsetPagination(LimitOffsetPagination): default_limit = 3 limit_query_param = 'limit' offset_query_param = 'offset' max_limit = 20
class BlogView(APIView): def get(self, request, *args, **kwargs): """ 博客列表 """ flag = models.Blog.objects.all().order_by('id') pager = CustomLimitOffsetPagination() queryset = pager.paginate_queryset(flag, request, self) ser = BlogSerializer(instance=queryset, many=True) return Response(ser.data)使用示例:
使用GenericAPIView对下面的代码进行简化
xxxxxxxxxxclass BlogView(APIView): authentication_classes = [BlogAuthentication, ] def get(self, request, *args, **kwargs): """ 博客列表 """ flag = models.Blog.objects.all().order_by('id') pager = CustomLimitOffsetPagination() queryset = pager.paginate_queryset(flag, request, self) ser = BlogSerializer(instance=queryset, many=True) return Response(ser.data)
def post(self, request): if not request.user: return Response({"code": 1, "error": "认证失败"}) ser = BlogSerializer(data=request.data) if not ser.is_valid(): return Response({"code": 2, "error": ser.errors}) ser.save(creator=request.user) return Response({"code": 1000, "data": ser.data})xxxxxxxxxxfrom rest_framework.generics import GenericAPIViewclass BlogView(GenericAPIView): authentication_classes = [BlogAuthentication, ] # 认证类 还可写其他的类 queryset = models.Blog.objects.all().order_by('id') # queryset 需要的原始数据 pagination_class = CustomLimitOffsetPagination # 分页类 serializer_class = BlogSerializer # 序列化类利用这些类就可以简化操作
xxxxxxxxxxself.get_queryset() # 获取queryset 前面已经配置过了queryset信息 这里可以直接获取self.paginate_queryset() # 构造分页后的数据 需要传入前面获取的querysetself.get_serializer() # 获取序列化数据xxxxxxxxxxfrom rest_framework.generics import GenericAPIViewclass BlogView(GenericAPIView): authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer
def get(self, request, *args, **kwargs): """ 博客列表 """ queryset = self.get_queryset() page = self.paginate_queryset(queryset) ser = self.get_serializer(page, many=True) return Response(ser.data)
def post(self, request): if not request.user: return Response({"code": 1, "error": "认证失败"}) ser = self.get_serializer(data=request.data) if not ser.is_valid(): return Response({"code": 2, "error": ser.errors}) ser.save(creator=request.user) return Response({"code": 1000, "data": ser.data})
最大的意义:
将数据库查询、序列化类提取到类变量中,简化操作
查看源码:

可以看出这个类里面类中没有定义任何代码,他就是继承
ViewSetMixin和GenericAPIView那他的功能也就是这两个类融合到一起
xxxxxxxxxxpath('api/blog/', views.BlogView.as_view())xxxxxxxxxxfrom rest_framework.viewsets import GenericViewSetclass BlogView(GenericViewSet): authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer
def get(self, request, *args, **kwargs): """ 博客列表 """ queryset = self.get_queryset() page = self.paginate_queryset(queryset) ser = self.get_serializer(page, many=True) return Response(ser.data)
def post(self, request): if not request.user: return Response({"code": 1, "error": "认证失败"}) ser = self.get_serializer(data=request.data) if not ser.is_valid(): return Response({"code": 2, "error": ser.errors}) ser.save(creator=request.user) return Response({"code": 1000, "data": ser.data})xxxxxxxxxxpath('api/blog/', views.BlogView.as_view({"get": "list", "post": "create"}))xxxxxxxxxxfrom rest_framework.viewsets import GenericViewSetclass BlogView(GenericViewSet): authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer
def list(self, request, *args, **kwargs): """ 博客列表 """ queryset = self.get_queryset() page = self.paginate_queryset(queryset) ser = self.get_serializer(page, many=True) return Response(ser.data)
def create(self, request): if not request.user: return Response({"code": 1, "error": "认证失败"}) ser = self.get_serializer(data=request.data) if not ser.is_valid(): return Response({"code": 2, "error": ser.errors}) ser.save(creator=request.user) return Response({"code": 1000, "data": ser.data})别看着改了这么多,实际上就是把get、post这些方法名对应到了具体的方法上
通过「路由映射」把 HTTP 方法(GET/POST/PUT/DELETE)关联到对应的操作(列表 / 创建 / 详情 / 更新 / 删除),能让路由配置更简洁
GenericViewSet 体系下的 5 个核心 Mixin 扩展类
所以使用的时候都必须继承GenericViewSet
xxxxxxxxxxpath('api/blog/', views.BlogView.as_view({"get": "list"}))xxxxxxxxxxclass BlogView(GenericViewSet, ListModelMixin): authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer
def list(self, request, *args, **kwargs): """ 博客列表 """ queryset = self.get_queryset() page = self.paginate_queryset(queryset) ser = self.get_serializer(page, many=True) return Response(ser.data)xxxxxxxxxxpath('api/blog/', views.BlogView.as_view({"get": "list"}))xxxxxxxxxxfrom rest_framework.viewsets import GenericViewSetfrom rest_framework.mixins import ListModelMixin
class BlogView(GenericViewSet, ListModelMixin): authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer这样更改之后效果一样,只不过返回的数据不一样了,但是可以通过重写list方法达成自定制返回的数据
xxxxxxxxxxfrom rest_framework.viewsets import GenericViewSetfrom rest_framework.mixins import ListModelMixin
class BlogView(GenericViewSet, ListModelMixin): authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer
def list(self, request, *args, **kwargs): """ 博客列表 """ response = super().list(request, *args, **kwargs) return Response({"code": 100, "data": response.data})xxxxxxxxxxpath('api/blog/', views.BlogView.as_view({"get": "list", "post": "create"})),xxxxxxxxxxclass BlogView(GenericViewSet, ListModelMixin): # 注意这里现在还没有继承其他的类 authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer
def create(self, request): if not request.user: return Response({"code": 1, "error": "认证失败"}) ser = self.get_serializer(data=request.data) if not ser.is_valid(): return Response({"code": 2, "error": ser.errors}) ser.save(creator=request.user) return Response({"code": 1000, "data": ser.data})xxxxxxxxxxfrom rest_framework.viewsets import GenericViewSetfrom rest_framework.mixins import ListModelMixin, CreateModelMixin
class BlogView(GenericViewSet, ListModelMixin, CreateModelMixin): authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer
def perform_create(self, serializer): serializer.save(creator=self.request.user)
def create(self, request, *args, **kwargs): try: if not self.request.user: return Response({"code": 1, "error": "认证失败"}) response = super().create(request, *args, **kwargs) return Response({"code": 1000, "data": response.data}) except ValidationError as e: return Response({"code": 2000, "errors": e.detail})方法会在调用super.create(request, *args, **kwargs)的时候调用perform_create
此处由于有认证组件的需求,所以重写了create方法,如果没有此需求,代码将会非常简洁:
xxxxxxxxxxclass BlogView(GenericViewSet, ListModelMixin, CreateModelMixin):authentication_classes = [BlogAuthentication, ]queryset = models.Blog.objects.all().order_by('id')pagination_class = CustomLimitOffsetPaginationserializer_class = BlogSerializerdef perform_create(self, serializer):serializer.save(creator=self.request.user)他会自动校验字段(
ser.is_valid())
xxxxxxxxxxpath('api/blog/<int:pk>/', views.BlogDetailView.as_view())xxxxxxxxxxclass BlogDetailView(APIView): def get(self, request, *args, **kwargs): """ 获取详细的博客信息 """ pk = kwargs.get('pk') queryset = models.Blog.objects.filter(id=pk).first() if not queryset: return Response({"code": 1001, "error": "不存在"}) ser = BlogSerializer(instance=queryset) return Response({"code": 1000, "data": ser.data})xxxxxxxxxxpath('api/blog/<int:pk>/', views.BlogDetailView.as_view({"get": "retrieve"}))xxxxxxxxxxclass BlogDetailView(GenericViewSet, RetrieveModelMixin): queryset = models.Blog.objects.all() serializer_class = BlogSerializer在这种写法中,如果url中的参数名不想写成pk的话就需要添加额外配置
xxxxxxxxxxclass BlogDetailView(GenericViewSet, RetrieveModelMixin): queryset = models.Blog.objects.all() serializer_class = BlogSerializer lookup_url_kwarg = 'id' # 改成id就可以这样写:
xxxxxxxxxxpath('api/blog/<int:id>/', views.BlogDetailView.as_view({"get": "retrieve"}))xxxxxxxxxxpath('api/blog/<int:pk>/', views.BlogDetailView.as_view({ put": 'update', 'patch': 'partial_update'})),xxxxxxxxxxclass BlogDetailView(GenericViewSet, RetrieveModelMixin, UpdateModelMixin): queryset = models.Blog.objects.all() serializer_class = BlogSerializerdrf会自动处理请求,包括了put(全量更新)/patch(局部更新)
put:必须传入所有字段内容更新
patch:传入多少更多少
xxxxxxxxxxclass BlogDetailView(GenericViewSet, RetrieveModelMixin, UpdateModelMixin): queryset = models.Blog.objects.all() serializer_class = BlogSerializer def perform_update(self, serializer): # ---------- 可以在这里面执行一些操作 print(serializer.validated_data) # ---------- serializer.save()xxxxxxxxxxpath('api/blog/<int:pk>/', views.BlogDetailView.as_view({'delete': 'destroy'}))xxxxxxxxxxfrom rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
class BlogDetailView(GenericViewSet,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin): queryset = models.Blog.objects.all() serializer_class = BlogSerializer如果按照上面的进行配置,删除数据后没有任何提示,可以通过重写方法自定制
xxxxxxxxxxclass BlogDetailView(GenericViewSet, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): queryset = models.Blog.objects.all() serializer_class = BlogSerializer
def perform_destroy(self, instance): print("可以在这里做一些额外的操作") instance.delete()
def destroy(self, request, *args, **kwargs): super().destroy(request, *args, **kwargs) return Response({"message": "删除成功"})通过上述代码可以看出如果每次编写类都要继承
xxxxxxxxxxListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet那代码显得很冗余,可不可以封装起来呢, drf已经帮我们做了这个事了
xxxxxxxxxxfrom rest_framework.viewsets import ModelViewSetModelViewSet
查看他的源码

就是帮我们汇总了,以后需要就只需要写一个类就可以了
这么多方法怎么取舍呢
如果操作与数据库接口无关,那就直接继承APIView,如果有关,那就继承ModelViewSet,如果功能不够用,就通过重写某些方法进行拓展,根据习惯选择五大类或者ModelViewSet
编写url匹配到change_title这个方法
xxxxxxxxxxpath('api/blog/<int:pk>/', views.BlogDetailView.as_view({"'post': 'change_title' }))编写change_title方法对应post请求
xxxxxxxxxxclass BlogDetailView(ModelViewSet): queryset = models.Blog.objects.all() serializer_class = BlogSerializer
(detail=True, methods=['post']) def change_title(self,request, pk=None): """ 自定义动作:修改博客标题(手动URL映射版) :param pk: URL中的主键(对应你写的 <int:pk>) """ # 1. 查指定pk的博客(自动处理404) blog = self.get_object()
# 2. 获取前端传的新标题 new_title = request.data.get('new_title') if not new_title: return Response( {"error": "新标题不能为空"}, status=status.HTTP_400_BAD_REQUEST )
# 3. 修改并保存 blog.title = new_title blog.save()
# 4. 返回响应 return Response({ "code": 1000, "msg": f"博客{pk}标题修改成功", "new_title": blog.title })detail=True的意思是标记为详情级动作,详细参照这个表格
参数值 detail=True detail=False URL 是否需 pk 是(/demo/1/single-action/) 否(/demo/batch-action/) 操作数据范围 单条数据(pk 对应的记录) 批量数据(所有记录) 能否用 get_object () 可以(获取单条数据) 不可以(无 pk,会报错) 函数参数 需接收 pk(pk=None) 无需接收 pk 典型场景 修改单条数据、删除单条数据、点赞单篇博客 查询数据列表、批量导出、批量创建
有时候还会需要接受url里面传递过来的参数
xxxxxxxxxxfrom django.urls import path, includefrom api import viewsfrom rest_framework import routers
router = routers.SimpleRouter()router.register(r'calculator', views.CalculatorViewSet, basename='calculator')
urlpatterns = [ path('api/', include((router.urls, 'api'), namespace='namespace-example'))]这样写的话
xxxxxxxxxxclass CalculatorViewSet(ModelViewSet): (detail=False, methods=['get'], url_path="add/(?P<num1>\d+)/(?P<num2>\d+)") def add(self, request, num1, num2): result = int(num1) + int(num2) return Response({ "method": "add", "num1": num1, "num2": num2, "result": result, "url": request.path })
就可以达成这样的效果
在之前定义权限类时,类中可以定义两个方法:has_permission 和 has_object_permission
has_permission ,在请求进入视图之前就会执行。
has_object_permission,当视图中调用 self.get_object时就会被调用(删除、更新、查看某个对象时都会调用),一般用于检查对某个对象是否具有权限进行操作。
xxxxxxxxxxclass 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 就可以进行权限的处理。
xxxxxxxxxxpath('api/blog/', views.BlogView.as_view('get': 'list', 'post': 'create'))# 还可以添加别的put/patch等xxxxxxxxxxclass BlogView(GenericViewSet, ListModelMixin, CreateModelMixin): authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer
def perform_create(self, serializer): serializer.save(creator=self.request.user)
def create(self, request, *args, **kwargs): try: if not self.request.user: return Response({"code": 1, "error": "认证失败"}) response = super().create(request, *args, **kwargs) return Response({"code": 1000, "data": response.data}) except ValidationError as e: return Response({"code": 2000, "errors": e.detail})我们原来定义的url的写法如果里面的参数过多,比如get/post/put这些写的过多了就会显得比较麻烦
xxxxxxxxxxfrom django.urls import pathfrom rest_framework import routers
router = routers.SimpleRouter()router.register(r'api/blog', views.BlogView)
urlpatterns = [ # 这里编写你的其他url]
urlpatterns += router.urls这样写了以后就相当于完成了和api/blog有关的所有方法的对应
| HTTP 方法 | URL 路径 | 视图方法 | 功能 |
|---|---|---|---|
| GET | /api/blog/ | list | 博客列表 |
| POST | /api/blog/ | create | 创建博客 |
| GET | /api/blog/<int:pk>/ | retrieve | 博客详情 |
| PUT | /api/blog/<int:pk>/ | update | 全量更新 |
| PATCH | /api/blog/<int:pk>/ | partial_update | 部分更新 |
| DELETE | /api/blog/<int:pk>/ | destroy | 删除博客 |
节省了很多时间
xxxxxxxxxxfrom django.urls import path, includefrom rest_framework import routers
router = routers.SimpleRouter()router.register(r'blog', views.BlogView, basename='front_blog')
urlpatterns = [ path('api/', include((router.urls, 'api'), namespace="api"))]参数说明:
register的第三个参数basename='front_blog'这个的意思是
给路由器自动生成的 URL 名称加「基础前缀」,最终形成
namespace:basename-action的完整 URL 名称(比如api:blog-list),用于反向解析如果对应的模型里面有queryset,例如此处制定了views.BlogView,如果BlogView里面制定了queryset,那basename就算不写也会默认指定为那个queryset的模型示例名称
xxxxxxxxxx# views.pyclass BlogView(viewsets.ModelViewSet):queryset = Blog.objects.all() # 模型名是Blogserializer_class = BlogSerializer# urls.pyrouter.register(r'blog', views.BlogView) # DRF自动推断basename='blog'
path()的第一个参数:'api/'这个的意思是前缀,就是所有通过后面的include挂载的url都会挂载到这个前缀下
include()的第一个参数:(router.urls, 'api')这是一个元组类型的对象,第一个时需要包含的url列表对象,也就是上面对router注册的url,用专业术语说就是要引入的「URL 模式列表」
include()的第二个参数:namespace="api"这是一个命名空间对象,用于反向解析url的时候不出问题,不会改变url的路径
xxxxxxxxxx# urls.pyurlpatterns = [# 前台用户模块:namespace=user_frontpath('user/', include(('user.urls', 'user'), namespace='user_front')),# 后台管理员模块:namespace=user_adminpath('admin/user/', include(('user.urls', 'user'), namespace='user_admin')),]这种情况下就可以发挥作用:
xxxxxxxxxx# 前台个人中心:/user/profile/reverse('user_front:profile')# 后台个人中心:/admin/user/profile/reverse('user_admin:profile')
basename和namespace都只在反向解析url的时候才有用:
xxxxxxxxxxrouter = routers.SimpleRouter()router.register(r'blog', views.BlogView, basename='basename-example')urlpatterns = [path('api/', include((router.urls, 'api'), namespace='namespace-example'))]反向解析:
xxxxxxxxxxfrom django.urls import reverselist_url = reverse('namespace-example:basename-example-list')print(list_url)
写法一(推荐):
xxxxxxxxxxfrom django.urls import path, includefrom rest_framework import routersfrom . import views # 假设你有BlogView、UserView、CommentView
# 1. 创建一个路由器实例(复用同一个即可)router = routers.SimpleRouter()
# 2. 多次调用register,注册多个视图集# 格式:router.register(URL前缀, 视图类, basename=URL名称前缀)router.register(r'blog', views.BlogView, basename='blog') # 注册博客APIrouter.register(r'user', views.UserView, basename='user') # 注册用户APIrouter.register(r'comment', views.CommentView, basename='comment') # 注册评论API
# 3. 依然只需一行include,挂载所有注册的URLurlpatterns = [ path('api/', include((router.urls, 'api'), namespace="api"))]写法二:
xxxxxxxxxxblog_router = routers.SimpleRouter()blog_router.register(r'blog', views.BlogView, basename='blog')blog_router.register(r'comment', views.CommentView, basename='comment')
user_router = routers.SimpleRouter()user_router.register(r'user', views.UserView, basename='user')
# 2. 合并所有路由器的URLall_router_urls = blog_router.urls + user_router.urls
# 3. 挂载到api/下urlpatterns = [ path('api/', include((all_router_urls, 'api'), namespace="api"))]xxxxxxxxxxfrom rest_framework.filters import BaseFilterBackend
class Filter1(BaseFilterBackend): def filter_queryset(self, request, queryset, view): category = request.query_params.get('category') if not category: return queryset return queryset.filter(category=category)
class Filter2(BaseFilterBackend): def filter_queryset(self, request, queryset, view): id = request.query_params.get('id') if not id: return queryset return queryset.filter(id__gt=id)
class BlogView(GenericViewSet, ListModelMixin, CreateModelMixin): filter_backends = [Filter1, Filter2] # 与其他配置项同级编写 authentication_classes = [BlogAuthentication, ] queryset = models.Blog.objects.all().order_by('id') pagination_class = CustomLimitOffsetPagination serializer_class = BlogSerializer在drf开发中有一个常用的第三方过滤器:
DjangoFilterBackend
xxxxxxxxxxpip install django-filterxxxxxxxxxxINSTALLED_APPS = [ ... 'django_filters', ...]xxxxxxxxxxfrom django_filters.rest_framework import DjangoFilterBackend
class BlogView(GenericViewSet, ListModelMixin, CreateModelMixin): filter_backends = [DjangoFilterBackend, ] filterset_class = ["id", "category"]就可以实现id、category的筛选,但是此处只能筛选等于这个条件,不能用其他的
xxxxxxxxxxclass MyFilter(FilterSet): min_id = filters.NumberFilter(field_name='id', lookup_expr='gte') category = filters.NumberFilter(field_name='category', lookup_expr='exact')
class Meta: model = models.Blog fields = ['min_id', 'category']
class BlogView(GenericViewSet, ListModelMixin, CreateModelMixin): # ... 其他配置 filter_backends = [DjangoFilterBackend, ] filterset_class = MyFilter此时发送
xxxxxxxxxxhttp://127.0.0.1:8000/api/blog/?min_id=21&category=2就会返回id大于等于21,并且category等于2的数据
当大于小于这些简单的逻辑不能满足我们的需求时,可以自定义一个过滤方法
xxxxxxxxxxclass MyFilterSet(FilterSet): # 1. 定义字段,通过 method 绑定方法名 custom_search = filters.CharFilter(method='my_custom_filter', distinct=False, required=False)
# 2. 实现该方法 def my_custom_filter(self, queryset, name, value): # 逻辑处理... return queryset参数说明:
self: 指向当前的 FilterSet 实例。
queryset: 尚未执行此过滤前的原始查询集(QuerySet)。
name: 字段的名称(在上面的例子中就是 "custom_search")。
value: 前端从 URL 传过来的具体数值(比如 ?custom_search=python,value 就是 "python")。
distinct=False:是否去重,默认值为False
比如一篇博文有两个标签
python和go现在需要搜索有python标签和有go标签的,这篇博文就会匹配两次,返回这样的数据
xxxxxxxxxx[{"id": 1,"title": "全栈开发笔记","tags": ["Python", "Go"]},{"id": 1,"title": "全栈开发笔记","tags": ["Python", "Go"]},{"id": 2,"title": "Java 核心技术","tags": ["Java"]}]此时如果添加这个参数就可以避免这个问题
required=False:是否必填,默认值为False
| 表达式 | 说明 | 数据库 SQL 逻辑 (示例) | 常用业务场景 |
|---|---|---|---|
exact | 精确匹配 | WHERE col = 'val' | 匹配 ID、分类 ID、状态码 |
iexact | 精确匹配 (忽略大小写) | WHERE col ILIKE 'val' | 匹配用户名、验证码、颜色名 |
contains | 包含 (区分大小写) | WHERE col LIKE '%val%' | 搜索文章正文中的特定词汇 |
icontains | 包含 (忽略大小写) | WHERE col ILIKE '%val%' | 最常用:搜索标题、简介关键词 |
startswith | 以...开头 | WHERE col LIKE 'val%' | 搜索姓氏、特定号段的手机号 |
istartswith | 以...开头 (忽略大小写) | WHERE col ILIKE 'val%' | 搜索 URL 前缀、文件路径 |
endswith | 以...结尾 | WHERE col LIKE '%val' | 搜索特定后缀的邮箱 |
iendswith | 以...结尾 (忽略大小写) | WHERE col ILIKE '%val' | 搜索图片格式 (如 .JPG) |
gt | 大于 (>) | WHERE col > 10 | 筛选价格高于 100 元的商品 |
gte | 大于等于 (>=) | WHERE col >= 10 | 筛选 18 岁及以上的用户 |
lt | 小于 (<) | WHERE col < 10 | 筛选库存低于 5 件的预警 |
lte | 小于等于 (<=) | WHERE col <= 10 | 筛选今天之前创建的文章 |
in | 在集合中 | WHERE col IN (1, 2, 3) | 批量查询多个 ID 或特定分类列表 |
range | 在范围内 | WHERE col BETWEEN 1 AND 10 | 价格区间、日期段筛选 |
isnull | 是否为空 | WHERE col IS NULL | 筛选“未绑定手机号”或“未删除”的数据 |
regex | 正则匹配 | WHERE col ~ 'pattern' | 复杂的文本规则校验(区分大小写) |
iregex | 正则匹配 (忽略大小写) | WHERE col ~* 'pattern' | 复杂的文本规则校验(忽略大小写) |
xxxxxxxxxx# settings.py 全局配置
REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend',]}这样以后
xxxxxxxxxxclass BlogView(GenericViewSet, ListModelMixin, CreateModelMixin):filter_backends = [DjangoFilterBackend, ] # 这一行可以省略filterset_class = MyFilterxxxxxxxxxxclass BlogView(GenericViewSet, ListModelMixin, CreateModelMixin):filterset_class = MyFilter但是如果你有多个配置项
xxxxxxxxxxfrom rest_framework.filters import SearchFilter, OrderingFilterclass UserView(ModelViewSet):# 局部配置会覆盖 settings 中的全局配置# 注意:如果你在这里写了,记得把 DjangoFilterBackend 也带上,否则它会失效filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]filterset_class = MyFilterSet
xxxxxxxxxxfrom rest_framework.filters import OrderingFilter
class BlogView(GenericViewSet, ListModelMixin, CreateModelMixin): filter_backends = [OrderingFilter, ] ordering_fields = ['id', 'category']此时他支持用id和category排序
xxxxxxxxxx?ordering=id?ordering=-id?ordering=age?ordering=-age当你传入
xxxxxxxxxxhttp://127.0.0.1:8000/api/blog/?ordering=id的时候就会以id从小到大排序
xxxxxxxxxxfrom rest_framework.filters import SearchFilter
class BlogView(GenericViewSet, ListModelMixin, CreateModelMixin): filter_backends = [OrderingFilter, SearchFilter] ordering_fields = ['id', 'category'] search_fields = ['title']此时就会支持以title模糊查询,可以写多个(你想在哪些字段里搜?)
xxxxxxxxxx?search=django例如我这里:
xxxxxxxxxxhttp://127.0.0.1:8000/api/blog/?search=djangoxxxxxxxxxx{ "category": "Python全栈", "image": "xxxx/xxxxx.png", "title": "django开发", "summary": "rqrqeqweqeq", "ctime": "2026年03月12日", "comment_count": 0, "favor_count": 0, "creator": { "id": 3, "username": "jelee" }}在这里我配置了title所以就模糊匹配到了title符合的这一项
search_fields里面的参数支持这些规则
| 符号 | 模式 | 说明 (SQL 逻辑) | 例子 |
|---|---|---|---|
| (无) | icontains | 模糊包含 (默认) | search_fields = ['title'] |
^ | istartswith | 以...开头 | search_fields = ['^title'] |
= | iexact | 完全匹配 | search_fields = ['=title'] |
$ | iregex | 正则匹配 | search_fields = ['$title'] |
如果用户在搜索框输入了空格,比如 ?search=Python Django:
默认逻辑:SearchFilter 会把关键词拆分成 Python 和 Django。
匹配方式:它会要求这条数据同时满足这两个词的搜索(相当于 AND 逻辑)
| 特性 | SearchFilter (搜索) | OrderingFilter (排序) |
|---|---|---|
| 代码示例 | search_fields = ['creator__name'] | ordering_fields = ['creator__age'] |
| 业务场景 | 根据“作者名”或“分类名”搜文章 | 按“作者活跃度”或“部门等级”排文章 |
| 深度支持 | 支持多级,如 creator__depart__title | 支持多级,如 creator__depart__level |
当你使用双下划线跨表排序或搜索时,Django 必须执行 JOIN 操作。如果你的 queryset 只是简单的 Blog.objects.all(),Django 在序列化每一条数据时,可能还会再去数据库查一次作者信息,导致性能差
解决方案:
配合 select_related 使用
只要涉及跨表(外键),就在 queryset 中提前“预加载”:
xxxxxxxxxxclass BlogView(ModelViewSet): # 使用 select_related 提前把作者信息 JOIN 进来 # 这样排序和搜索时,数据库一次性就能把数据给全 queryset = models.Blog.objects.all().select_related('creator') filter_backends = [SearchFilter, OrderingFilter] search_fields = ['creator__username'] ordering_fields = ['creator__age']