如何用Django REST ModelViewSet按指定图书馆过滤图书?
解决方法:用ModelViewSet实现按图书馆过滤图书
你的核心问题在于ModelViewSet的queryset属性是在类初始化时就执行的,无法直接获取请求时的URL参数,而且DefaultRouter的注册方式不支持带捕获参数的路径。下面是分步解决的方案:
1. 重写BookViewSet的get_queryset方法
把原来硬编码的queryset替换成动态获取URL参数并过滤的逻辑:
class BookViewSet(viewsets.ModelViewSet): serializer_class = BookSerializer def get_queryset(self): # 从URL的kwargs中提取library_id参数 library_id = self.kwargs.get('library_id') # 过滤出属于该图书馆的所有图书 return Book.objects.filter(which_library=library_id) # 可选:如果需要创建图书时自动关联到当前图书馆,重写perform_create def perform_create(self, serializer): # 获取指定的图书馆实例 library = Library.objects.get(pk=self.kwargs['library_id']) # 保存时自动设置which_library字段 serializer.save(which_library=library)
为什么要这么做?因为queryset属性是在类加载时就执行的,此时还没有请求进来,根本拿不到library_id这个URL参数。而get_queryset方法是在每次请求时调用的,能动态获取当前请求的参数。
2. 修正URL配置
DefaultRouter的register方法不支持带捕获组(比如(?P<library_id>[0-9]+))的路径,所以需要手动为BookViewSet映射URL,同时让路径更符合RESTful风格(把图书馆ID放在前面,比如/libraries/1/books/):
from django.urls import path, include from rest_framework import routers router = routers.DefaultRouter() router.register(r'libraries', LibraryViewSet) urlpatterns = [ # 保留原来的图书馆路由 path('', include(router.urls)), # 图书列表和创建接口 path('libraries/<int:library_id>/books/', BookViewSet.as_view({ 'get': 'list', 'post': 'create' }), name='library-books'), # 单本图书的详情、更新、删除接口 path('libraries/<int:library_id>/books/<int:pk>/', BookViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }), name='library-book-detail'), ]
这样配置后:
- GET
/libraries/1/books/会返回ID为1的图书馆下的所有图书 - POST
/libraries/1/books/会创建一本属于该图书馆的图书(如果重写了perform_create,不需要在请求体里传which_library) - GET
/libraries/1/books/2/会返回该图书馆下ID为2的图书(如果存在)
可选:使用嵌套路由包(更简洁)
如果你不想手动写URL,可以使用drf-nested-routers第三方包来实现嵌套路由:
- 安装包:
pip install drf-nested-routers - 修改URL配置:
from rest_framework_nested import routers router = routers.DefaultRouter() router.register(r'libraries', LibraryViewSet) # 创建嵌套路由,基于library路由 library_router = routers.NestedDefaultRouter(router, r'libraries', lookup='library') library_router.register(r'books', BookViewSet, basename='library-book') urlpatterns = router.urls + library_router.urls
这种方式会自动生成和上面手动配置一样的URL,而且不需要手动映射HTTP方法,更符合DRF的风格。
内容的提问来源于stack exchange,提问作者Thinker
相关产品推荐
相关产品推荐

