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

如何在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

关键注意点

  1. 多对多关系处理:单篇文章若归属多个分类,会同时出现在对应分类的列表中;若需限制仅显示主分类,需调整模型或业务逻辑。
  2. 性能优化:必须使用prefetch_related('category')提前加载分类数据,避免循环中反复查询数据库导致性能损耗。
  3. 分页兼容性:方法一可直接兼容分页逻辑,返回当前页数据的分组结果;方法二同样支持分页,分组范围为当前页数据。

内容的提问来源于stack exchange,提问作者Steven Matthews

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 10:20:37