DRF: маршрутизатор для ViewSet с полем поиска_иностранного ключа
Используя Django REST Framework 3.12.4, я не могу правильно заставить URL-адреса для набора ViewSet работать, если этот набор ViewSet имеет поле поиска иностранного ключа
У меня есть следующее в models.py
:
class Domain(models.Model):
name = models.CharField(max_length=191, unique=True)
class Token(rest_framework.authtoken.models.Token):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
domain_policies = models.ManyToManyField(Domain, through='TokenDomainPolicy')
class TokenDomainPolicy(models.Model):
token = models.ForeignKey(Token, on_delete=models.CASCADE)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
class Meta:
constraints = [models.UniqueConstraint(fields=['token', 'domain'], name='unique_entry')]
В views.py
у меня есть:
class TokenDomainPolicyViewSet(viewsets.ModelViewSet):
lookup_field = 'domain__name'
permission_classes = (IsAuthenticated,)
serializer_class = serializers.TokenDomainPolicySerializer
def get_queryset(self):
return models.TokenDomainPolicy.objects.filter(token_id=self.kwargs['id'], token__user=self.request.user)
Из TokenDomainPolicyViewSet.lookup_field
видно, что я хотел бы иметь возможность запрашивать конечную точку -detail
, используя поле Domain
связанной точки name
вместо ее первичного ключа. (name
является уникальным для данного токена.)
Я думал, что это можно сделать с помощью lookup_field = 'domain__name'
.
Однако это не совсем работает. Вот мой urls.py
:
tokens_router = SimpleRouter()
tokens_router.register(r'', views.TokenViewSet, basename='token')
tokendomainpolicies_router = SimpleRouter()
tokendomainpolicies_router.register(r'', views.TokenDomainPolicyViewSet, basename='token_domain_policies')
auth_urls = [
path('tokens/', include(tokens_router.urls)), # for completeness only; problem persists if removed
path('tokens/<id>/domain_policies/', include(tokendomainpolicies_router.urls)),
]
urlpatterns = [
path('auth/', include(auth_urls)),
]
Конечная точка list работает нормально (например, /auth/tokens/6f82e9bc-46b8-4719-b99f-2dc0da062a02/domain_policies/
); она возвращает список сериализованных объектов TokenDomainPolicy
.
Однако, допустим, существует объект Domain
с name = 'test.net'
, связанный с этим Token
. Я бы подумал, что могу GET /auth/tokens/6f82e9bc-46b8-4719-b99f-2dc0da062a02/domain_policies/test.net/
, чтобы получить этот объект, но результатом будет 404.
Дополнительные наблюдения:
Это работает , если я устанавливаю
lookup_field = 'domain'
. Однако это приводит к URL, содержащим IDDomain
(например,.../25/
), чего я не хочу. Но на основании этого я делаю вывод, что конечная точка-detail
в принципе маршрутизируется.Это работает , если я добавлю явное переопределение, например,
path('tokens/<id>/domain_policies/<domain__name>/', views.TokenDomainPolicyViewSet.as_view( {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'} ), name='token_domain_policies-detail'),
Однако, почему обязательно явно связывать методы таким образом? (Это не обязательно, если
lookup_field
не задает поиск по иностранному ключу!)Самое странное, что если я установлю
django_extensions
и запущуmanage.py show_urls
, то URL конечной точки-detail
отображается с правильным URL kwarg<domain__name>
, даже без переопределения из предыдущего пункта. Если я добавляю переопределение, то соответствующая строка в выводе отображается как идентичный дубликат.Как может быть так, что набор известных URL остается неизменным как с переопределением, так и без него, но в одном случае конечная точка работает как ожидалось, а в другом случае ответ 404?
Что я упускаю?
Согласно docs
, поиск соответствия по умолчанию игнорирует слеши и символы точки, поэтому test.name
не может быть найден:
Маршрутизатор будет сопоставлять значения поиска, содержащие любые символы, кроме косой черты и точки.
Вы можете найти его также в source
:
lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
Чтобы исправить, просто измените lookup_value_regex
, чтобы разрешить периоды в поиске набора представлений:
class TokenDomainPolicyViewSet(viewsets.ModelViewSet):
lookup_field = 'domain__name'
lookup_value_regex = '[^/]+'