viewsets.py

视图集

在路由确定针对请求使用哪个控制器后,控制器负责理解请求并生成适当的输出。

Ruby on Rails 文档

Django REST 框架允许你将一组相关视图的逻辑组合到一个名为 ViewSet 的单一类中。在其他框架中,你可能还会发现概念上类似的实现,其名称类似于“资源”或“控制器”。

ViewSet 类仅仅是 一种基于类的视图,它不提供任何方法处理程序,例如 .get().post(),而是提供诸如 .list().create() 等操作。

ViewSet 的方法处理程序仅在使用 .as_view() 方法完成视图时绑定到相应操作。

通常,与其在 urlconf 中显式注册视图集中的视图,不如将视图集注册到路由器类中,该类会自动为你确定 urlconf。

示例

让我们定义一个简单的视图集,该视图集可用于列出或检索系统中的所有用户。

from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from myapps.serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response

class UserViewSet(viewsets.ViewSet):
    """
    A simple ViewSet for listing or retrieving users.
    """
    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)

如果需要,我们可以将此视图集绑定到两个单独的视图,如下所示

user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})

通常我们不会这样做,而是将视图集注册到路由器中,并允许自动生成 urlconf。

from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter

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

与其编写自己的视图集,不如经常使用提供默认行为集的现有基类。例如

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset for viewing and editing user instances.
    """
    serializer_class = UserSerializer
    queryset = User.objects.all()

使用 ViewSet 类相对于使用 View 类有两个主要优点。

  • 重复的逻辑可以组合到一个类中。在上面的示例中,我们只需要指定一次 queryset,它将在多个视图中使用。
  • 通过使用路由器,我们不再需要自己处理 URL 配置的布线。

这两者都伴随着权衡。使用常规视图和 URL 配置更加明确,并且为您提供了更多控制。如果您希望快速启动和运行,或者当您拥有一个大型 API 并且希望在整个过程中强制执行一致的 URL 配置时,ViewSets 会很有用。

视图集操作

REST 框架中包含的默认路由器将为一组标准的创建/检索/更新/销毁样式操作提供路由,如下所示

class UserViewSet(viewsets.ViewSet):
    """
    Example empty viewset demonstrating the standard
    actions that will be handled by a router class.

    If you're using format suffixes, make sure to also include
    the `format=None` keyword argument for each action.
    """

    def list(self, request):
        pass

    def create(self, request):
        pass

    def retrieve(self, request, pk=None):
        pass

    def update(self, request, pk=None):
        pass

    def partial_update(self, request, pk=None):
        pass

    def destroy(self, request, pk=None):
        pass

内省视图集操作

在分发期间,以下属性在 ViewSet 上可用。

  • basename - 用于创建的 URL 名称的基础。
  • action - 当前操作的名称(例如,listcreate)。
  • detail - 布尔值,指示当前操作是针对列表视图还是详细视图配置的。
  • suffix - 视图集类型的显示后缀 - 镜像 detail 属性。
  • name - 视图集的显示名称。此参数与 suffix 互斥。
  • description - 视图集的单个视图的显示描述。

您可以检查这些属性以根据当前操作调整行为。例如,您可以将权限限制为除 list 操作之外的所有操作,类似于此

def get_permissions(self):
    """
    Instantiates and returns the list of permissions that this view requires.
    """
    if self.action == 'list':
        permission_classes = [IsAuthenticated]
    else:
        permission_classes = [IsAdminUser]
    return [permission() for permission in permission_classes]

标记额外操作以进行路由

如果您有应该可路由的临时方法,则可以使用 @action 装饰器将它们标记为临时方法。与常规操作一样,额外操作可能针对单个对象或整个集合。要指示这一点,请将 detail 参数设置为 TrueFalse。路由器将相应地配置其 URL 模式。例如,DefaultRouter 将配置详细操作以在其 URL 模式中包含 pk

额外操作的更完整示例

from django.contrib.auth.models import User
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from myapp.serializers import UserSerializer, PasswordSerializer

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset that provides the standard actions
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=True, methods=['post'])
    def set_password(self, request, pk=None):
        user = self.get_object()
        serializer = PasswordSerializer(data=request.data)
        if serializer.is_valid():
            user.set_password(serializer.validated_data['password'])
            user.save()
            return Response({'status': 'password set'})
        else:
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

    @action(detail=False)
    def recent_users(self, request):
        recent_users = User.objects.all().order_by('-last_login')

        page = self.paginate_queryset(recent_users)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(recent_users, many=True)
        return Response(serializer.data)

默认情况下,action 装饰器将路由 GET 请求,但也可以通过设置 methods 参数接受其他 HTTP 方法。例如

    @action(detail=True, methods=['post', 'delete'])
    def unset_password(self, request, pk=None):
       ...

参数 methods 还支持定义为 HTTPMethod 的 HTTP 方法。以下示例与上述示例相同

    from http import HTTPMethod

    @action(detail=True, methods=[HTTPMethod.POST, HTTPMethod.DELETE])
    def unset_password(self, request, pk=None):
       ...

该装饰器允许您覆盖任何视图集级别配置,例如 permission_classesserializer_classfilter_backends...

    @action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf])
    def set_password(self, request, pk=None):
       ...

然后,这两个新操作将在 URL ^users/{pk}/set_password/$^users/{pk}/unset_password/$ 中可用。使用 url_pathurl_name 参数更改操作的 URL 段和反向 URL 名称。

要查看所有额外操作,请调用 .get_extra_actions() 方法。

为额外操作路由其他 HTTP 方法

额外的操作可以将其他 HTTP 方法映射到单独的 ViewSet 方法。例如,上述密码设置/取消设置方法可以合并到一个路由中。请注意,其他映射不接受参数。

@action(detail=True, methods=["put"], name="Change Password")
def password(self, request, pk=None):
    """Update the user's password."""
    ...


@password.mapping.delete
def delete_password(self, request, pk=None):
    """Delete the user's password."""
    ...

反转操作 URL

如果您需要获取操作的 URL,请使用 .reverse_action() 方法。这是 reverse() 的便捷封装,自动传递视图的 request 对象,并使用 .basename 属性将 url_name 前置。

请注意,basename 是路由器在 ViewSet 注册期间提供的。如果您不使用路由器,则必须向 .as_view() 方法提供 basename 参数。

使用上一部分中的示例

>>> view.reverse_action("set-password", args=["1"])
'http://localhost:8000/api/users/1/set_password'

或者,您可以使用 @action 装饰器设置的 url_name 属性。

>>> view.reverse_action(view.set_password.url_name, args=['1'])
'http://localhost:8000/api/users/1/set_password'

.reverse_action()url_name 参数应与 @action 装饰器的相同参数匹配。此外,此方法可用于反转默认操作,例如 listcreate


API 参考

视图集

ViewSet 类继承自 APIView。您可以使用任何标准属性,例如 permission_classesauthentication_classes 来控制视图集上的 API 策略。

ViewSet 类不提供任何操作的实现。为了使用 ViewSet 类,您将覆盖该类并明确定义操作实现。

通用视图集

GenericViewSet 类继承自 GenericAPIView,并提供 get_objectget_queryset 方法和其他通用视图基本行为的默认设置,但默认情况下不包含任何操作。

为了使用 GenericViewSet 类,您将覆盖该类并混合所需的混合类,或明确定义操作实现。

模型视图集

ModelViewSet 类继承自 GenericAPIView,并通过混合各种混合类的行为来包含各种操作的实现。

ModelViewSet 类提供的操作是 .list().retrieve().create().update().partial_update().destroy()

示例

由于 ModelViewSet 扩展了 GenericAPIView,因此您通常需要至少提供 querysetserializer_class 属性。例如

class AccountViewSet(viewsets.ModelViewSet):
    """
    A simple ViewSet for viewing and editing accounts.
    """
    queryset = Account.objects.all()
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

请注意,你可以使用 GenericAPIView 提供的任何标准属性或方法覆盖。例如,要使用动态确定其应操作的查询集的 ViewSet,你可以执行类似以下操作

class AccountViewSet(viewsets.ModelViewSet):
    """
    A simple ViewSet for viewing and editing the accounts
    associated with the user.
    """
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

    def get_queryset(self):
        return self.request.user.accounts.all()

但是请注意,从 ViewSet 中删除 queryset 属性后,任何关联的 路由器 将无法自动派生模型的基本名称,因此你必须将 basename kwarg 指定为 路由器注册 的一部分。

还要注意,尽管此类默认情况下提供创建/列出/检索/更新/销毁操作的完整集,但你可以使用标准权限类来限制可用操作。

只读模型视图集

ReadOnlyModelViewSet 类也继承自 GenericAPIView。与 ModelViewSet 一样,它还包括对各种操作的实现,但与 ModelViewSet 不同,它只提供“只读”操作,即 .list().retrieve()

示例

ModelViewSet 一样,你通常需要至少提供 querysetserializer_class 属性。例如

class AccountViewSet(viewsets.ReadOnlyModelViewSet):
    """
    A simple ViewSet for viewing accounts.
    """
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

同样,与 ModelViewSet 一样,你可以使用 GenericAPIView 可用的任何标准属性和方法覆盖。

自定义视图集基类

你可能需要提供没有完整 ModelViewSet 操作集或以其他方式自定义行为的自定义 ViewSet 类。

示例

要创建一个提供 createlistretrieve 操作的基本视图集类,请继承自 GenericViewSet,并混合所需的 action

from rest_framework import mixins, viewsets

class CreateListRetrieveViewSet(mixins.CreateModelMixin,
                                mixins.ListModelMixin,
                                mixins.RetrieveModelMixin,
                                viewsets.GenericViewSet):
    """
    A viewset that provides `retrieve`, `create`, and `list` actions.

    To use it, override the class and set the `.queryset` and
    `.serializer_class` attributes.
    """
    pass

通过创建自己的基本 ViewSet 类,你可以提供可以在 API 中多个视图集中重复使用的通用行为。