Rest-framework专栏讲解(七):Router
Rest-framework专栏讲解(七):Router
目录
路由
一些 Web 框架(如 Rails)提供了一种能够自动确定应用程序的 URL 如何映射到处理请求的功能。
REST framework 增加了对 Django 自动 URL 路由的支持,并提供了一种将视图逻辑连接到一组 URL 的简单,高效和一致的方式。
用法
下面是一个使用 SimpleRouter
的简单 URL 配置示例。
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls
register()
方法有两个必须参数:
prefix
- 设置这组路由的前缀。viewset
- 设置对应的视图集类。
或者,您也可以指定一个附加参数:
base_name
- 用于创建的 URL 名称的基础。如果未设置,将根据视图集的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'
注意:
base_name
参数用于指定视图名称模式的初始部分。在上面的例子中,是user
或account
部分。
通常,您不需要指定 base_name
参数,但是如果您有一个视图集定义了自定义 get_queryset
方法,那么该视图集可能没有设置 .queryset
属性。如果此时尝试注册该视图,则会看到如下所示的错误:
'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.
'base_name'
参数未指定,并且无法自动确定视图中的名称,因为它没有'.queryset'
属性。
这时候就需要在注册视图集时显式设置 base_name
参数,因为它无法从模型名称中自动确定。
使用 include 与路由
路由实例上的 .urls
属性是一个标准的 URL patterns。关于如何包含这些 URL,有许多不同的样式。
例如,可以将 router.urls
附加到现有视图的列表中...
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
]
urlpatterns += router.urls
另外,你也可以使用 Django 的 include
函数,比如...
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
url(r'^', include(router.urls)),
]
还可以设置 namespace。
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
url(r'^api/', include(router.urls, namespace='api')),
]
如果对超链接序列化器使用命名空间,则还需要确保序列化器上的任何 view_name
参数都能正确反映命名空间。在上面的示例中,您需要为超链接到用户详细信息视图的序列化程序字段包含诸如 view_name='api:user-detail'
之类的参数。
额外的链接和操作
用 @detail_route
或 @list_route
装饰的 视图上的任何方法 也将被路由。例如,在 UserViewSet
类中给出这样的方法:
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 参数对其进行自定义。
例如,如果您想将我们的自定义操作的URL更改为 ^users/{pk}/change-password/$
,则可以编写:
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}/change-password/$
Name:'user-change-password'
如果您不想使用生成的默认名称,则可以使用 url_name 参数对其进行自定义。
例如,如果您想将自定义操作的名称更改为 'user-change-password'
,则可以编写:
from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import detail_route
class UserViewSet(ModelViewSet):
...
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_name='change-password')
def set_password(self, request, pk=None):
...
上面的例子现在将生成以下URL模式:
- URL pattern:
^users/{pk}/set_password/$
Name:'user-change-password'
可以同时使用 url_path
和 url_name
参数。
更多相关信息请看 视图集:标记额外的路由行为。
API 参考
SimpleRouter
SimpleRouter
包含标准的list
,create
,retrieve
,update
,partial_update
和destroy
action。SimpleRouter
还支持视图集使用@detail_route
或@list_route
装饰器标记其他要路由的方法。
URL Style | HTTP Method | Action | URL Name |
---|---|---|---|
{prefix}/ | GET | list | {basename}-list |
POST | create | ||
{prefix}/{methodname}/ | GET, 或者由 methods 参数指定 | @list_route 装饰的方法 | {basename}- |
{prefix}/{lookup}/ | GET | retrieve | {basename}-detail |
PUT | update | ||
PATCH | partial_update | ||
DELETE | destroy | ||
{prefix}/{lookup}/{methodname}/ | GET, 或者由 methods 参数指定 | @detail_route 装饰的方法 | {basename}- |
默认情况下,由 SimpleRouter
创建的 URL 附加了尾部斜杠。在实例化路由器时,可以通过将 trailing_slash
参数设置为 False
来修改此行为。例如:
router = SimpleRouter(trailing_slash=False)
尾部斜杠在 Django 中是常规的,但在其他一些框架(如 Rails)中默认不使用。选择使用哪种风格在很大程度上是一个偏好问题,尽管一些 JavaScript 框架可能会期望特定的路由风格。
SimpleRouter
将匹配包含除斜杠和句点字符以外的任何字符的 lookup 值。对于更严格(或宽松)的 lookup pattern,请在视图集上设置 lookup_value_regex
属性。例如,您可以将 lookup 限制为有效的 UUID:
class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'my_model_id'
lookup_value_regex = '[0-9a-f]{32}'
举个栗子
在我们定义好了 MedusaBlogViewSet
的情况下, 我们注册 URL 的时候仅需要:
#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'medusa/blog', MedusaBlogViewSet)
urlpatterns = router.urls
register()
有两个强制性
的参数:
prefix
:用于这组路由的 URL 前缀字符串, 用于路由匹配viewset
:你定义的视图集
如果你的视图集实现了获取列表/获取详情/新增/删除/修改的方法, 那你定义路由将会解析成这样:
URL 格式 | 请求方法 | 请求说明 | 视图集方法 |
---|---|---|---|
^medusa/blog$ | GET | 获取列表 | list() |
^medusa/blog/{pk}$ | GET | 获取详情 | retrieve() |
^medusa/blog$ | POST | 新增 | create() |
^medusa/blog/{pk}$ | PUT | 更新 | update() |
^medusa/blog/{pk}$ | DELETE | 删除 | destroy() |
在你的视图集没有指定 queryset
属性或者自定义了 get_queryset()
方法的时候, 你可能会看到这样一条错误信息:
'basename' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.
所以你需要指定一个可选参数:basename
, 默认情况下你不需要手动指定它的值, 它是创建 URL 名称的基础属性, 一般是用你指定的 queryset
属性值自动设置的。
默认情况下路由都需要添加 /
作为路由结尾, 你也可以修改该规则, 只需要修改参数 trailing_slash=False
即可:
router = SimpleRouter(trailing_slash=False)
该路由器生成 URL 的方式:
include
在上面的例子中, 我们使用了 urlpatterns = router.urls
的方式注册了路由对象, 我们通常会使用 include
进行路由注册, 在 Django 中可能有一些我们自定义的视图, 不需要使用 SimpleRouter
进行注册:
#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from django.conf.urls import url
from django.urls import include
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'medusa/blog', MedusaBlogViewSet)
urlpatterns = [
..., # 其他路由配置
url(r'^', include(router.urls)),
]
路由绑定其他操作
例如你的项目安排上, 安排关于用户模块是在配置的界面, 那么你的路由可能是这样的:
/api/v1.0/configure/user
那我对用户的配置可能有以下几个方法处理:
GET
:获取用户详情/列表PUT
:修改用户信息POST
:新增用户信息DELETE
:删除用户信息
问题来了, 如果我不想创建一个新的关于用户的视图类, 但是我又想增加一个修改密码的 API 接口怎么办? 如果你从事过其他产品 API 调度工作的时候, 你可能会浮现这样类型的接口:
/api/v1.0/configure/user/1/reset
很规范的接口方式, 那你在你的 ViewSet 里怎么体现呢?
#!/usr/bin/env python
# _*_ coding: UTF-8 _*_
from rest_framework.decorators import action
from rest_framework.viewsets import ModelViewSet
class UserViewSet(ModelViewSet):
...
@action(methods=['post'], detail=True)
def reset(self, request, pk=None):
...
是的, 使用 action
装饰器装饰, 并指定参数
即可, 默认情况下你生成的 URL 是根据你的函数名称生成了, 你也可以通过 url_name
和 url_path
制定路由的后缀名称, 也可以通过 permission_classes
来制定用户访问权限。
DefaultRouter
DefaultRouter
与上面的 SimpleRouter
相似,但还包含一个默认的 API 根视图,该视图返回一个包含指向所有列表视图的超链接的响应。它还为可选的 .json
风格格式后缀生成路由。 当然, 该路由的路径也会使用 /
结尾, 你可以用 trailing_slash=False
来弃用该规则:
router = DefaultRouter(trailing_slash=False)
该路由生成 URL 的方式:
URL Style | HTTP Method | Action | URL Name |
---|---|---|---|
[.format] | GET | 自动生成的根视图 | api-root |
{prefix}/[.format] | GET | list | {basename}-list |
POST | create | ||
{prefix}/{methodname}/[.format] | GET, 或者由 methods 参数指定 | @list_route 装饰的方法 | {basename}- |
{prefix}/{lookup}/[.format] | GET | retrieve | {basename}-detail |
PUT | update | ||
PATCH | partial_update | ||
DELETE | destroy | ||
{prefix}/{lookup}/{methodname}/[.format] | GET, 或者由 methods 参数指定 | @detail_route 装饰的方法 | {basename}- |
注意:我在使用 3.7.7 版本时,发现要写成
{prefix}[.format]/
风格才能访问,{prefix}/[.format]
风格会报 404,不知道是我设置问题还是官方更新了
自定义路由
自定义路由并不是你经常需要做的事情,但是如果你对 API 的 URL 是如何构建的有特定的要求的话,它会很有用。这样做可以让你以可重用的方式封装 URL 结构,确保你不必为每个新视图明确编写 URL 模式。
实现自定义路由的最简单方法是对现有路由类之一进行子类化。.routes
属性用于对将映射到每个视图集的 URL 模式进行模板化。.Routes
属性是一个 Route
列表(Route
的是一个 namedtuple)。
其实在开发中这不是你使用路由器的最好方式, 但是在你需要自定义 URL 格式的时候使用这个方式将会变得很有效, 而实现自定义路由是将现有路由作为子类之一, 其 .routes
属性是 Route
的命名元组的列表数据, 功能是用于模板化将映射到每个视图集的 URL 模式。
Route
命名元祖的参数有:
url
:代表需要路由的 URL 字符串, 可以包含以下格式字符串:{prefix}
:用于这组路由器的前缀字符串{lookup}
:匹配单个实例的 lookup field, 如ID{trailing_slash}
:可以是 '/' 或空字符串,具体取决于trailing_slash
参数
mapping
:HTTP 方法名称到视图方法的映射name
:用于调用reverse
时的 URL 的名称。可能包含以下格式字符串:{basename}
:创建的 URL 名称的基础
initkwargs
: 实例化视图时应传递的任何其他参数的字典。注意,suffix
参数被保留用于标识视图集类型,在生成视图名称和 breadcrumb 链接时使用。
其实以上文字内容是官方文档的描述内容加上我自己的理解装饰了一下, 但是看到这几行字的描述信息, 确实是不知道它的功能到底怎么样定义, 刚好看到某位大佬的博客, 参考并实践了一下:
附参考的博客地址:www.cnblogs.com/liubiao/p/6…
自定义动态路由
你还可以自定义 @list_route
和 @detail_route
装饰器的路由方式。要路由这两个装饰器中的一个或两个,请在 .routes
列表中包含一个 DynamicListRoute
和/或 DynamicDetailRoute
(别忘了类型是 namedtuple)。
DynamicListRoute
和 DynamicDetailRoute
的参数是:
url: 表示要路由的 URL 的字符串。可以包含与 Route
相同的格式字符串,并且还接受 {methodname}
和 {methodnamehyphen}
格式的字符串。
name: 用于调用 reverse
时的名称。可以包含以下格式字符串:{basename}
, {methodname}
和 {methodnamehyphen}
。
initkwargs: 实例化视图时应传递的任何其他参数的字典。
举个栗子
以下示例只会路由 list
和 retrieve
action,并且不使用尾部斜杠约定。
from rest_framework.routers import Route, DynamicDetailRoute, SimpleRouter
class CustomReadOnlyRouter(SimpleRouter):
"""
A router for read-only APIs, which doesn't use trailing slashes.
"""
routes = [
Route(
url=r'^{prefix}$',
mapping={'get': 'list'},
name='{basename}-list',
initkwargs={'suffix': 'List'}
),
Route(
url=r'^{prefix}/{lookup}$',
mapping={'get': 'retrieve'},
name='{basename}-detail',
initkwargs={'suffix': 'Detail'}
),
DynamicDetailRoute(
url=r'^{prefix}/{lookup}/{methodnamehyphen}$',
name='{basename}-{methodnamehyphen}',
initkwargs={}
)
]
让我们来看看 CustomReadOnlyRouter
为一个简单的视图集生成的路由。
views.py
:
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
A viewset that provides the standard actions
"""
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_field = 'username'
@detail_route()
def group_names(self, request, pk=None):
"""
Returns a list of all the group names that the given
user belongs to.
"""
user = self.get_object()
groups = user.groups.all()
return Response([group.name for group in groups])
router = CustomReadOnlyRouter()
router.register('users', UserViewSet)
urlpatterns = router.urls
将生成以下映射...
URL | HTTP Method | Action | URL Name |
---|---|---|---|
/users | GET | list | user-list |
/users/ | GET | retrieve | user-detail |
/users/{username}/group-names | GET | group_names | user-group-names |
有关设置 .routes
属性的另一个示例,请参阅 SimpleRouter
类的源代码。
自定义路由器进阶
如果想提供完全自定义的行为,可以继承 BaseRouter
并覆盖 get_urls(self)
方法。该方法应检查已注册的视图集并返回一组 URL 模式。可以通过访问 self.registry
属性来检查注册的 prefix,viewset 和 basename tuples。
你可能还想覆盖 get_default_base_name(self,viewset)
方法,或者在向路由注册视图集时始终显式设置 base_name
参数。
第三方软件包
以下是可用的第三方包。