如何在 django rest 框架中反转 ViewSet 自定义操作的 URL

Posted

技术标签:

【中文标题】如何在 django rest 框架中反转 ViewSet 自定义操作的 URL【英文标题】:how to reverse the URL of a ViewSet's custom action in django restframework 【发布时间】:2018-12-21 09:17:11 【问题描述】:

我已经为 ViewSet 定义了一个自定义操作

from rest_framework import viewsets

class UserViewSet(viewsets.ModelViewSet):
    @action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny]) 
    def gender(self, request):
        ....

并且viewset以常规方式注册到url

from django.conf.urls import url, include                                          

from rest_framework import routers                                                 
from api import views                                                              


router = routers.DefaultRouter()                                                   
router.register(r'users', views.UserViewSet, base_name='myuser')                   

urlpatterns = [                                                                    
    url(r'^', include(router.urls)),                                               
]   

网址/api/users/gender/ 有效。但我不知道如何在单元测试中使用reverse 来获取它。 (我当然可以对这个 URL 进行硬编码,但从代码中获取它会很好)

根据django documentation,下面的代码应该可以工作

reverse('admin:app_list', kwargs='app_label': 'auth')
# '/admin/auth/'

但我尝试了以下方法,但它们不起作用

reverse('myuser-list', kwargs='app_label':'gender')
# errors out
reverse('myuser-list', args=('gender',))
# '/api/users.gender'

在django-restframework documentation中,有一个函数叫做reverse_action。但是,我的尝试没有奏效

from api.views import UserViewSet
a = UserViewSet()
a.reverse_action('gender') # error out
from django.http import HttpRequest
req = HttpRequest()
req.method = 'GET'
a.reverse_action('gender', request=req)  # still error out

反转该操作的 URL 的正确方法是什么?

【问题讨论】:

【参考方案1】:

您可以使用 reverse 只需添加到视图集的基本名称操作:

reverse('myuser-gender') 

请参阅related part 的文档。

【讨论】:

您可能需要添加应用名称:reverse('api:myuser-gender') @A.J. docs 中的示例:view.reverse_action('set-password', args=['1']) 我可以补充一下,如果函数名称是gender_something,您将需要使用'gender-something' 如果您的操作由两个用“_”分隔的单词组成,那么在reverse 中您需要放置一个“-” 就像@MatthewHegarty 指出的那样,您可能还需要添加应用程序命名空间。您可以通过先运行python manage.py show_urls 来节省时间,如果您正在调试,可以从那里找到 URL 名称。【参考方案2】:

基于DRF Doc。

from rest_framework import routers

router = routers.DefaultRouter()

view = UserViewSet()
view.basename = router.get_default_basename(UserViewSet)
view.request = None

或者你可以根据需要设置请求。

view.request = req

最后,你可以得到一个反向操作的 URL 并使用它。

url = view.reverse_action('gender', args=[])

【讨论】:

【参考方案3】:

您可以在启动时使用get_urls() 方法打印给定router 的所有可逆url 名称

在urls.py中最后一项注册到router = DefaultRouter()变量后,可以添加:

#router = routers.DefaultRouter()
#router.register(r'users', UserViewSet)
#...
import pprint
pprint.pprint(router.get_urls())

下次初始化您的应用时,它将打印到标准输出,例如

[<RegexURLPattern user-list ^users/$>,
 <RegexURLPattern user-list ^users\.(?P<format>[a-z0-9]+)/?$>,
 <RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)/$>,
 <RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>,         
#...
]

其中 'user-list' 和 'user-detail' 是可以给 reverse(...) 的名称

【讨论】:

为什么users/pk后面有个点,不应该是'/'吗? [^/.]+)\.(?P[a-z0-9]+)/?$>【参考方案4】:

如果您想专门使用UserViewset().reverse_action(),您可以通过将basenamerequest = None 分配给您的ViewSet 来实现:

from rest_framework import viewsets

class UserViewSet(viewsets.ModelViewSet):
    basename = 'user'
    request = None

    @action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny]) 
    def gender(self, request):
        ....

在 urls.py 中:

router.register('user', UserViewSet, basename=UserViewSet.basename)

然后调用url = UserViewset().reverse_action('gender')url = UserViewset().reverse_action(UserViewSet().gender.url_name) 将返回正确的url。

编辑: 上述方法仅在调用reverse_action() 一次时有效,因为ViewSetMixin.as_view() 方法在实例化时会覆盖basename。这可以通过添加GenericViewSet(或ModelViewSet,如果愿意)的自定义子类来解决,如下所示:

from django.utils.decorators import classonlymethod
from rest_framework.viewsets import GenericViewSet

class ReversibleViewSet(GenericViewSet):
    basename = None
    request = None

    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        basename = cls.basename
        view = super().as_view(actions, **initkwargs)
        cls.basename = basename
        return view

并为特定的 ViewSet 子类化此类,根据需要设置 basename。

【讨论】:

以上是关于如何在 django rest 框架中反转 ViewSet 自定义操作的 URL的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django REST 框架中使用事务?

如何在 django rest 框架中添加自定义权限和角色

如何在 django rest 框架中定义列表字段?

如何在 Django REST 框架中更改字段名称

如何在 django REST 框架中修改 request.data

如何在 django rest 框架 ModelSerializer 中覆盖模型字段验证