DRF:带有外键的 ViewSet 路由器 lookup_field
DRF: router for ViewSet with a foreign-key lookup_field
使用 Django REST Framework 3.12.4,如果 ViewSet 具有外键查找字段,我无法正确获取 URLs 以使 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)
user = models.ForeignKey(User, on_delete=models.CASCADE)
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'
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
中看到,我希望能够通过使用相关的 Domain
的 name
字段而不是它的字段来查询 -detail
端点首要的关键。 (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)),
]
列表端点工作正常(例如/auth/tokens/6f82e9bc-46b8-4719-b99f-2dc0da062a02/domain_policies/
);它 returns 序列化 TokenDomainPolicy
个对象的列表。
但是,假设有一个 Domain
对象 name = 'test.net'
与此 Token
相关。我想我可以 GET /auth/tokens/6f82e9bc-46b8-4719-b99f-2dc0da062a02/domain_policies/test.net/
来检索这个对象,但结果是 404.
补充观察:
如果我设置 lookup_field = 'domain'
,它几乎 会 起作用。但是,这会导致 URL 包含 Domain
的 ID(如 .../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
,-detail
端点 URL 显示为正确 <domain__name>
URL kwarg,甚至 没有 上一个项目符号的覆盖。如果我添加覆盖,输出中的相应行会显示两次 相同的副本。
已知 URL 的集合如何在有或没有覆盖的情况下保持不变,但在一种情况下端点按预期工作,而在另一种情况下响应为 404?
我错过了什么?
根据 docs
,默认匹配查找将忽略斜杠和句点字符,这就是找不到 test.name
的原因:
The router will match lookup values containing any characters except slashes and period characters.
您也可以在 source
中找到它:
lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
所以要修复,只需更改 lookup_value_regex
以在视图集的查找中允许句点:
class TokenDomainPolicyViewSet(viewsets.ModelViewSet):
lookup_field = 'domain__name'
lookup_value_regex = '[^/]+'
使用 Django REST Framework 3.12.4,如果 ViewSet 具有外键查找字段,我无法正确获取 URLs 以使 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)
user = models.ForeignKey(User, on_delete=models.CASCADE)
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'
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
中看到,我希望能够通过使用相关的 Domain
的 name
字段而不是它的字段来查询 -detail
端点首要的关键。 (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)),
]
列表端点工作正常(例如/auth/tokens/6f82e9bc-46b8-4719-b99f-2dc0da062a02/domain_policies/
);它 returns 序列化 TokenDomainPolicy
个对象的列表。
但是,假设有一个 Domain
对象 name = 'test.net'
与此 Token
相关。我想我可以 GET /auth/tokens/6f82e9bc-46b8-4719-b99f-2dc0da062a02/domain_policies/test.net/
来检索这个对象,但结果是 404.
补充观察:
如果我设置
lookup_field = 'domain'
,它几乎 会 起作用。但是,这会导致 URL 包含Domain
的 ID(如.../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
,-detail
端点 URL 显示为正确<domain__name>
URL kwarg,甚至 没有 上一个项目符号的覆盖。如果我添加覆盖,输出中的相应行会显示两次 相同的副本。已知 URL 的集合如何在有或没有覆盖的情况下保持不变,但在一种情况下端点按预期工作,而在另一种情况下响应为 404?
我错过了什么?
根据 docs
,默认匹配查找将忽略斜杠和句点字符,这就是找不到 test.name
的原因:
The router will match lookup values containing any characters except slashes and period characters.
您也可以在 source
中找到它:
lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
所以要修复,只需更改 lookup_value_regex
以在视图集的查找中允许句点:
class TokenDomainPolicyViewSet(viewsets.ModelViewSet):
lookup_field = 'domain__name'
lookup_value_regex = '[^/]+'