You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 14:07:45