如何在Django REST Framework中构建category/product/层级URL结构
实现DRF中层级式的Category/Product URL结构
嘿,我懂你想要搭建category/{category-slug}/product/{product-slug}这种层级URL的需求,用Django REST Framework的话,有两种常用方案,我给你详细拆解:
方案一:使用drf-nested-routers(推荐,适配ViewSet)
这个第三方包专门用来处理DRF的嵌套路由,和你现有的ModelViewSet完美兼容。
1. 安装依赖
首先安装包:
pip install drf-nested-routers
2. 修改urls.py配置嵌套路由
替换原来的路由代码,改成嵌套结构:
from django.contrib import admin from django.urls import path, include from rest_framework import routers from rest_framework_nested import routers # 导入嵌套路由 from .views import ProductAPIList, CategoryAPIList # 根路由 router = routers.DefaultRouter() router.register(r'category', CategoryAPIList, basename='category') # 嵌套路由:基于category的product路由 category_router = routers.NestedDefaultRouter(router, r'category', lookup='category') category_router.register(r'product', ProductAPIList, basename='category-product') urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/', include(router.urls)), path('api/v1/', include(category_router.urls)) # 加入嵌套路由 ]
3. 修改views.py,根据Category Slug过滤产品
在ProductAPIList里重写get_queryset方法,从URL参数中获取category的slug,过滤对应的产品:
from rest_framework import viewsets, filters from rest_framework.permissions import IsAuthenticatedOrReadOnly from .models import Product, Category from .serializers import ProductSerializer class ProductAPIList(viewsets.ModelViewSet): serializer_class = ProductSerializer lookup_field = 'slug' permission_classes = (IsAuthenticatedOrReadOnly,) filter_backends = [filters.SearchFilter] search_fields = ['name', 'description', 'category__name'] def get_queryset(self): # 从URL中获取category的slug参数(对应路由里的lookup='category') category_slug = self.kwargs.get('category_slug') if category_slug: # 过滤属于该分类的产品 return Product.objects.filter(category__slug=category_slug) # 如果没有category参数,返回所有产品(兼容原来的/product/路由) return Product.objects.all() class CategoryAPIList(viewsets.ModelViewSet): queryset = Category.objects.all() serializer_class = CategorySerializer lookup_field = 'slug' permission_classes = (IsAuthenticatedOrReadOnly,) filter_backends = [filters.SearchFilter] search_fields = ['name']
4. 验证路由
现在你可以访问这些URL:
- 所有分类:
/api/v1/category/ - 单个分类详情:
/api/v1/category/{category-slug}/ - 某个分类下的所有产品:
/api/v1/category/{category-slug}/product/ - 某个分类下的单个产品:
/api/v1/category/{category-slug}/product/{product-slug}/
方案二:手动配置URL(无需第三方包)
如果你不想额外加依赖,可以手动写URL路径,拆分视图类:
1. 拆分Product视图
把原来的ProductAPIList拆成列表和详情两个视图(因为要分别处理分类下的列表和单个产品):
from rest_framework import generics, filters from rest_framework.permissions import IsAuthenticatedOrReadOnly from .models import Product, Category from .serializers import ProductSerializer # 分类下的产品列表 class CategoryProductList(generics.ListAPIView): serializer_class = ProductSerializer permission_classes = (IsAuthenticatedOrReadOnly,) filter_backends = [filters.SearchFilter] search_fields = ['name', 'description', 'category__name'] def get_queryset(self): category_slug = self.kwargs['category_slug'] return Product.objects.filter(category__slug=category_slug) # 分类下的单个产品详情 class CategoryProductDetail(generics.RetrieveAPIView): serializer_class = ProductSerializer lookup_field = 'slug' permission_classes = (IsAuthenticatedOrReadOnly,) def get_queryset(self): category_slug = self.kwargs['category_slug'] return Product.objects.filter(category__slug=category_slug) # 保留原来的所有产品视图(可选) class ProductAPIList(generics.ListAPIView): queryset = Product.objects.all() serializer_class = ProductSerializer permission_classes = (IsAuthenticatedOrReadOnly,) filter_backends = [filters.SearchFilter] search_fields = ['name', 'description', 'category__name'] class CategoryAPIList(viewsets.ModelViewSet): queryset = Category.objects.all() serializer_class = CategorySerializer lookup_field = 'slug' permission_classes = (IsAuthenticatedOrReadOnly,) filter_backends = [filters.SearchFilter] search_fields = ['name']
2. 修改urls.py
手动添加层级URL:
from django.contrib import admin from django.urls import path, include from rest_framework import routers from .views import ( ProductAPIList, CategoryAPIList, CategoryProductList, CategoryProductDetail ) router = routers.DefaultRouter() router.register(r'category', CategoryAPIList) router.register(r'product', ProductAPIList) # 保留原来的所有产品路由(可选) urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/', include(router.urls)), # 新增层级URL path('api/v1/category/<slug:category_slug>/product/', CategoryProductList.as_view(), name='category-product-list'), path('api/v1/category/<slug:category_slug>/product/<slug:slug>/', CategoryProductDetail.as_view(), name='category-product-detail'), ]
这样同样可以实现你想要的层级URL结构,缺点是需要手动拆分视图,不如嵌套路由灵活。
最后,你原来的serializers代码不需要大改,确保ProductSerializer里的category字段正确关联即可。如果想让产品的URL直接显示层级结构,可以在ProductSerializer的extra_kwargs里自定义url字段的视图名称,比如:
class ProductSerializer(serializers.ModelSerializer): category = CategorySerializer(many=False) class Meta: model = Product fields = ( 'id', 'url', "name", "slug", "category", "price", "photo", "description") lookup_field = 'slug' extra_kwargs = { 'url': { 'lookup_field': 'slug', 'view_name': 'category-product-detail' # 对应嵌套路由或手动路由的名称 } }
内容的提问来源于stack exchange,提问作者TheQQWEETT
相关产品推荐
相关产品推荐

