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

Django Rest Framework 实现多对多关联模型嵌套子路由访问方法

问题根源梳理
  • 当前视图继承的是ReadOnlyModelViewSet,按照DRF的内置定义,该基类仅提供列表查询、单条详情查询两个只读接口,本身不支持POST/PATCH/DELETE等写操作;如果你之前观测到接口支持PATCH,说明实际运行的视图代码和你贴出的版本不一致,需要先修正视图基类才能拿到完整操作能力
  • DRF 默认的路由注册逻辑,只会为当前模型本身生成资源路径,不会自动生成跨模型关联的嵌套路径(也就是formations/{id}/courses/这类关联接口),需要通过嵌套路由配置或自定义接口动作来实现
实现全功能嵌套关联接口的方案

第一步:修正基础视图能力

把原有只读视图替换为具备完整CRUD能力的视图基类:

# views.py 基础视图修正
from rest_framework import viewsets, mixins

# 如果需要全部增删改查能力,直接用ModelViewSet
class FormationView(viewsets.ModelViewSet):
    queryset = Formation.objects.all()
    serializer_class = FormationSerializer

class CourseView(viewsets.ModelViewSet):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

如果不需要开放删除接口,可以通过mixins组合自定义能力,比如继承ListModelMixin/RetrieveModelMixin/UpdateModelMixin/CreateModelMixin,不包含DestroyModelMixin即可。

第二步:配置嵌套关联接口

方案一:使用嵌套路由库(低代码、推荐)

通过嵌套路由的能力,自动生成符合REST规范的关联接口路径,复用原有Course视图的全部逻辑,不需要重复写接口代码:

  1. 先安装drf-nested-routers依赖
  2. 在urls.py中补充嵌套路由配置:
# urls.py 嵌套路由配置
from rest_framework_nested import routers

router = routers.DefaultRouter()
router.register('formations', views.FormationView)
router.register('courses', views.CourseView)

# 为Formation资源下的关联Course子资源生成路由
formation_course_router = routers.NestedDefaultRouter(router, 'formations', lookup='formation')
# 绑定子资源路由到已有的CourseView,访问路径为/api/formations/{formation_pk}/courses/
formation_course_router.register('courses', views.CourseView, basename='formation-courses')

urlpatterns = [
    path('api/', include(router.urls)),
    path('api/', include(formation_course_router.urls)),
]

配置完成后,嵌套路径会自动继承CourseView的全部能力:

  • 支持GET /api/formations/{id}/courses/:返回当前Formation关联的所有Course列表
  • 支持POST /api/formations/{id}/courses/:新增Course并绑定到当前Formation
  • 支持GET /api/formations/{id}/courses/{course_id}/:查询当前Formation下的指定关联Course详情
  • 支持PUT/PATCH /api/formations/{id}/courses/{course_id}/:修改当前Formation下的指定Course信息
  • 支持DELETE /api/formations/{id}/courses/{course_id}/:删除指定Course(可根据业务调整为仅解除关联关系)

方案二:自定义ViewSet动作(无额外依赖)

如果不想引入第三方依赖,可以直接在FormationView中通过自定义action实现关联接口,不需要修改原有路由结构:

# 在FormationView中补充自定义关联接口
from rest_framework.decorators import action
from rest_framework.response import Response

class FormationView(viewsets.ModelViewSet):
    queryset = Formation.objects.all()
    serializer_class = FormationSerializer

    # 实现当前Formation关联Course的列表查询、新增接口,路径为formations/{id}/courses
    @action(detail=True, methods=['get', 'post'], url_path='courses')
    def manage_course_list(self, request, pk=None):
        formation = self.get_object()
        # 处理GET请求:返回关联Course列表
        if request.method == 'GET':
            courses = formation.courses.all()
            return Response(CourseSerializer(courses, many=True).data)
        # 处理POST请求:新建Course并绑定到当前Formation
        serializer = CourseSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        course = serializer.save()
        formation.courses.add(course)
        return Response(serializer.data, status=201)

    # 实现单个关联Course的操作接口,路径为formations/{id}/courses/{course_id}
    @action(detail=True, methods=['get', 'put', 'patch', 'delete'], 
            url_path='courses/(?P<course_pk>[^/.]+)')
    def manage_single_course(self, request, pk=None, course_pk=None):
        formation = self.get_object()
        try:
            # 仅查询当前Formation关联的Course,避免跨资源越权
            course = formation.courses.get(pk=course_pk)
        except Course.DoesNotExist:
            return Response(status=404)
        # 单个Course查询
        if request.method == 'GET':
            return Response(CourseSerializer(course).data)
        # 单个Course更新
        if request.method in ['PUT', 'PATCH']:
            serializer = CourseSerializer(course, data=request.data, partial=request.method=='PATCH')
            serializer.is_valid(raise_exception=True)
            serializer.save()
            return Response(serializer.data)
        # 单个Course删除:可根据业务调整为仅解除关联(formation.courses.remove(course))或物理删除(course.delete())
        if request.method == 'DELETE':
            formation.courses.remove(course)
            return Response(status=204)
补充注意事项
  • 原有FormationSerializer中的courses = CourseSerializer(many=True)字段,建议根据业务配置read_only=True,避免和嵌套路由的写入逻辑产生冲突
  • 多对多关联操作时,要明确区分「解除Course和Formation的绑定关系」和「物理删除Course对象」的业务逻辑,不要混淆

内容的提问来源于stack exchange,提问作者ABDERRAHMANE HAMMIA

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 03:27:01