一、视图集和路由
REST 框架中的 ViewSet 非常类似 View,它基于一些常用约定,实现了 list, create, retrieve, update, partial_update, destroy 等方法。
ViewSet 在最后的实例化时,才会将 list, create 等方法绑定到 get, post, put, patch, delete 等 REST 操作对应的处理函数(通常由 Router 创建 URL 定义时自动绑定),最后绑定到 model-list, model-create, model-detail 等 URL。
1.1 使用 ViewSet 进行重构
将 UserList 和 UserDetail 视图重构到一个 UserViewSet 中:
# quickstart/views.py from rest_framework import viewsets class UserViewSet(viewsets.ReadOnlyModelViewSet): """ 这个用户视图集自动提供'list' 和'detail'行为 """ queryset = User.objects.all() serializer_class = UserSerializer
ReadOnlyModelViewSet 实现了只读的操作,如 list, detail 等。
接着将 SnippetList, SnippetDetail SnippetHighlight 重构到一个 SnippetViewSet 中:
# quickstart/views.py from rest_framework import viewsets from rest_framework.decorators import detail_route class SnippetViewSet(viewsets.ModelViewSet): """ 这个视图集 自动提供'list' 、'create'、'retrieve'、'update'、'destroy'行为 我们额外增加一个'highlight' 方法 """ queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,) @detail_route(renderer_classes=[renderers.StaticHTMLRenderer]) def highlight(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted) def perform_create(self, serializer): serializer.save(owner=self.request.user)
这里使用了 ModelViewSet,从而实现了默认的读写操作方法。
要实现一个自定义的操作,比如 highlight,需要为其定义一个方法,并且加上装饰器 @detail_route。该装饰器可用在所有非标准 create/update/delete 风格的自定义 API 上。
使用了 @detail_route 的自定义方法的 API 默认只响应 GET 请求,如果要响应 POST 等请求,需要在装饰器的 methods 参数中指定。自定义方法的 API,其 URL 默认就是函数名本身,如 highlight,要想修改,需要在 @detail_route 装饰器的 url_path 参数中指定。
1.2 手动将 ViewSet 绑定到 URL
通过手动将 ViewSet 转成具体的 View,以了解 ViewSet 的工作原理。
# quickstart/urls.py # -*- coding:utf-8 -*- from django.conf.urls import url import quickstart.views as views from rest_framework.urlpatterns import format_suffix_patterns from rest_framework import renderers app_name = 'qk' snippet_list = views.SnippetViewSet.as_view({ 'get': 'list', 'post': 'create' }) snippet_detail = views.SnippetViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }) snippet_highlight = views.SnippetViewSet.as_view({ 'get': 'highlight' }, renderer_classes=[renderers.StaticHTMLRenderer]) user_list = views.UserViewSet.as_view({ 'get': 'list' }) user_detail = views.UserViewSet.as_view({ 'get': 'retrieve' }) urlpatterns = [ url(r'api/$', views.api_root), url(r'snippets/$',snippet_list, name='snippet-list'), url(r'snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'), url(r'snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'), url(r'users/$', user_list, name='user-list'), url(r'users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail'), ] urlpatterns = format_suffix_patterns(urlpatterns)
现在,系统应该能和之前一样正常运行。
1.3 使用 Router 进一步重构
将 ViewSet 自动转成具体的 View 并绑定到 URL 的操作可以由 Router 自动完成。
# tutorial/urls.py from django.contrib import admin from django.urls import path from django.conf.urls import url, include from quickstart import views from rest_framework.routers import DefaultRouter # 创建路由器并注册我们的视图。 router = DefaultRouter() router.register(r'snippets', views.SnippetViewSet) router.register(r'users', views.UserViewSet) urlpatterns = [ path('admin/', admin.site.urls), # url(r'^user/', views.TestView.as_view()), # url(r'^', include('quickstart.urls', namespace='qk')), url(r'^', include(router.urls)), ] urlpatterns += [ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), ]
# quickstart/urls.py # -*- coding:utf-8 -*- from django.conf.urls import url import quickstart.views as views from rest_framework.urlpatterns import format_suffix_patterns from rest_framework import renderers app_name = 'qk' snippet_list = views.SnippetViewSet.as_view({ 'get': 'list', 'post': 'create' }) snippet_detail = views.SnippetViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }) snippet_highlight = views.SnippetViewSet.as_view({ 'get': 'highlight' }, renderer_classes=[renderers.StaticHTMLRenderer]) user_list = views.UserViewSet.as_view({ 'get': 'list' }) user_detail = views.UserViewSet.as_view({ 'get': 'retrieve' }) urlpatterns = [ # url(r'api/$', views.api_root), url(r'snippets/$',snippet_list, name='snippet-list'), url(r'snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'), url(r'snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'), url(r'users/$', user_list, name='user-list'), url(r'users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail'), ] # urlpatterns = format_suffix_patterns(urlpatterns)
Router 还自动生成了 api_root,并为 URL 加上了后缀功能,从而无需定义 api_view 视图及使用 format_suffix_patterns 了。