如何实现双模型数据按日期排序的统一响应并支持分页?
问题描述
现有两个Django模型:
class A(models.Model): ... date=models.DateTimeField(auto_add_now=True) ... class B(models.Model): ... date=models.DateTimeField(auto_add_now=True) ...
两者都包含date字段,需要编写一个GET接口向前端返回两个模型的所有数据并按日期倒序排序,且已为两个模型编写了包含全部字段的序列化器A_serializer和B_serializer。
当前实现逻辑如下:
A = A.objects.all() B = B.objects.all() aSerializer = A_serializer(A, many=True) bSerializer = B_serializer(B, many=True) all = aSerializer.data + bSerializer.data all = sorted(all, key=lambda item: item['date'],reverse=True) return Response(all)
但该方式无法实现分页等功能,寻求更优实现方案。
优化方案
方案一:数据库层面合并查询(优先推荐)
这种方式把合并和排序逻辑放到数据库层完成,能利用数据库的索引优化,性能更好,尤其适合数据量较大的场景,同时天然支持DRF的原生分页。
实现步骤
- 用
values()提取两个模型需要的字段,额外添加一个model_type字段区分数据属于A还是B; - 通过
union合并两个查询集,再按date倒序排序; - 使用DRF的分页类处理合并后的查询集;
- 遍历分页结果,根据
model_type找到对应模型实例并序列化。
代码示例
from django.db.models import Value, CharField from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response # 自定义分页类(可根据需求调整参数) class CustomPagination(PageNumberPagination): page_size = 10 page_size_query_param = 'page_size' max_page_size = 100 def combined_list(request): # 提取A模型的字段并标记类型 a_queryset = A.objects.all().values( 'id', 'date', # 替换为A的实际字段 # 其他字段... ).annotate(model_type=Value('A', output_field=CharField())) # 提取B模型的字段并标记类型 b_queryset = B.objects.all().values( 'id', 'date', # 替换为B的实际字段 # 其他字段... ).annotate(model_type=Value('B', output_field=CharField())) # 合并查询集并按日期倒序排序 combined_queryset = a_queryset.union(b_queryset).order_by('-date') # 分页处理 paginator = CustomPagination() paginated_items = paginator.paginate_queryset(combined_queryset, request) # 序列化数据 serialized_data = [] for item in paginated_items: if item['model_type'] == 'A': obj = A.objects.get(id=item['id']) serialized_data.append(A_serializer(obj).data) else: obj = B.objects.get(id=item['id']) serialized_data.append(B_serializer(obj).data) return paginator.get_paginated_response(serialized_data)
方案二:自定义分页类适配已合并的列表
如果不想改动查询逻辑,只是需要给现有合并后的列表加分页,可以自定义一个支持列表分页的类,直接对序列化后的合并数据进行分页处理。这种方式适合数据量较小的场景,不用修改数据库查询逻辑。
代码示例
from rest_framework.pagination import PageNumberPagination from rest_framework.exceptions import NotFound class ListPagination(PageNumberPagination): page_size = 10 page_size_query_param = 'page_size' max_page_size = 100 def paginate_list(self, data, request): self.page_size = self.get_page_size(request) paginator = self.django_paginator_class(data, self.page_size) page_number = request.query_params.get(self.page_query_param, 1) # 处理"last"页码参数 if page_number in self.last_page_strings: page_number = paginator.num_pages try: self.page = paginator.page(page_number) except self.django_paginator_class.InvalidPage as exc: raise NotFound(f"Invalid page {page_number}: {str(exc)}") self.request = request self.display_page_controls = paginator.num_pages > 1 and self.template is not None return list(self.page) def combined_list(request): # 原有查询和序列化逻辑 a_queryset = A.objects.all() b_queryset = B.objects.all() a_serialized = A_serializer(a_queryset, many=True).data b_serialized = B_serializer(b_queryset, many=True).data combined_data = sorted(a_serialized + b_serialized, key=lambda x: x['date'], reverse=True) # 应用自定义分页 paginator = ListPagination() paginated_data = paginator.paginate_list(combined_data, request) return paginator.get_paginated_response(paginated_data)
方案三:基于DRF GenericView的混合视图
如果想让代码更贴合DRF的视图规范,可以自定义一个GenericAPIView子类,把合并、排序、分页逻辑整合到视图中。
代码示例
from rest_framework.generics import GenericAPIView from rest_framework.pagination import PageNumberPagination class CombinedListView(GenericAPIView): pagination_class = PageNumberPagination def get(self, request, *args, **kwargs): # 获取两个模型的查询集 a_queryset = A.objects.all() b_queryset = B.objects.all() # 序列化数据 a_data = A_serializer(a_queryset, many=True).data b_data = B_serializer(b_queryset, many=True).data # 合并并排序 combined_data = sorted(a_data + b_data, key=lambda x: x['date'], reverse=True) # 分页处理 paginator = self.pagination_class() paginated_data = paginator.paginate_queryset(combined_data, request) return paginator.get_paginated_response(paginated_data)
内容的提问来源于stack exchange,提问作者Learning Developer
相关产品推荐
相关产品推荐

