教程6:ViewSets和Routers
REST framework提供了一种叫做ViewSets
的抽象行为,它可以使开发人员聚焦于API的状态和实现,基于常见的约定而自动进行URL配置。
ViewSet
和View
很像,除了它提供的是read
以及update
操作而不是HTTP的get
或者post
。
ViewSet
仅在被调用的时候才会和对应的方法进行绑定,当它被实例化时——通常是在使用Route
类管理URL配置的时候。
使用ViewSet重构代码
首先我们使用但一个UserViewSet
来取代UserList
和UserDetail
,我们先移除那两个类,并添加:
from rest_framework import viewsets
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
现在我们使用ReadOnlyModelViewSet
自动提供了“只读”方法,并且依然想使用常规视图是那样设置了queryset
和seriallizer_class
属性,但我们不需要写2个类了。
下面我们修改SnippetList
、SnippetDetail
和SnippetHighlight
类,同样删除它们并修改成一个类:
from rest_framework.decorators import detail_route
class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an extra `highlight` action.
"""
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
来获取完整的读和写操作。注意我们使用@detail_route
装饰器来创建自定义动作,这个装饰器可以用于任何不符合标准create/update/delete
的动作。
使用@detail_route
装饰的用户自定义动作默认相应GET请求,如果需要响应POST操作需要指定methods
参数。
默认情况下,自定义动作对应的URL取决于它们的函数名,也可以通过给装饰器传递url_path
参数来进行修改。
显式绑定URL和ViewSets
仅仅在我们定义URL配置时HTTP方法才会和我们定义的行为进行绑定。为了理解细节,我们先显式的在urls.py
中进行操作:
from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers
snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
'get': 'list'
})
user_detail = UserViewSet.as_view({
'get': 'retrieve'
})
注意我们为每个ViewSet
都创建了多个View,并且为每个View的行为和HTTP方法进行了绑定。
绑定后,我们可以像平常那样定义URL:
urlpatterns = format_suffix_patterns([
url(r'^$', 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')
])
使用Routers
因为我们使用了ViewSet
而不是View
,实际上我们不需要自己定义URL。使用Router
类可以自动的进行上述操作,我们需要做的仅仅是正确的注册View到Router中:
from django.conf.urls import url, include
from snippets import views
from rest_framework.routers import DefaultRouter
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by the router.
# Additionally, we include the login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
使用route生成了相同的URL路径,我们包含了2个参数——URL前缀以及Viewset本身。
DefaultRouter
类自动创建了根URL,所以也可以在views
中移除api_root
了。
权衡Views和Viewsets
viewsets是一种很用的抽象,它帮助我们确保URL符合惯例,减少代码编写量,使你专注于API交互和设计而不是URL配置上。
但这并不意味这总是一种好的选择,就好象函数视图和类视图之间的权衡一样,使用viewsets相比于显示构建vews,有些隐晦。
总结
通过少量的代码,我们构建出一个完备的Web API,完美支持浏览器访问、用户认证、权限管理,并且支持多种返回格式。
我们经历了每步设计的过程,并且知道了如果我们需要自定义功能,可以方便的使用Django原生的views.
展望
整个教程到这就结束了,如果你想得到更多信息,这里有几点建议:
- 在Github提问并且提交pull requests。
- 加入REST framework discussion组并为社区做贡献。
- 在Twitter上跟随作者并say hi。