如何在 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的主要内容,如果未能解决你的问题,请参考以下文章