DRF的使用

1. DRF概述

DRFdjango rest framework,是一个基于DjangoWeb API框架,专门用于构建RESTful API接口。DRF的核心特点包括:

  • 序列化:通过序列化工具,DRF能够轻松地将Django模型转换为JSON格式,也可以将JSON格式的数据反序列化为Python对象。
  • 认证与权限管理:DRF提供了多种认证方式,如Token认证、OAuth等以及权限控制,确保API的访问是安全的。
  • 视图与路由优化:DRF简化了基于类的视图,允许快速构建RESTful API端点,减少重复代码。ViewSet是它的一大特色,使得定义和处理API端点变得更加清晰。并且可以结合Router自动生成URL路由,简化API端点配置。
  • 分页与过滤:支持分页、搜索和过滤功能,可以轻松地管理大规模的数据集。
  • 强大的文档生成:DRF可以与Swagger等工具集成,自动生成API文档,帮助前端开发者和其他开发人员理解API接口。

总体来说,DRFDjango项目中的API开发更加高效、灵活、且可维护,非常适合构建前后端分离的项目 。推荐使用Django 4.2 + DRF 3.15.2进行新项目的开发。包管理工具推荐使用pip,如果涉及机器学习、数据分析等才使用conda,不建议两种混用。原因如下:

  • 🐍 官方标准包管理器:Django 官方文档、教程都基于 pip
  • 📦 丰富的第三方库:绝大多数 Django 插件、中间件都通过 PyPI 发布。
  • ⚙️ 轻量灵活:适合现代开发流程,如结合 poetrypipenv 使用。
  • 🚀 部署友好:与 requirements.txt 配合良好,便于 CI/CD 和容器化部署。

要在现有的Django 项目中集成DRF,需要安装 djangorestframework 包,如下:

cmd 复制代码
pip install django==4.2 djangorestframework==3.15.2

安装之后在settings.py中注册rest_framework,如下:

python 复制代码
INSTALLED_APPS = [
    # 其他Django应用
    'rest_framework',
]

如果想要修改drf的配置文件,只需要在settings.py中添加配置项REST_FRAMeWORK,如下:

python 复制代码
REST_FRAMEWORK = {
    # 关闭未认证用户的默认行为, 如自定义了身份认证机制[JWT、Token、Oauth2]
	"UNAUTHENTICATED_USER": None
}

2. 用户模型

Django的用户模型是用户认证系统的核心部分,用于表示网站的用户,包括用户名、密码、邮箱、权限等信息。Django默认的用户模型是django.contrib.auth.models.User模型,它继承自django.contrib.auth.models.AbstractUser,代码如下:

py 复制代码
class User(AbstractUser):
    class Meta(AbstractUser.Meta):
        # 标识这个模型是可以被交换的, 允许开发者通过AUTH_USER_MODEL指定一个自定义用户模型来替代默认的User模型
        swappable = "AUTH_USER_MODEL"

2.1 AbstractUser

AbstractUser继承自django.contrib.auth.base_user.AbstractBaseUser,有以下默认字段:该用户模型适合功能不复杂的后台管理系统,且无需扩展用户字段的项目

字段名称 类型 描述 默认值/约束条件
username CharField 唯一用户名,用于登录系统,允许字母、数字和特定符号 max_length=150,必填且唯一
password CharField 加密存储的密码,使用 PBKDF2Argon2 算法 max_length=128,必填
email EmailField 用户邮箱地址,非必填项 可选,默认空字符串
first_name CharField 用户名字段,用于存储用户的名 max_length=150,可选
last_name CharField 用户姓氏字段,用于存储用户的姓 max_length=150,可选
is_staff BooleanField 标识用户是否有权限访问 Django Admin 后台 默认False
is_active BooleanField 标识用户账号是否激活 默认True
is_superuser BooleanField 超级管理员标识,拥有所有权限 默认False
date_joined DateTimeField 用户注册时间 默认当前时间 【timezone.now
last_login DateTimeField 用户最后一次登录时间 可为空,初始值为None

2.2 自定义用户模型

由于AbstractUser的局限性,我们可以自定义用户模型,只需要继承AbstractUser 【推荐】或 AbstractBaseUser即可,如下:

py 复制代码
# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    # 扩展字段
    phone = models.CharField(max_length=11, unique=True, null=True, blank=True)
    nickname = models.CharField(max_length=20, blank=True, null=True)
    
    # 覆盖不想要的AbstractUser模型的字段
    first_name = None
    last_name = None
	
    class Meta:
        db_table = "custom_user"
        verbose_name = "用户"
        verbose_name_plural = "用户"
py 复制代码
# 指定用户模型为自定义用户模型
AUTH_USER_MODEL = 'users.CustomUser'

操作用户模型的相关API如下:

py 复制代码
from django.contrib.auth import get_user_model

# 获取当前项目正在使用的用户模型
User = get_user_model()
# 创建用户
user = User.objects.create_user(username='aaa', email='[email protected]', password='123456')
# 查询用户
user = User.objects.get(email='[email protected]')
# 检查密码
user.check_password('123456')

如果是多用户角色,有以下两种常见做法:

  • 单一用户模型 + 角色字段:该方法的局限性在于所有用户数据存储在一张表中,且由于不同角色字段的不同,会导致很多空字段。

    py 复制代码
    class User(AbstractUser):
        ROLE_CHOICES = (
            ('student', '学生'),
            ('teacher', '教师'),
            ('admin', '管理员'),
        )
        role = models.CharField(max_length=10, choices=ROLE_CHOICES)
        # 公共字段, 如手机号、头像等
        ...
        # 学生专属字段
        ...
        # 教师专属字段
        ...
  • User + OneToOne角色扩展表【推荐】:一个Django项目只能有一个全局的用户模型,因此多用户角色可以使用角色扩展表实现。该设计将不相同的字段存储在用户表中,将通用字段存储在了主表中。

    py 复制代码
    class User(AbstractUser):
        role = models.CharField(max_length=10, choices=[('student', '学生'), ('teacher', '教师'), ('admin', '管理员')])
        class Meta:
            verbose_name = '用户信息'
            verbose_name_plural = '用户信息'
            db_table = 'users'
    
    class StudentProfile(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='student_profile')
        student_id = models.CharField(max_length=20)
        grade = models.CharField(max_length=10)
    
    class TeacherProfile(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='teacher_profile')
        title = models.CharField(max_length=50)
        department = models.CharField(max_length=100)
    
    class AdminProfile(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='admin_profile')
        admin_code = models.CharField(max_length=50)
    py 复制代码
    # 创建超级管理员, 用于登录Django后台, 信息保存在users表中
    python manage.py createsuperuser
    py 复制代码
    # 创建其它用户角色
    class CreateStudentView(APIView):
        def post(self, request):
            username = request.data.get('username')
            password = request.data.get('password')
            role = request.data.get('role')
            # 创建学生用户
            user = User.objects.create_user(username=username, password=password, role=role)
            StudentProfile.objects.create(user=user, student_id="S123456", grade="2023")
    
            return Response({'message': 'ok'})
    py 复制代码
    # 将用户模型注册到管理后台中
    # users/admin.py
    # Register your models here.
    from django.contrib import admin
    from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
    from .models import User, StudentProfile, TeacherProfile, AdminProfile
    
    
    @admin.register(User)
    class UserAdmin(BaseUserAdmin):
        # 控制页面中要展示的字段
        list_display = ('username', 'email', 'role', 'is_active', 'is_staff', 'is_superuser')
        # 添加侧边栏过滤器,快速筛选数据
        list_filter = ('role', 'is_active', 'is_staff')
        # 启用后台搜索框,支持模糊查找,允许通过哪些字段查询
        search_fields = ('username', 'email')
        # 控制用户编辑页中字段的分组和顺序
        fieldsets = BaseUserAdmin.fieldsets + (
            ('角色信息', {'fields': ('role',)}),
        )
    
    
    @admin.register(StudentProfile)
    class StudentProfileAdmin(admin.ModelAdmin):
        list_display = ('user', 'student_id', 'grade')
        search_fields = ('student_id', 'user__username')
    
    
    @admin.register(TeacherProfile)
    class TeacherProfileAdmin(admin.ModelAdmin):
        list_display = ('user', 'title', 'department')
        # 注意双下划线
        search_fields = ('user__username', 'department')
    
    
    @admin.register(AdminProfile)
    class AdminProfileAdmin(admin.ModelAdmin):
        list_display = ('user', 'admin_code')
        search_fields = ('user__username', 'admin_code')

3. 认证后端

认证后端是Django中处理登录身份验证的核心机制,用来判断账户提供的用户名和密码【或其它凭证】是否合法,并返回合法的用户对象UserNone。当调用authenticate方法时会触发认证后端,如下:

py 复制代码
from django.contrib.auth import authenticate, login

user = authenticate(request, username="admin", password="123456")

# 返回成功后可以保存session
from django.contrib.auth import login
login(request, user)
# 当然也可以销毁session
logout(request)

Django会遍历AUTHENTICATION_BACKENDS 中配置的每一个认证后端,按顺序执行它们的authenticate方法,直到有一个返回User对象,否则返回None

Django中默认的认证后端是django.contrib.auth.backends.ModelBackend,它的行为是查找用户username,比对密码password。如果想要实现邮箱登录或其他登录,则需要自定义认证后端:

py 复制代码
# apps/users/backends.py
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model

User = get_user_model()

class EmailBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(email=username)  # username 实际是 email
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None
py 复制代码
# settings.py
AUTHENTICATION_BACKENDS = [
    'apps.users.backends.EmailBackend',
]
py 复制代码
# 在登录视图中调用
@api_view(['POST'])
def login_view(request):
    email = request.data.get('email')
    password = request.data.get('password')

    # 仍然传入username=email
    user = authenticate(request, username=email, password=password)
    if user:
        login(request, user)
        return Response({'msg': '登录成功'})
    return Response({'msg': '登录失败'}, status=401)

认证后端与认证组件的区别与联系如下:

项目 认证后端 认证组件
属于 Django核心 DRF
什么时候用 登录视图中调用authenticate 每个API请求识别用户
作用 识别账号 + 密码的是否合法 识别这个请求是谁发的
输入 用户名/密码或其它凭证 tokencookieJWT
举例 邮箱登录、自定义账号逻辑 自定义 tokencookieJWT
是否每次请求执行 ❌ 只在登录时用一次 ✅ 每个受保护 API 请求都会调用
是否与authenticate有关 无关

它们的配合流程如下:

py 复制代码
[ 用户请求登录 ]
      ↓
[ 调用 authenticate(username=xxx, password=xxx) ]
      ↓
[ 遍历 Authentication Backend → 返回 user ]
      ↓
[ 调用 login(request, user) → 设置 session或token ]

=========================

[ 之后每个 API 请求 ]
      ↓
[ 调用 Authentication Class → 从 token/session 中识别用户 ]
      ↓
[ 返回 (user, auth) → 自动赋值 request.user ]

✅ 正确理解整个 Token 登录流程:

阶段 你应该做的事 使用什么
登录视图中 ✅ 调用 authenticate(username=..., password=...) 验证账号密码 调用 Django 的认证后端(AUTHENTICATION_BACKENDS
登录成功后 ✅ 创建或获取 token 使用 Token.objects.get_or_create(user=user)
返回给前端 ✅ 返回 {"token": "xxx"} JsonResponse / Response 返回即可
后续请求验证 ❌ 不用你手动做,DRF 自动用 TokenAuthentication 来解析请求 它会从请求头中找 token,并识别用户

4. 认证组件

DRF中,认证组件用于验证用户的身份【你是谁 】,判断用户是否登录,确保只有经过授权的用户才能访问特定的API视图。DRF提供了多种认证类,每种认证类都实现了特定的认证逻辑。常见的认证类包括:

  • BasicAuthentication:基于HTTP基本认证,通过用户名和密码进行认证。
  • SessionAuthentication:基于Django的会话机制进行认证,适用于基于浏览器的交互。
  • TokenAuthentication:基于令牌Token进行认证,适用于前后端分离的应用。
  • JSONWebTokenAuthentication:需要安装 djangorestframework-jwt 库,基于JSON Web TokenJWT进行认证,是一种无状态的认证方式。
  • Custom Authentication:自定义认证类,有时内置的认证类无法满足特定需求,这时可以自定义认证类。DRF允许我们通过继承 BaseAuthentication 类来实现自己的认证逻辑。

认证组件必须配合权限组件一起使用,不然认证就没有意义,因为默认权限是AllowAny,表示所有人都可以访问。就好比你进入一栋写字楼,authentication是你出示身份证,确认你是谁?permission是保安根据规则判断你能不能进入大门。如果你只配置了认证,但没有规定保安不让陌生人进,那默认每个人都可以进 ------ 所以你访问不需要登录也能成功。

4.1 BasicAuthentication

BasicAuthentication 是一种简单的 HTTP 认证方式,通常用于 REST API 接口,在开发和测试环境中使用频繁,它也是Django中默认的认证组件,需配合isAuthenticated使用。客户端发送请求时,如果没有认证头时则返回401 Unauthorized响应,并且包含www-Authenticate头。当包含认证头时,则会解析认证头,提取用户民和密码,并验证其合法性。BasicAuthentication的优缺点如下:

  • 优点:简单易用,兼容性好,不依赖CookieSession,适用于无状态的API服务。适合用于开发和测试环境,但是不能用于生产环境

  • 缺点:安全性低,用户名及密码暴露在认证头中;无会话机制,每次请求都要携带账号密码,效率低且不适合长期登录;不支持登出机制,除非客户端不再发送请求头;用户体验差,需要频繁的输入用户名和密码等。

    py 复制代码
    REST_FRAMEWORK = {
        # 'UNAUTHENTICATED_USERNAME': None
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.BasicAuthentication',
        ),
        'DEFAULT_PERMISSION_CLASSES': (
            'rest_framework.permissions.IsAuthenticated',
        ),
    }
    py 复制代码
    class HelloView(APIView):
        def get(self, request):
            return Response({"message": f"Hello, {request.user.username}!"})

4.2 SessionAuthentication

SessionAuthentication 是基于 Django 自带的会话Session机制的认证方式。它的原理是:

  • 用户通过 Django 的登录视图登录成功,Django 创建一个 Session,服务器保存 session 数据,同时发给客户端一个 sessionid

  • 客户端在后续请求中自动携带 sessionidDRFSessionAuthentication 会从这个 session 中获取用户身份。

    py 复制代码
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.SessionAuthentication',
        ],
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',
        ],
    }
    py 复制代码
    # 自定义登录视图
    def custom_login_view(request):
        if request.method == "POST":
            try:
                data = json.loads(request.body)
                username = data.get("username")
                password = data.get("password")
            except Exception:
                return JsonResponse({"error": "Invalid input"}, status=400)
    
            user = authenticate(request, username=username, password=password)
            if user:
                login(request, user)  # 使用Django的session login
                return JsonResponse({"message": "Login success", "username": user.username})
            else:
                return JsonResponse({"error": "Invalid credentials"}, status=401)
        return JsonResponse({"error": "Only POST allowed"}, status=405)

    登录成功后会返会Session ID,在浏览器中,后续请求会自动携带Session ID。需要注意的是,SessionAuthentication需要依赖CookieCSRF,不适合前后端分离项目,有状态认证机制 【需在服务端存储Session】。

SessionAuthentication支持登出,如下:

py 复制代码
from django.contrib.auth import logout
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def logout_view(request):
    logout(request)  # 销毁 session
    return Response({'message': 'Logout successful'})

4.3 TokenAuthentication

TokenAuthentication是一种无状态的认证机制,其核心原理是用户登录后,服务端返回一个Token给客户端,客户端之后访问所有接口时都在请求头携带这个TokenAuthorization: Token <你的token>】,服务端通过它识别用户身份,返回给前端的token也会保存在authen_token数据表中,后续认证也就是查看数据表中是否有携带的token。其使用如下:

py 复制代码
INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework.authtoken',
]

# 迁移数据库创建token表
python manage.py migrate

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}
py 复制代码
from rest_framework.authtoken.views import obtain_auth_token

urlpatterns = [
    # 内置的登录视图只支持 username + password
    # 通过认证后端的用户会返回一个token
    path('login/', obtain_auth_token),
]

值得注意的是,使用DjanoTokenAuthentication只接受如下的Header,这也就意味着在postman中只能手动添加请求头:并且浏览器是不会自动携带Token

bash 复制代码
Authorization: Bearer <token值>

DjangoTokenAuthentication虽然使用很方便,但是有着很多局限性和缺点,因为生产环境中并不推荐使用TokenAuthentication

  • Token永不过期:一旦泄露,别人可永久使用,除非手动删除。

  • 不支持刷新:无法像JWT刷新token,有效期无法动态延长。

  • 不支持多端登录:默认用户只有一个token,不能区分Web、手机或其他设备。

  • 安全性低:Token是明文存储在数据库中的,盗用风险高,依赖HTTPS保证安全。

  • 无签名校验:服务端要查数据库,效率低且无法校验 token 真伪。

  • 不支持登出:没有标准的注销机制,只能手动删除 token 或换一个。

  • 不易扩展:不支持加自定义字段、角色、权限等高级内容。

4.4 JSONWebTokenAuthentication

JSONWebTokenAuthenticationJWT认证,是一种无状态的、签名验证的令牌认证机制,服务端签发token后,不再保存用户登录状态,客户端每次请求时携带token,服务端验证签名即可。在前后端分离、移动端或小程序场景中,JWT 是非常主流的认证方式。

py 复制代码
pip install djangorestframework-simplejwt
py 复制代码
INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework_simplejwt',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    # 每次刷新AccessToken时是否刷新RefreshToken
    'ROTATE_REFRESH_TOKENS': True,
    # 是否将旧Token加入黑名单,配合ROTATE_REFRESH_TOKENS=True使用
    'BLACKLIST_AFTER_ROTATION': True,
    
    # 自定义盐, 默认使用django的盐
    'SIGNING_KEY': 'my-very-secret-salt-used-to-sign-tokens-123456',
}
py 复制代码
from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,      # 登录
    TokenRefreshView,         # 刷新 token
    TokenVerifyView           # 校验 token 是否有效
)

urlpatterns = [
    # 通过认证后端时才返回token
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),  # 登录
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),  # 刷新
    path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),    # 验证
]

4.5 Custom Authentication

首先是需要清楚为什么需要自定义认证组件,对于上述的四种认证组件,如果对它们的默认行为不满意,比如token在请求体中、实现多端控制登录、支持企业微信、微信小程序登录等等,这时就需要使用自定义认证组件才能完成需求。

对于自定义的认证类,需要继承BaseAuthentication 类并重写authenticate方法,其中authenticate方法在认证成功时必须有返回值,其返回值为(requset.user, request.auth)

  • user :表示经过认证的用户对象,通常是 User 模型的实例,或者其他表示用户身份的对象。
  • auth :表示与认证相关的附加信息,通常是 None,除非你需要传递其他信息,如TokenAPI Key等认证数据。

对于BaseAuthentication,有以下两个方法:

  • authenticate:执行具体的认证逻辑,验证请求的合法性。若认证成功则返回(user, auth),返回None则表示允许其它认证类继续尝试,抛出异常则明确表示认证失败,终止后续认证流程。

  • authenticate_header:用于在认证失败时,为客户端返回一个 WWW - Authenticate 响应头,响应状态码为401,用于指导客户端如何重新认证。如果不重写此方法,认证失败时默认返回**403 Forbidden** 而非401 Unauthorized,可能导致前端无法正确处理认证流程。

    python 复制代码
    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    
    class MyCustomAuthentication(BaseAuthentication):
        def authenticate(self, request):
            # 模拟认证失败
            raise AuthenticationFailed('Invalid authentication credentials.')
    
        def authenticate_header(self, request):
            # 返回 WWW - Authenticate 头信息
            return 'Bearer realm="api"'
    python 复制代码
    HTTP/1.1 401 Unauthorized
    WWW - Authenticate: MyCustomAuth realm="api"
    Content - Type: application/json
    {
        "detail": "Invalid authentication credentials."
    }

下面是【局部认证 】的简单使用,一般使用CBV方式为某些视图指定认证类:

python 复制代码
from rest_framework.authentication import BaseAuthentication
from django.contrib.auth import get_user_model
from rest_framework.exceptions import AuthenticationFailed

User = get_user_model()

class CustomHeaderTokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get("HTTP_X_AUTH_TOKEN")  # 自定义header
        if not token:
            return None  # 返回 None 表示此认证组件不处理该请求

        try:
            user = User.objects.get(auth_token=token)
        except User.DoesNotExist:
            raise AuthenticationFailed("Invalid token")

        return (user, token)
py 复制代码
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'yourapp.auth.CustomHeaderTokenAuthentication',
    ]
}

如果不使用【全局认证】,就需要单独给每个视图单独指定认证类,难以管理。下面是全局认证的简单使用:

python 复制代码
REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "api.views.MyAuthentication",
    ],
}

如果使用全局认证,认证类就一定不能写在视图文件中了,其原因就是出现了循环引用。如果同时使用了全局认证和局部认证,则使用局部认证,本质上就是一个覆盖的问题。

4.6 DRF中的request

dispatch 方法是视图处理请求的核心调度器,它负责接收请求,进行一系列的初始化、权限检查、异常处理等操作,最后将响应返回给客户端。其源代码如下:

python 复制代码
def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?
    try:
        self.initial(request, *args, **kwargs)
        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        response = handler(request, *args, **kwargs)
    except Exception as exc:
        response = self.handle_exception(exc)
    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

其中request = self.initialize_request(request, *args, **kwargs)是对原始的Django请求对象进行封装,将其转换为 DRF Request对象,该对象包含了更多与RESTful API相关的属性和方法,例如请求的认证信息、解析后的请求数据等。drf中的request是对原始请求对象的封装,下面是几个常用的扩展方法:

  • request.query_params:用于获取URL中的查询参数,等同于Django原生 HttpRequest 对象的 request.GET
  • request.user:用于获取经过认证的用户对象。如果请求经过了认证,request.user 将是一个有效的用户实例,如果未认证,默认是 AnonymousUser
  • request.auth:用于获取认证后得到的认证信息,例如令牌Token。具体的值取决于使用的认证类。
  • request.is_authenticated:检查请求是否经过了认证。

4.7 多个认证类

DRF中,多个认证类的队列是非常重要的。DRF会按照在 settings.py 中配置的顺序依次尝试每个认证类,直到某个认证类成功认证为止,或者所有认证类都失败为止。每个认证类的 authenticate 方法会返回不同的结果,DRF 会根据这些结果进行相应处理:

  • 认证成功 :如果某个认证类的 authenticate 方法返回一个包含用户对象和认证信息的元组 (user, auth),则认为认证成功。此时,DRF会停止继续尝试后续的认证类,并将认证成功的用户对象和认证信息存储在 request.userrequest.auth 中,供后续的权限检查和视图处理使用。
  • 无法认证 :如果某个认证类的 authenticate 方法返回 None,表示该认证类无法对当前请求进行认证。DRF会忽略这个结果,并继续尝试下一个认证类。
  • 认证失败 :如果某个认证类的 authenticate 方法抛出 AuthenticationFailed 异常,则表示认证失败。DRF 会立即停止认证过程,并将该异常传递给异常处理机制,最终返回一个包含错误信息的响应给客户端,通常状态码为 401未授权。

如果所有配置的认证类的 authenticate 方法最终都返回 None,即没有任何一个认证类能够成功认证请求,这时候就会涉及到是否认证失败的判断,而这取决于权限类的配置:

  • 权限类允许未认证访问:若权限类允许未认证的用户访问视图,例如使用 AllowAny 权限类,即使所有认证类都返回 None,请求也不会被判定为认证失败,而是可以正常访问视图。示例代码如下:

    python 复制代码
    from rest_framework.views import APIView
    from rest_framework.permissions import AllowAny
    from rest_framework.response import Response
    
    class MyView(APIView):
        authentication_classes = [MyAuthentication, MyAuthentication2]
        permission_classes = [AllowAny]
    
        def get(self, request):
            return Response({'message': 'This view allows unauthenticated access.'})
  • 权限类要求认证:如果权限类要求用户必须经过认证才能访问视图,例如使用 IsAuthenticated 权限类,当所有认证类都返回None 时,就会被判定为认证失败。DRF会抛出 AuthenticationFailed 异常,并返回一个状态码为401的错误响应给客户端。示例代码如下:

    python 复制代码
    from rest_framework.views import APIView
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.response import Response
    
    class MyProtectedView(APIView):
        authentication_classes = [MyAuthentication, MyAuthentication2]
        permission_classes = [IsAuthenticated]
    
        def get(self, request):
            return Response({'message': 'This is a protected view.'})

总而言之,当返回(user, auth)时表示认证成功,如果所有认证类均返回None则表示认证未通过但不是认证失败,将返回匿名用户(AnonymousUser, None),如果不想匿名用户的userAnonymous,可以通过设置'UNAUTHENTICATED_USER': None。当用户端提供了无效的凭证,在认证组件中会明确的判断为认证失败即抛出AuthenticationFailed异常,DRF 会立即停止遍历其他认证组件。

5. 权限组件

DRF中,权限组件用于控制对API视图的访问,确保只有满足特定条件的用户才能访问相应的资源。DRF提供了多个内置的权限类,如下:

  • AllowAny:允许任何用户访问,不管用户是否经过认证。使用该组件时不需要配置认证组件
  • IsAuthenticated:只允许经过认证的用户访问。
  • IsAdminUser:只允许管理员用户,即 is_staffTrue 的用户访问。
  • IsAuthenticatedOrReadOnly:允许经过认证的用户进行任何操作,对于未认证的用户只允许进行只读操作,如 GET 请求。
  • 自定义权限组件:如果内置的权限类无法满足业务需求,你可以自定义权限类。自定义权限类需要继承 BasePermission 类,并实现 has_permissionhas_object_permission 方法。

对于权限组件,其配置方式和认证组件的配置方式一致。权限组件有以下注意点:

  • 权限类的顺序:当配置多个权限类时,DRF会按照它们在列表中的顺序依次检查权限。如果前面的权限类检查不通过,后面的权限类将不再检查。因此,需要合理安排权限类的顺序,通常将最宽松的权限类放在前面,最严格的放在后面。

  • 与认证组件协同工作:权限组件依赖于认证组件,只有在认证成功后才能进行权限检查。因此,需要确保在使用权限组件之前已经正确配置了认证组件。如果认证失败,权限检查将无法正常进行。

  • 异常处理:当权限检查不通过时,DRF会抛出 PermissionDenied 异常,并返回一个状态码为403禁止访问的响应。可以通过自定义异常处理函数来改变默认的错误响应,以满足特定的业务需求。

    python 复制代码
    from rest_framework.views import exception_handler
    from rest_framework.response import Response
    
    def custom_exception_handler(exc, context):
        response = exception_handler(exc, context)
        if response is not None and isinstance(exc, rest_framework.exceptions.PermissionDenied):
            response.data = {'error': 'You do not have permission to access this resource.'}
        return response
    python 复制代码
    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'your_app.utils.custom_exception_handler'
    }

对于自定义的权限组件,BasePermission中的has_permissionhas_object_permission默认返回True,表示权限通过,反之不通过。这两个方法有以下区别:

  • has_permission:决定是否允许整个视图类的访问。

    • 调用时机:在调用视图的get()post()方法之前执行。它主要针对视图级别 的权限控制,不涉及具体的对象实例。例如,当客户端发起一个请求访问某个视图时,DRF 会首先调用视图所配置的权限类的 has_permission 方法来决定是否允许该用户继续访问这个视图。
    • 应用场景:适用于对整个视图的访问进行权限控制,比如限制只有认证用户才能访问某个视图,或者只有特定角色的用户才能访问某个视图集。例如一个博客系统的文章列表视图,可能只允许已登录用户访问,就可以在 has_permission 方法中进行判断。
  • has_object_permission:针对单个对象如详情页、更新或删除操作的权限验证。

    • 调用时机:该方法在视图处理涉及具体对象的操作时被调用,前提是 has_permission 方法已经返回 True,即用户已经通过了视图级别的权限检查。has_object_permission 用于判断用户是否有权限对特定的对象实例进行操作,如对某个数据库记录进行更新或删除。

    • 应用场景:适用于对具体对象的操作进行权限控制,例如只有文章的作者才能对文章进行修改或删除操作。在这种情况下,需要根据具体的文章对象来判断用户是否有权限进行操作,就可以使用 has_object_permission 方法。

      python 复制代码
      class BasePermission(metaclass=BasePermissionMetaclass):
          """
          A base class from which all permission classes should inherit.
          """
          # 权限拒绝时的提示信息
          message = {"code": 1001, "msg": "无权访问"}
      
          def has_permission(self, request, view):
              """
              Return `True` if permission is granted, `False` otherwise.
              """
              return True
      
          def has_object_permission(self, request, view, obj):
              """
              Return `True` if permission is granted, `False` otherwise.
              """
              return True

上述两个方法中有两个参数,viewobj,它们都是由django自动传入到你的权限类方法中的,使用如下:

  • view:当前请求的视图实例,可以访问视图的 querysetserializer_classaction,以及视图类中的自定义属性。

    py 复制代码
    def has_permission(self, request, view):
        # 若视图类中定义了scope属性
        if view.scope == 'admin':
            return request.user.is_staff
        return True
  • obj:表示当前操作的具体对象的模型。

    py 复制代码
    def has_object_permission(self, request, view, obj):
        # 允许读操作,写操作需验证所有者
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.owner == request.user

5.1 单个权限类

python 复制代码
from rest_framework import permissions

class IsAuthorOrAdminOrReadOnly(permissions.BasePermission):
    """
    允许任何人进行只读操作
    只允许文章作者或管理员进行写操作
    """

    def has_permission(self, request, view):
        """
        负责视图级别的权限检查
        """
        # SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
        if request.method in permissions.SAFE_METHODS:
            return True

        # 如果是写操作如POST则要求用户必须登录
        return request.user and request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        """
        负责对象级别的权限检查
        """
        if request.method in permissions.SAFE_METHODS:
            return True

        # 如果是写操作如PUT、PATCH、DELETE则检查当前用户是否是该文章的作者或者是否是管理员
        return obj.author == request.user or request.user.is_staff
python 复制代码
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer
from .permissions import IsAuthorOrAdminOrReadOnly # 导入自定义权限类

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    # 在这里应用我们的权限类
    authentication_classes = [SessionAuthentication, TokenAuthentication]
    permission_classes = [IsAuthorOrAdminOrReadOnly]

    def perform_create(self, serializer):
        # 在创建文章时,自动将作者设置为当前登录用户
        serializer.save(author=self.request.user)

5.2 多个权限类

使用方式与单个权限类一致,需要注意的是当某个权限类未通过,之后的权限类将不再执行。但是在实际开发中,有时并不需要所有权限类都通过才能访问对应的资源,符合某一个条件就可以访问该资源。为了实现这一需求,可以使用如下:

python 复制代码
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.combinators import Or

# 满足任一权限即可
permission_classes = [Or(IsAuthenticated, IsAdminUser)]

6. 限流组件

DRF 中,限流是一个非常有用的功能,能够限制某个用户在一定时间内能发送多少次请求。限流通常用于防止恶意攻击、避免服务过载、保护 API 资源,或者简单地控制流量。限流通常是通过对请求进行计数,并且每个用户或 IP 在一定时间内只能发起有限次数的请求来实现的,例如每个用户每分钟最多能发起100个请求。DRF提供了几种常用的限流类,我们可以在 全局配置或 视图级别设置限流。

  • AnonRateThrottleAnonRateThrottle 用于限制未认证的用户的请求次数。

  • UserRateThrottleUserRateThrottle 用于限制已认证的用户的请求次数。

    py 复制代码
    # settings.py
    
    REST_FRAMEWORK = {
        # 限流类
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.UserRateThrottle',  # 针对认证用户的限流
            'rest_framework.throttling.AnonRateThrottle',  # 针对匿名用户的限流
        ],
        
        # 限流速率
        'DEFAULT_THROTTLE_RATES': {
            'user': '100/hour',  # 认证用户每小时最多 100 次请求
            'anon': '10/minute',  # 匿名用户每分钟最多 10 次请求
        },
    }
  • ScopedRateThrottle:针对不同视图或端点的限流。ScopedRateThrottle 可以为不同的视图或视图集设置不同的限流策略。

    python 复制代码
    class ContactListView(APIView):
        throttle_scope = 'contacts'  # 关联限流作用域
        ...
    
    # settings.py 配置
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_RATES': {'contacts': '50/day'}
    }
  • CustomThrottle:如果内置的限流类不满足需求,DRF允许自定义限流类。你可以继承 BaseThrottle 类来实现自定义的限流逻辑。对于BaseThrottle类,里面限流的逻辑都需要自己实现,较为麻烦。这时可以用到BaseThrottle类的子类SimpleRateThrottle

    py 复制代码
    from rest_framework.throttling import SimpleRateThrottle
    from django.core.cache import cache as default_cache
    
    class MyThrottle(SimpleRateThrottle):
        scope = 'user'
        # 配置节流
        THROTTLE_RATES = {scope: '3/m'}
        cache = default_cache
        def get_cache_key(self, request, view):
            if request.user:
                ident = request.user.pk
            else:
                # 获取用户的IP地址
                ident = self.get_ident(request)
            return self.cache_format % {
                'scope':  self.scope,
                'ident': ident
            }

DRF 限流依赖 Django 缓存系统,默认使用 LocMemCache,若需使用其他缓存如 Redis,需在 settings.py 中配置。

py 复制代码
# settings.py
CACHES = {
    "default": {  # 限流默认使用此配置
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/0",  # 使用 Redis 数据库 0
    },
    "rate_limit_cache": {  # 其他缓存配置
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",  # 使用 Redis 数据库 1
    }
}
py 复制代码
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import caches  # 导入多缓存管理器

class CustomRedisThrottle(SimpleRateThrottle):
    scope = "custom"  # 限流作用域
    cache = caches["rate_limit_cache"]
    rate = "100/day"  # 限流频率

    def get_cache_key(self, request, view):
        # 自定义逻辑生成唯一标识(如用户 ID 或 IP)
        return f"custom_{request.user.id if request.user else self.get_ident(request)}"

7. 版本控制

DRF里,版本控制是一项重要的功能,它允许你在同一个 API 端点上提供不同版本的服务,以满足不同客户端的需求,同时确保系统的兼容性和可维护性。版本控制的作用就是随着业务的发展,API 可能会发生变化。通过版本控制,旧版本的客户端可以继续使用旧版本的 API,而新版本的客户端可以使用新功能。DRF 提供了多种版本控制的实现方式,下面分别介绍:

  • 基于查询参数QueryParameterVersion:通过在 URL 的查询参数中指定版本号,例如 http://example.com/api/resource/?version=v1

    python 复制代码
    from rest_framework.versioning import QueryParameterVersioning
    class OrderView(APIView):
        authentication_classes = []
        versioning_class = QueryParameterVersioning
        def get(self, request):
            print(request.version)
            return Response('订单列表')
    python 复制代码
    # 控制版本的名称
    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',
        'ALLOWED_VERSIONS': ['v1', 'v2'],
        'VERSION_PARAM': 'version'
    }
  • 基于URL的版本控制URLPathVersioning:在 URL 路径中包含版本号,例如 http://example.com/api/v1/resource/

    python 复制代码
    REST_FRAMEWORK = {
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning'
    }
    python 复制代码
    from django.urls import path
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    class MyView(APIView):
        def get(self, request, version):
            return Response({'version': version})
    
    urlpatterns = [
        path('api/<str:version>/resource/', MyView.as_view()),
    ]
  • 基于请求头AcceptHeaderVersioning:在请求头的 Accept 字段中指定版本号,例如 Accept: application/json; version=v1

    python 复制代码
    REST_FRAMEWORK = {
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning'
    }
    python 复制代码
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    class MyView(APIView):
        def get(self, request):
            version = request.version
            return Response({'version': version})

DRF的版本控制机制里,versionversioning_scheme 是两个关键的概念,下面为你详细介绍它们的含义以及 versioning_scheme 的使用方法。

  • version :当前请求所使用的 API 版本号。在处理请求时,DRF 会根据所配置的版本控制方案从请求中提取版本信息,并将其存储在 request.version 属性中,方便在视图里获取和使用该版本号,从而依据不同的版本返回不同的响应。

    py 复制代码
    class UserView(APIView):
        def get(self, request):
            if request.version == 'v1':
                return Response({"name": "Alice"})
            elif request.version == 'v2':
                return Response({"name": "Alice", "email": "[email protected]"})
  • versioning_scheme :负责实现版本解析逻辑和反向生成 URL,反向生成url如下:

    python 复制代码
    class OrderView(APIView):
        authentication_classes = []
        versioning_class = QueryParameterVersioning
        def get(self, request):
            url = request.versioning_scheme.reverse('order-list', request=request)
            print(url)

8. 解析器

DRF中,解析器Parsers用于将传入的请求数据解析为 Python 对象,以便在视图中进行处理。DRF 提供了多种内置的解析器,其中下面的前三章解析器是drf默认的解析器:解析器仅用于格式转换,不执行任何数据验证

  • JSONParser:用于解析JSON格式的数据。这是最常用的解析器之一,适用于处理以 JSON 格式发送的 API 请求,例如前端通过AJAX发送JSON数据到后端。

  • FormParser:用于解析表单数据,通常用于处理 application/x-www-form-urlencoded 格式的数据,这种格式是传统的 HTML 表单提交数据的方式。

  • MultiPartParser:用于处理多部分表单数据,特别是包含文件上传的情况,格式为 multipart/form-data。当用户通过表单上传文件时,就需要使用这个解析器。

  • 自定义解析器:如果内置的解析器无法满足项目需求,还可以自定义解析器。自定义解析器需要继承 BaseParser 类,并实现 parse 方法。开发中使用默认的解析器足够使用。

    py 复制代码
    # 全局配置
    REST_FRAMEWORK = {
        'DEFAULT_PARSER_CLASSES': [
            'rest_framework.parsers.JSONParser',        # 解析 JSON 数据
            'rest_framework.parsers.FormParser',        # 解析表单数据x-www-form-urlencoded
            'rest_framework.parsers.MultiPartParser'     # 解析文件上传multipart/form-data
        ]
    }
    python 复制代码
    from rest_framework.versioning import QueryParameterVersioning
    class OrderView(APIView):
        parser_classes = [JSONParser, FormParser]
        def get(self, request):
            # 访问解析后的数据
            print(request.data)

9. 序列化器

DRF中,序列化Serialization是一个非常重要的概念,它主要用于将复杂的数据类型如 Django 模型实例、查询集转换为Python原生数据类型 ,以便可以轻松地将其渲染为 JSONXML 等格式的响应数据。同时,也能将传入的原始数据反序列化为 Django 模型实例或其他复杂数据类型 ,方便进行数据验证保存到数据库。

如果没有序列化器,通过model.Depart.objects.all().first()获得的模型实例是一个复杂的python对象,又或是model.Depart.objects.all()返回的一个QuerySet对象,是不能直接通过Response转换为JSON格式的,应该将其转换为python中的原生对象再传入Response,这时就需要用到序列化器。

对于创建序列化器实例时的参数问题,如下:

  • 序列化:传入instance参数,如ArticleSerializer(instance=article),通常发生在Get请求中。
  • 反序列化:传入data参数,如ArticleSerializer(data=article),通常发生在PostPutPatch请求中。通常会涉及serializer.save() 方法的使用,用于修改或保存数据,对于参数方面也有以下两种使用方式:
    • serializer = MySerializer(data=request.data):用于创建,会触发create方法。
    • serializer = MySerializer(instance=obj, data=request.data):用于更新,会触发update方法。

9.1 Serializer

Serializer :这是最基础的序列化器,需要手动定义每个字段。适用于需要自定义序列化和反序列化逻辑的复杂场景 ,如当你的数据源不涉及 Django 模型、需要高度定制序列化行为即默认行为createupdate不满足需求时才使用。

python 复制代码
# reports/serializers.py
from rest_framework import serializers
from datetime import datetime, timedelta


class EventSerializer(serializers.Serializer):
    # 字段定义
    -----------------------------------------------------------------------------------------------
    # 不涉及django模型时,序列化器字段必须与处理的数据字典的键名一致才能正确获取到值,因为Serializer不具有自动推断的功能
    -----------------------------------------------------------------------------------------------
    event_id = serializers.UUIDField(format='hex_verbose', read_only=True)  # UUID格式
    event_type = serializers.CharField(max_length=50)
    source_system = serializers.CharField(max_length=100, required=False, allow_blank=True, default="UNKNOWN")
    payload = serializers.JSONField()  # 任意JSON数据
    timestamp = serializers.DateTimeField(read_only=True)  # 表示处理时间

    # --- 字段级别验证示例 ---
    def validate_event_type(self, value):
        """
        验证事件类型是否为预定义类型之一
        """
        allowed_types = ['login', 'logout', 'purchase', 'error', 'page_view']
        if value.lower() not in allowed_types:
            raise serializers.ValidationError(
                f"无效的事件类型 允许的类型是: {', '.join(allowed_types)}"
            )
        return value

    # --- 对象级别验证示例 ---
    def validate(self, data):
        """
        验证特定事件类型下的数据规则。
        例如,如果是 'purchase' 事件,payload 必须包含 'amount' 字段。
        """
        if data['event_type'].lower() == 'purchase':
            if 'amount' not in data['payload'] or not isinstance(data['payload']['amount'], (int, float)):
                raise serializers.ValidationError(
                    {"payload": "Purchase事件的payload必须包含有效的'amount'字段"}
                )
            if data['payload']['amount'] <= 0:
                raise serializers.ValidationError(
                    {"payload": "Purchase事件的'amount'必须大于零"}
                )
        return data

    # --- create 方法示例 反序列化时使用 ---
    def create(self, validated_data):
        # 模拟生成一个事件ID
        validated_data['event_id'] = f"evt-{datetime.now().timestamp()}"
        validated_data['timestamp'] = datetime.now()

        # 模拟将事件数据发送到日志系统或消息队列
        print(f"--- 接收到并处理了新事件 ---")
        print(f"  Event Type: {validated_data['event_type']}")
        print(f"  Source: {validated_data['source_system']}")
        print(f"  Payload: {validated_data['payload']}")
        print(f"  Processed At: {validated_data['timestamp']}")
        print(f"---------------------------\n")

        # 返回处理后的数据,通常包含新生成的ID和时间戳
        return validated_data

    # --- update 方法示例 反序列化时使用 ---
    def update(self, instance, validated_data):
        """
        对于像事件日志这样的数据,通常是只创建不更新
        所以这里不实现 update 方法,或者直接抛出 NotImplementedError
        如果需要更新,例如更新一个临时事件的状态,你可以修改这里
        """
        # 简单模拟更新 instance 的部分字段
        instance['event_type'] = validated_data.get('event_type', instance['event_type'])
        instance['source_system'] = validated_data.get('source_system', instance['source_system'])
        instance['payload'] = validated_data.get('payload', instance['payload'])
        instance['timestamp'] = datetime.now()  # 更新时间戳

        print(f"--- 更新了事件 ---")
        print(f"  Event ID: {instance.get('event_id', 'N/A')}")
        print(f"  New Type: {instance['event_type']}")
        print(f"  Updated At: {instance['timestamp']}")
        print(f"-------------------\n")
        return instance
py 复制代码
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
from .serializers import EventSerializer

# 模拟一些内存中的事件数据 (实际应用中可能从数据库、缓存、文件等获取)
_mock_events_data = [
    {
        "event_id": uuid.UUID("a1b2c3d4-e5f6-7890-1234-567890abcdef"),
        "event_type": "login",
        "source_system": "web_app",
        "payload": {"user_id": 101, "ip_address": "192.168.1.1"},
        "timestamp": datetime.now() - timedelta(hours=2)
    },
    {
        "event_id": uuid.UUID("f0e9d8c7-b6a5-4321-fedc-ba9876543210"),
        "event_type": "page_view",
        "source_system": "mobile_app",
        "payload": {"page_url": "/dashboard", "user_id": 102},
        "timestamp": datetime.now() - timedelta(minutes=30)
    }
]


class EventListAPIView(APIView):
    permission_classes = [AllowAny]

    def get(self, request, format=None):
        """
        序列化操作:将Python列表转换为可供API响应的格式
        """
        serializer = EventSerializer(instance=_mock_events_data, many=True)
        # serializer.data 包含序列化后的 Python 字典或列表
        print("GET Request - Serialized data:", serializer.data) # 如{'id': 1, 'name': 'Alice', 'age': 30, 'email': '[email protected]'}
        return Response(serializer.data, status=status.HTTP_200_OK)

    def post(self, request, format=None):
        """
        反序列化操作:接收客户端数据,进行验证,并模拟处理
        """
        # 实例化序列化器,传入客户端发送的数据
        serializer = EventSerializer(data=request.data)

        # 调用is_valid()进行数据验证
        if serializer.is_valid():
            # serializer.validated_data包含了验证通过并转换后的Python字典
            print("POST Request - Validated data type:", type(serializer.validated_data))
            print("POST Request - Validated data:", serializer.validated_data)

            processed_event = serializer.save()

            # 将处理后的数据作为响应返回
            return Response(processed_event, status=status.HTTP_201_CREATED)
        else:
            # 如果验证失败,serializer.errors 包含错误信息
            print("POST Request - Validation Errors:", serializer.errors)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class EventDetailAPIView(APIView):
    permission_classes = [AllowAny]

    def get_object(self, event_id):
        # 模拟从 _mock_events_data 中查找一个事件
        for event in _mock_events_data:
            print("GET Request - Event ID:", event['event_id'])
            if event['event_id'] == event_id:
                return event
        raise status.HTTP_404_NOT_FOUND

    def get(self, request, event_id, format=None):
        print("GET Request - Event IDaaaaa:", event_id)
        """
        单个对象的序列化
        """
        try:
            event = self.get_object(event_id)
        except Exception:
            return Response({"detail": "Event not found."}, status=status.HTTP_404_NOT_FOUND)

        serializer = EventSerializer(event)  # 传入单个Python字典进行序列化
        return Response(serializer.data, status=status.HTTP_200_OK)

    def put(self, request, event_id, format=None):
        """
        单个对象的反序列化 更新操作
        """
        try:
            event = self.get_object(event_id)
        except Exception:
            return Response({"detail": "Event not found."}, status=status.HTTP_404_NOT_FOUND)

        # 实例化序列化器,传入现有实例 event 和新数据request.data
        # 传入一个instance调用的就是update而不是create
        serializer = EventSerializer(instance=event, data=request.data, partial=True)  # partial=True 允许部分更新

        if serializer.is_valid():
            # 调用 serializer.save(), 会触发 EventSerializer的update()方法
            updated_event = serializer.save()
            return Response(updated_event, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
py 复制代码
# 路由
path('events/', views.EventListAPIView.as_view(), name='event-list'),
path('events/<uuid:event_id>/', views.EventDetailAPIView.as_view(), name='event-detail'), # 使用 UUID 路径转换器

9.2 ModelSerializer

ModelSerializer:基于 Django 模型自动生成序列化器字段,大大减少了代码量,适用于大多数与模型相关的序列化需求。

python 复制代码
from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.CharField(max_length=100)
    published_date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title
python 复制代码
from rest_framework import serializers
from .models import Article

class ArticleSerializer(serializers.ModelSerializer):
    # 增强和定制模型的序列化行为,而不是覆盖模型本身
    # 时间输出格式
    published_date = models.DateTimeField(format="%Y-%m-%d %H:%M")
    
    class Meta:
        model = Article
        # fields = "__all__"
        # exclude = ['id', 'title'],用于排除指定字段
        fields = ['id', 'title', 'content', 'author', 'published_date']
python 复制代码
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Article
from .serializers import ArticleSerializer

class ArticleList(APIView):
    def get(self, request):
        # 获取所有文章实例
        articles = Article.objects.all()
        # 创建序列化器实例,many=True 表示处理QuertSet
        serializer = ArticleSerializer(instance=articles, many=True)
        return Response(serializer.data)
python 复制代码
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from .models import Article
from .serializers import ArticleSerializer

class ArticleCreate(APIView):
    def post(self, request):
        # 创建序列化器实例并传入请求数据
        serializer = ArticleSerializer(data=request.data)
        # 验证数据
        if serializer.is_valid():
            # 保存数据到数据库
            # 通过序列化器中参数决定调用create还是update方法
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

ModelSerializerSerializer中都可以自定义字段,并传入一些相关参数:意味着你可以在序列化器中定义一些不直接对应模型字段 的额外字段,或者对现有模型字段的表示方式进行修改MdoelSerializer支持自动类型推断,因此序列化器字段名和模型字段可以不一致,但是需要指定数据来源source

python 复制代码
from django.db import models


class Role(models.Model):
    title = models.CharField(verbose_name="标题", max_length=32)
    order = models.IntegerField(verbose_name="顺序")


class UserInfo(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=32)
    gender = models.SmallIntegerField(verbose_name="性别", choices=((1, "男"), (2, "女")))
    role = models.ForeignKey(verbose_name="角色", to="Role", on_delete=models.CASCADE)
    ctime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
python 复制代码
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from api import models

class InfoSerializer(serializers.ModelSerializer):
    # 调用UserInfo实例上的 get_gender_display()方法,这是Django模型choices字段自带的一个方法,用于获取选项的友好显示值
    -------------------------------------------------------------
    gender = serializers.CharField(source="get_gender_display")
    -------------------------------------------------------------
    role = serializers.CharField(source="role.title")
    ctime = serializers.DateTimeField(format="%Y-%m-%d")
    other_name = serializers.CharField(source="name")
    -------------------------------------------------------
    # 定义额外字段
    mine = serializers.SerializerMethodField()
	-------------------------------------------------------
    class Meta:
        model = models.UserInfo
        fields = ['id', 'name', 'gender', "role", 'ctime', "other_name", "mine"]
	
    # 固定搭配,以get_开头,这个方法返回的值就是 mine 字段在序列化输出中的值
    def get_mine(self, obj):
        # obj表示当前正在被序列化的模型实例
        return "x-x-{}".format(obj.name)
    
# 视图
class InfoView(APIView):
    def get(self, request):
        queryset = models.UserInfo.objects.all()
        ser = InfoSerializer(instance=queryset, many=True)
        print(type(ser.data), ser.data)
        return Response(ser.data)

9.3 序列化类的嵌套使用

DRF中,当处理包含 ForeignKeyManyToManyField 字段的 Django 模型时,我们可以使用序列化类的嵌套来完成关联表数据的序列化。下面将分别介绍基于 SerializerMethodField 自定义方法和基于嵌套的序列化类这两种实现方式。首先定义以下模型:

python 复制代码
# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Category(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    categories = models.ManyToManyField(Category, related_name='books')

    def __str__(self):
        return self.title

对于外键,也就是多对一关系,获取外键字段可以按照如下方法:

  • 获取ID:直接使用外键字段名或 _id 后缀【多对一/一对一】

    py 复制代码
    class BookSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = Book
            # fields = ['id', 'title', 'author']
            fields = ['id', 'title', 'author_id']
            # 效果一致
  • 获取关联对象的某个特定属性 如 name:使用source参数

    py 复制代码
    class BookSerializer(serializers.ModelSerializer):
    	author_name = serializers.CharField(source='author.name', read_only=True)
        class Meta:
            model = Book
            fields = ['id', 'title', 'author_name']
  • 获取关联对象的完整序列化表示:使用嵌套序列化

    py 复制代码
    # serializers.py
    from rest_framework import serializers
    from .models import Author, Category, Book
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ['id', 'name']
    
    class CategorySerializer(serializers.ModelSerializer):
        class Meta:
            model = Category
            fields = ['id', 'name']
    
    class BookSerializer(serializers.ModelSerializer):
        author = AuthorSerializer()
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'author']

对于多对多关系,获取字段可以按照如下方法:

  • 获取ID列表:直接使用字段名

    py 复制代码
    # serializers.py
    from rest_framework import serializers
    from .models import Author, Category, Book
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ['id', 'name']
    
    class CategorySerializer(serializers.ModelSerializer):
        class Meta:
            model = Category
            fields = ['id', 'name']
    
    class BookSerializer(serializers.ModelSerializer):
        class Meta:
            model = Book
            fields = ['id', 'title', 'categories']
            
    ---------------------------------------------
    {
        "id": 1,
        "title": "My Article",
        "categories": [1, 2] // 关联的Tag对象的ID列表
    }
  • 获取关联对象的某个特定属性列表:使用serializers.SlugRelatedField(many=True)

    py 复制代码
    class BookSerializer(serializers.ModelSerializer):
        categories = serializers.SlugRelatedField(
            queryset=Category.objects.all(),
            slug_field='name',
            many=True
        )
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'categories'] # 此时 'categories' 将是 name 列表
  • 获取完整关联对象列表:使用嵌套序列化

    py 复制代码
    # serializers.py
    from rest_framework import serializers
    from .models import Author, Category, Book
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ['id', 'name']
    
    class CategorySerializer(serializers.ModelSerializer):
        class Meta:
            model = Category
            fields = ['id', 'name']
    
    class BookSerializer(serializers.ModelSerializer):
        author = AuthorSerializer()
        # 注意many=True
        categories = CategorySerializer(many=True)
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'author', 'categories']

不管是多对一还是多对多,如果是想要以对象的形式获取关联对象中的部分属性,可以使用自定义方法实现:

py 复制代码
# serializers.py
from rest_framework import serializers
from .models import Author, Category, Book

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ['id', 'name']

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name']

class BookSerializer(serializers.ModelSerializer):
    author_info = serializers.SerializerMethodField()
    category_info = serializers.SerializerMethodField()

    def get_author_info(self, obj):
        author = obj.author
        return {
            'id': author.id,
            'name': author.name
        }

    def get_category_info(self, obj):
        categories = obj.categories.all()
        return [
            {
                'id': category.id,
                'name': category.name
            } for category in categories
        ]

    class Meta:
        model = Book
        fields = ['id', 'title', 'author_info', 'category_info']

9.4 序列化类的继承

DRF中,序列化类的继承是一种强大的机制,它允许你复用已有的序列化逻辑,如继承所有字段、Meta类及其属性、验证方法、create方法等 ,提高代码的可维护性和可扩展性,两个具有继承关系的序列化器,它们对应的模型不一定必须是继承关系。序列化类继承的常见使用常见如下:

python 复制代码
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
    'DEFAULT_RENDERER_CLASSES': [
        # 
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}
python 复制代码
# people/models.py
from django.db import models

class Person(models.Model):
    """
    基础人员模型
    """
    name = models.CharField(max_length=100, verbose_name="姓名")
    age = models.IntegerField(verbose_name="年龄")
    email = models.EmailField(verbose_name="邮箱", unique=True) # 邮箱唯一

    class Meta:
        verbose_name = "人员"
        verbose_name_plural = "人员"
        ordering = ['name'] # 按姓名排序

    def __str__(self):
        return self.name

class Employee(Person):
    """
    员工模型,继承自 Person
    """
    department = models.CharField(max_length=100, verbose_name="部门")
    employee_id = models.CharField(max_length=50, unique=True, verbose_name="员工编号")
    hire_date = models.DateField(verbose_name="入职日期")

    class Meta:
        verbose_name = "员工"
        verbose_name_plural = "员工"
        ordering = ['department', 'name'] # 按部门和姓名排序

    def __str__(self):
        return f"{self.name} ({self.employee_id})"
py 复制代码
# people/serializers.py
from rest_framework import serializers
from .models import Person, Employee

class PersonSerializer(serializers.ModelSerializer):
    """
    Person 模型的序列化器
    """
    # status = serializers.SerializerMethodField()
    # def get_status(self, obj):
    #     return "Active" if obj.age > 18 else "Minor"

    class Meta:
        model = Person
        # 包含所有 Person 模型的字段
        fields = ['id', 'name', 'age', 'email']
        read_only_fields = ['id'] # ID 通常是只读的

class EmployeeSerializer(PersonSerializer): # 继承 PersonSerializer
    """
    Employee 模型的序列化器,继承自 PersonSerializer
    """
    # 可以在这里添加 Employee 特有的计算字段或自定义显示
    hire_year = serializers.SerializerMethodField()

    class Meta(PersonSerializer.Meta): # 继承父类的 Meta 类
        model = Employee # 覆盖 model 属性,指向 Employee 模型
        # 继承父类的fields,并添加Employee 特有的字段
        fields = PersonSerializer.Meta.fields + ['department', 'employee_id', 'hire_date', 'hire_year']
        # 可以添加或覆盖 read_only_fields 等其他 Meta 配置
        read_only_fields = PersonSerializer.Meta.read_only_fields + ['employee_id', 'hire_year'] # 员工编号和入职年份是只读的

    def get_hire_year(self, obj):
        # 计算并返回入职年份
        if obj.hire_date:
            return obj.hire_date.year
        return None

    # 可以重写 create/update 方法来添加 Employee 特有的逻辑
    # 例如在创建 Employee 时,生成一个默认的 employee_id
    # def create(self, validated_data):
    #     if 'employee_id' not in validated_data or not validated_data['employee_id']:
    #         validated_data['employee_id'] = f"EMP-{datetime.now().strftime('%Y%m%d%H%M%S')}"
    #     return super().create(validated_data)
py 复制代码
# people/views.py
from rest_framework import generics
from rest_framework.permissions import AllowAny # 允许匿名访问,实际应用中可能需要认证
from .models import Person, Employee
from .serializers import PersonSerializer, EmployeeSerializer

class PersonListCreateAPIView(generics.ListCreateAPIView):
    """
    GET: 获取所有人员列表
    POST: 创建新人员
    """
    queryset = Person.objects.all()
    serializer_class = PersonSerializer
    permission_classes = [AllowAny]

class PersonRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
    """
    GET: 获取单个人员详情
    PUT/PATCH: 更新人员信息
    DELETE: 删除人员
    """
    queryset = Person.objects.all()
    serializer_class = PersonSerializer
    permission_classes = [AllowAny]
    lookup_field = 'pk' # 默认就是pk,可以省略

class EmployeeListCreateAPIView(generics.ListCreateAPIView):
    """
    GET: 获取所有员工列表
    POST: 创建新员工
    """
    queryset = Employee.objects.all()
    serializer_class = EmployeeSerializer
    permission_classes = [AllowAny]

class EmployeeRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
    """
    GET: 获取单个员工详情
    PUT/PATCH: 更新员工信息
    DELETE: 删除员工
    """
    queryset = Employee.objects.all()
    serializer_class = EmployeeSerializer
    permission_classes = [AllowAny]
    lookup_field = 'pk'

9.5 序列化器的校验

9.5.1 字段级别的验证

python 复制代码
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers


class InfoSerializer(serializers.Serializer):
    # 字段级别的验证
    title = serializers.CharField(required=True, max_length=20, min_length=6)
    order = serializers.IntegerField(required=False, max_value=100, min_value=10)
    level = serializers.ChoiceField(choices=[("1", "高级"), (2, "中级")])


class InfoView(APIView):
    def post(self, request):
        ser = InfoSerializer(data=request.data)
        ser.is_valid(raise_exeption=True):
        # ser.validated_data 是一个字典,包含通过验证的数据
        return Response(ser.validated_data)

除了字段自带的验证规则,还可以自定义的字段验证方法,即钩子验证 ,方法名遵循 validate_<field_name> 的命名规则。is_valid() 方法会调用这些自定义方法进行额外的验证。

python 复制代码
from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    username = serializers.CharField()

    def validate_username(self, value):
        if 'admin' in value.lower():
            raise serializers.ValidationError("用户名不能包含 'admin'")
        return value

data = {
    "username": "john_doe"
}

serializer = UserSerializer(data=data)
if serializer.is_valid():
    print("数据验证通过")
else:
    print("数据验证失败")

当任何一个字段的验证不通过时,serializer.is_valid() 方法会捕获这些错误,并采取以下行动:

  • serializer.is_valid() 返回 False

  • serializer.errors 属性被填充,如下:

    bash 复制代码
    {
        "title": [
            "此字段是必填项。", // required=True 失败
            "确保此字段的长度不大于 20。", // max_length 失败
            "确保此字段的长度不小于 6。"  // min_length 失败
        ],
        "order": [
            "确保此值不大于 100。", // max_value 失败
            "确保此值不小于 10。"   // min_value 失败
        ],
        "level": [
            ""invalid_choice"不是一个有效的选项。" // ChoiceField 失败
        ],
        "non_field_errors": [ // 如果有对象级别的验证错误
            "某个字段组合不合法。"
        ]
    }

9.5.2 对象级别的验证

可以通过定义 validate 方法对整个对象的数据进行综合验证,检查多个字段之间的关系。is_valid() 方法会调用这个方法进行验证。对象级别验证在字段验证之后进行

python 复制代码
from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    password = serializers.CharField()
    confirm_password = serializers.CharField()

    def validate(self, data):
        if data['password'] != data['confirm_password']:
            raise serializers.ValidationError("两次输入的密码不一致")
        return data

data = {
    "password": "abc123",
    "confirm_password": "abc123"
}

serializer = UserSerializer(data=data)
if serializer.is_valid():
    print("数据验证通过")
else:
    print("数据验证失败")

9.5.3 验证器的使用

可以在字段定义时使用 validators 参数传入自定义的验证器列表,is_valid() 方法会调用这些验证器进行验证。

  • 正则验证器 可借助正则表达式来校验数据是否符合特定的格式。在 DRF 里,可使用 RegexValidator 来实现正则验证。

    python 复制代码
    from rest_framework import serializers
    from django.core.validators import RegexValidator
    
    # 定义一个正则表达式,用于验证手机号码
    phone_regex = RegexValidator(
        regex=r'^1[3-9]\d{9}$',
        message="手机号码格式不正确,必须以1开头,第二位是3-9,后面跟9位数字"
    )
    
    class UserSerializer(serializers.Serializer):
        phone_number = serializers.CharField(validators=[phone_regex])
    
    data = {
        "phone_number": "13800138000"
    }
    
    serializer = UserSerializer(data=data)
    if serializer.is_valid():
        print("数据验证通过")
    else:
        print(serializer.errors)
  • EmailValidator:用于验证字符串是否为有效的电子邮件地址。

    python 复制代码
    from rest_framework import serializers
    from django.core.validators import EmailValidator
    
    class UserSerializer(serializers.Serializer):
        email = serializers.CharField(validators=[EmailValidator(message="邮箱格式错误")])
    
    data = {
        "email": "[email protected]"
    }
    
    serializer = UserSerializer(data=data)
    if serializer.is_valid():
        print("数据验证通过")
    else:
        print(serializer.errors)
  • URLValidator:用于验证字符串是否为有效的 URL 地址。

    python 复制代码
    from rest_framework import serializers
    from django.core.validators import URLValidator
    
    class ArticleSerializer(serializers.Serializer):
        link = serializers.CharField(validators=[URLValidator()])
    
    data = {
        "link": "https://www.example.com"
    }
    
    serializer = ArticleSerializer(data=data)
    if serializer.is_valid():
        print("数据验证通过")
    else:
        print(serializer.errors)
  • MinValueValidatorMaxValueValidator:分别用于验证数值字段的最小值和最大值。

    python 复制代码
    from rest_framework import serializers
    from django.core.validators import MinValueValidator, MaxValueValidator
    
    class ProductSerializer(serializers.Serializer):
        price = serializers.IntegerField(
            validators=[
                MinValueValidator(0, message="价格不能为负数"),
                MaxValueValidator(1000, message="价格不能超过 1000")
            ]
        )
    
    data = {
        "price": 500
    }
    
    serializer = ProductSerializer(data=data)
    if serializer.is_valid():
        print("数据验证通过")
    else:
        print(serializer.errors)
  • MinLengthValidatorMaxLengthValidator:分别用于验证字符串字段的最小长度和最大长度。

  • 自定义验证器:可以同时使用多个验证器

    python 复制代码
    from rest_framework import serializers
    
    def validate_email_domain(value):
        if not value.endswith('@example.com'):
            raise serializers.ValidationError("邮箱必须使用 @example.com 域名")
        return value
    
    class UserSerializer(serializers.Serializer):
        email = serializers.EmailField(validators=[validate_email_domain])
    
    data = {
        "email": "[email protected]"
    }
    
    serializer = UserSerializer(data=data)
    if serializer.is_valid():
        print("数据验证通过")
    else:
        print("数据验证失败")

9.5.4 Model验证

允许你为那些由 ModelSerializer 自动创即由模型字段推断而来的序列化器字段,指定或覆盖它们的额外参数和行为。

python 复制代码
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework import exceptions
from api import models
from django.core.validators import RegexValidator


class RoleSerializer(serializers.ModelSerializer):
    more = serializers.CharField(required=True)

    class Meta:
        model = models.Role
        fields = ["title", "order", "more"]
        extra_kwargs = {
            "title": {"validators": [RegexValidator(r"\d+", message="格式错误")]},
            "order": {"min_value": 5},
        }

    def validate_more(self, value):
        return value

    def validate(self, attrs):
        return attrs


class InfoView(APIView):
    def post(self, request):
        ser = RoleSerializer(data=request.data)
        if ser.is_valid():
            return Response(ser.validated_data)
        else:
            return Response(ser.errors)
参数名 类型 作用 示例用法
read_only 布尔值 字段仅用于序列化输出,不参与反序列化创建/更新 {'read_only': True}
write_only 布尔值 字段仅用于反序列化输入,不包含在API响应中 {'write_only': True}
required 布尔值 控制反序列化时是否必须提供字段值 {'required': False}
validators 列表 添加自定义验证逻辑 {'validators': [UniqueValidator(...)]}
error_messages 字典 自定义字段验证错误提示 {'error_messages': {'required': '必填项'}}
min_length 整数 字符串字段最小长度验证 {'min_length': 5}
max_length 整数 字符串字段最大长度验证 {'max_length': 100}
min_value 数字 数值字段最小值验证 {'min_value': 0}
max_value 数字 数值字段最大值验证 {'max_value': 100}
allow_null 布尔值 是否接受null作为有效输入 {'allow_null': True}
format 字符串 控制日期时间字段的序列化输出格式 {'format': '%Y-%m-%d %H:%M'}
input_formats 字符串列表 指定日期时间字段反序列化时的可接受输入格式 {'input_formats': ['%Y/%m/%d']}

9.5.5 ser.save()

ser.save() 方法主要用于将经过序列化器验证后的数据保存到数据库中。ser.validated_data是一个字典,包含了经过验证和清理后的数据,可以用于创建或更新数据库记录等操作。save()可以通过关键字参数的形式传入额外的数据,这些数据会和验证后的数据合并,然后用于创建或更新对象。

  • 创建新对象:RoleSerializer(data=request.data)

    python 复制代码
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework import serializers
    from .models import Role
    
    class RoleSerializer(serializers.ModelSerializer):
        class Meta:
            model = Role
            fields = '__all__'
    
    class InfoView(APIView):
        def post(self, request):
            ser = RoleSerializer(data=request.data)
            if ser.is_valid():
                # 创建新的 Role 对象并保存到数据库
                new_role = ser.save()
                return Response(ser.data)
            return Response(ser.errors)
  • 更新现有对象:RoleSerializer(instance=role, data=request.data)

    python 复制代码
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework import serializers
    from .models import Role
    
    class RoleSerializer(serializers.ModelSerializer):
        class Meta:
            model = Role
            fields = '__all__'
    
    class InfoView(APIView):
        def put(self, request, pk):
            try:
                role = Role.objects.get(pk=pk)
            except Role.DoesNotExist:
                return Response(status=404)
    
            ser = RoleSerializer(role, data=request.data)
            if ser.is_valid():
                # 更新现有的 Role 对象并保存到数据库
                updated_role = ser.save()
                return Response(ser.data)
            return Response(ser.errors)
  • 自定义保存逻辑

    python 复制代码
    from rest_framework import serializers
    from .models import Role
    
    class RoleSerializer(serializers.ModelSerializer):
        class Meta:
            model = Role
            fields = '__all__'
    
        def create(self, validated_data):
            # 自定义创建逻辑
            role = Role.objects.create(**validated_data)
            # 可以在这里添加额外的操作,比如记录日志等
            return role
    
        def update(self, instance, validated_data):
            # 自定义更新逻辑
            for attr, value in validated_data.items():
                setattr(instance, attr, value)
            instance.save()
            # 可以在这里添加额外的操作,比如发送通知等
            return instance

当使用 serializer.save() 方法来保存新对象或更新现有对象时,其背后的原理是它会尝试将 serializer.validated_data 中的键值对映射到模型实例的字段上。如果 validated_data 中的键与模型字段无法匹配,或者缺少必要的字段,就可能导致错误。

  • 序列化器接收的参数多于 模型字段:在使用save前使用 pop()移除多余的字段。

    py 复制代码
    validated_data.pop('category_name', None)
    ser.save()
  • 序列化器接收的参数少于 模型必需的字段:通过 **kwargs 传递额外参数。

    py 复制代码
    serializer.save(last_modified_by=self.request.user, ...)

对于FK,其默认验证规则是外键的表的主键值是否存在,存在即通过不存在即不通过。如果不满足需求,可以自定义验证钩子,如下:

py 复制代码
# myapp/serializers.py
from rest_framework import serializers
from .models import UserInfo, Department

class UserInfoSerializer(serializers.ModelSerializer):
    # 隐式,自动关联到外键表的主键
    # depart = serializers.PrimaryKeyRelatedField(queryset=Department.objects.all())

    class Meta:
        model = UserInfo
        fields = ['id', 'name', 'age', 'depart']
    
    def validate_depaer(self, value):
        # 这里的value是一个depart对象
        if value.id > 1:
            return value
        raise exceptions.ValidationError("部门错误")

对于多对多关系的字段,与外键【多对一】有所不同,如下:

py 复制代码
# myapp/models.py
from django.db import models

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    tags = models.ManyToManyField(Tag, related_name='articles') # 多对多字段

    def __str__(self):
        return self.title
py 复制代码
# myapp/serializers.py
from rest_framework import serializers
from .models import Article, Tag

class ArticleSerializer(serializers.ModelSerializer):
    # tags 字段会自动被 ModelSerializer 映射为 PrimaryKeyRelatedField(many=True)

    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'tags']
py 复制代码
# 有效请求
{
    "title": "学习DRF",
    "content": "这是一篇关于DRF的文章。",
    "tags": [1, 2]  // 存在 Tag ID 为 1 和 2
}
py 复制代码
# 默认也是查看id是存在,也可以自定义验证规则
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'tags']

    def validate_tags(self, values): # values 是 Tag 实例的列表,而不是ID列表
        if len(values) > 5:
            raise serializers.ValidationError("文章标签不能超过5个")

        # 检查是否有禁用标签
        disabled_tag_names = ["禁止标签"]
        for tag in values:
            if tag.name in disabled_tag_names:
                raise serializers.ValidationError(f"'{tag.name}' 标签已被禁用")

        return values

10. 分页组件

DRF中,分页是处理大量数据时非常重要的功能,它允许将数据分成多个页面进行展示,避免一次性返回过多数据,从而提高系统性能和用户体验。分页的核心目的是将数据集分成多个小块,每个小块称为一页,客户端可以通过指定页码或偏移量来获取特定页面的数据。DRF 中的分页器负责处理分页逻辑,包括计算总页数、当前页的数据范围等。常见的分页器如下:

  • PageNumberPagination:这是最常用的分页方式,通过页码来访问不同页面的数据。客户端可以通过 page 参数指定要访问的页码。响应会包含总记录数、当前页码、下一页和上一页的链接,查询参数通常是 ?page=X?page_size=Y

    • 全局配置:只要视图继承了GenericAPIViewViewSet,就会自动使用全局配置中的分页器。APIView不会自动执行全局配置的分页器,但是可以按照如下使用内置的分页器

      py 复制代码
      # settings.py
      # 全局配置
      REST_FRAMEWORK = {
          'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
          'PAGE_SIZE': 10  # 每页显示 10 条数据
      }
      py 复制代码
      # 局部配置
      # views.py
      from rest_framework import generics
      from.models import Book
      from.serializers import BookSerializer
      
      class BookList(generics.ListAPIView):
          queryset = Book.objects.all()
          from rest_framework.pagination import PageNumberPagination
          # 分页处理
          pager = PageNumberPagination()
          result = pager.paginate_queryset(queryset, request, view.self)
          
          # 序列化
          ser = BookSerializer(instance=result, many=True)
          response = pager.get_paginated_response(ser.data)
    • 自定义:自定义PageNumberPagination可以修改一些相关的配置项:

      py 复制代码
          class PageNumberPagination(BasePagination):
              page_size = api_settings.PAGE_SIZE
          	# 用于指定页码的查询参数名称
              page_query_param = 'page'
              page_query_description = _('A page number within the paginated result set.')
          	# 设置为None时客户端不能通过查询参数指定page_size, 设置为字符串时表示通过该字符串设置page_size
              page_size_query_param = None
              page_size_query_description = _('Number of results to return per page.')
          	# 最大每页记录数量
              max_page_size = None
              # 客户端请求最后一页时可以使用的字符串,?page=last
              last_page_strings = ('last',)
              template = 'rest_framework/pagination/numbers.html'
              invalid_page_message = _('Invalid page.')
    • 局部配置:对于generics.ListAPIView / ViewSet,局部配置指定pagination_class属性即可。对于APIView使用较为繁琐,因为也不建议分页数据通过APIView返回。对于generics.ListAPIView / ViewSet,还是跟之前一样使用Response返回数据即可。

      py 复制代码
      # myapp/views.py
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination
      
      from .models import Product
      from .serializers import ProductSerializer
      
      class ProductListAPIView(APIView):
          pagination_class = ProductPageNumberPagination # 使用自定义分页器
      
          def get(self, request, *args, **kwargs):
              queryset = Product.objects.all().order_by('created_at')
              paginator = self.pagination_class()
      
              # 对查询集进行分页
              page_data = paginator.paginate_queryset(queryset, request, view=self)
      
              # 序列化分页后的数据
              serializer = ProductSerializer(page_data, many=True)
      
              # 4. 返回分页响应
              return paginator.get_paginated_response(serializer.data)
      
          # 访问示例: /products-apiview/?page=2&page_size=10
  • LimitOffsetPagination:这种分页方式通过 limitoffset 参数来控制数据的显示。limit 表示每页显示的数据数量,offset 表示从数据集的哪个位置开始获取数据。客户端可以通过 ?limit=20&offset=10 这样的参数来获取从第 10 条数据开始的 20 条数据。使用方法与PageNumberPagination一致。

    python 复制代码
    class LimitOffsetPagination(BasePagination):
        """
        A limit/offset based style. For example:
    
        http://api.example.org/accounts/?limit=100
        http://api.example.org/accounts/?offset=400&limit=100
        """
        default_limit = api_settings.PAGE_SIZE
        limit_query_param = 'limit'
        limit_query_description = _('Number of results to return per page.')
        offset_query_param = 'offset'
        offset_query_description = _('The initial index from which to return the results.')
        max_limit = None
        template = 'rest_framework/pagination/numbers.html'
  • CursorPaginationCursorPagination 是一种基于游标的分页方式,它使用一个加密的游标来标记当前页面的位置,适用于处理大量数据和需要高效分页的场景。这种分页方式不支持任意页码的访问,只能依次向前或向后翻页。客户端可以通过响应中返回的 nextprevious 链接来进行翻页操作。

    python 复制代码
    # settings.py
    REST_FRAMEWORK = {
        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
        'PAGE_SIZE': 10  # 每页显示 10 条数据
    }
    
    # views.py
    from rest_framework import generics
    from.models import Book
    from.serializers import BookSerializer
    
    class BookList(generics.ListAPIView):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        ordering = '-id'  # 必须指定排序字段

10. 视图

DRF里,视图是处理客户端请求并返回响应的核心组件。DRF 提供了多种类型的视图,能满足不同场景下的开发需求。

10.1. 基于函数的视图

基于函数的视图是最基础的视图类型,本质上就是普通的 Python 函数,接收 request 对象作为参数,并返回 Response 对象。

python 复制代码
from rest_framework.decorators import api_view
from rest_framework.response import Response
from.models import Book
from.serializers import BookSerializer

@api_view(['GET', 'POST'])
def book_list(request):
    if request.method == 'GET':
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)

10.2 基于类的视图

APIViewAPIViewDRF 中最基础的基于类的视图,需要手动实现各个 HTTP 方法的处理逻辑。只提供认证、权限、限流、内容协商等核心 DRF 功能,不处理模型和查询集。在开发中,对于视图的选择,可以按照如下规则:

  • 优先使用 ModelViewSetReadOnlyModelViewSet:它们是 DRF 中最推荐的方式,能以最少的代码提供最丰富的功能,并且与路由器完美配合。
  • 其次考虑通用视图 :如果你只需要一个特定操作的 API,例如,只列出不创建,或者不想使用 ViewSet 的全部功能,它们提供了一个很好的折衷。
  • 最后才考虑 APIView :只有当你的需求非常特殊,不符合任何标准 RESTful 模式,或者需要完全手写 HTTP 方法处理逻辑时,才使用 APIView

10.2.1 GenericAPIView

GenericAPIViewDRF 中一个非常重要的基类,它扩展了最基本的 APIView,并为构建 RESTful API常见模式 提供了许多开箱即用的功能和属性。它被称为通用视图 是因为它抽象了许多与模型相关的、通用的 API 行为。

APIView 的基础上,增加了与模型、查询集、序列化器、分页、过滤 相关的通用行为和方法。它本身不提供 HTTP 方法如 get, post的实现,通常需要与 DRF 提供的 Mixins 结合使用。其核心属性如下:

  • queryset:用于从此视图返回对象的默认查询集。它是你的 API 要操作的数据的基础。如queryset = Product.objects.all()
  • serializer_class:用于验证和反序列化输入数据,以及序列化输出数据的序列化器类。如 serializer_class = ProductSerializer
  • lookup_field:用于获取单个模型实例时,模型实例上用于查找的字段名称。默认值是主键 ,其它需设置如 lookup_field = 'slug' 【如果你的 URL 中通过 slug 查找对象】,当你的 URL pattern 使用了 lookup_field 以外的名称例如 /products/<str:product_slug>/,你还需要设置 Workspace_kwarg
  • Workspace_kwargURLconf中用于查找的关键字参数名称,默认值与 lookup_field 的值相同,如果你的 URLpath('products/<slug:item_slug>/', ProductDetail.as_view())lookup_field = 'slug',你就需要设置 Workspace_kwarg = 'item_slug'

其核心方法如下:这些方法是GenericAPIView的对APIView中处理查询结果、序列化、分页的方法的封装,包括但不限于以下方法。

  • get_queryset(self) :返回视图应该操作的查询集。当你需要根据请求如 request.user、URL 参数动态地调整查询集时,应该重写此方法而不是直接设置 queryset 属性,则需要重写

    py 复制代码
    def get_queryset(self):
        user = self.request.user
        return Product.objects.filter(owner=user).order_by('-created_at')
  • get_serializer_class(self) :返回视图应该使用的序列化器类。 当你需要根据请求的类型【GET vs. POST】、用户角色或其他条件,动态地选择不同的序列化器时,则需要重写

    py 复制代码
    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductReadOnlySerializer
        return ProductWriteSerializer
  • get_serializer(self, *args, **kwargs) :返回序列化器实例。这是实际创建序列化器对象的方法。在视图方法内部需要一个序列化器实例时调用,如 serializer = self.get_serializer(instance=obj)

GenericAPIView 本身不实现任何 HTTP 方法如 get, post, put 等。它需要与 DRF 提供的 Mixins 结合使用才能提供具体的行为。如下:

  • ListModelMixin :提供 list() 方法来获取一个对象列表。

  • CreateModelMixin :提供 create() 方法来创建一个新对象。

  • RetrieveModelMixin :提供 retrieve() 方法来获取单个对象。

  • UpdateModelMixin :提供 update() 方法来更新单个对象。

  • DestroyModelMixin :提供 destroy() 方法来删除单个对象。

    py 复制代码
    # myapp/views.py
    from rest_framework import generics, mixins
    from .models import Product
    from .serializers import ProductSerializer
    from rest_framework.permissions import IsAuthenticated
    
    class ProductListCreateAPIView(
        mixins.ListModelMixin,       # 提供 list() 方法
        mixins.CreateModelMixin,      # 提供 create() 方法
        generics.GenericAPIView       # 提供 get_queryset, get_serializer_class, pagination_class 等核心功能
    ):
        queryset = Product.objects.all().order_by('created_at')
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticated] # 示例权限设置
        pagination_class = generics.PageNumberPagination # 示例分页设置
    
        def get(self, request, *args, **kwargs):
            # 调用 ListModelMixin 提供的 list 方法
            return self.list(request, *args, **kwargs)
    
        def post(self, request, *args, **kwargs):
            # 调用 CreateModelMixin 提供的 create 方法
            return self.create(request, *args, **kwargs)

DRF 的实际开发中,**直接使用 GenericAPIView 的情况相对较少,但它是一个非常重要的基石,理解它能帮助你更好地理解 DRF 的工作原理,并在需要高度定制化时使用它。**通常情况下,开发者会优先选择使用:

  • 具体通用视图:这是最常见和推荐的方式,例如 ListAPIViewCreateAPIViewRetrieveAPIView,、UpdateAPIView,、DestroyAPIView, ListCreateAPIViewRetrieveUpdateDestroyAPIView 等。其实它们就是它们是 GenericAPIView 和不同 Mixins 的预组合,旨在快速构建标准的 RESTful API 端点。
  • ModelViewSetReadOnlyModelViewSet 对于资源集合的操作,ViewSets 是一个更强大的抽象,可以将多个视图逻辑封装在一个类中,配合 Routers 使用可以大大简化 URL 配置。它们进一步封装了 GenericAPIViewMixins,提供了完整的 CRUD 行为,并能与 DRF 的路由系统无缝集成

10.2.2 具体通用视图

  • ListAPIViewGenericAPIView + ListModelMixin,供只读的列表 视图。处理 GET 请求,返回一个资源集合。当你需要获取某个资源的所有实例列表时使用。GET /products/

    py 复制代码
    # views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    from rest_framework.permissions import IsAuthenticatedOrReadOnly # 导入权限类
    
    class ProductListView(generics.ListAPIView):
        queryset = Product.objects.all().order_by('name') # 列表视图需要排序
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticatedOrReadOnly] # 允许认证用户和匿名用户查看
        # pagination_class = PageNumberPagination # 如果需要分页,在这里或settings.py配置
  • CreateAPIViewGenericAPIView + CreateModelMixin,提供只写的创建 视图。处理 POST 请求,用于创建一个新资源。当你需要创建某个资源的单个实例时时使用。POST /products/

    py 复制代码
    # views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    from rest_framework.permissions import IsAuthenticated # 导入权限类
    
    class ProductCreateView(generics.CreateAPIView):
        queryset = Product.objects.all() # queryset 仍然需要,即使 CreateAPIView 不直接查询它
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticated] # 只有认证用户才能创建
  • RetrieveAPIViewGenericAPIView + RetrieveModelMixin, 提供只读的详情 视图。处理 GET 请求,返回单个资源实例。当你需要获取某个资源的单个特定实例详情时使用。``GET /products/1/`。

    py 复制代码
    # views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    from rest_framework.permissions import IsAuthenticatedOrReadOnly
    
    class ProductDetailView(generics.RetrieveAPIView):
        queryset = Product.objects.all()
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticatedOrReadOnly]
        # lookup_field = 'slug' # 如果你通过 slug 查找,可以设置
        # lookup_url_kwarg = 'product_slug' # 如果 URL 参数名不同于 lookup_field
  • UpdateAPIViewGenericAPIView + UpdateModelMixin,提供只写的更新 视图。处理 PUTPATCH 请求,用于更新单个资源实例。当你需要修改某个资源的单个特定实例时使用。 PUT /products/{id}/PATCH /products/{id}/

    py 复制代码
    # views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    from rest_framework.permissions import IsAuthenticated
    
    class ProductUpdateView(generics.UpdateAPIView):
        queryset = Product.objects.all()
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticated]
  • DestroyAPIViewGenericAPIView + DestroyModelMixin,提供只写的删除 视图。处理 DELETE 请求,用于删除单个资源实例。当你需要删除某个资源的单个特定实例时使用。DELETE /products/{id}/

    py 复制代码
    # views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    from rest_framework.permissions import IsAuthenticated
    
    class ProductDestroyView(generics.DestroyAPIView):
        queryset = Product.objects.all()
        serializer_class = ProductSerializer # serializer_class 仍然需要,尽管不用于序列化输出
        permission_classes = [IsAuthenticated]
  • ListCreateAPIViewGenericAPIView + ListModelMixin + CreateModelMixin,提供列表和创建功能。处理 GET 列表 和 POST 创建 请求。

    py 复制代码
    # views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    from rest_framework.permissions import IsAuthenticatedOrReadOnly
    
    class ProductListCreateAPIView(generics.ListCreateAPIView):
        queryset = Product.objects.all().order_by('created_at')
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticatedOrReadOnly]
        # pagination_class = PageNumberPagination # 列表部分会分页
  • RetrieveUpdateDestroyAPIViewGenericAPIView + RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin, 提供详情、更新和删除功能。

    py 复制代码
    # views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    from rest_framework.permissions import IsAuthenticatedOrReadOnly
    
    class ProductRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
        queryset = Product.objects.all()
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticatedOrReadOnly]

10.2.3 视图集

  • ModelViewSetModelViewSet 提供了完整的CRUD功能 ,是最常用的 ViewSet 类型。五种mixin + GenericAPIView

    py 复制代码
    # myapp/models.py
    from django.db import models
    from django.contrib.auth.models import User
    
    class Product(models.Model):
        name = models.CharField(max_length=100)
        description = models.TextField(blank=True)
        price = models.DecimalField(max_digits=10, decimal_places=2)
        owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='products')
        created_at = models.DateTimeField(auto_now_add=True)
    
        def __str__(self):
            return self.name
    
    # myapp/serializers.py
    from rest_framework import serializers
    from .models import Product
    from django.contrib.auth.models import User # 假设User模型存在
    
    class UserSerializer(serializers.ModelSerializer):
        class Meta:
            model = User
            fields = ['id', 'username']
    
    class ProductSerializer(serializers.ModelSerializer):
        owner = UserSerializer(read_only=True) # 嵌套序列化,只读显示owner信息
    
        class Meta:
            model = Product
            fields = ['id', 'name', 'description', 'price', 'owner', 'created_at']
            read_only_fields = ['owner', 'created_at'] # owner和created_at自动设置
    
        def create(self, validated_data):
            # 在创建时,将当前请求用户设置为owner
            validated_data['owner'] = self.context['request'].user
            return Product.objects.create(**validated_data)
    
    
    # myapp/views.py
    from rest_framework import viewsets
    from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated
    from .models import Product
    from .serializers import ProductSerializer
    
    class ProductViewSet(viewsets.ModelViewSet):
        """
        一个用于查看、编辑、创建或删除产品的视图集
        """
        queryset = Product.objects.all().order_by('-created_at') # 列表通常需要排序
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticatedOrReadOnly] # 允许认证用户写,匿名用户读
    
        # 如果需要,可以重写 get_queryset 来根据用户过滤
        # def get_queryset(self):
        #     user = self.request.user
        #     if user.is_authenticated:
        #         return Product.objects.filter(owner=user).order_by('-created_at')
        #     return Product.objects.none() # 匿名用户不允许看任何产品
    
        # 可以添加自定义动作 使用@action装饰器
        # from rest_framework.decorators import action
        # from rest_framework.response import Response
        # @action(detail=True, methods=['post']) # detail=True 表示作用于单个实例
        # def mark_as_sold(self, request, pk=None):
        #     product = self.get_object()
        #     product.status = 'sold'
        #     product.save()
        #     return Response({'status': 'product marked as sold'})
  • ReadOnlyModelViewSetmixins.ListModelMixin + mixins.RetrieveModelMixin + GenericViewSet,提供了只读功能

    py 复制代码
    # myapp/views.py (继续上面的示例)
    from rest_framework import viewsets
    from rest_framework.permissions import AllowAny # 导入权限类
    
    class ReadOnlyProductViewSet(viewsets.ReadOnlyModelViewSet):
        """
        一个只读的,用于查看产品列表和详情的视图集。
        """
        queryset = Product.objects.all().order_by('name')
        serializer_class = ProductSerializer
        permission_classes = [AllowAny] # 允许任何人访问

当定义好视图集后,需要配置路由,如下:

py 复制代码
urlpatterns = [
    path('admin/', admin.site.urls),

    # 定义资源列表和创建的路由
    # GET 请求映射到 list 动作
    # POST 请求映射到 create 动作
    path('api/products/', ProductViewSet.as_view({
        'get': 'list',
        'post': 'create'
    }), name='product-list-create'),

    # 定义单个资源详情、更新和删除的路由
    # GET 请求映射到 retrieve 动作
    # PUT 请求映射到 update 动作
    # PATCH 请求映射到 partial_update 动作
    # DELETE 请求映射到 destroy 动作
    path('api/products/<int:pk>/', ProductViewSet.as_view({
        'get': 'retrieve',
        'put': 'update',
        'patch': 'partial_update',
        'delete': 'destroy'
    }), name='product-detail-update-destroy'),

    # 如果你有自定义动作,也需要手动映射
    # ProductViewSet有一个@action(detail=False, methods=['get']) def recent(self, request):
    # path('api/products/recent/', ProductViewSet.as_view({
    #     'get': 'recent'
    # }), name='product-recent'),
]

10.2.4 路由器

当使用视图集后,路由配置也是较为繁琐的,这时可以使用路由器,DRF中提供了两种路由器:

  • DefaultRouter:推荐使用,自动为你的 ViewSet 生成所有标准的 CRUD URL

  • SimpleRouterDefaultRouter 的简化版本,

    py 复制代码
    # myapp/models.py
    from django.db import models
    
    class Product(models.Model):
        name = models.CharField(max_length=100)
        price = models.DecimalField(max_digits=10, decimal_places=2)
        def __str__(self): return self.name
    
    # myapp/serializers.py
    from rest_framework import serializers
    from .models import Product
    
    class ProductSerializer(serializers.ModelSerializer):
        class Meta:
            model = Product
            fields = '__all__'
    
    # myapp/views.py
    from rest_framework import viewsets
    from rest_framework.permissions import IsAuthenticatedOrReadOnly
    from .models import Product
    from .serializers import ProductSerializer
    
    class ProductViewSet(viewsets.ModelViewSet):
        queryset = Product.objects.all().order_by('name')
        serializer_class = ProductSerializer
        permission_classes = [IsAuthenticatedOrReadOnly]
    
    # 你也可以有 ReadOnlyModelViewSet 或者自定义的 GenericViewSet
    # class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
    #     queryset = Category.objects.all()
    #     serializer_class = CategorySerializer
    py 复制代码
    # myproject/urls.py
    from django.contrib import admin
    from django.urls import path, include
    from rest_framework.routers import DefaultRouter, SimpleRouter # 导入路由器
    
    # 导入你的 ViewSets
    from myapp.views import ProductViewSet # CategoryViewSet
    
    # 创建路由器实例
    # router = SimpleRouter() 
    router = DefaultRouter()
    
    # 注册你的 ViewSets
    # router.register(r'URL前缀', ViewSet类, basename='可选,建议设置')
    # basename 用于生成URL名称,例如 'product-list', 'product-detail'
    # 如果 ViewSet 没有定义 queryset 属性,basename 必须显式设置
    # 如果 ViewSet 定义了 queryset 属性,DRF 可以从 queryset.model._meta.model_name 推断出 basename
    
    router.register(r'products', ProductViewSet, basename='product')
    # router.register(r'categories', CategoryViewSet, basename='category')
    
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        # 将路由器生成的 URL 模式包含到你的 URL 配置中
        path('api/', include(router.urls)), # 所有的 ViewSet URL 都会在 /api/ 下
    ]
    URL 模式 对应动作 HTTP 方法 说明
    /api/products/ list() GET 获取全部产品列表
    /api/products/ create() POST 创建新产品
    /api/products/{pk}/ retrieve() GET 获取单个产品详情
    /api/products/{pk}/ update() PUT 全量更新产品信息
    /api/products/{pk}/ partial_update() PATCH 部分更新产品信息
    /api/products/{pk}/ destroy() DELETE 删除指定产品
    /api/products/{action_name}/detail=False action_name 自定义 自定义动作
    /api/products/{pk}/{action_name}/detail=True action_name 自定义 自定义动作

10.2.5 筛选器

DRF中,筛选器Filters 允许你的 API 用户通过 URL 查询参数来细化他们请求的数据集。这对于构建灵活和功能强大的 API 至关重要,因为你不太可能总是想返回所有数据。DRF 提供了多种方式来实现数据筛选:

  • 基于 Django ORM 的简单查询,纯手动

    py 复制代码
    # myapp/views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    
    class ProductListManualFilterView(generics.ListAPIView):
        serializer_class = ProductSerializer
    
        def get_queryset(self):
            # 总是先获取完整的 queryset
            queryset = Product.objects.all()
    
            # 根据查询参数进行过滤
            name = self.request.query_params.get('name')
            price = self.request.query_params.get('price')
            min_price = self.request.query_params.get('min_price')
            max_price = self.request.query_params.get('max_price')
    
            if name:
                # 模糊查询,不区分大小写
                queryset = queryset.filter(name__icontains=name)
            if price:
                # 精确查询
                queryset = queryset.filter(price=price)
            if min_price:
                # 大于等于查询
                queryset = queryset.filter(price__gte=min_price)
            if max_price:
                # 小于等于查询
                queryset = queryset.filter(price__lte=max_price)
    
            # 确保始终有排序,尤其是在分页时
            return queryset.order_by('id')
    
    # URL /products/?name=apple&min_price=10.00
  • 使用 DRF 内置的 DjangoFilterBackend ,推荐且最常用

    bash 复制代码
    pip install django-filter
    
    INSTALLED_APPS = [
        # ...
        'django_filters', # 确保在rest_framework之前或之后
        'rest_framework',
        'myapp',
        # ...
    ]
    
    REST_FRAMEWORK = {
        'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
        # ... 其他 DRF 设置
    }
    
    # 局部配置
    class ProductListFilteredView(generics.ListAPIView):
        # ...
        filter_backends = [DjangoFilterBackend]
        # ...
    py 复制代码
    # 创建fileSet类
    # myapp/filters.py
    import django_filters
    from .models import Product
    
    class ProductFilter(django_filters.FilterSet):
        # name 字段支持模糊查询 contains
        name = django_filters.CharFilter(lookup_expr='icontains') # icontains 不区分大小写
    
        # price 支持范围查询
        min_price = django_filters.NumberFilter(field_name='price', lookup_expr='gte')
        max_price = django_filters.NumberFilter(field_name='price', lookup_expr='lte')
    
        # created_at 支持日期范围
        start_date = django_filters.DateFilter(field_name='created_at', lookup_expr='date__gte')
        end_date = django_filters.DateFilter(field_name='created_at', lookup_expr='date__lte')
    
        class Meta:
            model = Product
            # 直接列出允许精确匹配的字段
            fields = ['name', 'price', 'owner'] # 'owner' 默认是精确匹配,例如 ?owner=1
            # 也可以在这里指定所有可筛选的字段
            # fields = {
            #     'name': ['exact', 'icontains'],
            #     'price': ['exact', 'gte', 'lte'],
            #     'owner': ['exact'],
            #     'created_at': ['date__gte', 'date__lte'],
            # }
    py 复制代码
    # myapp/views.py
    from rest_framework import generics
    from django_filters.rest_framework import DjangoFilterBackend
    from .models import Product
    from .serializers import ProductSerializer
    from .filters import ProductFilter # 导入你定义的 FilterSet
    
    class ProductListFilteredView(generics.ListAPIView):
        queryset = Product.objects.all().order_by('id')
        serializer_class = ProductSerializer
        filter_backends = [DjangoFilterBackend] # 使用 DjangoFilterBackend
        filterset_class = ProductFilter # 指定你的 FilterSet 类
    py 复制代码
    # url示例
    GET /products/?name=apple
    GET /products/?price=9.99
    GET /products/?min_price=10.00&max_price=50.00
    GET /products/?owner=1
    GET /products/?start_date=2023-01-01&end_date=2023-12-31
  • 自定义筛选后端:如果你有非常复杂的筛选逻辑,django-filter 无法满足,或者你需要一种完全不同的筛选方式,例如基于请求头、自定义JSON体等,你可以创建自己的筛选后端。

    py 复制代码
    # myapp/filters_custom.py
    from rest_framework import filters
    
    class IsOwnerFilterBackend(filters.BaseFilterBackend):
        """
        Filter that only allows users to see their own objects.
        """
        def filter_queryset(self, request, queryset, view):
            if request.user.is_authenticated:
                return queryset.filter(owner=request.user)
            return queryset.none() # 匿名用户不能看到任何产品
    
    # myapp/views.py
    from rest_framework import generics
    from .models import Product
    from .serializers import ProductSerializer
    from .filters_custom import IsOwnerFilterBackend
    
    class ProductListOwnerFilterView(generics.ListAPIView):
        queryset = Product.objects.all().order_by('id')
        serializer_class = ProductSerializer
        filter_backends = [IsOwnerFilterBackend] # 使用自定义筛选后端

你可以在 filter_backends 属性中指定多个筛选后端。它们会按顺序应用到查询集上,每个后端在前一个后端过滤的基础上继续过滤。

py 复制代码
# myapp/views.py
from rest_framework import generics
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter # DRF 内置的搜索和排序过滤器
from .models import Product
from .serializers import ProductSerializer
from .filters import ProductFilter # 导入 ProductFilter

class ProductListCombinedFilterView(generics.ListAPIView):
    queryset = Product.objects.all().order_by('id')
    serializer_class = ProductSerializer
    filter_backends = [
        DjangoFilterBackend, # 允许 ?name=X, ?min_price=Y 等
        SearchFilter,        # 允许 ?search=Keyword
        OrderingFilter       # 允许 ?ordering=-price, ?ordering=name
    ]
    filterset_class = ProductFilter # 配合 DjangoFilterBackend
    search_fields = ['name', 'description'] # SearchFilter 搜索的字段
    ordering_fields = ['name', 'price', 'created_at'] # OrderingFilter 排序的字段
    # ordering = ['-created_at'] # 默认排序
相关推荐
FreakStudio4 小时前
一文速通Python并行计算:11 Python多进程编程-进程之间的数据安全传输-基于队列和管道
python·嵌入式·面向对象·并行计算·电子diy
DevangLic6 小时前
ffmpeg baidu
人工智能·pytorch·python·学习·ai·ffmpeg
deephub6 小时前
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
人工智能·python·深度学习·神经网络·架构·大语言模型
Mr.鱼6 小时前
关于uv 工具的使用总结(uv,conda,pip什么关系)
python·uv·mcp 命令工具
声声codeGrandMaster6 小时前
Django实现文件上传
数据库·后端·python·django
Mi Manchi266 小时前
力扣热题100之二叉树的中序遍历
python·算法·leetcode
zdy12635746887 小时前
python第35天打卡
开发语言·python
不知几秋7 小时前
计算机视觉-滤镜小程序
python
※DX3906※7 小时前
小土堆pytorch--神经网络搭建小实战&Sequential的使用
人工智能·pytorch·python
安卓机器7 小时前
如何撰写一篇优质 Python 相关的技术文档 进阶指南
开发语言·python