为啥我的 Django REST Framework 视图集 URL 没有解析?

Posted

技术标签:

【中文标题】为啥我的 Django REST Framework 视图集 URL 没有解析?【英文标题】:Why isn't my Django REST Framework viewset URL resolving?为什么我的 Django REST Framework 视图集 URL 没有解析? 【发布时间】:2020-04-06 13:53:25 【问题描述】:

我正在使用 Django REST 框架构建 API,使用:

Viewsets 非标准命名空间 HyperlinkedModelSerializer URLPathVersioning

我正在尝试删除许多我认为与我的应用程序无关的方面,因为它是大型、成熟和封闭源代码的。但是,如果您能想到我遗漏的任何内容,我会提供更多信息。

我已配置我的 HyperlinkedModelSerializer 以支持我的自定义命名空间视图集。

class ItemSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        fields = ["id", "url", "description", "source_id", "location", "test"]
        model = models.Item
        extra_kwargs = 
            "url": 
                "view_name": "api:v1:questions:item-detail",
                "lookup_field": "pk",
            
        

我的Viewset

class ItemViewSet(APIMixin, viewsets.ReadOnlyModelViewSet):
    queryset = models.Item.objects.all()
    serializer_class = serializers.ItemSerializer

# This is largely inconsequential, except for maybe the versioning class.
class APIMixin(SerializerExtensionsAPIViewMixin):

    authentication_classes = [authentication.SessionAuthentication]

    filter_backends = [
        django_filters.DjangoFilterBackend,
        filters.OrderingFilter,
    ]

    ordering_fields = []

    pagination_class = pagination.DefaultPageNumberPagination

    parser_classes = [
        parsers.JSONParser,
        parsers.FormParser,
        parsers.MultiPartParser,
    ]

    permission_classes = [
        permissions.IsAuthenticated,
        permissions.DjangoObjectPermissions,
    ]

    versioning_class = versioning.URLPathVersioning

urls.py文件:

from rest_framework.routers import DefaultRouter
from django.conf.urls import url, include
from . import viewsets


router = DefaultRouter()

router.register(r"^test", viewsets.TestViewSet)

urlpatterns = [url(r"^", include(router.urls, namespace="questions"))]

项目中一系列最小的其他 urls.py 文件包含这些文件,这些文件只是应用了命名空间。不包括在内,因为这似乎不是问题,因为我可以在下面证明命名空间按预期工作。

从视图集(列表或详细信息)加载视图会引发以下问题:

Traceback:

File "/usr/local/lib/python3.5/dist-packages/rest_framework/reverse.py" in reverse
  41.             url = scheme.reverse(viewname, args, kwargs, request, format, **extra)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/versioning.py" in reverse
  88.             viewname, args, kwargs, request, format, **extra

File "/usr/local/lib/python3.5/dist-packages/rest_framework/versioning.py" in reverse
  25.         return _reverse(viewname, args, kwargs, request, format, **extra)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/reverse.py" in _reverse
  60.     url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)

File "/usr/local/lib/python3.5/dist-packages/django/urls/base.py" in reverse
  91.     return force_text(iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)))

File "/usr/local/lib/python3.5/dist-packages/django/urls/resolvers.py" in _reverse_with_prefix
  497.         raise NoReverseMatch(msg)

During handling of the above exception (Reverse for 'item-detail' with keyword arguments ''pk': UUID('380fda25-196d-41ef-93c6-5216d54561a2'), 'version': 'v1'' not found. 2 pattern(s) tried: ['api/(?P<version>v1)/questions/^item/(?P<pk>[^/.]+)\\.(?P<format>[a-z0-9]+)/?$', 'api/(?P<version>v1)/questions/^item/(?P<pk>[^/.]+)/$']), another exception occurred:

File "/usr/local/lib/python3.5/dist-packages/rest_framework/relations.py" in to_representation
  393.             url = self.get_url(value, self.view_name, request, format)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/relations.py" in get_url
  331.         return self.reverse(view_name, kwargs=kwargs, request=request, format=format)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/reverse.py" in reverse
  45.             url = _reverse(viewname, args, kwargs, request, format, **extra)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/reverse.py" in _reverse
  60.     url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)

File "/usr/local/lib/python3.5/dist-packages/django/urls/base.py" in reverse
  91.     return force_text(iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)))

File "/usr/local/lib/python3.5/dist-packages/django/urls/resolvers.py" in _reverse_with_prefix
  497.         raise NoReverseMatch(msg)

During handling of the above exception (Reverse for 'item-detail' with keyword arguments ''pk': UUID('380fda25-196d-41ef-93c6-5216d54561a2'), 'version': 'v1'' not found. 2 pattern(s) tried: ['api/(?P<version>v1)/questions/^item/(?P<pk>[^/.]+)\\.(?P<format>[a-z0-9]+)/?$', 'api/(?P<version>v1)/questions/^item/(?P<pk>[^/.]+)/$']), another exception occurred:

File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/exception.py" in inner
  41.             response = get_response(request)

File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "/usr/local/lib/python3.5/dist-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/views/decorators/csrf.py" in wrapped_view
  58.         return view_func(*args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/viewsets.py" in view
  114.             return self.dispatch(request, *args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py" in _wrapper
  67.             return bound_func(*args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/views/decorators/cache.py" in _wrapped_view_func
  57.         response = view_func(request, *args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/django/utils/decorators.py" in bound_func
  63.                 return func.__get__(self, type(self))(*args2, **kwargs2)

File "/opt/project/project/api/v1/views.py" in dispatch
  57.         return super(APIMixin, self).dispatch(*args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/views.py" in dispatch
  505.             response = self.handle_exception(exc)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/views.py" in handle_exception
  465.             self.raise_uncaught_exception(exc)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/views.py" in raise_uncaught_exception
  476.         raise exc

File "/usr/local/lib/python3.5/dist-packages/rest_framework/views.py" in dispatch
  502.             response = handler(request, *args, **kwargs)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/mixins.py" in list
  43.             return self.get_paginated_response(serializer.data)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py" in data
  757.         ret = super().data

File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py" in data
  261.                 self._data = self.to_representation(self.instance)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py" in to_representation
  675.             self.child.to_representation(item) for item in iterable

File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py" in <listcomp>
  675.             self.child.to_representation(item) for item in iterable

File "/usr/local/lib/python3.5/dist-packages/rest_framework/serializers.py" in to_representation
  526.                 ret[field.field_name] = field.to_representation(attribute)

File "/usr/local/lib/python3.5/dist-packages/rest_framework/relations.py" in to_representation
  408.             raise ImproperlyConfigured(msg % self.view_name)

Exception Type: ImproperlyConfigured at /api/v1/questions/item/
Exception Value: Could not resolve URL for hyperlinked relationship using view name "api:v1:questions:item-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.

虽然这就是问题本身的表现方式,但在故障排除过程中,我尝试移除所有中间人并将问题提炼成最纯粹的形式。

在 Django shell 中,向解析器提供(虚构的)URL 成功。我可以(重要地)看到:

url_name namespaces kwargs
In [26]: resolve('/api/v1/questions/item/25/')
Out[26]: ResolverMatch(func=project.questions.api.v1.viewsets.ItemViewSet, args=(), kwargs='version': 'v1', 'pk': '25', url_name=item-detail, app_names=[], namespaces=['api', 'v1', 'questions'])

现在,如果我使用从中收集到的信息来反转给定的 URL 名称:

In [24]: reverse('api:v1:questions:item-detail', kwargs='version': 'v1', 'pk': '25')
---------------------------------------------------------------------------
NoReverseMatch                            Traceback (most recent call last)
<ipython-input-24-922da70ed04c> in <module>()
----> 1 reverse('api:v1:questions:item-detail', kwargs='version': 'v1', 'pk': '25')

/usr/local/lib/python3.5/dist-packages/django/urls/base.py in reverse(viewname, urlconf, args, kwargs, current_app)
     89             resolver = get_ns_resolver(ns_pattern, resolver)
     90 
---> 91     return force_text(iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)))
     92 
     93 

/usr/local/lib/python3.5/dist-packages/django/urls/resolvers.py in _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs)
    495                 "a valid view function or pattern name." % 'view': lookup_view_s
    496             )
--> 497         raise NoReverseMatch(msg)
    498 
    499 

NoReverseMatch: Reverse for 'item-detail' with keyword arguments ''version': 'v1', 'pk': '25'' not found. 2 pattern(s) tried: ['api/(?P<version>v1)/questions/^item/(?P<pk>[^/.]+)\\.(?P<format>[a-z0-9]+)/?$', 'api/(?P<version>v1)/questions/^item/(?P<pk>[^/.]+)/$']

(我使用的是 Django 的标准 reverse,而不是 DRF 的 reverse,因此没有与版本控制相关的事情妨碍)。

我的理解是,此异常文本表明 URL 名称已成功匹配(因此显示的尝试匹配),但 kwargs 不匹配。但是,据我所知,我提供的 kwargs 完全符合 Django 的预期,正如上面的 resolve()ing 所示。

【问题讨论】:

urls.py的路径是什么? 你使用的是哪个版本的 Django? 【参考方案1】:

传递给reverseviewname 参数可以包含URL namespaces,而不是路径组件。

既然文件路径(大概)是api/questions/urls.py,那么"view_name"应该是:

"view_name": "api:questions:item-detail",

【讨论】:

以上是关于为啥我的 Django REST Framework 视图集 URL 没有解析?的主要内容,如果未能解决你的问题,请参考以下文章

尽管有 AllowAny 权限,django-rest-framework 在 POST、PUT、DELETE 上返回 403 响应

为啥我的 Django REST Framework 视图集 URL 没有解析?

django.test.client 上的 Django rest 框架导入错误

无法使用 Django Rest 框架发送压缩的 gzip 数据

Django REST to React - 无需密码即可获取社交身份验证令牌

如何使用 Django Rest Framework 将 url 字段添加到序列化程序