如何在Django REST Framework中按分类分组文章?
在Django REST Framework中按分类分组文章的实现方案
问题描述
我正在尝试在Django REST Framework(DRF)中按分类对所有文章进行分组。现有代码如下:
serializers.py
class GroupPageSerializer(serializers.ModelSerializer): sound = serializers.FileField(required=False) image = Base64ImageField(max_length=None, use_url=True) category = serializers.PrimaryKeyRelatedField( many=True, queryset=Category.objects.all()) url = serializers.CharField(allow_null=True, required=False, default=None, allow_blank=True) english = serializers.CharField(source="base", required=False, allow_blank=True) per_language = PerLanguageCondensedSerializer(many=True, required=False, read_only=True) target = serializers.CharField(required=False, allow_null=True, allow_blank=True) base = serializers.CharField(required=False) class Meta: model = Page fields = ['per_language', 'base', 'target', 'english', 'date', "json", "id", "category", "title", "image", "sound", "url", "slug"]
views.py
class GroupedArticleListView(generics.ListAPIView): queryset = Page.objects.select_related().all() serializer_class = GroupPageSerializer filter_backends = (filters.DjangoFilterBackend, OrderingFilter) pagination_class = LimitOffsetPagination ordering_fields = ['date'] filter_class = ArticleMultiValue
我尝试过编写列表序列化器类、各种to_representation函数等,但仍未实现需求。希望最终返回结果按分类分组,格式如下:
"business": [ {article1...}, {article2...} ], "technology": [ {article3...}, {article8...}, ]
解决方案
方法一:在视图中手动分组并序列化
由于Page和Category是多对多关系,需先处理单篇文章归属多分类的场景,再按分类名称分组:
修改views.py中的GroupedArticleListView:
from collections import defaultdict from rest_framework.response import Response class GroupedArticleListView(generics.ListAPIView): queryset = Page.objects.prefetch_related('category').all() # 用prefetch_related优化多对多查询 serializer_class = GroupPageSerializer filter_backends = (filters.DjangoFilterBackend, OrderingFilter) pagination_class = LimitOffsetPagination ordering_fields = ['date'] filter_class = ArticleMultiValue def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) # 处理分页逻辑 page = self.paginate_queryset(queryset) articles = page if page is not None else queryset # 按分类名称分组数据 grouped_data = defaultdict(list) serializer = self.get_serializer(articles, many=True) for article in serializer.data: # 遍历文章关联的所有分类 for category_id in article.pop('category'): # 通过分类ID获取分类名称 category = Category.objects.get(id=category_id) grouped_data[category.name].append(article) if page is not None: return self.get_paginated_response(grouped_data) return Response(grouped_data)
注意:如果
category字段序列化后直接返回名称而非ID,可跳过查询Category对象,直接用category值作为分组键。
方法二:自定义分组序列化器
通过重写ListSerializer的to_representation方法实现分组:
在serializers.py中新增分组序列化器:
from collections import defaultdict class GroupedPageListSerializer(serializers.ListSerializer): def to_representation(self, data): grouped_data = defaultdict(list) # 先序列化所有文章 articles = super().to_representation(data) for article in articles: categories = article.pop('category') for category_id in categories: category = Category.objects.get(id=category_id) grouped_data[category.name].append(article) return grouped_data # 修改原序列化器的ListSerializer类 class GroupPageSerializer(serializers.ModelSerializer): # 保留原有字段定义 sound = serializers.FileField(required=False) image = Base64ImageField(max_length=None, use_url=True) category = serializers.PrimaryKeyRelatedField( many=True, queryset=Category.objects.all()) url = serializers.CharField(allow_null=True, required=False, default=None, allow_blank=True) english = serializers.CharField(source="base", required=False, allow_blank=True) per_language = PerLanguageCondensedSerializer(many=True, required=False, read_only=True) target = serializers.CharField(required=False, allow_null=True, allow_blank=True) base = serializers.CharField(required=False) class Meta: model = Page fields = ['per_language', 'base', 'target', 'english', 'date', "json", "id", "category", "title", "image", "sound", "url", "slug"] list_serializer_class = GroupedPageListSerializer
视图仅需优化查询集即可:
class GroupedArticleListView(generics.ListAPIView): queryset = Page.objects.prefetch_related('category').all() serializer_class = GroupPageSerializer filter_backends = (filters.DjangoFilterBackend, OrderingFilter) pagination_class = LimitOffsetPagination ordering_fields = ['date'] filter_class = ArticleMultiValue
关键注意点
- 多对多关系处理:单篇文章若归属多个分类,会同时出现在对应分类的列表中;若需限制仅显示主分类,需调整模型或业务逻辑。
- 性能优化:必须使用
prefetch_related('category')提前加载分类数据,避免循环中反复查询数据库导致性能损耗。 - 分页兼容性:方法一可直接兼容分页逻辑,返回当前页数据的分组结果;方法二同样支持分页,分组范围为当前页数据。
内容的提问来源于stack exchange,提问作者Steven Matthews
相关产品推荐
相关产品推荐

