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

image-20260502184922086

安装Django REST Framework

pip install djangorestframework

image-20260502183004318

注册应用

INSTALLED_APPS = [
    ...
    'rest_framework',
]

image-20260502185112781

实现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
        """

image-20260502185451754

实现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

image-20260502185741319

配置路由

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

image-20260502185952374

添加测试数据

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/

image-20260502190203429

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发送getpost请求

# 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'>

image-20260503183827508

浏览器访问出错:TemplateDoesNotExist at /api/v1/app01/demo/

http://127.0.0.1:8000/api/v1/app01/demo/

浏览器访问时:使用BrowsableAPIRenderer需要模版rest_framework/api.html,但是没有注册rest_framework,这里使用方案一即可

image-20260503184318288

解决方案一:注册 DRF(推荐开发环境)

INSTALLED_APPS = [
    'rest_framework',
]

解决方案2:关闭浏览器渲染(生产常用)

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer'
    ]
}

image-20260503185641282

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)

image-20260503214250078

迁移数据库

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

image-20260503214339985

添加测试数据

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 + 批量插入")

image-20260503214407887

创建序列化器

  • 定义接口输出字段
  • 定义接口输入校验规则
  • 用于:JSON ↔ Python对象转换
from rest_framework import serializers

class StudentSerializer(serializers.Serializer):
    # 写要序列化的字段--->他们是一一对应的
    id=serializers.IntegerField()
    age = serializers.IntegerField()
    name = serializers.CharField()
    school=serializers.CharField()

image-20260503214427291

创建视图

查询所有

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)

image-20260503215043351

查询单个

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)

image-20260503215102209

配置路由

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

image-20260503214602407

测试

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

image-20260503215207415

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

image-20260503221241126

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)  # 返回给前端错误信息

image-20260503221727208

测试

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

image-20260503223209099

再次查询

image-20260503223232880

如果不想输入idStudentSerializer配置一下read_only

image-20260503223438598

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

image-20260503222459211

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)

image-20260503222517203

测试

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

image-20260503223637706

再次访问

http://127.0.0.1:8000/api/v1/app01/student/12/

image-20260503223807582

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',
    }
}

image-20260503231614250

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

image-20260503233709056

测试

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

image-20260503233858811

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)

image-20260509223829955

4.8.2source指定字段不能和使用字段相同

new_school = serializers.CharField(source="new_school", read_only=True)

image-20260509221654591

改个名即可

peng_school = serializers.CharField(source="new_school", read_only=True)

image-20260509221754378

4.8.3跨表使用

StudentClassRoom是一对一关系

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

image-20260509221920437

使用source跨表取值

image-20260509223339946

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)

image-20260509224125077

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

image-20260509230038058

serializers.py

ListField校验列表格式的字段

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    publish_detail = serializers.DictField()
    author_list=serializers.ListField()

image-20260509230112260

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

image-20260509230836441

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)

image-20260509231122694

4.9.2一对多

4.9.2.1在模型中实现

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

image-20260509231353029

serializers.py

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

image-20260509231508855

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

image-20260509231930159

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)

image-20260509232145235

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 book
    
    • pop('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)

image-20260508221712812

目录 作用 里面一般有什么 为什么重要
scripts 目录 当前脚本所在目录 当前运行文件附近的 .py 文件 Python 导入时优先搜索
项目根目录 demo04 项目主目录 app01manage.py Django 导入 app 依赖它
pycharm_display PyCharm 调试辅助目录 IDE 内部工具 支持 PyCharm 控制台显示
python312.zip Python 压缩标准库 部分标准库模块 Python 可直接从 zip 导入
DLLs Python 动态库目录 .dll.pyd 文件 C 扩展模块运行依赖
Lib Python 标准库目录 osjsonre import os 等来源
django_env 虚拟环境根目录 Python 解释器相关文件 当前 Python 环境
site-packages 第三方库安装目录 djangorequests 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('代码继续')

image-20260508222941601

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]

image-20260508225018404

使用方式二: 全局配置(settings.py)

# 解析类
REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        # 'rest_framework.parsers.FormParser',
        # 'rest_framework.parsers.MultiPartParser',
    ],
}

image-20260508225105473

使用方式三: 全局配置(settings.py),局部再定制

这种就是上面俩中混用,不多做介绍了

7.3渲染类(Renderer)

决定响应返回格式

return Response({'name':'lqz'})

最终返回:

  • JSON
  • HTML 页面
  • XML

由Renderer 决定。

为什么浏览器打开 DRF 很漂亮?

因为DRF 默认BrowsableAPIRenderer,会生成可视化 API 页面

Postman 为什么返回 JSON?

因为Postman 请求头Accept: application/json

DRF 自动选择JSONRenderer

接口配置JSONRenderer,浏览器也只返回Json

image-20260509161749717

使用方式一: 局部配置

class BookView(APIView):
    ...
    renderer_classes = [JSONRenderer]

image-20260508230522196

使用方式二: 全局配置(settings.py)

REST_FRAMEWORK = {
    ...
    # =========================
    # 渲染器(Renderer)
    # 返回给前端的格式
    # =========================
    'DEFAULT_RENDERER_CLASSES': [

        # JSON 返回(生产常用)
        # 'rest_framework.renderers.JSONRenderer',

        # DRF 浏览器页面(调试用)
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}

image-20260508230624450

使用方式三: 全局配置(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)

image-20260508231010323

如果表有 20 个字段 或者 30 个字段 或者 更多字段,会非常麻烦。所以 DRF 提供 ModelSerializer

本质:Serializer 的增强版

特点:可以自动根据模型生成字段

ModelSerializer 必须写 Meta

class PublishSerializer(serializers.ModelSerializer):
    class Meta:
        model = Publish
        fields = [
            'id',
            'name',
            'addr',
            'city'
        ]

image-20260508231149804

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

image-20260509153901168

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

image-20260509182242607

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] 接口限流

常用方法

image-20260509171016856

最核心四个

方法/属性 作用
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': '删除成功'
        })

image-20260509182629728

9.3对象属性和类属性

Python属性查找:先找对象自己,对象没有,再找类属性

class Person():
    school="清华大学"

p1 = Person()
p1.school="北京大学"
print(p1.school)

p2 = Person()
print(p2.school)

image-20260509174422678

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

image-20260517231522861

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

image-20260517232815427

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'
    })),
]

image-20260517234951557

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 #加入到总路由中

image-20260518003533448

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,然后定位到实现

image-20260518230910154

找到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() 方法。

image-20260518231520727

APIView查看dispatch具体实现

image-20260518231712900

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

image-20260518232204537

15.1.5总结

DRF 请求完整生命周期

APIView
↓
dispatch
↓
认证
↓
权限
↓
频率
↓
视图方法
↓
过滤
↓
排序
↓
分页
↓
序列化
↓
Response

查看self.initial(request, *args, **kwargs)

image-20260518232808539

self.initial(request, *args, **kwargs)会依次实现:

  • 认证
  • 权限
  • 频率
# Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)

image-20260518232854754

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)

image-20260520003236817

15.2.2登录接口

根据 usernamepassword验证登录,登录成功保存一个随机 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': "用户名或密码错误"})

image-20260520003403877

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-请先登录')

image-20260520003559118

15.2.4认证配置

全局配置,这里是自定义的认证控制

REST_FRAMEWORK = {
    #  认证
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'app01.auth.LoginAuthentication'
    ]
}

image-20260520003830151

局部配置

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

image-20260520003917127

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

image-20260520004933093

先登录获取token

image-20260520005016535

book列表接口不需要认证

image-20260520005044191

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

image-20260520005122904

没有token无法请求

image-20260520005153632

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

image-20260520005648985

15.3.2配置

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

image-20260520005710502

局部配置

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

image-20260520005831370

15.3.3测试

数据库2个用户

  • user_type=1:普通用户:
  • user_type=2:管理员
  • user_type=3:超级管理员

image-20260520010420203

登录获取tomtoken

http://127.0.0.1:8000/api/v1/app01/user/login/
{
    "username":"tom",
    "password":"123"
}

image-20260520010600199

books/1不能访问

image-20260520010638387

使用pengtoken才可以访问

image-20260520010705588

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

image-20260520010849825

15.4.2配置

全局配置

REST_FRAMEWORK = {
    ...
    # 频率
    'DEFAULT_THROTTLE_CLASSES': [
        'app01.throttle.CommonThrottle'
    ]
}

image-20260520010926191

局部配置

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

image-20260520011010767

15.4.3测试

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

image-20260520011112299

books/不受频率控制

image-20260520011142373

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']

image-20260520011854163

测试

# 价格升序
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

image-20260520012003536

15.6过滤

15.6.1配置

安装依赖

pip install django-filter

# 卸载
# pip uninstall django-filter

image-20260520012534115

注册应用

INSTALLED_APPS = [
    ...
    'django_filters',
]

image-20260520012611742

全局配置

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

image-20260520012707769

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']

image-20260520013131754

image-20260520013245371

15.6.4django-filter

exact精确查询

image-20260520013537777

测试

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

image-20260520013603432

15.6.5自定义过滤

  • name__icontains:名字模糊查询,忽略大小写
  • price__gt:价格大于

image-20260520013741874

测试

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

image-20260520014219640

15.7分页

15.7.1使用

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

image-20260520014649149

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条数据

image-20260520014938134

测试

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

image-20260520014918521

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

image-20260520015429221

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,获取nextnext就是下一页地址

image-20260520015704866

再次访问就会有previous

  • next:下一页链接
  • previous:上一页链接

image-20260520015802132

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)

image-20260520204613439

配置

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

image-20260520205030643

15.8.3测试

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

image-20260520205129078

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

image-20260520205904708

开启接口文档

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

image-20260520205950444

配置地址。如果开启了权限、认证、频率接口文档需要关闭

地址 作用 核心功能 是否必须 访问效果 依赖关系
/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
    ...
]

image-20260520210053676

15.9.3测试

http://127.0.0.1:8000/api/docs/

image-20260520212745439

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("完成")

image-20260520213434285

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

image-20260520214233182

配置INSTALLED_APPS

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

image-20260520215047508

默认用户体系说明

  • SimpleJWT 默认使用 Django 内置 auth_user 表

  • 不需要额外创建用户表

  • 只需要执行迁移即可:

    makemigrations
    migrate
    

image-20260520214302434

如果自定义了用户表,需要配置AUTH_USER_MODEL

AUTH_USER_MODEL = 'app01.UserInfo'  # 指定以后auth的user表使用我们定义的这个表

image-20260520214433622

配置url

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

image-20260520214556863

16.3.3测试

写一个测试接口,配置他的认证和权限

# 登录后才能访问
class BookView(APIView):
    # 认证类,只有请求头中带了token,叫Authorization,value值必须用:Bearer 开头 才校验token是否合法
    # 如果没带,就不校验了,
    # 配合一个权限类,实现完成的登录认证
    authentication_classes = [JWTTokenUserAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response('查询所有书本')

image-20260520215416629

首先登录,获取token,数据库里要有测试数据

POST http://127.0.0.1:8000/api/v1/app01/login/
Body JSON
{
  "username": "admin",
  "password": "123456"
}

image-20260520215908356

测试刷新token的接口

因为我们重新请求了refresh/接口,token被刷新了,所以请求需要认证权限的接口,需要使用这里的access

刷新 token 
POST http://127.0.0.1:8000/api/v1/app01/refresh/
Body JSON
{
   "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc3OTg4OTkyMywiaWF0IjoxNzc5Mjg1MTIzLCJqdGkiOiI5NWE1YWI3NjU2ODI0NGU5YjBkOWQ1NDg5MjM5NmM2YSIsInVzZXJfaWQiOjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.9oI53Mkz9amZHFlZAfrZt-XQYZv5j6Nzgi3G7B4v5lA"
}

image-20260520220025550

如果Header不带token无法访问

image-20260520220204011

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

image-20260520220219978

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

image-20260520221508053

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

image-20260520220445297

配置自定义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",
}

image-20260520221302442

成功定制返回格式

image-20260520221101891

打开

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"                               # 自定义的用户名称
}

image-20260520220814106

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'}})

image-20260520221816826

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

image-20260520221829673

配置路由

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

image-20260520222203305

测试

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

image-20260520222353650

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)

image-20260520222740102

序列化层

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

image-20260520222822664

视图层

获取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
        }})

image-20260520223904268

从请求头提取 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格式不合法')

image-20260520224028982

视图类 AuthorView 使用了自定义的 CommonLoginAuthentication 作为认证方式,当客户端访问 GET 接口时,DRF 会先执行该认证类解析并校验请求中的 JWT token,认证成功后才会进入 get 方法并返回“查询所有作者”,否则直接在认证阶段抛出异常并拒绝访问。

from .auth import CommonLoginAuthentication

class AuthorView(APIView):
    authentication_classes = [CommonLoginAuthentication]
    def get(self,request):
        return Response('查询所有作者')

image-20260520224233642

配置路由

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

image-20260520224317276

通过mylogin获取token

POST http://127.0.0.1:8000/api/v1/app01/mylogin/
{
  "username": "peng",
  "password": "123456"
}

image-20260520224501853

然后带上token请求接口

GET http://127.0.0.1:8000/api/v1/app01/author/
Authorization Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc5MjkxODc0LCJpYXQiOjE3NzkyODgyNzQsImp0aSI6ImZjMzYxZWU2MGNmNzQ3MzlhMmY4NTZjYTBkOWU1NDFiIiwidXNlcl9pZCI6IjEifQ.5CsMGil9BtHBJMiUW8YEghYpwOxAvLO1o95m-gGqdn0

image-20260520224740828

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

微信打赏

posted @ 2026-06-09 01:27  peng_boke  阅读(35)  评论(0)    收藏  举报