文章目录
一,视图集
REST framework 允许将一组相关类视图的逻辑组合在一个类中,称为 ViewSet
视图集。在其他框架中,你可能也会发现概念上类似的实现,比如“Resources”或“Controllers”。
ViewSet
可认为是一组类视图的抽象,因此它不提供任何诸如 .get()
或 .post()
这类特定的方法处理程序,而是提供诸如 .list()
和 .create()
之类的操作。
ViewSet
的方法处理程序只在结束视图时使用.as_view()
方法绑定到相应的操作。
通常,不需要在 URL 中显式地注册视图集,而是将视图集注册到一个Router
路由器中,它会自动为你确定的路由。
(一)视图集
1,为什么需要视图集
首先定义两个个独立的简单的类视图,分别获取用户列表和用户详情:
class UsersList(APIView):
def get(self, request, format=None):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
class UserDetail(APIView):
def get_object(self, pk):
try:
return User.objects.get(pk=pk)
except User.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user)
return Response(serializer.data)
def put(self, request, pk, format=None):
user = self.get_object(pk)
serializer = UserSerializer(user, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
user = self.get_object(pk)
user.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
路由:
from MyAPP import views
urlpatterns = [
url(r'^userlist/$', views.UsersList.as_view()),
url(r'^userdetail/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
]
显然我们可以使用通用类视图和 Mixin 类来简化这两个类视图中处理不同请求方法的方法:
class UsersList(mixins.ListModelMixin,
generics.GenericAPIView):
queryset = Users.objects.all()
serializer_class = UsersSerializer
class UserDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
路由:
from MyAPP import views
urlpatterns = [
url(r'^userlist/$', views.UsersList.as_view()),
url(r'^userdetail/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
]
这样一来,我们将普通类视图中重写在 get()
方法中的逻辑交由 ListModelMixin
提供的 list()
方法来实现,同理,UpdateModelMixin
的 update
替换 put
,DestroyModelMixin
的 destroy
替换 delete
,等等,总之,通用视图类与Mixin类结合使用的结果就是将 http_methods 替换为 actions 。
尽管这两个类视图分别通过 actions 操作的是资源集合和单个资源,但对同一类资源的操作无非 CRUD,只是因为“惯性”,我们将它们分散在了不同的类视图中。
为此,我们可以进一步将这些 actions 抽象到一起,会比较方便管理,而这个抽象的手段就是 ViewSet
视图集。
现在定义一个简单的视图集:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
ModelViewSet
提供了对所有 actions 的支持。
使用视图集可能是一个非常有用的抽象。它最大限度地减少需要编写的代码量。
但却产生了一个新问题:如何进行 URL 映射?
2,路由器 Routers 与视图集中的额外操作
如果我们需要,我们仍然可以在 URL 中将这个视图集绑定到两个单独的视图,想这样:
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
通常我们不会这么做,只需要用一个路由器来注册我们的视图集,然后就能自动完成 URL 映射而不用再使用标准路由系统:
from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = router.urls
通过使用路由器,我们不再需要自己写路由映射。
REST framework 中内置的DefaultRouter
将为一套标准的 CRUD 操作提供路由,如下所示:
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
如果有需要映射的其它方法,可以用 @action
装饰器来标记它们。与常规 actions 一样,额外的 actions 也可用于对象列表或单个实例。
为标明这一点,需要设置 detail
参数为 True 或 False。路由器将相应地配置其 URL 模式。
更完整的额外 actions 的例子:
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):
"""
提供标准操作的视图集
"""
queryset = User.objects.all()
serializer_class = UserSerializer
@action(methods=['post'], detail=True) # DefaultRouter 将在其 URL 模式中配置包含 `pk` 的详细操作。
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)
另外,装饰器可以为路由视图设置额外的参数:
@action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
...
@action
装饰器默认路由 GET 请求,但也可以通过设置 methods
参数来接受其他 HTTP 方法:
@action(methods=['post', 'delete'], detail=True)
def unset_password(self, request, pk=None):
...
当然也能用前面提到的 @detail_route
或 @list_route
来一定程度上代替 @action
。
如果你要查看所有额外操作,请调用 .get_extra_actions()
方法。
如果你需要获取操作的 URL,请使用 .reverse_action()
方法。这是 Reverse()
的便捷封装,它自动传递视图的 request
对象,并在 url_name
前加上 .basename
属性。
请注意,basename
是路由器在视图集注册期间提供的。如果不使用路由器,则必须向 .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
装饰器匹配相同的参数。此外,此方法可用于反转默认操作,例如list
和create
。
3,视图 VS 视图集
与使用 View
类相比,使用 ViewSet
类有两个主要优点。
- 重复的逻辑可以组合成一个类。在上面的例子中,我们只需要指定一次 queryset,它将在多个视图中使用。
- 通过使用 routers,不再需要自己处理URLconf。
这并不意味着视图集总是正确的方法,需要考虑一组类似的权衡:
- 使用常规的 views 和 URL 能更明确的提供更多的控制。
- ViewSets 有助于快速启动和运行,或者当你有大型的API,并且希望在整个过程中执行一致的 URL 配置。
(二)视图集 API 参考
1,ViewSet
ViewSet
类继承自 APIView
。可以使用任何标准属性 (例如 permission_classes
,authentication_classes
等) 来控制视图集上的 API 策略。
它不提供任何操作的实现,通常需要子类化它并显式地定义操作的实现。
2,GenericViewSet
GenericViewSet
继承自 GenericAPIView
,并提供默认的 get_object
,get_queryset
方法和 GenericAPIView
的其他基本行为,但默认情况下不包含任何操作。
通常需要子类化它,或混合所需的 mixin
类,或者显式定义操作的实现。
3,ModelViewSet
ModelViewSet
类也继承自 GenericAPIView
,并通过混合各种 mixin
类的行为来包含各种操作的实现,它提供.list(),.retrieve(),.create(),.update(),.partial_update()
和 .destroy()
的实现。
因为ModelViewSetextends
继承自 GenericAPIView
,所以在使用时需要提供queryset
和serializer_class
属性
class AccountViewSet(viewsets.ModelViewSet):
"""
用于查看和编辑帐户的简单 ViewSet。
"""
queryset = Account.objects.all()
serializer_class = AccountSerializer
permission_classes = [IsAccountAdminOrReadOnly]
注意,可以使用 GenericAPIView
提供的任何标准属性或方法重写。例如,要动态确定视图集应该操作的queryset
,则可以重写 get_queryset()
方法:
class AccountViewSet(viewsets.ModelViewSet):
"""
一个简单的视图集,用于查看和编辑与用户相关的帐户。
"""
serializer_class = AccountSerializer
permission_classes = [IsAccountAdminOrReadOnly]
def get_queryset(self):
return self.request.user.accounts.all()
前面讲到,路由器注册时必须有个依据来指定 name
,当从视图集中删除 queryset
属性后,任何关联的路由器将可能无法自动导出模型的 base_name
,因此您必须指定 base_name
作为路由器注册的一部分。
回到教程👨💻。
这里就来使用ModelViewSet
统一几处与 Snippet 相关的分散的类视图,其中 get、post 和 delete 方法将被 list
,create
,retrieve
,update
和 destroy
替代。
但 SnippetHighlight 中的 get 的操作并逻辑不是标准的create
/update
/delete
actions,它只获取 Snippet 模型的一个字段的信息,因此应该作为一个额外操作独立出来,并接受@action
的装饰。
同时,为了依然能在正式保存数据之前添加 owner 数据,perform_create()
方法会得到保留,但不作为一个额外操作,因为它重写自 ModelViewSet
所继承的 CreateModelMixin
。
...
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import permissions
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]
@action(detail=True,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)
...
4,ReadOnlyModelViewSet
ReadOnlyModelViewSet
类也继承自 GenericAPIView
。它只提供只读操作.list()
和 .retrieve()
。
class AccountViewSet(viewsets.ReadOnlyModelViewSet):
"""
用于查看帐户的简单 ViewSet。
"""
queryset = Account.objects.all()
serializer_class = AccountSerializer
回到教程👨💻。
考虑到我们完全不希望在除了后台之外的地方设置用户信息,所以我们直接使用ReadOnlyModelViewSet
来抽象所有与用户相关的类视图:
...
from rest_framework import viewsets
...
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `retrieve` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
最后,这些内置视图集的关系如下:
(三)自定义 ViewSet 基类
如果需要提供现有内置视图集之外的其他方式自定义操作,一个方法是继承它们,并提供实现,另一种方法就是继承GenericViewSet
并完全自定义实现,或者结合一些mixin
类。
举个例子🌰:
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
二,路由器
(一)django中的路由系统
django中的路由系统又叫做 URL 调度器。
当一个用户请求到达 django 后:
- Django 确定使用根 URLconf 模块(由
ROOT_URLCONF
设置,通常是”djangoprojectname.urls”,但如果传入的HttpRequest
对象提前被中间件设置了urlconf
属性,这个值将被用来代替ROOT_URLCONF
设置)。 - Django 加载该 Python 模块并寻找可用的
urlpatterns
,它是django.urls.path()
与django.urls.re_path()
实例的序列。 - Django 会按顺序遍历序列中的每个 URL 模式,然后会在所请求的URL 匹配到第一个模式后,并与
HttpRequest
对象的path_info
匹配。 - 一旦有 URL 匹配成功,Djagno 导入并调用相关的视图。
- 如果没有 URL 被匹配,或者匹配过程中出现了异常,Django 会调用一个适当的错误处理视图。
在这个 URL 调度过程中,还能实现更详细的工作:
- 将 URL 从根 URLconf 模块分配到具体的应用 URLconf 模块。
- 在 URL 中传递额外参数给视图。
- 配合模板里的
url
标签、视图中的reverse()
等函数、模型中的get_absolute_url()
方法来反向解析 URL。
总之,路由系统的工作就是实现 URL 与视图的正确映射。
(二)Routers in the Rest Framework
Rest Framework 支持自动映射 URL 到视图,并为此提供了一种简单、快速和一致的方式——路由器Routers。
1,使用示例
举个例子🌰:
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls
- 实例化了一个路由器类
SimpleRouter
。 - 调用
register()
方法,将视图集 UserViewSet - 注册到以 ‘users’ 开头的 URL 模式,自动生成一些 URL。
- 将这这些自动生成的 URL 赋值给
urlpatterns
,等待被请求匹配。
实际上register()
方法还允许提供可选的第三个参数:base_name
,目的就是手动指定l类似普通路由的 name
参数的值——这个值是必须的,没有 base_name
时由queryset
属性代替,连queryset
都没有时必须手动指定base_name
。
上面的这个示例将生成以下URL模式:
- URL pattern: ^users/$ Name: ‘user-list’
- URL pattern: ^users/{pk}/$ Name: ‘user-detail’
- URL pattern: ^accounts/$ Name: ‘account-list’
- URL pattern: ^accounts/{pk}/$ Name: ‘account-detail’
URL 模式的开头由 register()
方法的第一个参数指定,会被映射到由 register()
方法的第二个参数指定的视图集的 actions,URL Name 的开头由 register()
方法的第三个参数指定。
当然,这只是最基础的用法,类似于:
re_path(r'^snippets/v1/$', views.SnippetList.as_view(), name='snippet-list'),
但 urls
确实就返回了这种最简单但标准的 URL 。
2,路由器与命名空间
我们可以像这样将自动生成的路由附加到路由列表中:
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
]
urlpatterns += router.urls
如果要将路由转移到一个 URLconf 模块,同样能像标准 URL 那样使通用 include()
函数来实现:
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
url(r'^', include(router.urls)),
]
都到了能转移路由的程度了,自然也应该支持命名空间:
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
url(r'^api/', include(router.urls, namespace='api')),
]
如果使用带超链接序列化器的命名空间,你还需要确保序列化器上的任何 view_name
参数正确地反映命名空间:
# 方法一,让超链接到用户详细信息视图的序列化器字段的view_name参数。
highlight = serializers.HyperlinkedIdentityField(view_name='v1:snippet-highlight', format='html')
# 方法二,在超链接序列化器 Mete 的 extra_kwargs 属性中设置view_name和lookup_field中的一个或两个。
extra_kwargs = {
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
'users': {'lookup_field': 'username'}
}
3,路由器与视图集中的额外操作
通常来说,路由器与视图集配合使用。尽管视图集已经相抽象,但难免不需要自定义一些方法,此时就需要一些装饰器来辅助路由器完成路由映射工作:
@detail_route
@list_route
举个例子🌰:
# 视图集
from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import detail_route
class UserViewSet(ModelViewSet):
...
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
...
将另外生成以下 URL 模式:
URL pattern: ^users/{pk}/set_password/$ Name: 'user-set-password'
如果不想让自定义的额外操作使用自动生成的默认 URL,则可以用装饰器中的url_path
参数进行自定义。举个例子🌰:
from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import detail_route
class UserViewSet(ModelViewSet):
...
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_path='change-password')
def set_password(self, request, pk=None):
...
以上示例现在将生成以下 URL 格式:
URL pattern: ^users/{pk}/set_password/$ Name: 'user-change-password'
还可以同时设置url_path
和url_name
参数对自定义视图的 URL 生成进行额外的控制。具体参考视图集部分。
(三)API 参考
1,SimpleRouter
该路由器包括标准集合 list
, create
, retrieve
, update
, partial_update
和 destroy
这几个 actions 的路由。视图集中还可以使用@detail_route
或@list_route
装饰器标记要被路由的其他方法。
该路由器产生如下格式的 URL。
2,DefaultRouter
该路由器类继承自SimpleRouter
,添加了默认 API 根视图,并可以将格式后缀模式添加到 URL。
回到教程👨💻。
在前面,我们已经将类视图替换为视图集,接下来要做的,自然就是实现路由映射了。
第一种方法,我们还是显式地将路由映射到一组具体的视图中去:先将请求方法与对应的 action 操作通过视图集的 as_view()
方法关联起来,然后挨个放入 app URLconf 模块,这样做的一个好处是能指定 URL 模式(比如显式添加版本号);
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'
})
# app URLconf 模块
urlpatterns = format_suffix_patterns([
path('', api_root),
path('snippets/v1/', snippet_list, name='snippet-list'),
path('snippets/v1/<int:pk>/', snippet_detail, name='snippet-detail'),
path('snippets/v1/<int:pk>/highlight/', snippet_highlight, name='snippet-highlight'),
path('users/v1/', user_list, name='user-list'),
path('users/v1/<int:pk>/', user_detail, name='user-detail')
])
# 根 URLconf 模块不变
urlpatterns = [
path('', include(('snippet.urls', 'snippet'), namespace='v1')),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
第二种方法就是使用路由器,让系统自动为我们实现映射功能,我们需要做的就是向路由器注册适当的视图集到 URL:
# app URLconf 模块
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippet import views
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets/v1', views.SnippetViewSet)
router.register(r'users/v1', views.UserViewSet)
# The API URLs are now determined automatically by the router.
urlpatterns = [
path('', include(router.urls)),
]
- 正在使用的
DefaultRouter
类会自动为我们创建 API 根视图,因此我们现在可以从 views 模块中删除 api_root 方法。
看下一效果:
(四)自定义路由器
路由器的用法相对固定,不太建议自定义路由器类,具体自定义过程,请参考Custom Routers。
到这里,教程就结束啦!!!完结撒花!!!!🎉🎉🎉✨✨✨
在实际中要多加运用和积累呀!!!💪💪💪
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/98053.html