Django REST framework
前言
莫道桑榆晚,为霞尚满天
1.RESTful
RESTful 核心思想:面向资源(Resource-Oriented),用统一的 URL 表示资源,用 HTTP 方法表示操作
RESTful 设计的五大核心约束
-
资源(Resource)
-
一切皆资源(用户、订单、书籍)
-
用 URL 表示
/users /books /orders
-
-
统一接口(Uniform Interface)
-
通过 HTTP 方法表达行为:
- 查询: GET
- 新增: POST
- 更新: PUT / PATCH
- 删除: DELETE
-
无状态(Stateless)
- 每个请求必须包含所有信息
- 服务端不保存客户端状态
- 常见实现:Token(JWT)、Header 传认证信息
-
分层系统(Layered System)
- 前端 → 网关 → 服务 → 数据库
- 客户端不关心内部结构
-
可缓存(Cacheable)
- GET 可缓存
- 提高性能
RESTful 规范要求
-
1.使用 HTTPS,保证传输安全(TLS)
https://api.xxx.com -
2.API 前缀,明确区分 Web 页面 vs API
/api -
3.版本控制,避免接口升级破坏兼容性
/api/v1/users /api/v2/users -
4.URL 使用名词(资源化)
/users /books 错误示例: /getUsers /createBook -
5.用 HTTP 方法表达行为(最核心)
GET /users # 查询列表 POST /users # 新增 GET /users/1 # 查询单个 PUT /users/1 # 全量更新 PATCH /users/1 # 局部更新 DELETE /users/1 # 删除 -
6.查询参数(过滤条件), QueryString,不要写路径里
GET /users?name=张三&age=18 -
7.使用标准 HTTP 状态码,不要滥用自定义 code 替代 HTTP 状态码
状态码 含义 200 成功 201 创建成功 204 删除成功(无内容) 400 请求错误 401 未认证 403 无权限 404 资源不存在 500 服务器错误 -
8.统一响应结构
{ "code": 100, "message": "success", "data": {...} } 错误: { "code": 101, "message": "用户名或密码错误" } -
9.返回数据规范
请求 返回 GET /users 数组 GET /users/1 单对象 POST 新对象 PUT 更新后对象 DELETE 空(或状态) -
10.返回资源链接(HATEOAS,可选)
{ "id": 1, "name": "红楼梦", "url": "/api/v1/books/1" }
总结
RESTful 本质是:
- URL 表示资源(名词)
- HTTP 方法表示操作(动词)
- 使用标准状态码
- 无状态通信
- 统一返回结构
2.DRF(rest_framework)
2.1简介
DRF(Django REST Framework)本质是:Django 的 API 开发增强框架
Django 原生问题:
- 只能返回 HTML(模板)
- JSON 需要手写
- 参数校验麻烦
- API 不规范
- 没有统一接口结构
DRF 解决:
- 自动 JSON 输出
- 自动序列化 / 反序列化
- 自动参数校验
- 自动生成 API(ViewSet + Router)
- 支持认证 / 权限 / 分页
DRF 核心架构
Request
↓
URL Router(路由分发)
↓
View / ViewSet(业务逻辑)
↓
Serializer(数据转换 + 校验)
↓
Model(数据库)
↓
Response(JSON)
2.2基本使用
创建项目app01,并创建测试实体book,然后数据迁移
startapp app01
makemigrations
migrations

安装Django REST Framework
pip install djangorestframework

注册应用
INSTALLED_APPS = [
...
'rest_framework',
]

实现Serializer
class BookSerializer(serializers.ModelSerializer):
"""
Book 模型序列化器
作用:
1. 将 Book 模型对象转换为 JSON 数据(序列化)
2. 将前端 JSON 数据转换为 Book 对象并保存(反序列化)
使用场景:
- API 返回数据
- API 接收前端数据
"""
class Meta:
# 绑定的模型
model = Book
# 序列化字段配置
# '__all__' 表示使用模型中的所有字段
fields = '__all__'
"""
如果只需要部分字段,可以这样写:
fields = ['id', 'title', 'price']
"""
"""
可选配置:
# 只读字段(不会被前端写入)
read_only_fields = ['id']
# 外键自动嵌套展示(简单场景使用)
depth = 1
"""

实现ViewSet
# Book 视图集(ViewSet)
# 作用:提供一组标准的 CRUD API(增删改查)
# ModelViewSet 是 DRF 提供的高级封装类
class BookView(ModelViewSet):
"""
Book 资源的接口视图
自动提供以下 REST API:
GET /books/ -> 获取列表(list)
POST /books/ -> 创建数据(create)
GET /books/{id}/ -> 获取单条数据(retrieve)
PUT /books/{id}/ -> 全量更新(update)
PATCH /books/{id}/ -> 局部更新(partial_update)
DELETE /books/{id}/ -> 删除数据(destroy)
=====================================================
"""
# 查询集(数据来源)
# 作用:告诉 DRF 当前视图操作哪一张表的数据
queryset = Book.objects.all()
# 序列化器
# 作用:
# 1. 返回数据时:模型 -> JSON
# 2. 接收数据时:JSON -> 模型
serializer_class = BookSerializer

配置路由
from django.contrib import admin
from django.urls import path
from rest_framework.routers import SimpleRouter
from app01.views import BookView
# 创建 DRF 路由器(Router)
# 作用:自动生成 RESTful API 路由,无需手动写 path
router = SimpleRouter()
# 注册视图集(ViewSet)
# 参数说明:
# 1. 'books' -> URL 前缀
# 最终生成的接口路径以 /books/ 开头
#
# 2. BookView -> 绑定的视图集
# DRF 会自动根据 ModelViewSet 生成 CRUD 接口
#
# 最终生成的路由包括:
# GET /books/ -> 列表查询
# POST /books/ -> 创建数据
# GET /books/{id}/ -> 查询单条
# PUT /books/{id}/ -> 全量更新
# PATCH /books/{id}/ -> 局部更新
# DELETE /books/{id}/ -> 删除数据
router.register('books', BookView)
# Django 主路由配置
urlpatterns = [
# Django 后台管理系统路由
path('admin/', admin.site.urls),
]
# 将 DRF 自动生成的路由追加到 Django 路由中
# router.urls 会自动生成 books 相关的 REST API 路由
urlpatterns += router.urls

添加测试数据
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo01.settings')
django.setup()
from decimal import Decimal
from django.db import connection, transaction
from app01.models import Book
books = [
{"title": "Python入门", "price": Decimal("59.00")},
{"title": "Django实战", "price": Decimal("79.00")},
{"title": "DRF开发指南", "price": Decimal("88.00")},
{"title": "Java核心技术", "price": Decimal("99.00")},
{"title": "Spring Boot实战", "price": Decimal("109.00")},
{"title": "微服务架构设计", "price": Decimal("129.00")},
{"title": "MySQL高级教程", "price": Decimal("69.00")},
{"title": "Redis实战", "price": Decimal("75.00")},
{"title": "Kubernetes指南", "price": Decimal("139.00")},
{"title": "算法与数据结构", "price": Decimal("89.00")},
]
def reset_table_and_insert():
table_name = Book._meta.db_table
with transaction.atomic():
# 1️⃣ 清空数据
Book.objects.all().delete()
# 2️⃣ 重置自增ID(根据数据库类型)
with connection.cursor() as cursor:
if connection.vendor == 'sqlite':
cursor.execute(
f"DELETE FROM sqlite_sequence WHERE name='{table_name}'"
)
elif connection.vendor == 'mysql':
cursor.execute(
f"ALTER TABLE {table_name} AUTO_INCREMENT = 1"
)
else:
raise NotImplementedError(f"不支持的数据库类型: {connection.vendor}")
# 3️⃣ 批量插入
Book.objects.bulk_create([Book(**b) for b in books])
if __name__ == "__main__":
reset_table_and_insert()
print("完成:清空 + 重置ID + 批量插入")
启动项目,访问http://127.0.0.1:8000/books/

2.3四大核心组件
DRF = Django 的 API 开发框架,用来把 Django 快速变成“后端接口服务”。
Model(数据层)
-
定义数据库结构
-
Django ORM
class Book(models.Model):
title = models.CharField(max_length=128)
price = models.DecimalField(max_digits=8, decimal_places=2)
Serializer(序列化)
- 负责 数据转换 + 数据校验
- Python对象 → JSON
- JSON → Python对象
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
ViewSet(业务层)
- 处理接口逻辑(CRUD)
- 自动支持:
- 查
- 增
- 改
- 删
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
Router(路由层)
- 自动生成 API 路径
router = SimpleRouter()
router.register('books', BookView)
urlpatterns = [
# Django 后台管理系统路由
path('admin/', admin.site.urls),
]
urlpatterns += router.urls
自动生成接口
| 方法 | 接口 | 功能 |
|---|---|---|
| GET | /books/ | 列表 |
| POST | /books/ | 新增 |
| GET | /books/1/ | 详情 |
| PUT | /books/1/ | 更新 |
| DELETE | /books/1/ | 删除 |
3.DRF Request/Response对象
3.1Request对象
当你继承 **Django REST framework 的 APIView **时:
request确实被重写了- 类型从:
django.core.handlers.wsgi.WSGIRequest转换成rest_framework.request.Request - 本质:DRF 对 Django 原生 request 做了一层“增强封装”
继承关系
APIView
↓
Django View
DRF Request
↓ (内部包含)
WSGIRequest(原始 request)
以下全部仍然可用:
DRF Request 内部代理(delegation)到原始 WSGIRequest
request.method
request.GET
request.POST
request.FILES
request.body
request.get_full_path()
request.META
DRF 新增的核心能力
| 属性 | 本质来源 | 作用 | 适用请求方式 | 支持的数据格式 |
|---|---|---|---|---|
request.data |
DRF 封装后的数据 | 统一获取请求体数据 | POST / PUT / PATCH | JSON、form-data、x-www-form-urlencoded |
request.query_params |
request.GET |
获取 URL 查询参数(?key=value) | GET(也可用于其他请求) | URL 查询字符串 |
request._request |
Django 原生 WSGIRequest |
获取底层原始 request 对象 | 所有 | 原生支持(POST / GET / FILES 等) |
3.2.Response对象
在 Django REST framework 中:统一使用 Response 返回数据,不再直接使用 HttpResponse / JsonResponse
Response 用法
基本用法:支持字典(最常用)、列表、字符串(不推荐用于接口规范)
from rest_framework.response import Response
return Response({"msg": "success"})
return Response([1, 2, 3])
return Response("ok")
设置状态码:
from rest_framework import status
return Response("ok", status=201)
# 推荐写法:
return Response("ok", status=status.HTTP_201_CREATED)
设置响应头
return Response(
data="ok",
status=201,
headers={"xxx": "yyy"}
)
Response 参数详解
| 参数名 | 类型 | 作用 | 示例 | 备注 |
|---|---|---|---|---|
| data | 任意(dict/list/str) | 响应体数据 | Response({"msg": "ok"}) |
最核心参数,通常返回 JSON 结构 |
| status | int | HTTP 状态码 | status=201 |
推荐使用 status.HTTP_XXX 常量 |
| headers | dict | 自定义响应头 | headers={"token": "123"} |
用于传 token / 标识信息 |
| exception | bool | 标记是否为异常响应 | exception=True |
一般由 DRF 自动处理 |
| content_type | str | 指定响应类型 | application/json |
通常不用手动设置 |
| template_name | str | 指定模板(浏览器渲染) | "api.html" |
很少用 |
Response(
data=None,
status=None,
headers=None,
exception=False,
content_type=None,
template_name=None
)
3.3示例
示例
使用apifox发送get和post请求
# Create your views here.
class DemoView(APIView):
def get(self,request):
print(type(request))
print(request.META)
print("==============",request.META.get('REMOTE_ADDR'))
return Response('ok--get--demo')
def post(self,request):
print(type(request))
print(request.POST)
print(request.data)
print(request.GET is request.query_params)
print(type(request._request))
return Response(data='ok--post--demo',status=HTTP_403_FORBIDDEN,headers={'aaa':'aaaaa'})
# get 请求
<class 'rest_framework.request.Request'>
{'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPCODE_VM_OPTIONS': '...', 'APPDATA': '...', 'CLION_VM_OPTIONS': '...'}
# post 请求
<class 'rest_framework.request.Request'>
<QueryDict: {}>
{}
True
<class 'django.core.handlers.wsgi.WSGIRequest'>

浏览器访问出错:TemplateDoesNotExist at /api/v1/app01/demo/
http://127.0.0.1:8000/api/v1/app01/demo/
浏览器访问时:使用BrowsableAPIRenderer需要模版rest_framework/api.html,但是没有注册rest_framework,这里使用方案一即可

解决方案一:注册 DRF(推荐开发环境)
INSTALLED_APPS = [
'rest_framework',
]
解决方案2:关闭浏览器渲染(生产常用)
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer'
]
}

4.DRF序列化器
4.1简介
Serializer = 数据转换器 + 校验器 + 持久化桥梁
序列化(对象 → JSON)
model对象 → dict → JSON
用于:
- 查询接口
- 返回数据
反序列化(JSON → 对象)
JSON → dict → model对象
用于:
- 新增
- 修改
数据校验(核心价值)
serializer.is_valid()
自动完成:
- 类型校验
- 长度校验
- 必填校验
4.2基本使用
创建模型
# Create your models here.
class Student(models.Model):
age=models.IntegerField()
name=models.CharField(max_length=32)
school=models.CharField(max_length=32)

迁移数据库
- 生成迁移文件
- 创建数据库表
makemigrations
migrate

添加测试数据
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo02.settings')
django.setup()
from django.db import connection, transaction
from app01.models import Student
students = [
{"name": "张三", "age": 18, "school": "清华大学"},
{"name": "李四", "age": 20, "school": "北京大学"},
{"name": "王五", "age": 22, "school": "复旦大学"},
{"name": "赵六", "age": 19, "school": "浙江大学"},
{"name": "孙七", "age": 21, "school": "上海交通大学"},
{"name": "周八", "age": 23, "school": "南京大学"},
{"name": "吴九", "age": 18, "school": "中山大学"},
{"name": "郑十", "age": 20, "school": "华中科技大学"},
{"name": "钱十一", "age": 22, "school": "武汉大学"},
{"name": "冯十二", "age": 19, "school": "同济大学"},
]
def reset_table_and_insert():
table_name = Student._meta.db_table
with transaction.atomic():
# 1️.清空表数据
Student.objects.all().delete()
# 2️.重置自增ID
with connection.cursor() as cursor:
if connection.vendor == 'sqlite':
cursor.execute(
f"DELETE FROM sqlite_sequence WHERE name='{table_name}'"
)
elif connection.vendor == 'mysql':
cursor.execute(
f"ALTER TABLE {table_name} AUTO_INCREMENT = 1"
)
else:
raise NotImplementedError(f"不支持的数据库类型: {connection.vendor}")
# 3️.批量插入
Student.objects.bulk_create([Student(**s) for s in students])
if __name__ == "__main__":
reset_table_and_insert()
print("完成:Student表清空 + 重置ID + 批量插入")

创建序列化器
- 定义接口输出字段
- 定义接口输入校验规则
- 用于:JSON ↔ Python对象转换
from rest_framework import serializers
class StudentSerializer(serializers.Serializer):
# 写要序列化的字段--->他们是一一对应的
id=serializers.IntegerField()
age = serializers.IntegerField()
name = serializers.CharField()
school=serializers.CharField()

创建视图
查询所有
class StudentView(APIView):
# 查询所有 http://192.168.1.252:8887/api/v1/app01/students/
def get(self, request):
# 1 取出所有数据
students = Student.objects.all()
# 2 序列化--->drf提供的序列化类来完成-->实例化得到对象时,需要传参数
# 如果是多条(单个对象 还是 qs对象),一定要传many=True
serializer = StudentSerializer(instance=students, many=True)
# 3 返回给前端
return Response(serializer.data)

查询单个
class StudentDetailView(APIView):
# 查询所有 http://192.168.1.252:8887/api/v1/app01/students/1/
def get(self, request, pk):
# 1 根据id,取出数据
student = Student.objects.filter(pk=pk).first()
# 2 序列化
serializer = StudentSerializer(instance=student)
# 3 返回给前端
return Response(serializer.data)

配置路由
urlpatterns = [
path('demo/', DemoView.as_view()),
path('students/', StudentView.as_view()),
path('student/<int:pk>/', StudentDetailView.as_view()),
]

测试
http://127.0.0.1:8000/api/v1/app01/students/
http://127.0.0.1:8000/api/v1/app01/student/1/

4.3常用字段
4.3.1基础字段
| Serializer字段 | 作用 | 是否对应 Model 字段 | 说明 |
|---|---|---|---|
| CharField | 字符串 | ✔ 是 | 最常用字符串字段 |
| IntegerField | 整数 | ✔ 是 | int类型 |
| FloatField | 浮点数 | ✔ 是 | 一般数值 |
| DecimalField | 精确小数 | ✔ 是 | 金额/精度计算 |
| BooleanField | 布尔值 | ✔ 是 | True/False |
| NullBooleanField | 可空布尔 | ❌ 否(已废弃) | 已被 BooleanField(null=True) 替代 |
4.3.2时间日期类
| Serializer字段 | 作用 | 是否对应 Model 字段 | 说明 |
|---|---|---|---|
| DateTimeField | 日期时间 | ✔ 是 | 最常用时间字段 |
| DateField | 日期 | ✔ 是 | 年月日 |
| TimeField | 时间 | ✔ 是 | 时分秒 |
4.3.3校验增强类
重点:这些 Model 不一定有对应字段,主要是 Serializer 做“校验层增强”
| Serializer字段 | 作用 | 是否对应 Model 字段 | 说明 |
|---|---|---|---|
| EmailField | 邮箱校验 | ❌ 否(CharField增强) | 带格式校验 |
| RegexField | 正则校验 | ❌ 否 | 自定义规则 |
| URLField | URL校验 | ❌ 否(CharField增强) | 链接校验 |
| UUIDField | UUID | ✔ 部分对应 | Model有UUIDField |
| IPAddressField | IP地址 | ❌ 否 | 校验用 |
4.3.4选择类
| Serializer字段 | 作用 | 是否对应 Model 字段 | 说明 |
|---|---|---|---|
| ChoiceField | 单选 | ✔ 是 | 对应 choices |
| MultipleChoiceField | 多选 | ✔ 是(间接) | 通常配数组/中间表 |
4.3.5文件类
| Serializer字段 | 作用 | 是否对应 Model 字段 | 说明 |
|---|---|---|---|
| FileField | 文件上传 | ✔ 是 | Django FileField |
| ImageField | 图片上传 | ✔ 是 | Django ImageField |
4.3.6结构化数据
| Serializer字段 | 作用 | 是否对应 Model 字段 | 说明 |
|---|---|---|---|
| ListField | 列表 | ❌ 否 | API结构用 |
| DictField | 字典 | ❌ 否 | API结构用 |
4.4反序列化新增
重写create方法
-
create() 是 Serializer 在执行 save() 时用于新增数据的核心方法。
-
当 serializer 没有 instance,仅传入 data 时,调用 save() 会自动触发 create(validated_data)。
-
validated_data 是经过 is_valid() 校验后的干净数据,create 方法负责将其通过 ORM 写入数据库,并返回创建的模型实例。
class StudentSerializer(serializers.Serializer):
# 写要序列化的字段--->他们是一一对应的
id=serializers.IntegerField()
age = serializers.IntegerField()
name = serializers.CharField()
school=serializers.CharField()
def create(self, validated_data):
# validated_data 是字典 前端传入校验过后的数据
# 保存到 咱们指定的表中
student = Student.objects.create(**validated_data)
# 返回新增的对象
return student

在StudentView重写post方法用于保存数据
- 前端数据通过 request.data 进入后端
- 使用 Serializer(data=request.data) 进行反序列化
- 调用 is_valid() 进行数据校验
- 校验通过后生成 validated_data
- 调用 serializer.save()
- save() 内部触发 create(validated_data)
- create 方法通过 ORM 将数据写入数据库
- 返回 serializer.data 给前端
post方法的本质就是:接收数据 → 校验数据 → 调用 create() → 入库 → 返回结果
class StudentView(APIView):
# 查询所有 http://192.168.1.252:8887/api/v1/app01/students/
def get(self, request):
...
def post(self, request):
# 1 取出前端传入的,请求体中 数据(json,urlencoded,form-data)
# request.data
# 2 序列化类实例化得到对象--->反序列化保存,传入data--》就是前端传入的数据
serializer = StudentSerializer(data=request.data)
# 3 数据校验
if serializer.is_valid():
# 校验通过,保存
# 4 保存--》不知道要保存到哪个表中---》一定要在序列化类中重写 create方法
# serializer.save 如果是新增,会触发serializer.create方法的执行
serializer.save()
# 5 返回给前端
return Response(serializer.data) # 返回给前端新增的对象
else:
# 校验不通过
return Response(serializer.errors) # 返回给前端错误信息

测试
{
"id": 11,
"age": 29,
"name": "Peng",
"school": "家里蹲大学"
}

再次查询

如果不想输入id,StudentSerializer配置一下read_only

4.5反序列化修改
重写update方法:
-
update() 是 DRF 在执行 serializer.save() 时用于更新数据的方法。
-
当 serializer 传入 instance 和 data 时,调用 save() 会触发 update(instance, validated_data)。
-
validated_data 是经过 is_valid() 校验后的数据,update 方法通过 setattr 或手动赋值的方式更新模型对象属性,并调用 save() 方法将修改持久化到数据库。
class StudentSerializer(serializers.Serializer):
# 写要序列化的字段--->他们是一一对应的
id=serializers.IntegerField()
age = serializers.IntegerField()
name = serializers.CharField()
school=serializers.CharField()
def create(self, validated_data):
...
def update(self, student, validated_data):
# instance 待修改对象 就是我们当时传入的student对象 validated_data前端传入,校验过后的数据
# 笨办法--》只针对于student
# student.name=validated_data.get('name')
# student.age=validated_data.get('age')
# student.school=validated_data.get('school')
# 高级一点
for key in validated_data:
setattr(student, key, validated_data.get(key))
# setattr(student, 'name', '刘清政')
# 对象保存到数据库的方法
student.save()
return student

在StudentDetailView重写put方法用于修改数据
- 根据 pk 查询数据库对象 instance
- 使用 Serializer(instance, data=request.data) 进行实例化
- 调用 is_valid() 进行数据校验
- 校验通过后生成 validated_data
- 调用 serializer.save()
- save() 内部判断 instance 存在,触发 update()
- update 方法将 validated_data 逐字段赋值给 instance
- 调用 save() 持久化到数据库
- 返回 serializer.data
class StudentDetailView(APIView):
# 查询所有 http://192.168.1.252:8887/api/v1/app01/students/1/
def get(self, request, pk):
...
def put(self, request, pk):
# 1 根据pk,取出数据库中得对象
student = Student.objects.filter(pk=pk).first()
# 2 实例化得到序列化类对象
# 最终会根据data的数据,更新instance
serializer = StudentSerializer(instance=student, data=request.data)
# 3 校验数据
if serializer.is_valid():
# 3.1 调用save会触发 update方法执行---》如果instance不为None,就触发update
# 4 保存
serializer.save()
# 5 返回给前端
return Response(serializer.data)
else:
return Response(serializer.errors)

测试
# 修改前
{
"id": 12,
"age": 29,
"name": "Tom",
"school": "社会大学"
}
# 修改后
{
"id": 12,
"age": 30,
"name": "Jack",
"school": "北京路大学"
}

再次访问
http://127.0.0.1:8000/api/v1/app01/student/12/

4.6使用mysql8
配置mysql8数据库连接
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME':'django_demo',
'HOST':'192.168.188.180',
'PORT':3306,
'USER':'root',
'PASSWORD':'root',
}
}

4.7反序列化校验
字段级校验(单字段)
特点:
- 只校验一个字段
- 参数是字段值
- 必须 return value
def validate_字段名(self, value):
return value
全局校验(多字段)
特点:
- 校验多个字段关系
- 参数是 dict(attrs)
- 必须 return attrs
def validate(self, attrs):
return attrs
示例
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from .models import Student
class StudentSerializer(serializers.Serializer):
# 写要序列化的字段--->他们是一一对应的
# id=serializers.IntegerField()
# 返回时有 id,提交时不能传 id
id = serializers.IntegerField(read_only=True)
# 允许字段传空字符串 ""(不报错,能进入 validate)
age = serializers.IntegerField(allow_null=True)
name = serializers.CharField(allow_blank=True)
school = serializers.CharField(allow_blank=True)
def create(self, validated_data):
...
def update(self, student, validated_data):
...
# 年龄不能小于0,不能太离谱
def validate_age(self, value):
if value is None:
raise ValidationError('年龄不能为空')
if value < 0:
raise ValidationError('年龄不能小于0')
if value > 120:
raise ValidationError('年龄不能大于120')
return value
# 名字不能是“admin”,学校不能为空
def validate(self, attrs):
errors = {}
name = attrs.get('name')
school = attrs.get('school')
if name == 'admin':
errors['name'] = '名字不能为 admin'
if not name:
errors['name'] = '名字不能为空'
if not school:
errors['school'] = '学校不能为空'
if errors:
raise ValidationError(errors)
return attrs

测试
{
"age": 28,
"name": "admin",
"school": ""
}

4.8定制返回格式source
source 的作用:指定“序列化字段”从“模型对象”的哪个属性取值。
4.8.1基本使用
source='school' 的作用是:返回时把模型中的 school 字段,映射成新的接口字段名,例如 school_name。本质上只是“改接口字段名”,数据库字段并没有变化,常用于前后端字段名不一致、接口字段更友好等场景。
# 场景1:修改返回字段名(最常见)
# 返回结果中:school_name = school
school_name = serializers.CharField(source="school", read_only=True)

4.8.2source指定字段不能和使用字段相同
new_school = serializers.CharField(source="new_school", read_only=True)

改个名即可
peng_school = serializers.CharField(source="new_school", read_only=True)

4.8.3跨表使用
Student和ClassRoom是一对一关系
source='classroom.name' 的作用是:通过关联关系继续向下取值,相当于执行 obj.classroom.name。它可以直接获取外键、一对一等关联对象中的字段,避免写复杂嵌套序列化,非常适合多表关联展示。

使用source跨表取值

4.8.4指定方法或者property
source='new_school' 的作用是:获取模型中的 @property 属性或实例方法返回值。DRF 会自动执行对应属性/方法,把动态计算后的结果返回给前端,常用于拼接字段、格式化数据、动态计算等业务逻辑。
# 场景3:取模型中的 property 或方法
# new_school 是 models.py 里的 @property
peng_school = serializers.CharField(source="new_school", read_only=True)

4.8.5完整代码
models.py
class ClassRoom(models.Model):
"""
班级表
"""
# 班级名称
name = models.CharField(max_length=32)
# 班主任
master = models.CharField(max_length=32)
def __str__(self):
return self.name
class Student(models.Model):
# 年龄:整型字段
age = models.IntegerField()
# 姓名:字符串,最大长度32
name = models.CharField(max_length=32)
# 学校:字符串,最大长度32
school = models.CharField(max_length=32)
@property
def new_school(self):
"""
计算属性(不会存数据库)
作用:
在原有 school 字段基础上做加工
使用场景:
- 给前端返回“加工后的字段”
- 类似“展示字段”
示例:
school = "清华大学"
new_school = "Peng_清华大学"
注意:
- 不是数据库字段
- 不参与 ORM 查询
- Serializer 需要手动声明才能返回
"""
return "Peng_" + self.school
# 外键:所属班级
classroom = models.ForeignKey(
ClassRoom,
on_delete=models.CASCADE, # 级联删除
related_name='students', # 班级反向查询学生时使用 默认是 student_set
null=True, # 数据库允许为空
blank=True # 表单/序列化校验允许为空
)
serializers.py
class StudentSerializer(serializers.Serializer):
"""
Serializer 作用:
1. 反序列化:前端 -> Python对象(校验 + 入库)
2. 序列化:Python对象 -> JSON(返回给前端)
"""
# ===================== 字段定义 =====================
# id:
# - read_only=True:只参与“返回”,不参与“提交”
# - 前端不能传这个字段,否则会被忽略
id = serializers.IntegerField(read_only=True)
# age:
# - allow_null=True:允许传 None
# - 注意:允许为空 ≠ 合法,后面 validate_age 还会进一步校验
age = serializers.IntegerField(allow_null=True)
# name:
# - allow_blank=True:允许空字符串 ""
name = serializers.CharField(allow_blank=True)
# school:
# - allow_blank=True:允许空字符串
school = serializers.CharField(allow_blank=True)
# ===================== source 用法 =====================
# 场景1:修改返回字段名(最常见)
# 返回结果中:school_name = school
school_name = serializers.CharField(source="school", read_only=True)
# 场景2:跨表取值(链式调用)
class_room = serializers.CharField(source="classroom.name")
# 场景3:取模型中的 property 或方法
# new_school 是 models.py 里的 @property
peng_school = serializers.CharField(source="new_school", read_only=True)
# ❌ 错误写法(字段名不能和 source 一样)
# new_school = serializers.CharField(source="new_school", read_only=True)
# ===================== 新增逻辑 =====================
def create(self, validated_data):
"""
调用时机:
serializer.save() 且 instance=None 时触发
参数:
validated_data:已经通过校验的数据(字典)
作用:
手动控制如何入库
"""
student = Student.objects.create(**validated_data)
return student
# ===================== 更新逻辑 =====================
def update(self, student, validated_data):
"""
调用时机:
serializer.save() 且 instance存在时触发
参数:
student:要修改的对象(instance)
validated_data:校验后的数据
"""
# 遍历所有字段,动态赋值(通用写法)
for key in validated_data:
setattr(student, key, validated_data.get(key))
# 保存到数据库
student.save()
return student
# ===================== 单字段校验 =====================
def validate_age(self, value):
"""
单字段校验(钩子函数)
命名规则:validate_字段名
执行顺序:
字段级校验 -> validate -> create/update
"""
if value is None:
raise ValidationError('年龄不能为空')
if value < 0:
raise ValidationError('年龄不能小于0')
if value > 120:
raise ValidationError('年龄不能大于120')
return value
# ===================== 全局校验 =====================
def validate(self, attrs):
"""
全局校验(跨字段)
参数 attrs = 所有字段组成的字典
"""
errors = {}
name = attrs.get('name')
school = attrs.get('school')
# 业务规则:名字不能是 admin
if name == 'admin':
errors['name'] = '名字不能为 admin'
# 名字不能为空(比 allow_blank 更严格)
if not name:
errors['name'] = '名字不能为空'
# 学校校验(你这里注释掉了)
# if not school:
# errors['school'] = '学校不能为空'
# 如果有错误,统一抛出
if errors:
raise ValidationError(errors)
return attrs
4.9定制返回格式
4.9.1多对多
4.9.1.1在模型中实现
models.py
在模型层自己封装一个“作者列表属性”,序列化时直接读取这个属性。
适用于:
- 自定义返回格式
- 多对多字段不想嵌套序列化
- 想快速返回关联数据
- 不使用嵌套序列化器
# 多对多:一本书可以有多个作者,一个作者也可以写多本书
authors = models.ManyToManyField(to='Author')
@property
def author_list(self):
l = []
for author in self.authors.all(): # 查询所有作者
l.append({
'name': author.name,
'age': author.age,
'addr': author.addr
})
return l

serializers.py
ListField校验列表格式的字段
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=32)
price = serializers.IntegerField()
publish_detail = serializers.DictField()
author_list=serializers.ListField()

4.9.1.2在序列化类中实现
SerializerMethodField本质就是通过 get_字段名 方法动态生成序列化字段数据
# ======================
# 多对多关系定制返回格式
# 方式二: 在序列化类中写
# ======================
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=32)
price = serializers.IntegerField()
publish_detail = serializers.DictField()
author_list_aaa = serializers.SerializerMethodField()
def get_author_list_aaa(self, obj):
l = []
for author in obj.authors.all(): # 拿出所有作者
l.append({'name': author.name, 'age': author.age, 'addr': author.addr})
return l

4.9.1.3子序列化
使用子序列化器批量序列化多对多对象
# ======================
# 多对多关系定制返回格式
# 方式三: 子序列化
# ======================
class AuthorSerializer(serializers.Serializer):
id = serializers.CharField()
name = serializers.CharField()
age = serializers.IntegerField()
addr = serializers.CharField()
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=32)
price = serializers.IntegerField()
publish_detail = serializers.DictField()
# 3 多对多---》子序列化-->多个一定要写many=True
authors = AuthorSerializer(many=True)

4.9.2一对多
4.9.2.1在模型中实现
在 模型层手动组装一对多数据,在 序列化器中用 ListField 接收。

serializers.py
# ======================
# 一对多关系定制返回格式
# 方式一: 在表模型中写方法,在序列化列中使用DictField接收
# ======================
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=32)
price = serializers.IntegerField()
publish_detail = serializers.DictField()
author_list=serializers.ListField()

4.9.2.2在序列化类中实现
在序列化器中使用 SerializerMethodField 手动控制一对多/外键字段的返回结构,同时可扩展“计算字段”
DRF 自动匹配:
get_字段名(self, obj)
例如
publish_detail_aaa → get_publish_detail_aaa
new_name_bbb → get_new_name_bbb
示例
# ======================
# 一对多关系定制返回格式
# 方式二: 在序列化类中定制
# ======================
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=32)
price = serializers.IntegerField()
publish_detail_aaa = serializers.SerializerMethodField()
def get_publish_detail_aaa(self, obj): # 这个obj就是当次序列化到得book对象
return {'name': obj.publish.name, 'addr': obj.publish.addr, 'city': obj.publish.city}
new_name_bbb = serializers.SerializerMethodField()
def get_new_name_bbb(self, obj):
return "new_" + obj.name
author_list = serializers.ListField()

4.9.2.3子序列化
用“子序列化器”递归描述对象结构,实现树形数据自动序列化
# ======================
# 一对多关系定制返回格式
# 方式三: 子序列化定制
# ======================
class AuthorSerializer(serializers.Serializer):
id = serializers.CharField()
name = serializers.CharField()
age = serializers.IntegerField()
addr = serializers.CharField()
class PublishSerializer(serializers.Serializer):
id = serializers.CharField()
name = serializers.CharField()
addr = serializers.CharField()
city = serializers.CharField()
class BookSerializer(serializers.Serializer):
id = serializers.CharField()
name = serializers.CharField(max_length=32)
price = serializers.IntegerField()
publish=PublishSerializer() # publish 对象会按照PublishSerializer 序列化类完成序列化
authors = AuthorSerializer(many=True)

4.10多表反序列化
用 ModelSerializer + source 映射字段 + 手动处理 ManyToMany + 嵌套序列化,实现完整 CRUD + 多表关系 API
-
外键(ForeignKey)
publish_id = serializers.PrimaryKeyRelatedField( queryset=Publish.objects.all(), source='publish', write_only=True, error_messages={ 'does_not_exist': '出版社不存在', 'required': '出版社不能为空' } )- 前端传
publish_id - 自动转成
Publish对象 - 写入
publish字段 - write_only(只用于写)
- 前端传
-
多对多(ManyToMany)
author_ids = serializers.PrimaryKeyRelatedField( queryset=Author.objects.all(), many=True, source='authors', write_only=True, error_messages={ 'does_not_exist': '作者不存在', 'required': '作者不能为空' } )- 前端传 id 列表
- 自动转换为 Author 对象列表
- validated_data 中是
authors=[对象] - 不能直接 create,需要 set()
-
展示字段(嵌套序列化)
publish = PublishSerializer(read_only=True) authors = AuthorSerializer(many=True, read_only=True)- 只用于返回数据
- 自动 ORM 关系展开
- 不参与写入
-
自定义字段
def get_publish_detail(self, obj): """ 自定义出版社详情 """ ... def get_author_detail(self, obj): """ 自定义作者详情 """ ...- 完全自定义返回逻辑
- 由 get_字段名 方法驱动
- 只读
-
create 核心逻辑
def create(self, validated_data): authors = validated_data.pop( 'authors', [] ) book = Book.objects.create( **validated_data ) book.authors.set(authors) return bookpop('authors')拆分多对多- 先创建 Book
- 再
book.authors.set(authors) - 多对多必须分两步处理
-
update 核心逻辑
def update(self, instance, validated_data): authors = validated_data.pop( 'authors', None ) for key, value in validated_data.items(): setattr(instance, key, value) if authors is not None: instance.authors.set(authors) return instance- pop 掉 authors
- setattr 批量更新普通字段
- instance.save()
- authors 使用 set() 重建关系
完整代码
class PublishSerializer(serializers.ModelSerializer):
class Meta:
model = Publish
fields = ['id', 'name', 'addr', 'city']
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'age', 'addr']
class BookSerializer(serializers.ModelSerializer):
# ========== 写入字段 ==========
publish_id = serializers.PrimaryKeyRelatedField(
queryset=Publish.objects.all(),
source='publish',
write_only=True
)
author_ids = serializers.PrimaryKeyRelatedField(
queryset=Author.objects.all(),
many=True,
source='authors',
write_only=True
)
# ========== 展示字段 ==========
publish = PublishSerializer(read_only=True)
authors = AuthorSerializer(many=True, read_only=True)
publish_detail = serializers.SerializerMethodField()
author_detail = serializers.SerializerMethodField()
class Meta:
model = Book
fields = [
'id', 'name', 'price',
'publish_id', 'author_ids',
'publish', 'authors',
'publish_detail', 'author_detail'
]
read_only_fields = ['id']
extra_kwargs = {
'name': {
'min_length': 2,
'max_length': 32,
'error_messages': {
'blank': '书名不能为空',
'required': '书名不能为空'
}
},
'price': {
'min_value': 0.01,
'error_messages': {'required': '价格不能为空'}
}
}
# ========== 局部校验 ==========
def validate_name(self, value):
value = value.strip()
if len(value) < 2:
raise serializers.ValidationError('书名长度不能小于2')
if '测试' in value:
raise serializers.ValidationError('书名不能包含测试')
return value
# ========== 全局校验 ==========
def validate(self, attrs):
if attrs.get('price', 0) > 99999:
raise serializers.ValidationError('价格过高')
return attrs
# ========== 自定义字段 ==========
def get_publish_detail(self, obj):
if not obj.publish:
return None
return {
'id': obj.publish.id,
'name': obj.publish.name,
'addr': obj.publish.addr,
'city': obj.publish.city
}
def get_author_detail(self, obj):
return [
{
'id': a.id,
'name': a.name,
'age': a.age,
'addr': a.addr
}
for a in obj.authors.all()
]
# ========== create ==========
def create(self, validated_data):
authors = validated_data.pop('authors', [])
book = Book.objects.create(**validated_data)
book.authors.set(authors)
return book
# ========== update ==========
def update(self, instance, validated_data):
authors = validated_data.pop('authors', None)
for k, v in validated_data.items():
setattr(instance, k, v)
instance.save()
if authors is not None:
instance.authors.set(authors)
return instance
5.模块和包及导入规则
模块和包
模块: 一个 .py 文件。
脚本: 被直接运行的 .py 文件。
包: 包含 __init__.py 的目录。
导入
Python 导入本质: 去sys.path中找模块。
绝对导入: 从sys.path根路径开始导入。
from app import utils
相对导入: 相对当前模块所在包导入。
from . import utils
“.”表示当前包,不是当前项目。
环境变量
import sys
print(sys.path)

| 目录 | 作用 | 里面一般有什么 | 为什么重要 |
|---|---|---|---|
scripts 目录 |
当前脚本所在目录 | 当前运行文件附近的 .py 文件 |
Python 导入时优先搜索 |
项目根目录 demo04 |
项目主目录 | app01、manage.py 等 |
Django 导入 app 依赖它 |
pycharm_display |
PyCharm 调试辅助目录 | IDE 内部工具 | 支持 PyCharm 控制台显示 |
python312.zip |
Python 压缩标准库 | 部分标准库模块 | Python 可直接从 zip 导入 |
DLLs |
Python 动态库目录 | .dll、.pyd 文件 |
C 扩展模块运行依赖 |
Lib |
Python 标准库目录 | os、json、re 等 |
import os 等来源 |
django_env |
虚拟环境根目录 | Python 解释器相关文件 | 当前 Python 环境 |
site-packages |
第三方库安装目录 | django、requests 等 |
pip 安装库都在这里 |
pycharm_matplotlib_backend |
PyCharm 图形后端 | matplotlib 支持代码 | 支持绘图显示 |
pycharm_altair_backend |
PyCharm Altair 支持 | Altair 图表支持 | IDE 图形展示 |
pycharm_plotly_backend |
PyCharm Plotly 支持 | Plotly 图表支持 | IDE 图形展示 |
导入规则
| 场景 | 推荐写法 |
|---|---|
| 第三方包内部导入 | from 包名 import xx |
| 包内部简单导入 | from . import xx |
| 不推荐 | import xx |
| 脚本运行 | 只能绝对导入 |
| 模块运行 | 绝对/相对都行 |
6.断言
断言
我断定某个条件一定成立
- 条件成立: 程序继续执行。
- 条件不成立: 立即抛异常(
AssertionError)
基本语法
assert 条件
带错误提示
assert 条件, "错误信息"
示例
name = input("请输入名字:")
assert name == 'peng', '名字不为peng,不能继续执行'
print('代码继续')

DRF 源码中的断言
断定self对象必须有_errors属性。
因为只有调用.is_valid()后才会生成_errors
所以必须serializer.is_valid()之后才能serializer.save()
否则直接报错。
DRF 为什么大量使用 assert?
因为 assert 非常适合检查“程序员调用是否正确”
例如:
- 是否调用了 is_valid
- 是否传了 queryset
- 是否配置 serializer_class
- 是否实现某个方法
assert hasattr(self, '_errors'), (
'You must call `.is_valid()` before calling `.save()`.'
)
assert常见使用场景
判断值必须存在
assert token, 'token不能为空'
判断类型
assert isinstance(name, str), 'name必须是字符串'
判断对象有某属性
assert hasattr(obj, 'save'), '对象必须有save方法'
判断列表不能为空
assert len(data) > 0, '数据不能为空'
判断状态是否合法
assert status in [1, 2, 3], '状态非法'
判断配置必须存在
assert settings.SECRET_KEY, 'SECRET_KEY必须配置'
assert 和 if raise 的区别
assert
- 简洁
- 适合内部逻辑校验
- 适合开发阶段
- DRF源码大量使用
if raise
-
更灵活
-
更正式
-
适合业务异常
assert不是给用户看的异常,而是给程序员看的“开发阶段逻辑断言”。
7.DRF请求响应
7.1DRF请求处理流程
当前端发送请求:
前端数据 ---> 解析类(Parser) ---> request.data
DRF会先使用解析类把请求体数据解析成 Python 数据。
然后
Python数据 ---> 渲染类(Renderer) ---> 浏览器/Postman
DRF 再使用渲染类,把 Python 数据渲染成:
- JSON
- HTML
- XML
7.2解析器(Parser)
| 解析器 | 处理的 Content-Type | 前端常见提交方式 | request.data 数据来源 |
|---|---|---|---|
JSONParser |
application/json |
axios、fetch 发送 JSON | 请求体中的 JSON |
FormParser |
application/x-www-form-urlencoded |
HTML 普通 form 表单 | 表单键值对 |
MultiPartParser |
multipart/form-data |
form-data、文件上传 | 表单 + 文件 |
使用方式一: 局部配置
class BookView(APIView):
parser_classes = [JSONParser]

使用方式二: 全局配置(settings.py)
# 解析类
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
# 'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
],
}

使用方式三: 全局配置(settings.py),局部再定制
这种就是上面俩中混用,不多做介绍了
7.3渲染类(Renderer)
决定响应返回格式
return Response({'name':'lqz'})
最终返回:
- JSON
- HTML 页面
- XML
由Renderer 决定。
为什么浏览器打开 DRF 很漂亮?
因为DRF 默认
BrowsableAPIRenderer,会生成可视化 API 页面Postman 为什么返回 JSON?
因为Postman 请求头
Accept: application/jsonDRF 自动选择
JSONRenderer接口配置
JSONRenderer,浏览器也只返回Json
使用方式一: 局部配置
class BookView(APIView):
...
renderer_classes = [JSONRenderer]

使用方式二: 全局配置(settings.py)
REST_FRAMEWORK = {
...
# =========================
# 渲染器(Renderer)
# 返回给前端的格式
# =========================
'DEFAULT_RENDERER_CLASSES': [
# JSON 返回(生产常用)
# 'rest_framework.renderers.JSONRenderer',
# DRF 浏览器页面(调试用)
'rest_framework.renderers.BrowsableAPIRenderer',
],
}

使用方式三: 全局配置(settings.py),局部再定制
这种就是上面俩中混用,不多做介绍了
8.ModelSerializer
之前一直使用
serializers.Serializer
特点:所有字段都要自己写
例如:
class StudentSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
age = serializers.IntegerField(allow_null=True)
name = serializers.CharField(allow_blank=True)
school = serializers.CharField(allow_blank=True)

如果表有 20 个字段 或者 30 个字段 或者 更多字段,会非常麻烦。所以 DRF 提供 ModelSerializer
本质:Serializer 的增强版
特点:可以自动根据模型生成字段
ModelSerializer 必须写 Meta
class PublishSerializer(serializers.ModelSerializer):
class Meta:
model = Publish
fields = [
'id',
'name',
'addr',
'city'
]

ModelSerializer核心功能
- 自动生成字段
- 自动生成校验
- 自动实现 create()
- 自动实现 update()
示例
实际没有这么复杂,继承ModelSerializer,然后配置Meta即可
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['name', 'price', 'publish', 'authors', 'publish_detail', 'author_list']
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True},
}
下面这个示例写的比较复杂,为了了解ModelSerializer的实现
-
外键反序列化
-
多对多反序列化
-
嵌套序列化
-
自定义字段
-
局部校验
-
全局校验
-
create
-
update
-
extra_kwargs
from rest_framework import serializers
from .models import Book, Publish, Author
# =========================
# 出版社
# =========================
class PublishSerializer(serializers.ModelSerializer):
class Meta:
model = Publish
fields = ['id', 'name', 'addr', 'city']
# =========================
# 作者
# =========================
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'age', 'addr']
# =========================
# 图书(核心)
# =========================
class BookSerializer(serializers.ModelSerializer):
# ========== 1. 反序列化(写入)字段 ==========
# 外键:publish_id -> publish对象
publish_id = serializers.PrimaryKeyRelatedField(
queryset=Publish.objects.all(),
source='publish',
write_only=True
)
# 多对多:author_ids -> authors对象列表
author_ids = serializers.PrimaryKeyRelatedField(
queryset=Author.objects.all(),
many=True,
source='authors',
write_only=True
)
# ========== 2. 序列化(读取)字段 ==========
publish = PublishSerializer(read_only=True)
authors = AuthorSerializer(many=True, read_only=True)
# ========== 3. 自定义字段 ==========
author_count = serializers.SerializerMethodField()
def get_author_count(self, obj):
return obj.authors.count()
# ========== 4. Meta配置 ==========
class Meta:
model = Book
fields = [
'id',
'name',
'price',
# 写入字段
'publish_id',
'author_ids',
# 读取字段
'publish',
'authors',
# 自定义字段
'author_count',
]
read_only_fields = ['id']
extra_kwargs = {
'name': {
'min_length': 2,
'max_length': 32,
'error_messages': {
'blank': '书名不能为空',
'required': '书名必须填写'
}
},
'price': {
'min_value': 0.01,
'error_messages': {
'required': '价格不能为空'
}
}
}
# ========== 5. 局部校验 ==========
def validate_name(self, value):
value = value.strip()
if '测试' in value:
raise serializers.ValidationError("书名不能包含测试")
return value
# ========== 6. 全局校验 ==========
def validate(self, attrs):
if attrs.get('price') and attrs['price'] > 99999:
raise serializers.ValidationError("价格过高")
return attrs
# ========== 7. create ==========
def create(self, validated_data):
authors = validated_data.pop('authors', [])
book = Book.objects.create(**validated_data)
book.authors.set(authors)
return book
# ========== 8. update ==========
def update(self, instance, validated_data):
authors = validated_data.pop('authors', None)
for k, v in validated_data.items():
setattr(instance, k, v)
instance.save()
if authors is not None:
instance.authors.set(authors)
return instance

9.视图基类
DRF 中最核心的两个视图基类:
| 基类 | 作用 |
|---|---|
| APIView | DRF 最基础视图类 |
| GenericAPIView | 对 APIView 的进一步封装 |
9.1APIView
相比 Django View,APIView 增加了
| 功能 | 说明 |
|---|---|
| request.data | 自动解析请求体 |
| Response | 自动渲染响应 |
| 认证 | authentication |
| 权限 | permission |
| 频率限制 | throttle |
| 异常处理 | exception |
| parser | 解析器 |
| renderer | 渲染器 |
APIView只是“增强版 View”,不会帮你封装业务逻辑
示例
# region APIView
class BookView(APIView):
"""
图书视图类
功能:
1. 查询所有
2. 查询单条
3. 新增
4. 修改
5. 删除
"""
# =====================================================
# 查询所有
# GET /books/
# =====================================================
def get(self, request, pk=None):
# -------------------------------------------------
# 查询单条
# -------------------------------------------------
if pk:
# 获取单个对象
book = Book.objects.filter(pk=pk).first()
# 对象不存在
if not book:
return Response({
'code': 101,
'msg': '图书不存在'
})
# 序列化
serializer = BookSerializer(instance=book)
return Response({
'code': 100,
'msg': '查询成功',
'result': serializer.data
})
# -------------------------------------------------
# 查询所有
# -------------------------------------------------
books = Book.objects.all()
serializer = BookSerializer(
instance=books,
many=True
)
return Response({
'code': 100,
'msg': '查询成功',
'results': serializer.data
})
# =====================================================
# 新增
# POST /books/
# =====================================================
def post(self, request):
# 反序列化
serializer = BookSerializer(
data=request.data
)
# 校验
serializer.is_valid(
raise_exception=True
)
# 保存
serializer.save()
return Response({
'code': 100,
'msg': '新增成功',
'result': serializer.data
})
# =====================================================
# 修改
# PUT /books/1/
# =====================================================
def put(self, request, pk=None):
# 获取对象
book = Book.objects.filter(
pk=pk
).first()
# 对象不存在
if not book:
return Response({
'code': 101,
'msg': '图书不存在'
})
# 序列化
serializer = BookSerializer(
instance=book,
data=request.data
)
# 校验
serializer.is_valid(
raise_exception=True
)
# 保存
serializer.save()
return Response({
'code': 100,
'msg': '修改成功',
'result': serializer.data
})
# =====================================================
# 删除
# DELETE /books/1/
# =====================================================
def delete(self, request, pk=None):
# 获取对象
book = Book.objects.filter(
pk=pk
).first()
# 对象不存在
if not book:
return Response({
'code': 101,
'msg': '图书不存在'
})
# 删除
book.delete()
return Response({
'code': 100,
'msg': '删除成功'
})
# endregion

9.2GenericAPIView
GenericAPIView 是 DRF 提供的一个“通用视图基类”。
它本质上:
GenericAPIView = APIView + 数据操作封装
相比 APIView,APIView 需要自己写:
- 查询数据
- 获取对象
- 创建序列化类
而 GenericAPIView:
- 已经帮我们封装好了很多公共逻辑
- 我们只需要配置:
- queryset
- serializer_class
DRF 自动帮你:
- 查询数据
- 获取对象
- 创建序列化器
- 分页
- 过滤
- 搜索
- 排序
- 等。
常用属性
| 名称 | 作用 | 常用写法 | 说明 |
|---|---|---|---|
| queryset | 指定操作的数据 | queryset = Book.objects.all() |
当前视图操作哪张表的数据 |
| serializer_class | 指定序列化类 | serializer_class = BookSerializer |
当前视图使用哪个序列化器 |
| lookup_field | 根据哪个字段查询 | lookup_field = 'pk' |
默认根据主键查询 |
| lookup_url_kwarg | 路由参数名 | lookup_url_kwarg = 'id' |
路由转换器名字 |
| pagination_class | 分页类 | pagination_class = PageNum |
配置分页 |
| filter_backends | 过滤组件 | filter_backends = [SearchFilter] |
支持搜索过滤 |
| permission_classes | 权限组件 | permission_classes = [IsAuthenticated] |
控制访问权限 |
| authentication_classes | 认证组件 | authentication_classes = [JWTAuthentication] |
用户认证 |
| throttle_classes | 限流组件 | throttle_classes = [UserRateThrottle] |
接口限流 |
常用方法

最核心四个
| 方法/属性 | 作用 |
|---|---|
| queryset | 配置所有数据 |
| serializer_class | 配置序列化类 |
| self.get_queryset() | 获取所有数据 |
| self.get_serializer() | 获取序列化器 |
示例
class BookView(APIView):
"""
图书视图类
功能:
1. 查询所有
2. 查询单条
3. 新增
4. 修改
5. 删除
"""
# =====================================================
# 查询所有
# GET /books/
# =====================================================
def get(self, request, pk=None):
# -------------------------------------------------
# 查询单条
# -------------------------------------------------
if pk:
# 获取单个对象
book = Book.objects.filter(pk=pk).first()
# 对象不存在
if not book:
return Response({
'code': 101,
'msg': '图书不存在'
})
# 序列化
serializer = BookSerializer(instance=book)
return Response({
'code': 100,
'msg': '查询成功',
'result': serializer.data
})
# -------------------------------------------------
# 查询所有
# -------------------------------------------------
books = Book.objects.all()
serializer = BookSerializer(
instance=books,
many=True
)
return Response({
'code': 100,
'msg': '查询成功',
'results': serializer.data
})
# =====================================================
# 新增
# POST /books/
# =====================================================
def post(self, request):
# 反序列化
serializer = BookSerializer(
data=request.data
)
# 校验
serializer.is_valid(
raise_exception=True
)
# 保存
serializer.save()
return Response({
'code': 100,
'msg': '新增成功',
'result': serializer.data
})
# =====================================================
# 修改
# PUT /books/1/
# =====================================================
def put(self, request, pk=None):
# 获取对象
book = Book.objects.filter(
pk=pk
).first()
# 对象不存在
if not book:
return Response({
'code': 101,
'msg': '图书不存在'
})
# 序列化
serializer = BookSerializer(
instance=book,
data=request.data
)
# 校验
serializer.is_valid(
raise_exception=True
)
# 保存
serializer.save()
return Response({
'code': 100,
'msg': '修改成功',
'result': serializer.data
})
# =====================================================
# 删除
# DELETE /books/1/
# =====================================================
def delete(self, request, pk=None):
# 获取对象
book = Book.objects.filter(
pk=pk
).first()
# 对象不存在
if not book:
return Response({
'code': 101,
'msg': '图书不存在'
})
# 删除
book.delete()
return Response({
'code': 100,
'msg': '删除成功'
})

9.3对象属性和类属性
Python属性查找:先找对象自己,对象没有,再找类属性
class Person():
school="清华大学"
p1 = Person()
p1.school="北京大学"
print(p1.school)
p2 = Person()
print(p2.school)

9.4GenericAPIView源码分析
GenericAPIView 最核心:
get_queryset()
get_serializer()
get_object()
DRF 后面所有:
- 9个子类视图
- ViewSet
- ModelViewSet
几乎都基于它们。
9.4.1get_queryset
源码
def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
return queryset
执行步骤
本质:get_queryset() 最终返回一个 QuerySet
DRF 会对 queryset 做一次“安全包装”
1.self.queryset
↓
Book.objects.all() # 你在类中写的 queryset
--------------------------------------------------
2.isinstance(queryset, QuerySet)
判断是否为 QuerySet 类型
✔ 成立(True)
--------------------------------------------------
3.queryset = queryset.all()
↓
Book.objects.all().all()
作用:
- 重新生成一个 QuerySet(避免直接引用原始对象)
- 保证 queryset 是“干净可链式调用”的结构
- 兼容 Manager / QuerySet 混用情况
--------------------------------------------------
4.return queryset
↓
返回最终 QuerySet
Book.objects.all()
```
9.4.2get_serializer
源码
def get_serializer_class(self):
return self.serializer_class
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
return serializer_class(*args, **kwargs)
执行步骤
核心:先拿到序列化器类 → 再实例化成序列化器对象
get_serializer() = 先找 serializer_class → 再实例化成 serializer 对象
1.self.get_serializer()
↓
入口方法(对外调用)
--------------------------------------------------
2.self.get_serializer_class()
↓
获取序列化器“类”
--------------------------------------------------
3.return self.serializer_class
↓
返回你在视图中定义的 serializer_class
例如:
BookSerializer
--------------------------------------------------
4.serializer_class(*args, **kwargs)
↓
实例化序列化器对象
等价于:
BookSerializer(
instance=book, # 查询出来的模型对象(用于序列化)
data=request.data # 请求数据(用于反序列化)
)
```
---
## 最终结果
```text
BookSerializer(
instance=book,
data=request.data
)
```
9.4.3get_object
源码
def get_object(self):
queryset = self.filter_queryset(
self.get_queryset()
)
lookup_url_kwarg = (
self.lookup_url_kwarg or self.lookup_field
)
filter_kwargs = {
self.lookup_field:
self.kwargs[lookup_url_kwarg]
}
obj = get_object_or_404(
queryset,
**filter_kwargs
)
return obj
执行步骤
核心:通过 URL 参数 → 过滤 queryset → 获取单个对象
关键理解点:
- lookup_url_kwarg: 控制 URL 参数名字(默认 pk)
- lookup_field: 控制查询字段(默认 pk)
- 本质就是:queryset.filter(pk=xxx).first()
1.self.get_queryset()
↓
获取查询集入口
--------------------------------------------------
2.queryset = Book.objects.all()
↓
得到基础查询集
--------------------------------------------------
3.lookup_url_kwarg = (
self.lookup_url_kwarg or self.lookup_field
)
↓
确定 URL 参数名
✔ 如果你没写 lookup_url_kwarg:
默认 = lookup_field(一般是 pk)
👉 示例:
lookup_url_kwarg = 'rr'
--------------------------------------------------
4.请求路径解析参数
URL:
/book/1/
Django 捕获:
self.kwargs = {
'rr': 1
}
--------------------------------------------------
5.组装查询条件 filter_kwargs
filter_kwargs = {
self.lookup_field: self.kwargs[lookup_url_kwarg]
}
↓
等价于:
{
'pk': 1
}
--------------------------------------------------
6.查询数据库
get_object_or_404(queryset, pk=1)
↓ 等价 ↓
Book.objects.get(pk=1)
--------------------------------------------------
7.返回结果
return obj
9.5总结
APIView:
- 给你一个“空壳”,所有CRUD你自己写
- 只负责请求分发 + Response
GenericAPIView:
- 帮你封装“数据层工具方法”,你只写配置 + 调用
- 在 APIView 基础上封装 queryset / serializer / object 操作
APIView vs GenericAPIView
| 对比项 | APIView | GenericAPIView |
|---|---|---|
| 层级 | 基础层 | 进阶封装 |
| CRUD | ❌ 全手写 | ✔ 半自动 |
| 数据查询 | ❌ 自己写 | ✔ get_queryset |
| 单条查询 | ❌ 自己写 | ✔ get_object |
| 序列化 | ❌ 自己写 | ✔ get_serializer |
| 代码量 | 多 | 少 |
| 可扩展性 | 高(但麻烦) | 高(更规范) |
| 企业使用 | 少 | 常用 |
10.视图扩展类(Mixin)
10.1简介
为什么会有Mixin?
因为CRUD逻辑重复,DRF把:
-
新增
-
删除
-
修改
-
查询
拆成了5个功能类。
Mixin核心类
| Mixin类 | 作用 | 对应HTTP方法 | 默认方法名 | 功能说明 |
|---|---|---|---|---|
CreateModelMixin |
新增数据 | POST |
create() |
接收前端数据,序列化校验,保存到数据库 |
ListModelMixin |
查询所有 | GET |
list() |
查询 queryset 中所有数据并返回列表 |
RetrieveModelMixin |
查询单条 | GET |
retrieve() |
根据主键查询单个对象 |
UpdateModelMixin |
修改数据 | PUT / PATCH |
update() / partial_update() |
更新指定对象,支持全量和局部更新 |
DestroyModelMixin |
删除数据 | DELETE |
destroy() |
删除指定对象 |
各Mixin内部核心做了什么
| Mixin | 核心流程 |
|---|---|
CreateModelMixin |
serializer = self.get_serializer(data=request.data) → serializer.is_valid() → serializer.save() |
ListModelMixin |
queryset = self.filter_queryset(self.get_queryset()) → 序列化 → 返回列表 |
RetrieveModelMixin |
obj = self.get_object() → 序列化 → 返回单条 |
UpdateModelMixin |
obj = self.get_object() → 序列化校验 → serializer.save() |
DestroyModelMixin |
obj = self.get_object() → obj.delete() |
10.2示例
views.py
from rest_framework.mixins import CreateModelMixin,RetrieveModelMixin,DestroyModelMixin,ListModelMixin,UpdateModelMixin
class BookView(GenericAPIView,ListModelMixin,CreateModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
def get(self,request,*args,**kwargs):
return self.list(request,*args,**kwargs)
def post(self,request,*args,**kwargs):
return self.create(request,*args,**kwargs)
class BookDetailView(GenericAPIView,RetrieveModelMixin,DestroyModelMixin,UpdateModelMixin):
serializer_class = BookSerializer
queryset = Book.objects.all()
def put(self,request,*args,**kwargs):
return self.update(request,*args,**kwargs)
def get(self,request,*args,**kwargs):
return self.retrieve(request,*args,**kwargs)
def delete(self,request,*args,**kwargs):
return self.destroy(request,*args,**kwargs)
serializers.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'name', 'price', 'publish', 'authors', 'publish_detail', 'author_list']
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True},
}
app01/urls.py
urlpatterns = [
# 查询全部、新增
path('books/', BookView.as_view()),
# 单查、修改、删除
path('books/<int:pk>/', BookDetailView.as_view())
]
demo05/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/app01/', include('app01.urls')),
]

11.通用视图类(APIView)
11.1简洁
为什么会有9个类
因为Mixin + GenericAPIView虽然减少代码。但:
def get():
return self.list()
def post():
return self.create()
还是重复。所以 DRF 又封装了一层。
9个通用视图类本质
例如
class ListAPIView(
GenericAPIView,
ListModelMixin
):
def get(self):
return self.list()
DRF提前帮你写好了。
单功能
| 类 | 功能 |
|---|---|
| ListAPIView | 查询所有 |
| CreateAPIView | 新增 |
| RetrieveAPIView | 查询单条 |
| UpdateAPIView | 修改 |
| DestroyAPIView | 删除 |
组合功能
| 类 | 功能 |
|---|---|
| ListCreateAPIView | 查询所有 + 新增 |
| RetrieveUpdateAPIView | 查询单条 + 修改 |
| RetrieveDestroyAPIView | 查询单条 + 删除 |
| RetrieveUpdateDestroyAPIView | 查询单条 + 修改 + 删除 |
使用方式
继承即可
class BookView(ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
11.2示例
from rest_framework.generics import ListAPIView,CreateAPIView,UpdateAPIView,RetrieveAPIView,DestroyAPIView
from rest_framework.generics import ListCreateAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView,RetrieveDestroyAPIView,RetrieveUpdateAPIView
# 单功能
# ListAPIView 查询所有
# CreateAPIView 新增
# RetrieveAPIView 查询单条
# UpdateAPIView 修改
# DestroyAPIView 删除
# 组合功能
# ListCreateAPIView 查询所有 + 新增
# RetrieveUpdateAPIView 查询单条 + 修改
# RetrieveDestroyAPIView 查询单条 + 删除
# RetrieveUpdateDestroyAPIView 查询单条 + 修改 + 删除
# 查询所有 + 新增
class BookView(ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 查询单条 + 修改 + 删除
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer

12.视图集(ViewSet)
12.1简介
在 Django REST framework 中,APIView 是“HTTP方法驱动”,即:
get()
post()
put()
delete()
分别处理不同请求。
而 ViewSet 是“动作驱动”,不再写:
get/post/put/delete
而是写:
list 查询全部
retrieve 查询单个
create 新增
update 修改
partial_update 局部修改
destroy 删除
ViewSet 的核心思想是:把一组资源操作放到一个类中管理
例如:
图书:
查询
新增
修改
删除
都放到一个:
BookViewSet
12.2示例
views.py
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 有5个方法:list retrieve update create destroy
app01\urls.py
ViewSet 调用 as_view() 必须传 actions 映射表
urlpatterns = [
# Viewset
path('books/', BookView.as_view({
'get': 'list',
'post': 'create'
})),
path('books/<int:pk>/', BookView.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
})),
]

13.视图层总结
13.1视图基类(Base View)
13.1.1最底层视图(APIView)
核心特点
- 最原始 DRF 视图类
- 请求方式 = 方法名(get/post/put/delete)
- 路由直接绑定方法
特点总结
- 所有逻辑必须手写
- 不封装 ORM 操作
- 最自由,但代码最多
13.1.2增强版基类(GenericAPIView)
核心作用
在 APIView 基础上封装“数据操作能力”
必备属性
queryset
serializer_class
核心方法
- get_queryset() → 获取列表数据
- get_object() → 获取单条数据
- get_serializer() → 实例化序列化器
特点总结
- 适合“模型 + 序列化”的接口
- 减少重复数据库代码
13.2Mixin机制
五大功能 Mixin:
-
CreateModelMixin(新增)
-
ListModelMixin(查询所有)
-
RetrieveModelMixin(查询单条)
-
UpdateModelMixin(更新)
-
DestroyModelMixin(删除)
使用规则
必须搭配:
GenericAPIView + Mixin
Mixin 依赖:
- get_queryset()
- get_object()
- get_serializer()
标准写法
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
13.3通用视图类(9个)
单功能视图类
| 类名 | 功能 |
|---|---|
| ListAPIView | 查询所有 |
| CreateAPIView | 新增 |
| RetrieveAPIView | 查询单条 |
| UpdateAPIView | 修改 |
| DestroyAPIView | 删除 |
组合功能视图类
| 类名 | 功能 |
|---|---|
| ListCreateAPIView | 查 + 增 |
| RetrieveUpdateAPIView | 查单 + 改 |
| RetrieveDestroyAPIView | 查单 + 删 |
| RetrieveUpdateDestroyAPIView | 查 + 改 + 删 |
使用特点
- 直接可用
- 不需要写 Mixin
- 不需要写 GenericAPIView
13.4视图集(ViewSet)
ViewSetMixin
作用:把“HTTP方法”改为“方法映射”
{'get': 'list'}
{'post': 'create'}
ViewSet
ViewSet = ViewSetMixin + APIView
特点:
- 方法可以随便命名
- 必须手动映射路由
GenericViewSet
GenericViewSet = ViewSetMixin + GenericAPIView
特点:
- 支持 queryset / serializer_class
- 支持 Mixin
ModelViewSet
本质
GenericViewSet + 5个Mixin
自动拥有
- list
- create
- retrieve
- update
- destroy
ReadOnlyModelViewSet
-
list
-
retrieve
-
只读接口:只提供查询,不提供增删改
13.5视图集路由机制
手动映射路由
path('book/', BookView.as_view({'get': 'list'}))
path('book/<int:pk>/', BookView.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
}))
13.6完整体系结构图
APIView
↓
GenericAPIView
↓
Mixin(5个功能)
↓
9个通用视图类
↓
ViewSet体系
↓
ModelViewSet(企业主流)
↓
Router + action(自动路由)
14.路由
14.1自动生成路由
Router自动生成:
- GET /book/ → list
- POST /book/ → create
- GET /book/
/ → retrieve - PUT /book/
/ → update - DELETE /book/
/ → destroy
from rest_framework.routers import SimpleRouter
from app01.views import BookView
# 2 实例化得到对象
router=SimpleRouter()
# 3 注册路由
# 第一个参数是路径
# 第二个参数是对应的视图类
# 第三个参数是别名,一般跟路径同名
router.register('book',BookView,'book')
# 如果有多个,要注册多次
# router.register('publish',PublishView,'publish')
# 自动生成的路由: [
# <URLPattern '^book/$' [name='book-list']>,
# <URLPattern '^book/(?P<pk>[^/.]+)/$' [name='book-detail']>
# ]
# Router 自动生成两类路由:
# 1. book/
# -> list(GET)
# -> create(POST)
# 表示集合资源
# 2. book/<pk>/
# -> retrieve(GET)
# -> update(PUT/PATCH)
# -> destroy(DELETE)
# 表示单个资源
# book-list = 集合操作
# book-detail = 单个资源操作
print('自动生成的路由:',router.urls)
# 4 在总路由中加入
urlpatterns = [
path('', include(router.urls)), # 自动加入到总路由中
]
# router.urls 是个列表
# 列表=列表+列表1
# 列表+=列表1
# urlpatterns=urlpatterns+router.urls
urlpatterns+=router.urls #加入到总路由中

14.2action装饰器
为什么需要 action?
Router只能处理:list / create / update / delete / retrieve
不能处理:
- login
- register
- send_sms
action作用:把“自定义方法”变成路由接口
使用方式
# 注册路由
router.register('user', UserView, 'user')
# 定义方法
from rest_framework.decorators import action
class UserView(ViewSet):
@action(methods=['POST'], detail=False)
def register(self, request):
return Response('register')
# 自动生成路由
POST /user/register/
detail参数
| 值 | 含义 |
|---|---|
| False | 不带 pk |
| True | 带 pk |
14.3ViewSet对象中的关键属性
request对象
当前请求对象(全局可用)
self.request
action属性
当前执行的方法名
self.action
常见用途
动态序列化器
def get_serializer_class(self):
if self.action == 'list':
return BookListSerializer
return BookSerializer
15.DRF核心组件
15.1视图层源码分析
15.1.1APIView本质
APIView 是 DRF 提供的视图基类
继承关系:
APIView
↓
View(Django原生)
所以APIView 本质还是 Django 的类视图
只是 DRF 在 Django View 基础上,增强了:
- request
- response
- 认证
- 权限
- 频率
- 异常处理
- csrf处理
15.1.2整体执行流程
路由:path('book/', BookView.as_view())
↓
请求来了:http://127.0.0.1:8000/book/
↓
Django路由匹配成功
↓
执行:BookView.as_view()(request)
↓
执行APIView中的as_view()
↓
本质:实例化视图类对象
↓
执行:self.dispatch(request)
↓
dispatch内部:
1 把原生request包装成新的Request
2 执行认证
3 执行权限
4 执行频率
5 找请求方式对应的方法
6 执行视图方法
7 统一异常处理
8 统一响应处理
15.1.3定位dispatch源码
找到as_view,然后定位到实现

找到ViewSetMixin的实现,但是无法直接跳转到dispatch的实现,dispatch 最终来自 APIView
在
ViewSetMixin.as_view()里调用了self.dispatch(),但ViewSetMixin本身没有dispatch方法,所以必须去看继承链;通过class ViewSet(ViewSetMixin, APIView)可以知道,dispatch最终来自APIView。Python 调用方法时会按 MRO(方法解析顺序)从当前类一路往父类查找,因此self.dispatch()最终执行的是APIView.dispatch()。而 ViewSet 的核心原理是在as_view({'get':'list'})中动态绑定self.get = self.list,所以dispatch()在处理 GET 请求时,实际上执行的是list()方法。

在 APIView查看dispatch具体实现

15.1.4dispatch具体实现
APIView核心增强:
-
1.request变成新的Request对象
-
2.执行认证、权限、频率
-
3.请求方式自动映射
-
4.异常统一处理
-
5.Response统一处理
-
6.默认取消csrf校验
# =========================================================
# APIView核心源码:dispatch
# =========================================================
# APIView在as_view中已经取消csrf校验
# 本质:
# csrf_exempt(view)
# 所以前后端分离中一般不需要csrf_token
def dispatch(self, request, *args, **kwargs):
# 1 保存路由参数
self.args = args
self.kwargs = kwargs
# kwargs示例:
# path('book/<int:id>/')
# /book/1/
# kwargs = {'id':1}
# =====================================================
# 2 原生request -> DRF Request
# =====================================================
request = self.initialize_request(request, *args, **kwargs)
"""
Django原生:
WSGIRequest
DRF包装后:
Request
新增:
request.data
request.query_params
request.user
request.auth
"""
# 保存到self,以后可直接 self.request
self.request = request
# 默认响应头
self.headers = self.default_response_headers
try:
# =================================================
# 3 执行三大认证
# =================================================
self.initial(request, *args, **kwargs)
"""
内部执行:
self.perform_authentication(request)
-> 认证
self.check_permissions(request)
-> 权限
self.check_throttles(request)
-> 频率
"""
# =================================================
# 4 请求方式映射
# =================================================
if request.method.lower() in self.http_method_names:
# 反射获取方法
handler = getattr(
self,
request.method.lower(),
self.http_method_not_allowed
)
"""
GET:
self.get
POST:
self.post
PUT:
self.put
DELETE:
self.delete
"""
else:
# 请求方式不允许 -> 405
handler = self.http_method_not_allowed
# =================================================
# 5 执行视图方法
# =================================================
response = handler(request, *args, **kwargs)
# 真正执行:
# get/post/put/delete
except Exception as exc:
# =================================================
# 6 异常统一处理
# =================================================
response = self.handle_exception(exc)
"""
统一处理:
认证异常
权限异常
频率异常
代码异常
"""
# =====================================================
# 7 统一处理Response
# =====================================================
self.response = self.finalize_response(
request,
response,
*args,
**kwargs
)
"""
统一处理:
状态码
渲染器
响应头
content-type
"""
# 返回响应
return self.response

15.1.5总结
DRF 请求完整生命周期
APIView
↓
dispatch
↓
认证
↓
权限
↓
频率
↓
视图方法
↓
过滤
↓
排序
↓
分页
↓
序列化
↓
Response
查看self.initial(request, *args, **kwargs)

self.initial(request, *args, **kwargs)会依次实现:
- 认证
- 权限
- 频率
# Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)

15.2认证
Django 内置 django.contrib.auth,主要包含:
- User 模型
- 认证后端 Authentication Backend
- session 登录机制
- 权限系统(Permission / Group)
默认用户表:django.contrib.auth.models.User
15.2.1自定义用户表
# 1 用户表,用来写登录接口
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32) # 明文放,没有加密
age = models.IntegerField()
user_type = models.IntegerField(default=1, choices=((1, '普通用户'), (2, '管理员'), (3, '超级管理员')))
# 2 用户token表,用来记录用户的登录情况
class UserToken(models.Model):
token = models.CharField(max_length=36)
user = models.OneToOneField(to=User, on_delete=models.CASCADE)

15.2.2登录接口
根据 username 和 password验证登录,登录成功保存一个随机 token,并把token返回前端
class UserView(ViewSet):
# 禁用认证
authentication_classes = []
# 禁用权限
permission_classes = []
@action(methods=['POST'], detail=False)
def login(self, request):
# 1 取出用户名密码
username = request.data.get('username')
password = request.data.get('password')
# 2 校验
user = User.objects.filter(username=username, password=password).first()
# 3 返回给前端
if user:
# 生成一个随机字符串
token_str = str(uuid.uuid4()) # asdf-asdfas-asdfas-asdfads
print(token_str)
# 保存到UserToken中--》如果之前没有数据,就是新增,如果之前有数据就修改
# 根据当前用户,去UserToken表中查,如果查到,修改token,如果查不到,就新增
'''
Look up an object with the given kwargs, updating one with defaults
if it exists, otherwise create a new one.
'''
UserToken.objects.update_or_create(user=user, defaults={'token': token_str})
# 返回给前端登录成功
return Response({'code': 100, 'msg': "登录成功", 'token': token_str})
else:
# 返回给前端用户名或密码错误
return Response({'code': 101, 'msg': "用户名或密码错误"})

15.2.3实现认证
根据请求接口的header携带的token判断用户有没有登录,用户登录成功时会保存一个token
from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed
'''
-1 写个类,继承BaseAuthentication
-2 在类中重写 authenticate
-在方法内,取出前端传入的token,校验用户是否登录
-如果token正确,且是登录用户,返回两个之,继续往后走
-如果token错误或数据库中没有数据,抛异常
-3 使用:在视图类上配置或在配置文件中配置---》类似于之前学的请求和响应的配置
'''
class LoginAuthentication(BaseAuthentication):
def authenticate(self, request):
# 1 取出用户携带的token---》统一要求放在请求头中
"""
Apifox 里不要写 HTTP_TOKEN,只写 Token,Django 会自动帮你变成 HTTP_TOKEN。
你不能写 HTTP_TOKEN,因为它不是 HTTP 协议字段,它只是 Django 把 Token 转换后的内部表示。
:param request:
:return:
"""
token=request.META.get('HTTP_TOKEN')
if token:
# 2 校验是否正确---UserToken表
user_token=UserToken.objects.filter(token=token).first()
if user_token: # 正常登录用户
# return 当前登录用户,token 返回两个数据:后续在request中通过request.user 就会取出,返回的第一个参数:当前登录用户
return user_token.user,token
else:
raise AuthenticationFailed('token失效')
else:
raise AuthenticationFailed('请携带token-请先登录')

15.2.4认证配置
全局配置,这里是自定义的认证控制
REST_FRAMEWORK = {
# 认证
'DEFAULT_AUTHENTICATION_CLASSES': [
'app01.auth.LoginAuthentication'
]
}

局部配置
- 局部配置用局部
- 局部没有配置用全局
class BookView(ListCreateAPIView):
# 禁用认证
authentication_classes = []
...
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer

15.2.5测试
/books接口不需要认证,/books/1需要认证
# 登录
http://127.0.0.1:8000/api/v1/app01/user/login/
# 不需要认证
http://127.0.0.1:8000/api/v1/app01/books/
# 需要认证
http://127.0.0.1:8000/api/v1/app01/books/1

先登录获取token

book列表接口不需要认证

books/1需要认证,在Header中带上token

没有token无法请求

15.3权限
Django 的权限是基于:
- User(用户)
- Group(用户组)
- Permission(权限点)
三者关系:
User ←→ Group ←→ Permission
这里没有涉及太复杂,只对user做了基本权限控制
15.3.1实现权限
当开启权限时,用户必须是超级用户,否则不能访问
from rest_framework.permissions import BasePermission
class SuperPermission(BasePermission):
def has_permission(self, request, view):
#1 校验用户权限---》拿到当前登录用户
if request.user.user_type==3:
return True
else:
self.message='您不是超级用户,不能操作,您是:【%s】用户'%request.user.get_user_type_display()
return False

15.3.2配置
全局配置,必须是超级管理员权限,这里是自己自定义的权限控制

局部配置
BookView禁用权限,不需要权限BookDetailView使用全局权限
class BookView(ListCreateAPIView):
...
# 禁用权限
permission_classes = []
...
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer

15.3.3测试
数据库2个用户
- user_type=1:普通用户:
- user_type=2:管理员
- user_type=3:超级管理员

登录获取tom的token
http://127.0.0.1:8000/api/v1/app01/user/login/
{
"username":"tom",
"password":"123"
}

books/1不能访问

使用peng的token才可以访问

15.4频率
15.4.1实现频率
rate = '3/m':一分钟访问三次
# 一分钟访问三次
class CommonThrottle(SimpleRateThrottle):
"""
1. 秒(s)级限流
rate = '3/10s' # 10秒最多3次(防刷点击/点赞)
rate = '1/5s' # 5秒最多1次(防连点/防重复提交)
2. 分钟(m)级限流
rate = '5/m' # 1分钟最多5次(登录/注册/验证码)
rate = '60/m' # 1分钟最多60次(普通API/列表查询)
3. 小时(h)级限流
rate = '100/h' # 1小时最多100次(中等接口/搜索)
rate = '1000/h' # 1小时最多1000次(高频业务接口)
4. 天(d)级限流
rate = '10/d' # 1天最多10次(敏感操作:改密码/绑定手机)
rate = '100/d' # 1天最多100次(用户级API访问限制)
"""
rate = '3/m' # s m h d
def get_cache_key(self, request, view):
# 返回ip,以ip做限制
return request.META.get('REMOTE_ADDR')

15.4.2配置
全局配置
REST_FRAMEWORK = {
...
# 频率
'DEFAULT_THROTTLE_CLASSES': [
'app01.throttle.CommonThrottle'
]
}

局部配置
BookView不配置频率BookDetailView使用全局配置

15.4.3测试
books/1一分钟只能访问3次

books/不受频率控制

15.5排序
ordering_fields:可排序字段ordering:默认排序字段
分页组件的排序可能和ordering的排序有冲突
filter_backends = [
OrderingFilter,
SearchFilter,
DjangoFilterBackend,
CommonFilter
]
# =========================
# 排序功能(Ordering)
# http://127.0.0.1:8000/api/v1/app01/books/?ordering=price
# http://127.0.0.1:8000/api/v1/app01/books/?ordering=-price # 降序
# http://127.0.0.1:8000/api/v1/app01/books/?ordering=price ,-id
# =========================
# 指定过滤后端使用 OrderingFilter
# OrderingFilter 作用:允许客户端通过 URL 参数控制排序规则
# 例如:
# /books/?ordering=price → 按 price 升序
# /books/?ordering=-price → 按 price 降序
# /books/?ordering=id → 按 id 升序
# /books/?ordering=-id → 按 id 降序
# from rest_framework.filters import OrderingFilte
# 允许排序的字段白名单(安全控制)
# 作用:
# 只有在 ordering_fields 中声明的字段才能被前端用于排序
# 防止用户随意对敏感字段排序(如 password / is_admin 等)
ordering_fields = ['price', 'id']
# 默认排序规则(非常重要)
# 作用:当前端没有传 ordering 参数时,系统默认使用该排序
# 规则:
# 'price' → 升序(从小到大)
# '-price' → 降序(从大到小,注意负号)
# 当前配置效果:默认按 price 升序排序
ordering = ['price']

测试
# 价格升序
http://127.0.0.1:8000/api/v1/app01/books/?ordering=price
# 价格降序
http://127.0.0.1:8000/api/v1/app01/books/?ordering=-price
# 先按 price 升序排序,如果 price 相同,再按 id 降序排序
http://127.0.0.1:8000/api/v1/app01/books/?ordering=price ,-id

15.6过滤
15.6.1配置
安装依赖
pip install django-filter
# 卸载
# pip uninstall django-filter

注册应用
INSTALLED_APPS = [
...
'django_filters',
]

全局配置
REST_FRAMEWORK = {
...
# 过滤
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend'
]
}

15.6.2Lookup汇总
精确 / 模糊查询
| Lookup | 含义 | 示例 |
|---|---|---|
exact |
精确匹配 | name__exact="Tom" |
iexact |
忽略大小写精确 | name__iexact="tom" |
contains |
包含(区分大小写) | name__contains="a" |
icontains |
包含(忽略大小写)⭐常用 | name__icontains="a" |
数值比较
| Lookup | 含义 | 示例 |
|---|---|---|
gt |
大于 | price__gt=100 |
gte |
大于等于 ⭐常用 | price__gte=100 |
lt |
小于 | price__lt=100 |
lte |
小于等于 ⭐常用 | price__lte=100 |
集合 / 范围
| Lookup | 含义 | 示例 |
|---|---|---|
in |
在集合中 ⭐常用 | id__in=[1,2,3] |
range |
范围 | price__range=(10,100) |
字符串匹配
| Lookup | 含义 | 示例 |
|---|---|---|
startswith |
以…开头 | name__startswith="A" |
istartswith |
忽略大小写开头 | name__istartswith="a" |
endswith |
以…结尾 | name__endswith="Z" |
iendswith |
忽略大小写结尾 | name__iendswith="z" |
15.6.3search_fields
name模糊查询
# 方式一: drf内置的,只能用search模糊匹配
# http://127.0.0.1:8000/api/v1/app01/books/?ordering=price&search=python
search_fields = ['name']


15.6.4django-filter
exact精确查询

测试
http://127.0.0.1:8000/api/v1/app01/books/?name=Python入门-1&publish=人民出版社

15.6.5自定义过滤
name__icontains:名字模糊查询,忽略大小写price__gt:价格大于

测试
http://127.0.0.1:8000/api/v1/app01/books/?name=python&price_gt=210

15.7分页
15.7.1使用
class BookView(ListCreateAPIView):
...
# 分页 注意排序和 OrderingFilter 冲突
# pagination_class = CommonPageNumberPagination
# pagination_class = CommonLimitOffsetPagination
pagination_class = CommonCursorPagination

15.7.2基本分页
优点
- 最直观(用户友好)
- 前端最好用
- 管理后台常用
- 支持跳页
缺点
- 数据量大时性能差(OFFSET 越大越慢)
- 数据插入/删除后页码会漂移
关键参数解释
| 参数 | 含义 |
|---|---|
| page_size | 默认每页条数 |
| page_query_param | 页码参数名 |
| page_size_query_param | 前端可控制每页大小 |
| max_page_size | 最大限制(防止拖垮数据库) |
# =========================================================
# 1. PageNumberPagination(最常用分页)
# =========================================================
class CommonPageNumberPagination(PageNumberPagination):
"""
页码分页(最常见)
特点:
- 支持 page 参数分页
- 可选支持 size 动态控制每页数量
- 支持跳页(page=1,2,3...)
"""
# 默认每页数据量
page_size = 5
# 页码参数名
# 例如:/books/?page=2
page_query_param = 'page'
# 每页条数参数名(前端可动态控制)
# 例如:/books/?page=2&size=5
page_size_query_param = 'size'
# 最大允许每页条数(防止一次查太多拖垮数据库)
max_page_size = 5
# http://127.0.0.1:8000/api/v1/app01/books/?page=1 # 查询第一页,返回2条数据
# http://127.0.0.1:8000/api/v1/app01/books/?page=2 # 查询第二页,返回2条数据
# http://127.0.0.1:8000/api/v1/app01/books/?page=2&size=4 # 查询第二页,返回4条数据
# http://127.0.0.1:8000/api/v1/app01/books/?page=1&size=4000000 # 查询第1页,返回5条数据

测试
http://127.0.0.1:8000/api/v1/app01/books/?page=2&size=10

5.7.3偏移分页
优点
- 非常接近 SQL
- 前端可以精确控制数据切片
- 实现简单
缺点(很重要)
- offset 越大越慢(数据库要“跳过”数据)
- 数据变动会导致重复或漏数据
- 不适合大数据分页
# =========================================================
# 2. LimitOffsetPagination(偏移分页)
# =========================================================
class CommonLimitOffsetPagination(LimitOffsetPagination):
"""
偏移分页(SQL风格分页)
特点:
- limit 控制取多少条
- offset 控制从第几条开始取
- 不支持页码概念(不能 page=2)
"""
# 默认每次取多少条
default_limit = 5
# limit 参数名
# 例如:/books/?limit=10
limit_query_param = 'limit'
# offset 参数名(从第几条开始)
# 例如:/books/?offset=5&limit=2
offset_query_param = 'offset'
# 最大 limit(防止一次性拉太多数据)
max_limit = 5
测试
http://127.0.0.1:8000/api/v1/app01/books/?offset=5&limit=5

15.7.4游标分页
优点(核心优势)
- 性能最好(没有 OFFSET)
- 数据量再大也稳定(百万级/亿级)
- 不会重复/漏数据(适合实时数据)
缺点
- 不能跳页(不能 page=10)
- 不适合后台管理
- 依赖排序字段
# =========================================================
# 3. CursorPagination(游标分页,性能最好)
# =========================================================
class CommonCursorPagination(CursorPagination):
"""
游标分页(高性能分页)
特点:
- 不支持跳页,只能上一页/下一页
- 基于数据库排序字段 + cursor 标记
- 适合大数据量场景(比 offset 更高效)
"""
# cursor 参数名
# 例如:/books/?cursor=xxx
cursor_query_param = 'cursor'
# 每页数据量
page_size = 5
# 必须排序字段(必须唯一或接近唯一,否则可能重复)
# 常用:id / create_time
ordering = 'id'
测试
先访问/books,获取next,next就是下一页地址

再次访问就会有previous
next:下一页链接previous:上一页链接

15.7.5总结
| 类型 | 是否可跳页 | 性能 | 原理 | 适用场景 |
|---|---|---|---|---|
| PageNumber | 是 | 中 | page + offset | 后台/常规列表 |
| LimitOffset | 否 | 差 | SQL offset | 工具/导出 |
| Cursor | 否 | 最好 | 游标定位 | feed流/大数据 |
15.8全局异常
15.8.1简介
为什么需要全局异常
如果不做统一异常处理:
- 前后端返回格式不统一
- 无法统一记录日志
- 无法统一状态码
- 不方便处理业务异常
所以企业项目都会:
- 自定义异常
- 自定义异常返回格式
- 全局统一拦截
DRF 默认异常机制
DRF 内部已经有:
rest_framework.views.exception_handler
它会处理:
- ValidationError
- AuthenticationFailed
- NotAuthenticated
- PermissionDenied
- NotFound
- Throttled
Django 异常大体分
| 类型 | 说明 |
|---|---|
| Python异常 | ZeroDivisionError |
| Django核心异常 | Http404、PermissionDenied |
| ORM异常 | DoesNotExist |
| DRF异常 | ValidationError |
| 数据库异常 | IntegrityError |
| JWT异常 | TokenError |
执行流程
APIView.dispatch()
↓
handle_exception()
↓
common_exception_handler(exc, context)
15.8.2实现全局异常
import time
from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError,Throttled,AuthenticationFailed,APIException
def common_exception_handler(exc, context):
# 区分是drf异常,还是python的异常
# response=exception_handler(exc, context)
# if response: # 有值,说明是drf异常
# # 如果是drf异常,咱们应该取出 detail,返回给前端
# if isinstance(response.data, list):
# msg=response.data[0]
# elif isinstance(response.data, dict):
# msg=response.data.get('detail',None) or 'drf异常,请稍后再试'
# else:
# msg='未知错误'
# return Response({'code': '999', 'msg': '请求异常-drf:【%s】'%msg})
# else:# 没有值,没处理,是python的异常
# msg=str(exc)
# return Response({'code':'888','msg':'请求异常-普通:【%s】'%msg})
# 扩展:错误码正常应该更细
if isinstance(exc, ValidationError):
msg = exc.detail
data={'code':'103','msg':msg}
elif isinstance(exc, AuthenticationFailed):
msg= exc.detail
data = {'code': '104', 'msg': msg}
elif isinstance(exc, Throttled):
msg = exc.detail
data = {'code': '105', 'msg': msg}
elif isinstance(exc, IndexError) :
data={'code': '106', 'msg': "索引超出范围"}
elif isinstance(exc, ZeroDivisionError) :
data={'code': '107', 'msg': "不能除以0"}
elif isinstance(exc, APIException):
msg = exc.detail
data = {'code': '999', 'msg': msg}
else:
data = {'code': '888', 'msg': "未知错误,请联系系统管理员"}
# 扩展2,执行到这,说明出异常了,咱们应该记录日志
request=context.get('request')
view=context.get('view')
print(f'''程序错误:
错误原因是【{str(exc)}】----
时间是:【{time.time()}】----
请求地址是:【{request.get_full_path()}】----
请求方式是:【{request.method}】----
用户是:【{request.user.username or '未登录用户'}】----
出错的视图类是:【{str(view)}】''')
# %(str(exc),time.time(),request.get_full_path(),request.method)]
return Response(data)

配置
REST_FRAMEWORK = {
...
# 异常
'EXCEPTION_HANDLER': 'app01.exception.common_exception_handler',
}

15.8.3测试
不登录 访问一个需要认证的接口

15.9接口文档(drf-spectacular)
15.9.1简介
Django 本身不提供 REST API 接口文档功能。
真正常用的是:
- Django + DRF(Django REST Framework)
- 再配合接口文档工具:
- drf-yasg(老牌)
- drf-spectacular(现在更推荐)
- DRF 自带 schema(功能弱)
默认提供:
- /docs(Swagger UI)
- /redoc(ReDoc)
- /openapi.json
作用:
| 功能 | 说明 |
|---|---|
| 前后端联调 | 前端直接调试 |
| 自动测试 | Swagger 可直接测试 |
| 降低沟通成本 | 不需要一直问后端 |
| 接口规范化 | 统一风格 |
| 自动生成 | 不需要手写文档 |
15.9.2基本使用
安装
pip install drf-spectacular
# 卸载
# pip uninstall drf-spectacular

开启接口文档
INSTALLED_APPS = [
...
# 接口文档
'drf_spectacular'
]

配置地址。如果开启了权限、认证、频率接口文档需要关闭
| 地址 | 作用 | 核心功能 | 是否必须 | 访问效果 | 依赖关系 |
|---|---|---|---|---|---|
/api/schema/ |
OpenAPI Schema 接口 | 生成整个项目的接口结构 JSON | 必须 | 返回 OpenAPI JSON 数据 | Swagger / Redoc 都依赖它 |
/api/docs/ |
Swagger UI 界面 | 可视化接口调试页面 | 推荐 | 浏览器可直接调试接口 | 内部请求 /api/schema/ |
/api/redoc/ |
ReDoc 文档界面 | 更美观的接口文档展示 | 可选 | 只适合阅读文档 | 内部请求 /api/schema/ |
urlpatterns = [
...
# region 接口文档
# =========================
# schema 接口(核心)
# =========================
path(
"api/schema/",
SpectacularAPIView.as_view(
# ❗ 关闭认证机制
# 否则会继承 DRF DEFAULT_AUTHENTICATION_CLASSES
# 可能导致访问 schema 时出现 401/403
authentication_classes=[],
# ❗ 关闭权限控制
# 默认 DRF 可能是 IsAuthenticated
# 这里必须改成允许匿名访问
permission_classes=[],
# ❗ 关闭限流
# 避免 throttle 影响文档访问(尤其生产环境)
throttle_classes=[],
),
name="schema", # swagger / redoc 都依赖这个 URL name
),
# =========================
# Swagger UI(接口调试界面)
# =========================
path(
"api/docs/",
SpectacularSwaggerView.as_view(
# ❗ 指定 schema 来源
# Swagger UI 会请求 /api/schema/ 获取接口结构
url_name="schema",
# ❗ 同样关闭认证 / 权限 / 限流
# 防止 Swagger 页面本身被拦截
authentication_classes=[],
permission_classes=[],
throttle_classes=[],
),
name="swagger-ui",
),
# =========================
# ReDoc UI(文档展示界面)
# =========================
path(
"api/redoc/",
SpectacularRedocView.as_view(
# ❗ 同样依赖 schema
url_name="schema",
# ❗ 防止访问 redoc 页面被 DRF 拦截
authentication_classes=[],
permission_classes=[],
throttle_classes=[],
),
name="redoc",
),
# endregion
...
]

15.9.3测试
http://127.0.0.1:8000/api/docs/

16.jwt
16.1简介
JWT 是现在最主流的用户认证方案之一
JWT 全称:JSON Web Token
JWT 结构
| 部分 | 作用 |
|---|---|
| Header | 头部 |
| Payload | 载荷(数据) |
| Signature | 签名 |
JWT 常见字段
| 字段 | 含义 |
|---|---|
| exp | 过期时间 |
| iat | 签发时间 |
| nbf | 生效时间 |
| iss | 签发者 |
| sub | 主题 |
| aud | 接收方 |
| jti | token唯一ID |
JWT 登录流程
1 用户登录
↓
2 服务端验证用户名密码
↓
3 生成 JWT
↓
4 返回 JWT
↓
5 客户端保存 token
↓
6 以后请求携带 token
↓
7 服务端验证 token
JWT 优点
| 优点 | 说明 |
|---|---|
| 无状态 | 服务端不存 session |
| 跨域方便 | 前后端分离友好 |
| 微服务友好 | 不共享 session |
| 扩展性强 | 多服务认证简单 |
| 移动端友好 | App 很适合 |
JWT 缺点
| 缺点 | 说明 |
|---|---|
| 无法主动失效 | 服务端不存储 |
| token 泄露危险 | 谁拿谁能用 |
| 体积比 session 大 | 每次请求都带 |
| 不适合存敏感数据 | payload 可解码 |
16.2base64编码解码
作用:
- 编码
- 解码
- 图片编码解码
import base64
import json
user_info = {
"user_id": 1,
"user_name": "peng",
"role_id": 2,
"role_name": "管理员",
"enterprise_id": 19
}
# 转成 json 字符串
user_str = json.dumps(user_info)
print("原数据:",user_str)
# base64编码
res = base64.b64encode(user_str.encode("utf-8"))
print("编码:",res)
# base64解码
res1 = base64.b64decode(res)
print("解码:",res1)
# 把一段 Base64 编码的图片数据还原成 PNG 文件并保存到本地。
# 1. 读取真实图片(二进制)
with open('1.png', 'rb') as f:
data = f.read()
# 2. 转 base64(如果你需要 res1)
res2 = base64.b64encode(data)
print(res2)
# 3. 再还原成图片(验证)
img = base64.b64decode(res2)
with open('2.png', 'wb') as f:
f.write(img)
print("完成")

16.3SimpleJWT
16.3.1简介
djangorestframework-simplejwt 是 Django REST Framework(DRF)生态中用于实现 JWT 认证机制 的官方推荐方案。
它本质上做了三件事:
-
1.登录时签发 JWT(Access + Refresh)
-
2.请求时验证 JWT
-
3.支持刷新 / 失效 / 黑名单
Access Token(访问令牌)
- 用于访问 API
- 生命周期短(比如 5~30 分钟)
- 放在请求头
Refresh Token(刷新令牌)
- 用于换新的 access token
- 生命周期长(几天/几周)
- 只用于刷新接口
16.3.2基本使用
安装
pip install djangorestframework-simplejwt
# 卸载
pip uninstall djangorestframework-simplejwt

配置INSTALLED_APPS
INSTALLED_APPS = [
...
'rest_framework_simplejwt', # jwt
'rest_framework' # drf
]

默认用户体系说明
-
SimpleJWT 默认使用 Django 内置 auth_user 表
-
不需要额外创建用户表
-
只需要执行迁移即可:
makemigrations migrate

如果自定义了用户表,需要配置AUTH_USER_MODEL
AUTH_USER_MODEL = 'app01.UserInfo' # 指定以后auth的user表使用我们定义的这个表

配置url
urlpatterns = [
...
# 登录接口(签发 token)
path('login/', TokenObtainPairView.as_view()),
# 刷新 access token
path('refresh/', TokenRefreshView.as_view()),
]

16.3.3测试
写一个测试接口,配置他的认证和权限
# 登录后才能访问
class BookView(APIView):
# 认证类,只有请求头中带了token,叫Authorization,value值必须用:Bearer 开头 才校验token是否合法
# 如果没带,就不校验了,
# 配合一个权限类,实现完成的登录认证
authentication_classes = [JWTTokenUserAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
return Response('查询所有书本')

首先登录,获取token,数据库里要有测试数据
POST http://127.0.0.1:8000/api/v1/app01/login/
Body JSON
{
"username": "admin",
"password": "123456"
}

测试刷新token的接口
因为我们重新请求了refresh/接口,token被刷新了,所以请求需要认证权限的接口,需要使用这里的access
刷新 token
POST http://127.0.0.1:8000/api/v1/app01/refresh/
Body JSON
{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3OTg4OTkyMywiaWF0IjoxNzc5Mjg1MTIzLCJqdGkiOiI5NWE1YWI3NjU2ODI0NGU5YjBkOWQ1NDg5MjM5NmM2YSIsInVzZXJfaWQiOjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.9oI53Mkz9amZHFlZAfrZt-XQYZv5j6Nzgi3G7B4v5lA"
}

如果Header不带token无法访问

带上正确的token才能访问接口

16.4SimpleJWT配置文件
基本用不上看一遍即可
# region
# SIMPLE_JWT配置
# SIMPLE_JWT = {
# 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Access Token的有效期
# 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期
#
# # 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整
#
# # 是否自动刷新Refresh Token
# 'ROTATE_REFRESH_TOKENS': False,
# # 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
# 'BLACKLIST_AFTER_ROTATION': False,
# 'ALGORITHM': 'HS256', # 加密算法
# 'SIGNING_KEY': settings.SECRET_KEY, # 签名密匙,这里使用Django的SECRET_KEY
# # 如为True,则在每次使用访问令牌进行身份验证时,更新用户最后登录时间
# "UPDATE_LAST_LOGIN": False,
# # 用于验证JWT签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。
# "VERIFYING_KEY": "",
# "AUDIENCE": None, # JWT中的"Audience"声明,用于指定该JWT的预期接收者。
# "ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。
# "JSON_ENCODER": None, # 用于序列化JWT负载的JSON编码器。默认为Django的JSON编码器。
# "JWK_URL": None, # 包含公钥的URL,用于验证JWT签名。
# "LEEWAY": 0, # 允许的时钟偏差量,以秒为单位。用于在验证JWT的过期时间和生效时间时考虑时钟偏差。
# # 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer"
# "AUTH_HEADER_TYPES": ("Bearer",),
# # 包含JWT的HTTP请求头的名称。默认为"HTTP_AUTHORIZATION"
# "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
# # 用户模型中用作用户ID的字段。默认为"id"。
# "USER_ID_FIELD": "id",
# # JWT负载中包含用户ID的声明。默认为"user_id"。
# "USER_ID_CLAIM": "user_id",
#
# # 用于指定用户身份验证规则的函数或方法。默认使用Django的默认身份验证方法进行身份验证。
# "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
# # 用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"。
# "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
# # JWT负载中包含令牌类型的声明。默认为"token_type"。
# "TOKEN_TYPE_CLAIM": "token_type",
# # 用于指定可以使用的用户模型类。默认为"rest_framework_simplejwt.models.TokenUser"。
# "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
# # JWT负载中包含JWT ID的声明。默认为"jti"。
# "JTI_CLAIM": "jti",
# # 在使用滑动令牌时,JWT负载中包含刷新令牌过期时间的声明。默认为"refresh_exp"。
# "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
# # 滑动令牌的生命周期。默认为5分钟。
# "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
# # 滑动令牌可以用于刷新的时间段。默认为1天。
# "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
# # 用于生成访问令牌和刷新令牌的序列化器。
# "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
# # 用于刷新访问令牌的序列化器。默认
# "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
# # 用于验证令牌的序列化器。
# "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
# # 用于列出或撤销已失效JWT的序列化器。
# "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
# # 用于生成滑动令牌的序列化器。
# "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
# # 用于刷新滑动令牌的序列化器。
# "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
# }
# endregion

16.5定制登录返回结果
validate() → 控制接口返回数据
get_token() → 控制 JWT 内部载荷
class TokenSerializer(TokenObtainPairSerializer):
# 1 定制返回格式
def validate(self, attrs):
# 调用父类,返回之前的数据 {access,refresh}--》从之前的数据中取出access和refresh
data = super().validate(attrs)
access = data.get('access')
refresh = data.get('refresh')
# 当前登录用户 self.user中取出来
user = self.user
return {'code': 100, 'msg': '登录成功', 'data': {
'access': access,
'refresh': refresh
}}
# 2 定制payload 荷载的字段
@classmethod
def get_token(cls, user):
token = super().get_token(user)
token['user_id'] = user.id
token['username'] = user.username
return token

配置自定义jwt接口返回格式和荷载
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), # Access Token的有效期
'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh Token的有效期
# 登录成功签发token返回格式--》走咱们自己的
"TOKEN_OBTAIN_SERIALIZER": "app01.serializers.TokenSerializer",
}

成功定制返回格式

打开
https://www.jwt.io/
将里面的token进行解码,发现get_token中定义的字段成功解码
{
"token_type": "access", # 标识这个 JWT 是什么类型
"exp": 1779288770, # 过期时间
"iat": 1779285170, # issued at(签发时间)
"jti": "bdfc8d657a264ca7b4dd94bfc0a28e4b", # JWT ID(唯一标识)
"user_id": 1, # 自定义的用户id
"username": "admin" # 自定义的用户名称
}

16.6多方式登录(用户名/邮箱/手机号)
views.py
class LoginView(APIView):
def post(self, request):
'''1 都写在视图类的方法中
1 取出用户名,密码
2 去数据库校验
3 校验通过,签发token
4 不通过返回错误信息
'''
'''2 换种思路
1 实例化得到序列化类对象
2 序列化类对象调用--》is_valid---》走字段自己,局部钩子,全局钩子校验
-全局钩子中:取出用户名,密码--》去数据库校验--》校验通过,签发token
3 is_valid通过,继续往下走,取出token
4 返回登录成功信息
'''
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
refresh = serializer.context.get('refresh')
access = serializer.context.get('access')
return Response({'code': 100, 'msg': '登录成功', 'data': {'access_token': refresh, 'token_type': 'refresh'}})

serializers.py
通过传入的username使用正则验证是用户名、手机号、邮箱然后进行登录
# 多方式登录接口序列化类--->只用来做校验,不用来做序列化或反序列化
from rest_framework import serializers
from rest_framework.exceptions import ValidationError, APIException
from .models import UserInfo
import re
from rest_framework_simplejwt.tokens import RefreshToken
class LoginSerializer(serializers.ModelSerializer):
# 坑---》username--》映射过来的--》这个字段唯一
# 登录---》带着数据库有的用户名过来了--》走字段自己的校验---》unique=True--》去数据库查询数据库中是否有这个名字--》如果有
# 这个自读自己的校验规则就过不了--》因为它是unique的
# 重写这个字段,自己自己没有校验规则
username = serializers.CharField()
class Meta:
model=UserInfo
fields=['username','password'] # 只用来做校验 前端传入什么,就写什么
def validate(self, attrs):
# 完成校验和签发,并且把access和refresh放到 self.context中
username=attrs.get('username')
password=attrs.get('password')
if re.match(r'^1[3-9][0-9]{9}$', username):
# 手机登录--密码是加密的,现在password是明文--》不能直接filter(mobile=username,password=password)
user = UserInfo.objects.filter(mobile=username).first()
elif re.match(r'^.+@.+$', username): # lqz@lqz
user = UserInfo.objects.filter(email=username).first()
else:
user = UserInfo.objects.filter(username=username).first()
if user and user.check_password(password):
refresh_token=RefreshToken.for_user(user)
# 把两个token 放入到序列化类对象的context这个字典中了
self.context['refresh']=str(refresh_token)
self.context['access']=str(refresh_token.access_token)
else:
# 校验失败--》抛异常
raise ValidationError('用户名或密码错误')
return attrs

配置路由
urlpatterns = [
...
# 多方式登录
path('customlogin/', LoginView.as_view()),
path('student/', StudentView.as_view()),
]

测试
POST http://127.0.0.1:8000/api/v1/app01/customlogin/
# 用户登录
{
"username": "admin",
"password": "123456"
}
# 邮箱
{
"username": "admin@test.com",
"password": "123456"
}
# 电话
{
"username": "13800138000",
"password": "123456"
}

16.7自定义用户表-签发和认证
自定义用户表
##### 纯自定义用户表,签发和认证
class User(models.Model):
username=models.CharField(max_length=32)
password=models.CharField(max_length=32) # 密码没加密--》正常应该加密--》作业---》通过make_password进行加密
age=models.IntegerField()
gender=models.IntegerField(choices=((0,'未知'),(1,'男'),(2,'女')),default=0)

序列化层
RefreshToken.for_user(user) 会基于 user 生成 refresh token,并通过 refresh.token 生成 access token;通常在 serializer 的 validate 中返回给 attrs 或 validated_data,而不是依赖 context 传递结果,因为 context 是用于输入上下文而不是输出数据的。
from .models import User
class MyLoginSerialzier(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
user = User.objects.filter(username=username, password=password).first()
# assert user, APIException('用户名密码错误')
if not user:
raise ValidationError("用户名或密码错误")
# 签发token # 默认荷载有 user_id:id ,所以自定义用户表的用户id必须叫 id
refresh = RefreshToken.for_user(user)
self.context['refresh'] = str(refresh)
self.context['access'] = str(refresh.access_token)
return attrs

视图层
获取serializer 层生成的token
# from rest_framework.viewsets import GenericAPIView
from rest_framework.generics import GenericAPIView
from .serializers import MyLoginSerialzier
class MyLoginView(GenericAPIView):
serializer_class = MyLoginSerialzier
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
refresh = serializer.context.get('refresh')
access = serializer.context.get('access')
return Response({'code': 100, 'msg': '登录成功', 'data': {
'refresh': refresh,
'access': access
}})

从请求头提取 Bearer Token,用 SimpleJWT 校验解析出 user_id,再回表查询用户并返回认证结果。
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.exceptions import TokenError
from .models import User
from rest_framework_simplejwt.tokens import AccessToken as AuthToken
class CommonLoginAuthentication(BaseAuthentication):
def authenticate(self, request):
# 1 取出前端传入的 token串---》根据请求头:HTTP_Authorization--》Bearer ---》我们定的
token=request.META.get('HTTP_AUTHORIZATION')
if token and token.startswith('Bearer '):
token=token.split(' ')[-1]
# 2 校验token是否合法
try:
validate_token=AuthToken(token)
user=User.objects.filter(pk=validate_token.get('user_id')).first()
print('当前登录用户',user.username)
return user,token
except TokenError as e:
print('---',str(e))
raise AuthenticationFailed(str(e))
else:
raise AuthenticationFailed('token没携带或token格式不合法')

视图类 AuthorView 使用了自定义的 CommonLoginAuthentication 作为认证方式,当客户端访问 GET 接口时,DRF 会先执行该认证类解析并校验请求中的 JWT token,认证成功后才会进入 get 方法并返回“查询所有作者”,否则直接在认证阶段抛出异常并拒绝访问。
from .auth import CommonLoginAuthentication
class AuthorView(APIView):
authentication_classes = [CommonLoginAuthentication]
def get(self,request):
return Response('查询所有作者')

配置路由
urlpatterns = [
...
# 自定义登录
path('mylogin/', MyLoginView.as_view()),
path('author/', AuthorView.as_view()),
]

通过mylogin获取token
POST http://127.0.0.1:8000/api/v1/app01/mylogin/
{
"username": "peng",
"password": "123456"
}

然后带上token请求接口
GET http://127.0.0.1:8000/api/v1/app01/author/
Authorization Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc5MjkxODc0LCJpYXQiOjE3NzkyODgyNzQsImp0aSI6ImZjMzYxZWU2MGNmNzQ3MzlhMmY4NTZjYTBkOWU1NDFiIiwidXNlcl9pZCI6IjEifQ.5CsMGil9BtHBJMiUW8YEghYpwOxAvLO1o95m-gGqdn0

17.权限
17.1简介
在 Django REST Framework(DRF)中:
- 权限控制核心入口:
BasePermission - 关键方法:
has_permission(self, request, view) - 返回:
True→ 有权限False→ 无权限(403)
典型结构:
class MyPermission(BasePermission):
def has_permission(self, request, view):
return request.user.is_authenticated
17.2ACL(Access Control List)
核心思想:直接给“用户”分配“权限”
用户表
| id | name |
|---|---|
| 1 | 张三 |
| 2 | 李四 |
权限表
| id | perm |
|---|---|
| 1 | 开直播 |
| 2 | 评论 |
| 3 | 发视频 |
用户-权限关系(核心)
| user_id | permission_id |
|---|---|
| 1 | 1 |
| 1 | 2 |
表达形式:
- 张三 → [开直播, 评论]
- 李四 → [开直播]
优点:
- 颗粒度最细
- 灵活
缺点:
- 用户多时维护成本极高
- 权限爆炸
17.3RBAC(Role-Based Access Control)
核心思想:用户 → 角色 → 权限
用户表
| id | name |
|---|---|
| 1 | 张三 |
| 2 | 李四 |
角色表
| id | role |
|---|---|
| 1 | 总裁 |
| 2 | HR |
| 3 | 开发 |
权限表
| id | perm |
|---|---|
| 1 | 开会 |
| 2 | 写代码 |
| 3 | 删除代码 |
| 4 | 发工资 |
关系表:
用户-角色:
- 张三 → 总裁
- 李四 → 开发
角色-权限:
- 总裁 → [开会, 发工资]
- 开发 → [写代码, 删除代码]
优点:
- 企业级标准模型
- 易管理(按角色分配)
缺点:
- 灵活性不如 ACL
- 复杂业务会膨胀角色
17.4ABAC(Attribute-Based Access Control)
核心思想:基于“属性 + 策略”控制权限
控制维度不是简单 user/role,而是:
- 用户属性:部门、等级、年龄
- 资源属性:文档级别、归属部门
- 环境属性:时间、IP、设备
示例
只有:
部门=HR
且时间=工作时间
才能访问薪资系统
优点:
- 最灵活(工业级权限模型)
- 支持复杂策略
缺点:
- 实现复杂
- 性能开销大
17.5Django Admin权限
Django Admin 本质是 RBAC + ACL混合模型
六张核心表
-
① 用户表
-
② 组表(角色)
-
③ 权限表
-
④ 用户-组
-
⑤ 组-权限
-
⑥ 用户-权限(增强ACL)
结构图
User
├── UserGroup ── Group ── GroupPermission ── Permission
└────────────── UserPermission ───────────────┘
作用
| 模型 | 作用 |
|---|---|
| RBAC | 组织级权限管理 |
| ACL | 精细化补充权限 |
18.总结
18.1DRF 核心思想
DRF 本质:Django 基础上的 Web API 开发框架。
它帮你解决:
- API接口开发
- 请求解析
- 响应封装
- 数据校验
- 序列化
- 认证权限
- 分页过滤
- 自动路由
- 接口文档
最终目标:快速开发符合 RESTful 规范 的接口
18.2DRF 整体架构
DRF 最核心的几个部分:
客户端请求
↓
路由层 urls
↓
视图层 View/APIView/ViewSet
↓
认证 authentication
↓
权限 permission
↓
频率 throttle
↓
解析器 parser
↓
Request对象
↓
序列化 serializer
↓
Response对象
↓
渲染器 renderer
↓
返回客户端
18.3DRF 九大核心组件
| 组件 | 作用 |
|---|---|
| Serializer | 序列化/反序列化/校验 |
| Request | 封装请求 |
| Response | 封装响应 |
| APIView | 请求入口 |
| GenericAPIView | 通用视图 |
| Mixin | CRUD功能扩展 |
| ViewSet | 视图集 |
| Router | 自动路由 |
| Authentication/Permission/Throttle | 三大认证 |
18.4APIView执行流程
请求
↓
APIView.dispatch()
↓
initialize_request()
↓
新的Request对象
↓
initial()
↓
认证
↓
权限
↓
频率
↓
执行 get/post/put/delete
↓
Response
↓
finalize_response()
↓
返回响应
18.5Request对象
DRF 不再使用 Django 原生 request。而是:
from rest_framework.request import Request
包装了 Django request。
-
request.data:用于获取 POST PUT PATCH DELETE 等请求中的数据。它是 DRF 对 Django request 的再次封装。 -
request.query_params:用于获取URL ? 后面的参数。等价于 Django 的request.GET。 -
request._request:这是 DRF Request 对象。内部包装的 原生 Django HttpRequest 对象。
18.6Serializer序列化器
Serializer 三大作用:
- 1.序列化
- 2.反序列化
- 3.数据校验
18.7序列化
对象 → JSON
Serializer写法
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.IntegerField()
使用
serializer = BookSerializer(instance=book)
serializer.data
18.8many=True源码
核心:
serializer = BookSerializer(queryset, many=True)
实际上:
many=True
触发:
Serializer.__new__()
源码:
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
最终:
many=True
得到的不是:
BookSerializer对象
而是:
ListSerializer对象
内部:
child = BookSerializer()
本质:
ListSerializer(
child=BookSerializer()
)
所以:
many=True
本质就是:ListSerializer 批量调用子序列化器。
18.9反序列化
JSON → Python对象 → ORM对象
新增
serializer = BookSerializer(data=request.data)
serializer.is_valid()
serializer.save()
执行:
create()
修改
serializer = BookSerializer(
instance=book,
data=request.data
)
serializer.is_valid()
serializer.save()
执行:
update()
18.10is_valid源码
核心流程
serializer.is_valid()
↓
run_validation()
↓
to_internal_value()
↓
字段校验
↓
局部钩子
↓
全局钩子
↓
validated_data
局部钩子
def validate_name(self, value):
return value
源码反射:
getattr(
self,
'validate_' + field_name
)
全局钩子
def validate(self, attrs):
return attrs
所有字段校验完成后执行。
18.11ModelSerializer
继承关系:
ModelSerializer
↓
Serializer
↓
BaseSerializer
作用:自动生成
字段
校验
create
update
写法
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
extra_kwargs
extra_kwargs = {
'password':{
'write_only':True
}
}
18.12Serializer高级定制
source
字段改名。
username = serializers.CharField(source='name')
SerializerMethodField
最常用。
publish_name = serializers.SerializerMethodField()
def get_publish_name(self, obj):
return obj.publish.name
子序列化
多表嵌套。
class PublishSerializer(serializers.ModelSerializer):
class Meta:
model = Publish
fields = '__all__'
class BookSerializer(serializers.ModelSerializer):
publish = PublishSerializer()
class Meta:
model = Book
fields = '__all__'
18.13APIView 与 GenericAPIView
APIView
DRF最底层视图类。
特点:
- 支持认证
- 支持权限
- 支持频率
- 支持异常处理
- 支持Request/Response
GenericAPIView
在APIView基础上增加:
- queryset
- serializer_class
- get_queryset()
- get_object()
- get_serializer()
18.14Mixin
Mixin:功能扩展类。只提供功能。不处理请求。
| Mixin | 方法 |
|---|---|
| ListModelMixin | list |
| CreateModelMixin | create |
| RetrieveModelMixin | retrieve |
| UpdateModelMixin | update |
| DestroyModelMixin | destroy |
18.15九个子视图
| 类 | 功能 |
|---|---|
| ListAPIView | 查所有 |
| CreateAPIView | 新增 |
| RetrieveAPIView | 查单个 |
| UpdateAPIView | 修改 |
| DestroyAPIView | 删除 |
| ListCreateAPIView | 查所有+新增 |
| RetrieveUpdateAPIView | 查单个+修改 |
| RetrieveDestroyAPIView | 查单个+删除 |
| RetrieveUpdateDestroyAPIView | 单查+改+删 |
18.16ViewSet
ModelViewSet 直接拥有:增删改查 五个接口。
原理
GenericAPIView
+
5个Mixin
+
ViewSetMixin
18.17Router 自动路由
普通APIView
path('books/', views.BookView.as_view())
ViewSet
router = SimpleRouter()
router.register(
'books',
BookViewSet,
'books'
)
urlpatterns += router.urls
自动生成:
GET /books/
POST /books/
GET /books/1/
PUT /books/1/
DELETE /books/1/
18.18action装饰器
自定义接口。
detail=True:操作单个对象。
/books/1/publish/
detail=False:操作集合。
/books/hot/
示例
@action(methods=['GET'], detail=False)
def hot(self, request):
pass
18.19认证 Authentication
核心:谁登录了。
authenticate()
认证类必须重写:
authenticate()
返回:
(user, token)
request.user
认证成功后:
request.user
就是当前用户。
18.20权限 Permission
核心:有没有权限。
has_permission
def has_permission(self, request, view):
return True
has_object_permission
对象级权限。
18.21频率 Throttle
核心:防止恶意请求。
SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
rate = '5/m'
def get_cache_key(self, request, view):
return request.META.get('REMOTE_ADDR')
18.22过滤、搜索、排序
搜索
SearchFilter
接口:
/books/?search=红
排序
OrderingFilter
接口:
/books/?ordering=-price
DjangoFilterBackend
精准过滤。
/books/?price=99
18.23分页
PageNumberPagination
页码分页。
?page=1
LimitOffsetPagination
偏移分页。
?limit=10&offset=20
CursorPagination
游标分页。
大数据量性能最好。
18.24全局异常
DRF默认:
-
只处理 DRF异常。
-
不能统一格式。
-
所以企业必须自定义。
自定义异常函数
from rest_framework.views import exception_handler
def common_exception_handler(exc, context):
response = exception_handler(exc, context)
return Response({
'code':100,
'msg':'失败'
})
配置
REST_FRAMEWORK = {
'EXCEPTION_HANDLER':
'app.utils.common_exception_handler'
}
18.25JWT
JWT结构
header.payload.signature
登录流程
用户名密码登录
↓
服务器签发token
↓
客户端保存token
↓
以后请求带token
JWT优点
1 无状态
2 分布式友好
3 适合微服务
4 不依赖session
JWT认证流程
请求头携带token
↓
认证类解析token
↓
得到用户
↓
request.user
18.26DRF源码主线
必须抓住:
APIView.dispatch
Serializer.is_valid
Serializer.data
ModelViewSet
Router
📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!
💬 加入交流群(QQ群):576434538


浙公网安备 33010602011771号