我注意到SO和Reddit等网站使用url结构<basename>/<pk>/<slug>/
来路由到其帖子的详细视图,这让我认为这应该是标准的。有没有一种方法可以通过使用DefaultRouter
和ModelViewset
的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
功能。