Django REST Framework 递归嵌套模型的接口实现与分页方案
最简实现方案
你初步设计的端点结构完全匹配业务需求,无需大幅调整,仅需通过DRF原生能力补充混合序列化、查询合并、分页逻辑即可,全程不需要引入第三方复杂依赖。
最终落地路由
直接复用你规划的路径结构:
lockers/<int:locker_id>/metadata/:返回指定Locker根层级(所有内容parent为None)下的混合文件、文件夹列表lockers/<int:locker_id>/folders/<int:pk>/metadata/:返回指定文件夹内部嵌套的混合文件、文件夹列表lockers/<int:locker_id>/uploads/:文件上传创建接口lockers/<int:locker_id>/folders/:文件夹的创建、独立列表接口
核心逻辑实现
1. 统一结构的独立序列化器
为两类模型编写结构对齐的独立序列化器,添加统一类型标识字段供前端做交互区分,不需要复杂的多态序列化继承:
class FolderSerializer(serializers.ModelSerializer): item_type = serializers.SerializerMethodField() class Meta: model = Folder fields = ["id", "name", "parent", "item_type", "created_time", "modified_time"] def get_item_type(self, obj): return "folder" class UploadSerializer(serializers.ModelSerializer): item_type = serializers.SerializerMethodField() class Meta: model = Upload fields = ["id", "name", "parent", "item_type", "file", "created_time", "modified_time"] def get_item_type(self, obj): return "upload"
统一的
item_type字段让前端拿到列表后可以直接判断条目类型,不需要额外发起请求做二次识别。
2. 合并查询逻辑
两类模型的查询条件完全对称,只需要根据请求的层级范围(根目录/指定文件夹)拉取对应数据,再合并为统一列表:
- 根目录场景查询规则:
- 匹配条件1:
Folder表中locker=目标对象、parent=None的所有记录 - 匹配条件2:
Upload表中locker=目标对象、parent=None的所有记录
- 匹配条件1:
- 指定文件夹场景查询规则:
- 匹配条件1:
Folder表中locker=目标对象、parent=当前文件夹对象的所有记录 - 匹配条件2:
Upload表中locker=目标对象、parent=当前文件夹对象的所有记录
- 匹配条件1:
注意:不要直接对两个不同模型的QuerySet做union操作,因为两类模型字段不完全一致会触发报错,正确的轻量实现是将两个查询集序列化后按统一排序规则合并即可。
3. 复用式视图与分页实现
两个元数据端点的逻辑完全一致,可以抽成一个公共视图,路由层做参数透传,避免重复代码:
from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response from rest_framework.views import APIView from django.shortcuts import get_object_or_404 # 自定义分页规则,复用DRF原生分页能力 class MetadataPagination(PageNumberPagination): page_size = 20 page_size_query_param = "page_size" max_page_size = 100 class LockerMetadataView(APIView): pagination_class = MetadataPagination def get(self, request, locker_id, folder_pk=None): # 先做权限校验,确保用户只能访问自己的Locker locker = get_object_or_404(Locker, pk=locker_id, owner=request.user) # 校验目标文件夹的归属,避免越权 target_folder = None if folder_pk: target_folder = get_object_or_404(Folder, pk=folder_pk, locker=locker) # 拉取两类条目 folders = Folder.objects.filter(locker=locker, parent=target_folder) uploads = Upload.objects.filter(locker=locker, parent=target_folder) # 合并序列化结果 combined_entries = [] combined_entries += FolderSerializer(folders, many=True).data combined_entries += UploadSerializer(uploads, many=True).data # 统一按创建时间倒序排序,和文件管理器的展示逻辑对齐 combined_entries.sort(key=lambda x: x["created_time"], reverse=True) # 对合并后的全量列表做分页,不是分开对两类条目单独分页 page = self.paginate_queryset(combined_entries) if page is not None: return self.get_paginated_response(page) return Response(combined_entries)
路由层直接把两个元数据路径绑定到同一个视图,不需要写两套重复逻辑:
urlpatterns = [ # Locker根目录元数据列表 path("lockers/<int:locker_id>/metadata/", LockerMetadataView.as_view()), # 指定文件夹下的元数据列表 path("lockers/<int:locker_id>/folders/<int:folder_pk>/metadata/", LockerMetadataView.as_view()), # 上传、文件夹的独立CRUD接口走常规ModelViewSet实现即可,无需特殊处理 ]
实现优势
- 全程基于DRF原生能力,没有额外学习成本和依赖
- 分页作用在合并后的总列表上,返回的单页结果是按规则排序的混合条目,和前端文件管理器的交互体验完全对齐
- 两个元数据端点逻辑完全复用,后续调整列表返回字段只需要修改序列化器一处即可
内容的提问来源于stack exchange,提问作者Aryan Iyappan
相关产品推荐
相关产品推荐

