python - Django 递归注解
问题描述
我正在构建一个具有递归注释结构的 Django 应用程序。
问题:我的评论数据结构的递归性质意味着我正在努力编写一个查询来用回复数量来注释每个帖子,然后在我的模板中遍历这些帖子/回复。
我建立的评论模型区分了帖子回复(顶级评论)和评论回复(对其他评论的回复)。
(Post)
3 Total Comments
-----------------
one (post reply)
└── two (comment reply)
└── three (comment reply)
(more)
我发表了如下评论:
class Comment(TimeStamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
content = models.TextField(max_length=2000)
post = models.ForeignKey("Post", on_delete=models.CASCADE, related_name="comments")
# Top level comments are those that aren't replies to other comments
reply = models.ForeignKey(
"self", on_delete=models.PROTECT, null=True, blank=True, related_name="replies"
)
这个效果很好,图片相关
什么有效
我可以预取帖子的所有评论回复,如下所示:
comment_query = Comment.objects.annotate(num_replies=Count("replies"))
post = Post.objects.prefetch_related(Prefetch("comments", comment_query)).get(id="1")
正确显示每条评论的回复数:
>>> post.comments.values_list('num_replies')
<QuerySet [(1,), (1,), (0,)]>
什么不起作用
此查询仅注释顶层post.comments
>>> post.comments.first().replies.all()
<QuerySet [<Comment: two>]>
>>> post.comments.first().replies.first().num_replies
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-132-8151a7d13021> in <module>
----> 1 post.comments.first().replies.first().num_replies
AttributeError: 'Comment' object has no attribute 'num_replies'
为了正确地按模板渲染,我需要comment.replies
对每个顶级响应进行迭代。因此,任何嵌套的评论响应都缺少原始num_replies
注释。
在我的模板/视图逻辑中,我使用大致以下逻辑呈现评论树:
{% for comment in post.comments.all %}
{% if not comment.reply %}
{% include "posts/comment_tree.html" %}
{% endif %}
{% endfor %}
其中post/comments_tree.html
包含:
{{ post.content }}
{% for reply in comment.replies.all %}
{% include "posts/comment_tree.html" with comment=reply %}
{% endfor %}
我试过的
我可以通过执行以下操作在一定程度上解决此问题,这将注释第一级回复:
comment_query = Comment.objects.prefetch_related(
Prefetch("replies", Comment.objects.annotate(num_replies=Count("replies")))
).annotate(num_replies=Count("replies"))
这成功注释了第二条评论,这是一个嵌套响应
>>> post.comments.first().replies.first().num_replies
1
但它不适用于任何进一步的嵌套评论(即第三个)
>>> post.comments.first().replies.first().replies.first().num_replies
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-127-7d5b9798b7b1> in <module>
----> 1 post.comments.first().replies.first().replies.first().num_replies
AttributeError: 'Comment' object has no attribute 'num_replies'
显然,这种方法是完全有缺陷的,因为我将被迫为我想要支持的嵌套评论的总数添加一个嵌套的 Prefetch 语句。理想情况下,我想要一个允许我注释任意嵌套(自引用)数据结构的解决方案。
TLDR:这种类型的查询甚至可以在 Django 的 ORM 中使用,还是我必须使用 SQL?
解决方案
看看django-cte。您想定义一个包含注释的 CTE(公用表表达式)。然后在查询中使用该 CTE 来获取帖子的评论。
来自 django-cte 的文档:
class Region(Model):
objects = CTEManager()
name = TextField(primary_key=True)
parent = ForeignKey("self", null=True, on_delete=CASCADE)
def make_regions_cte(cte):
return Region.objects.filter(
# start with root nodes
parent__isnull=True
).values(
"name",
path=F("name"),
depth=Value(0, output_field=IntegerField()),
).union(
# recursive union: get descendants
cte.join(Region, parent=cte.col.name).values(
"name",
path=Concat(
cte.col.path, Value("\x01"), F("name"),
output_field=TextField(),
),
depth=cte.col.depth + Value(1, output_field=IntegerField()),
),
all=True,
)
cte = With.recursive(make_regions_cte)
regions = (
cte.join(Region, name=cte.col.name)
.with_cte(cte)
.annotate(
path=cte.col.path,
depth=cte.col.depth,
)
.order_by("path")
)
推荐阅读
- python - 为什么即使没有相同的变量和函数名称也会出现类型错误?
- docker - docker COPY 命令不是持久的
- python - 使用 python 2.7 调用用 python 3.7 编写的函数
- android - 在所有设备上仅使用一个注册领域用户
- javascript - 多个状态在 React 中共享相同的视图或组件
- android - 避免清洁任务的 gradle 构建参数
- java - Android studio gradle 与 firebase 同步错误:响应 204:No Content 没有内容
- progressive-web-apps - 内容的后台同步
- git - 克隆 git 存储库后不是 git 存储库
- ruby-on-rails - Ruby On Rails 将 JSON 转换为 firebase