此文档为官方文档的翻译,原文地址:https://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/

REST框架包括一个用于处理ViewSets的抽象,它允许开发人员集中精力对API的状态和交互进行建模,并根据常规约定自动处理URL映射。

ViewSet类与View类几乎相同,不同之处在于它们提供诸如retrieveupdate之类的操作,而不是getput等方法。(注意:操作比方法的抽象等级更高)

ViewSet类仅在最后一刻绑定到一组方法处理程序,当它被实例化为一组视图时,通常使用 Router类来处理定义 URL映射的复杂操作。

使用ViewSets重构

首先,让我们将 UserListUserDetail视图重构为单个 UserViewSet。我们可以删除这两个视图,并用一个类替换它们:

  1. from rest_framework import viewsets
  2. class UserViewSet(viewsets.ReadOnlyModelViewSet):
  3. """
  4. 此视集自动提供“列表”和“检索”操作。
  5. """
  6. queryset = User.objects.all()
  7. serializer_class = UserSerializer

在这里,我们使用 ReadOnlyModelViewSet 类自动提供默认的“只读”操作。我们仍然像使用常规视图时那样设置querysetserializer_class 属性,但是我们不再需要向两个独立的类提供相同的信息。(这波操作属于又做了一层抽象!减少任何重复语句!)

接下来我们将替换SnippetListSnippetDetailSnippetHighlight 视图类。我们可以删除这三个视图,然后再用一个类替换它们。

  1. from rest_framework.decorators import action
  2. from rest_framework.response import Response
  3. from rest_framework import permissions
  4. class SnippetViewSet(viewsets.ModelViewSet):
  5. """
  6. 这个视图集自动提供“列表”、“创建”、“检索”,“更新”和“删除”操作。
  7. (牛逼!一个视图覆盖了增、删、改、查和列表!!!)
  8. 此外,我们还提供额外的“高亮”动作。
  9. """
  10. queryset = Snippet.objects.all()
  11. serializer_class = SnippetSerializer
  12. permission_classes = [permissions.IsAuthenticatedOrReadOnly,
  13. IsOwnerOrReadOnly]
  14. @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
  15. def highlight(self, request, *args, **kwargs):
  16. snippet = self.get_object()
  17. return Response(snippet.highlighted)
  18. def perform_create(self, serializer):
  19. serializer.save(owner=self.request.user)

这一次,我们使用 ModelViewSet 类来获得完整的默认读写操作集。

请注意,我们还使用@action 装饰器 创建了一个名为 highlight自定义操作(默认的五种操作:增(create)、删(destroy)、改(update\partial_update)、查(retrieve)、列表(list))。该装饰器可用于添加任何不适合标准create/update/delete操作的自定义端点。

默认情况下,使用@action 装饰器的自定义操作将响应GET请求。如果我们想要一个响应POST请求的动作,我们可以使用methods参数传入一个请求类型的list来设置所有允许的请求类型。

默认情况下自定义操作的 url 取决于方法名称本身。如果您希望更改 url 的构造方式,可以为装饰器设置url_path关键字参数。

显式地将 ViewSets 绑定到 url

只有在定义 URLConf 时,处理程序方法才会绑定到操作。为了了解在幕后发生的事情,让我们首先显式地从 ViewSets 创建一组视图。
snippets/urls.py 文件中,我们将ViewSet 类绑定到一组具体视图中。

  1. from snippets.views import SnippetViewSet, UserViewSet, api_root
  2. from rest_framework import renderers
  3. snippet_list = SnippetViewSet.as_view({
  4. 'get': 'list',
  5. 'post': 'create'
  6. })
  7. snippet_detail = SnippetViewSet.as_view({
  8. 'get': 'retrieve',
  9. 'put': 'update',
  10. 'patch': 'partial_update',
  11. 'delete': 'destroy'
  12. })
  13. snippet_highlight = SnippetViewSet.as_view({
  14. 'get': 'highlight'
  15. }, renderer_classes=[renderers.StaticHTMLRenderer])
  16. user_list = UserViewSet.as_view({
  17. 'get': 'list'
  18. })
  19. user_detail = UserViewSet.as_view({
  20. 'get': 'retrieve'
  21. })

注意我们是如何从每个ViewSet 类创建多个视图的,方法是:将 http 方法绑定到每个视图所需的操作
现在我们已经将资源绑定到具体的视图中,我们可以像往常一样使用 URL 配置注册视图。

  1. urlpatterns = format_suffix_patterns([
  2. path('', api_root),
  3. path('snippets/', snippet_list, name='snippet-list'),
  4. path('snippets/<int:pk>/', snippet_detail, name='snippet-detail'),
  5. path('snippets/<int:pk>/highlight/', snippet_highlight, name='snippet-highlight'),
  6. path('users/', user_list, name='user-list'),
  7. path('users/<int:pk>/', user_detail, name='user-detail')
  8. ])

使用路由器

因为我们使用的是 ViewSet类而不是View类,实际上我们不需要自己设计 URL 配置。将资源连接到视图和 url 的常规操作可以使用Router类自动处理。我们所需要做的就是向路由器注册合适的视图集,然后让它完成剩下的工作。

这是我们重写的snippets/urls.py文件。

  1. from django.urls import path, include
  2. from rest_framework.routers import DefaultRouter # 导入路由器
  3. from snippets import views
  4. # 创建路由器并用它注册我们的视图集。
  5. router = DefaultRouter()
  6. router.register(r'snippets', views.SnippetViewSet, basename="snippet") # 官网这地方的basename加了s,错了!这里的basename应该和模型名完全一致!(除了大小写)
  7. router.register(r'users', views.UserViewSet, basename="user")
  8. # 现在由路由器自动确定API URL。
  9. urlpatterns = [
  10. path('', include(router.urls)),
  11. ]

在路由器上注册视图集类似于提供一个 urlpattern。我们包括两个参数 — 视图的 URL 前缀视图集本身。

我们正在使用的 DefaultRouter 类也会自动为我们创建 API 根视图,因此我们现在可以从我们的视图模块中删除 api_root 方法。

视图与视图集之间的权衡

使用视图集可以是一个非常有用的抽象。它有助于确保 URL 约定在你的 API 中保持一致,最大限度地减少你需要编写的代码数量,并且允许你专注于你的 API 提供的交互和表示,而不是 URL 配置的细节。

这并不意味着这始终是正确的方法。就像当使用基于类的视图而不是基于函数的视图时,也有一组类似的权衡值得考虑。使用视图集比单独构建您的视图的明确性要少。