Django DRF中如何通过Serializer高效获取对象的同级兄弟记录
Django DRF 返回相邻兄弟记录优化方案
方案1:序列化器内嵌相邻字段(无需额外action)
直接在原有业务的模型序列化器中新增自定义的上/下一篇字段,请求单条记录详情时自动返回,完全不需要额外开发独立的action接口:
from rest_framework import serializers from .models import Service class ServiceSerializer(serializers.ModelSerializer): # 自定义上下篇字段 prev = serializers.SerializerMethodField() next = serializers.SerializerMethodField() class Meta: model = Service # 新增prev、next到你原本的字段列表即可 fields = ["id", "title", "parent", "content", "prev", "next"] def get_prev(self, obj): # 取同父级下id小于当前记录的最大id对应记录 prev_obj = Service.objects.filter(parent=obj.parent, id__lt=obj.id).order_by("-id").first() if prev_obj: # 仅返回跳转必要字段即可,降低序列化开销 return {"id": prev_obj.id, "title": prev_obj.title} return None def get_next(self, obj): # 取同父级下id大于当前记录的最小id对应记录 next_obj = Service.objects.filter(parent=obj.parent, id__gt=obj.id).order_by("id").first() if next_obj: return {"id": next_obj.id, "title": next_obj.title} return None
配置后原有详情接口不需要做任何修改,返回结果会自动携带上下篇信息。
方案2:窗口函数优化查询性能
如果对接口响应性能要求较高,可以使用Django支持的Lag/Lead窗口函数,一次查询即可拿到当前记录和相邻记录的id,避免多次数据库查询:
from django.db.models import Window, F from django.db.models.functions import Lag, Lead from rest_framework.response import Response # 重写视图的retrieve方法 def retrieve(self, request, *args, **kwargs): # 给查询集增加上下篇id注解,按父级分组、id排序取前后行id queryset = self.queryset.annotate( prev_id=Window( expression=Lag("id"), partition_by=F("parent"), order_by=F("id").asc() ), next_id=Window( expression=Lead("id"), partition_by=F("parent"), order_by=F("id").asc() ) ) instance = queryset.get(pk=kwargs["pk"]) base_data = self.get_serializer(instance).data # 若仅需要上下篇id,直接将prev_id、next_id插入返回结果即可,全程仅1次数据库查询 base_data["prev_id"] = instance.prev_id base_data["next_id"] = instance.next_id # 若需要返回上下篇完整信息,多1次批量查询即可,比原方案少1次查询 related_ids = list(filter(None, [instance.prev_id, instance.next_id])) if related_ids: related_map = {obj.id: self.get_serializer(obj).data for obj in self.queryset.filter(id__in=related_ids)} base_data["prev"] = related_map.get(instance.prev_id) base_data["next"] = related_map.get(instance.next_id) else: base_data["prev"] = base_data["next"] = None return Response(base_data)
原有代码优化建议
如果你需要保留独立的action接口,可以做两处优化:
- 冗余条件删除:过滤
id__lt/id__gt后已经排除了当前记录的id,不需要再加exclude(pk=current_service.id)条件 - 修复next查询bug:你原有代码中查询next时使用
order_by("-id")会拿到同父级下最大的id,不是相邻的下一篇,需要改为order_by("id")
内容的提问来源于stack exchange,提问作者Spirconi
相关产品推荐
相关产品推荐

