【问题标题】:how to reverse the URL of a ViewSet's custom action in django restframework如何在 django rest 框架中反转 ViewSet 自定义操作的 URL
【发布时间】: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 的正确方法是什么?

【问题讨论】:

    标签: python django url django-rest-framework


    【解决方案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。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-12-04
        • 1970-01-01
        • 1970-01-01
        • 2021-01-16
        • 2020-12-24
        • 1970-01-01
        • 2016-05-27
        • 1970-01-01
        相关资源
        最近更新 更多