Django REST Framework 中 @action 装饰器详解

发布于:2025-09-10 ⋅ 阅读:(16) ⋅ 点赞:(0)

概述

@action 装饰器是 Django REST Framework (DRF) 中 ViewSet 的一个核心功能,用于定义自定义路由方法。它允许开发者在标准的 CRUD 操作(list、create、retrieve、update、destroy)之外,创建符合特定业务需求的接口,并自动生成相应的 URL 路由。

参数详解

1. methods - HTTP 方法配置

  • 类型: List[str]
  • 作用: 指定该动作响应哪些 HTTP 请求方法
  • 默认值: ['get'] (仅响应 GET 请求)
  • 示例:
    @action(methods=['post', 'put'], detail=True)
    def custom_action(self, request, pk=None):
        # 同时响应 POST 和 PUT 请求
        pass
    

2. detail - 操作级别配置

  • 类型: bool
  • 必填: 是
  • 作用: 决定动作是针对单个对象还是整个集合
  • 取值:
    • True: 针对单个对象,URL 中包含对象ID
    • False: 针对整个集合,URL 中不包含对象ID
  • 示例:
    # 单个对象操作: /users/{pk}/activate/
    @action(detail=True, methods=['post'])
    def activate(self, request, pk=None):
        pass
    
    # 集合操作: /users/recent/
    @action(detail=False, methods=['get'])
    def recent(self, request):
        pass
    

3. url_path - URL 路径自定义

  • 类型: str
  • 作用: 自定义 URL 路径段,覆盖默认的方法名
  • 默认值: 使用被装饰的方法名
  • 示例:
    @action(detail=True, methods=['post'], url_path='change-password')
    def set_password(self, request, pk=None):
        pass
    # 生成 URL: /users/{pk}/change-password/
    

4. url_name - 反向解析名称

  • 类型: str
  • 作用: 定义反向解析时使用的名称
  • 默认值: 方法名(下划线替换为连字符)
  • 示例:
    @action(detail=True, url_name='user-activation')
    def activate(self, request, pk=None):
        pass
    # 反向解析: reverse('user-viewset-user-activation', kwargs={'pk': 1})
    

5. **kwargs - 额外配置参数

  • 类型: dict
  • 作用: 覆盖视图级别的配置设置
  • 常用选项:
    • permission_classes: 权限控制
    • authentication_classes: 认证方式
    • throttle_classes: 限流配置
    • renderer_classes: 响应渲染器
    • parser_classes: 请求解析器
    • serializer_class: 序列化器

实战案例

用户管理系统示例

from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from django.contrib.auth.models import User
from .serializers import UserSerializer, PasswordResetSerializer

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    
    # 1. 密码重置 - 单个对象操作
    @action(
        detail=True,
        methods=['post'],
        url_path='reset-password',
        permission_classes=[IsAuthenticated],
        serializer_class=PasswordResetSerializer
    )
    def reset_password(self, request, pk=None):
        user = self.get_object()
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        # 执行密码重置逻辑
        user.set_password(serializer.validated_data['new_password'])
        user.save()
        
        return Response(
            {'status': '密码重置成功'},
            status=status.HTTP_200_OK
        )
    
    # 2. 获取活跃用户 - 集合操作
    @action(
        detail=False,
        methods=['get'],
        url_path='active-users',
        permission_classes=[IsAdminUser]
    )
    def get_active_users(self, request):
        active_users = User.objects.filter(is_active=True)
        page = self.paginate_queryset(active_users)
        
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        
        serializer = self.get_serializer(active_users, many=True)
        return Response(serializer.data)
    
    # 3. 批量导入用户 - 集合操作,文件上传
    @action(
        detail=False,
        methods=['post'],
        url_path='bulk-import',
        permission_classes=[IsAdminUser],
        parser_classes=[MultiPartParser]  # 支持文件上传
    )
    def bulk_import_users(self, request):
        import_file = request.FILES.get('file')
        
        if not import_file:
            return Response(
                {'error': '请提供导入文件'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 执行批量导入逻辑
        try:
            imported_count = self.process_import_file(import_file)
            return Response({
                'status': f'成功导入 {imported_count} 个用户',
                'imported_count': imported_count
            })
        except Exception as e:
            return Response(
                {'error': f'导入失败: {str(e)}'},
                status=status.HTTP_400_BAD_REQUEST
            )
    
    # 4. 用户统计信息 - 集合操作
    @action(detail=False, methods=['get'])
    def statistics(self, request):
        total_users = User.objects.count()
        active_users = User.objects.filter(is_active=True).count()
        staff_users = User.objects.filter(is_staff=True).count()
        
        return Response({
            'total_users': total_users,
            'active_users': active_users,
            'staff_users': staff_users,
            'inactive_users': total_users - active_users
        })
    
    # 辅助方法
    def process_import_file(self, import_file):
        # 实现文件处理逻辑
        return 10  # 返回导入的用户数量

自动生成的路由

使用 DefaultRouter 注册 ViewSet:

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')

urlpatterns = router.urls

生成的路由包括:

HTTP 方法 URL 路径 调用的方法名 方法类型 操作级别 说明
GET /users/ list 内置方法 集合操作 获取用户列表
POST /users/ create 内置方法 集合操作 创建新用户
GET /users/{pk}/ retrieve 内置方法 单个对象 获取指定用户详情
PUT /users/{pk}/ update 内置方法 单个对象 完整更新用户信息
PATCH /users/{pk}/ partial_update 内置方法 单个对象 部分更新用户信息
DELETE /users/{pk}/ destroy 内置方法 单个对象 删除指定用户
POST /users/{pk}/reset-password/ reset_password 自定义@action方法 单个对象 重置用户密码
GET /users/active-users/ get_active_users 自定义@action方法 集合操作 获取所有活跃用户列表
POST /users/bulk-import/ bulk_import_users 自定义@action方法 集合操作 批量导入用户数据
GET /users/statistics/ statistics 自定义@action方法 集合操作 获取用户统计信息

详细说明

1. 内置标准方法

ModelViewSet
这些方法是 Django REST Framework 的 ModelViewSet 自动提供的:

方法名 作用 对应 HTTP 方法
list 查询资源集合 GET
create 创建新资源 POST
retrieve 获取单个资源 GET
update 完整更新资源 PUT
partial_update 部分更新资源 PATCH
destroy 删除资源 DELETE

2. 自定义 @action 方法

这些是通过 @action 装饰器添加的自定义业务方法:

方法名 @action 配置 业务功能
reset_password @action(detail=True, methods=['post']) 用户密码重置
get_active_users @action(detail=False, methods=['get']) 筛选活跃用户
bulk_import_users @action(detail=False, methods=['post']) 批量导入用户
statistics @action(detail=False, methods=['get']) 统计数据分析

URL 路由生成规则

DRF 的路由器按照以下规则生成 URL:

  • 标准方法: /{model_name}//{model_name}/{pk}/
  • 自定义方法:
    • detail=True: /{model_name}/{pk}/{action_name}/
    • detail=False: /{model_name}/{action_name}/

高级用法

自定义权限和序列化器

from rest_framework import permissions

class IsOwnerOrAdmin(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj == request.user or request.user.is_staff

@action(
    detail=True,
    methods=['get'],
    permission_classes=[IsOwnerOrAdmin],
    serializer_class=UserDetailSerializer
)
def detailed_profile(self, request, pk=None):
    user = self.get_object()
    serializer = self.get_serializer(user)
    return Response(serializer.data)

组合多个动作

class ProductViewSet(viewsets.ModelViewSet):
    # ... 其他代码 ...
    
    @action(detail=True, methods=['post'])
    def add_to_cart(self, request, pk=None):
        # 添加到购物车逻辑
        pass
    
    @action(detail=True, methods=['post'])
    def add_to_wishlist(self, request, pk=None):
        # 添加到收藏夹逻辑
        pass
    
    @action(detail=True, methods=['get'])
    def reviews(self, request, pk=None):
        # 获取商品评论
        product = self.get_object()
        reviews = product.reviews.all()
        serializer = ReviewSerializer(reviews, many=True)
        return Response(serializer.data)

最佳实践

  1. 明确操作级别: 始终明确设置 detail 参数,确保 URL 结构正确
  2. 合理使用 HTTP 方法: 根据操作性质选择正确的 HTTP 方法
    • GET: 获取数据
    • POST: 创建或执行非幂等操作
    • PUT/PATCH: 更新数据
    • DELETE: 删除数据
  3. 适当的权限控制: 为每个动作设置合适的权限类
  4. 使用专用序列化器: 为不同的动作使用专门的序列化器
  5. 错误处理: 提供清晰的错误响应
  6. 文档注释: 为每个自定义动作添加详细的文档注释

常见问题解答

Q: 什么时候应该使用 @action?

A: 当需要实现超出标准 CRUD 操作的业务逻辑时,如状态变更、批量操作、统计查询等。

Q: detail=True 和 detail=False 有什么区别?

A: detail=True 针对单个对象,URL 包含对象ID;detail=False 针对整个集合,URL 不包含对象ID。

Q: 如何为 @action 方法编写测试?

A: 使用 DRF 的 APITestCase,像测试标准端点一样测试自定义动作:

def test_reset_password_action(self):
    url = reverse('user-reset-password', kwargs={'pk': self.user.pk})
    response = self.client.post(url, {'new_password': 'newpass123'})
    self.assertEqual(response.status_code, status.HTTP_200_OK)

总结

@action 装饰器是 DRF ViewSet 的强大扩展工具,它提供了:

  • 灵活性: 轻松创建自定义 API 端点
  • 一致性: 保持与标准 CRUD 操作一致的代码风格
  • 自动化: 自动生成 URL 路由
  • 可配置性: 支持细粒度的权限、认证和序列化配置

通过合理使用 @action 装饰器,可以构建出既符合 RESTful 原则又能满足复杂业务需求的 API 接口。


网站公告

今日签到

点亮在社区的每一天
去签到