DRF默认路由器pk/slug



我注意到SO和Reddit等网站使用url结构<basename>/<pk>/<slug>/来路由到其帖子的详细视图,这让我认为这应该是标准的。有没有一种方法可以通过使用DefaultRouterModelViewset的django-rest框架来实现这一点?

示例views.py:

class PostViewSet(viewsets.ModelViewSet):
...
lookup_fields = ['pk', 'slug']

示例urls.py:

router = DefaultRouter()
router.register('posts', PostViewSet, basename='post')
app_name = 'api'
urlpatterns = [
path('', include(router.urls)),
]

URL路由:

/api/post/   api.views.PostViewSet  api:post-list
/api/post/<pk>/<slug>/    api.views.PostViewSet  api:post-detail
/api/post/<pk>/<slug>.<format>/  api.views.PostViewSet  api:post-detail
/api/post.<format>/ api.views.PostViewSet  api:post-list

您可以使用MultipleLookupField混合策略,并为此在视图集中定义自定义get_object方法。

class PostViewSet(viewsets.ModelViewSet):
lookup_fields = ['pk', 'slug']
# ...
def get_object(self):
if all(arg in self.kwargs for arg in self.lookup_fields):
# detected the custom URL pattern; return the specified object
qs = self.get_queryset()
qs_filter = {field: self.kwargs[field] for field in self.lookup_fields}
obj = get_object_or_404(qs, **qs_filter)
self.check_object_permissions(self.request, obj)
return obj
else: # missing lookup fields
raise InvalidQueryError("Missing fields")
class InvalidQueryError(APIException):
status_code = status.HTTP_400_BAD_REQUEST

在这种情况下,我只是直接在视图集中覆盖get_object。但是您可以将其作为一个mixin类,以便轻松地将其包含到其他视图集中。

然而,默认路由器不会自动将此URL模式添加到您的应用程序中,因此您必须手动添加。

urlpatterns = [
...,
path('api/post/<int:pk>/<str:slug>', views.PostViewSet.as_view()),
]

我深入研究了DefaultRouter的源代码,并提出了一个通过单元测试的解决方案。

DRF路由器源代码

创建文件路由器.py

from rest_framework.routers import DefaultRouter
class CustomRouter(DefaultRouter):
def get_lookup_regex(self, viewset, lookup_prefix=''):
combined_lookup_field = getattr(viewset, 'combined_lookup_field', None)
if combined_lookup_field:
multi_base_regex_list = []

for lookup_field in combined_lookup_field:
base_regex = '(?P<{lookup_prefix}>{lookup_value})'
lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
multi_base_regex_list.append(
base_regex.format(
lookup_prefix=lookup_field,
lookup_value=lookup_value
)
)
return'/'.join(multi_base_regex_list)
return super().get_lookup_regex(viewset, lookup_prefix)

替换lookup_field或在views.py 中添加以下内容

class PostViewSet(viewsets.ModelViewSet):
...
combined_lookup_field = ['pk', 'slug']

在urls.py 中将DefaultRouter替换为CustomRouter

from .routers import CustomRouter
router = CustomRouter()
router.register('posts', PostViewSet, basename='post')
app_name = 'api'
urlpatterns = [
path('', include(router.urls)),
]

就这样!它仍然包含DefaultRouter的所有其他功能,并且将仅在定义了combined_lookup_field的视图上使用添加的逻辑。它还支持视图中的@action功能。

相关内容

最新更新