Django中通过自关联ForeignKey递归统计关联对象总数
解决方案
假设你的Story模型结构如下(补充了必要业务字段示例):
from django.db import models class Story(models.Model): CONTENT_TYPES = ( ('story', '帖子'), ('comment', '评论'), ('reply', '回复'), ) content_type = models.CharField(max_length=20, choices=CONTENT_TYPES) parent = models.ForeignKey( 'self', on_delete=models.CASCADE, null=True, blank=True, related_name='children' ) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True)
下面提供三种实现get_total_comments_count方法的方案,可根据数据量和技术场景选择:
1. 原生递归方法(小数据量首选)
代码简单直观,适合评论/回复数量不多的场景:
def get_total_comments_count(self): total = 0 # 统计当前帖子的直接子评论 comments = self.children.filter(content_type='comment') total += comments.count() # 递归统计每个评论下的所有嵌套回复 for comment in comments: total += comment._count_all_replies() return total def _count_all_replies(self): # 辅助递归方法:统计当前节点下的所有子级回复 replies = self.children.filter(content_type='reply') count = replies.count() for reply in replies: count += reply._count_all_replies() return count
2. 数据库递归查询(大数据量首选)
利用数据库递归语法(PostgreSQL、MySQL 8.0+支持),在数据库层面完成统计,性能远优于Python递归:
from django.db import connection def get_total_comments_count(self): with connection.cursor() as cursor: cursor.execute(""" WITH RECURSIVE story_hierarchy AS ( SELECT id FROM story WHERE parent_id = %s AND content_type IN ('comment', 'reply') UNION ALL SELECT s.id FROM story s JOIN story_hierarchy sh ON s.parent_id = sh.id WHERE s.content_type IN ('comment', 'reply') ) SELECT COUNT(*) FROM story_hierarchy; """, [self.id]) result = cursor.fetchone() return result[0] if result else 0
3. 第三方库辅助(树形结构场景首选)
如果项目频繁处理树形结构数据,推荐使用django-mptt封装的树形操作能力:
步骤1:安装依赖
pip install django-mptt
步骤2:修改模型
from mptt.models import MPTTModel, TreeForeignKey class Story(MPTTModel): CONTENT_TYPES = ( ('story', '帖子'), ('comment', '评论'), ('reply', '回复'), ) content_type = models.CharField(max_length=20, choices=CONTENT_TYPES) parent = TreeForeignKey( 'self', on_delete=models.CASCADE, null=True, blank=True, related_name='children' ) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) class MPTTMeta: order_insertion_by = ['created_at']
步骤3:实现统计方法
def get_total_comments_count(self): # 直接获取当前帖子的所有后代节点,筛选评论和回复后统计数量 return self.get_descendants().filter(content_type__in=['comment', 'reply']).count()
内容的提问来源于stack exchange,提问作者Anirban Biswas
相关产品推荐
相关产品推荐

