viewsets.py

ViewSets

路由确定了哪个控制器用于请求后,您的控制器负责理解请求并生成适当的输出。

Ruby on Rails Documentation

Django REST框架允许您在一个类中组合一组相关视图的逻辑,称为ViewSet 在其他框架中,您还可以找到类似“资源”或“控制器”之类的概念上类似的实现。

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

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

通常,您不是在urlconf中的视图集中显式注册视图,而是使用路由器类注册视图集,该类会自动为您确定urlconf。

Example

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

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)

If we need to, we can bind this viewset into two separate views, like so:

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

Typically we wouldn't do this, but would instead register the viewset with a router, and allow the urlconf to be automatically generated.

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

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

Rather than writing your own viewsets, you'll often want to use the existing base classes that provide a default set of behavior. For example:

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

There are two main advantages of using a ViewSet class over using a View class.

  • Repeated logic can be combined into a single class. In the above example, we only need to specify the queryset once, and it'll be used across multiple views.
  • By using routers, we no longer need to deal with wiring up the URL conf ourselves.

Both of these come with a trade-off. Using regular views and URL confs is more explicit and gives you more control. ViewSets are helpful if you want to get up and running quickly, or when you have a large API and you want to enforce a consistent URL configuration throughout.

ViewSet actions

The default routers included with REST framework will provide routes for a standard set of create/retrieve/update/destroy style actions, as shown below:

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

Introspecting ViewSet actions

During dispatch, the following attributes are available on the ViewSet.

  • basename - the base to use for the URL names that are created.
  • action - the name of the current action (e.g., list, create).
  • detail - boolean indicating if the current action is configured for a list or detail view.
  • suffix - the display suffix for the viewset type - mirrors the detail attribute.

You may inspect these attributes to adjust behaviour based on the current action. For example, you could restrict permissions to everything except the list action similar to this:

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 = [IsAdmin]
    return [permission() for permission in permission_classes]

Marking extra actions for routing

如果您有可以路由的临时方法,可以使用@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(methods=['post'], detail=True)
    def set_password(self, request, pk=None):
        user = self.get_object()
        serializer = PasswordSerializer(data=request.data)
        if serializer.is_valid():
            user.set_password(serializer.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('-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)

装饰器还可以采用仅为路由视图设置的额外参数。 For example:

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

These decorator will route GET requests by default, but may also accept other HTTP methods by setting the methods argument. For example:

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

The two new actions will then be available at the urls ^users/{pk}/set_password/$ and ^users/{pk}/unset_password/$

To view all extra actions, call the .get_extra_actions() method.

Reversing action URLs

If you need to get the URL of an action, use the .reverse_action() method. This is a convenience wrapper for reverse(), automatically passing the view's request object and prepending the url_name with the .basename attribute.

Note that the basename is provided by the router during ViewSet registration. If you are not using a router, then you must provide the basename argument to the .as_view() method.

Using the example from the previous section:

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

Alternatively, you can use the url_name attribute set by the @action decorator.

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

The url_name argument for .reverse_action() should match the same argument to the @action decorator. Additionally, this method can be used to reverse the default actions, such as list and create.


API Reference

ViewSet

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

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

GenericViewSet

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

为了使用GenericViewSet类,您将覆盖该类并混合所需的mixin类,或者显式定义动作实现。

ModelViewSet

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

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

Example

因为ModelViewSet扩展GenericAPIView,所以通常需要至少提供querysetserializer_class属性。 For example:

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属性后,任何关联的路由器将无法自动派生模型的base_name,并且因此,您必须在路由器注册中指定base_name kwarg。

另请注意,虽然此类默认提供完整的create / list / retrieve / update / destroy操作集,但您可以使用标准权限类来限制可用操作。

ReadOnlyModelViewSet

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

Example

As with ModelViewSet, you'll normally need to provide at least the queryset and serializer_class attributes. For example:

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

Again, as with ModelViewSet, you can use any of the standard attributes and method overrides available to GenericAPIView.

Custom ViewSet base classes

You may need to provide custom ViewSet classes that do not have the full set of ModelViewSet actions, or that customize the behavior in some other way.

Example

To create a base viewset class that provides create, list and retrieve operations, inherit from GenericViewSet, and mixin the required actions:

from rest_framework import mixins

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

By creating your own base ViewSet classes, you can provide common behavior that can be reused in multiple viewsets across your API.