前言
上一篇Comment
模型有一个问题,当我们要获取一篇文章的所以评论时
需要通过parent
为空的Comment
对象,一级一级的通过commet.children.all()
获取
如果层级或者评论很多,需要对每一条评论都查询是否有回复,每一次查询都要对数据库查询,并且数据库查询都是相对很慢的
什么是MPTT
MPTT(Modified Preorder Tree Traversal)
是一种在数据库中存储层次结构的技术,它可以让相关操作更加高效
MPTT
使得大多数树操作在查询方面更高效。实际上,所有这些操作最多需要一个查询,有时为零:
- 获取节点的后代
- 获取节点的祖先
- 在给定的级别上获取所有节点
- 获取叶子节点
这个不需要再次查询数据库:
- 计算给定节点的后代的数量
使用MPTT
为了在评论模型里使用MPTT
,可以使用django-mptt
django-mptt
是一个帮助你在django模型中使用MPTT的应用
安装django-mptt
pip3 install django-mptt
将django-mptt
添加到INSTALLED_APPS
里
INSTALLED_APPS = (
# ...
'mptt',
)
创建评论模型,需要继承MPTTModel,并且有一个parent
字段
由于继承MPTTModel
,评论模型就有了level
,lft
, rght
和tree_id
字段,这些字段是MPTT
算法所使用的,一般不会用到
from mptt.fields import TreeForeignKey
from mptt.models import MPTTModel
class Comments(MPTTModel):
user = models.ForeignKey(User)
content = models.TextField()
parent = models.TreeForeignKey('self', null=True, blank=True, related_name='children',db_index=True)
post = models.ForeignKey(Post)
created_time = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.content
class MPTTMeta:
order_insertion_by = ['-created_time']
接着迁移数据库
python manage.py makemigrations
python manage.py migrate
将上篇文章的Comment
模型都替换成Comments
blog/forms.py
里的CommentForm
+from blog.models import Comments
class CommentForm(ModelForm):
class Meta:
- model = Comment
+ model = Comments
fields = ('content', 'parent', 'user', 'post')
PostView
的get_object
方法,不需要再进行过滤了
def get_object(self, queryset=None):
...
- post.comments = post.comment_set.all().filter(parent=None).order_by('-created_time')
+ post.comments = post.comments_set.all()
return post
在blog/templates/blog/detail.html
里直接使用recursetree
标签,就能将评论全部显示出来了
{% load mptt_tags %}
<ul>
{% recursetree post.comments %}
<li>
{{ node.content }}
{% if not node.is_leaf_node %}
<ul>
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
recursetree
标签里,会添加两个上下文变量node
和children
node
是一个MPTT
模型对象.children
是一个变量,持有node
节点的子节点的HTML
recursetree
标签会遍历所有根节点,再对根节点的子节点进行递归处理
我们无法知道当前遍历的是第几个节点,不能像for
标签那样通过forloop
获取
所以要修改一下recursetree
标签,将遍历的index
添加到上下文里
将recursetree
的源码复制到blog/templatetags/post_tags.py
,并添加我们的修改
from django.utils.safestring import mark_safe
from mptt.templatetags.mptt_tags import cache_tree_children
from django.utils.translation import ugettext as _
class RecurseTreeNode(template.Node):
def __init__(self, template_nodes, queryset_var):
self.template_nodes = template_nodes
self.queryset_var = queryset_var
def _render_node(self, context, node, length=0, index=0):
bits = []
context.push()
for child in node.get_children():
bits.append(self._render_node(context, child))
#将index添加到上下文里,和forloop一样,也提供四种计数方式
context['counter'] = index + 1
context['counter0'] = index
context['revcounter'] = length - index
context['revcounter0'] = length - index - 1
context['node'] = node
context['children'] = mark_safe(''.join(bits))
rendered = self.template_nodes.render(context)
context.pop()
return rendered
def render(self, context):
queryset = self.queryset_var.resolve(context)
roots = cache_tree_children(queryset)
bits = []
#通过enumerate,获取当前的index
for index, node in enumerate(roots):
bits.append(self._render_node(context, node, len(roots), roots.index(node)))
return ''.join(bits)
@register.tag
def recursetree(parser, token):
"""
Iterates over the nodes in the tree, and renders the contained block for each node.
This tag will recursively render children into the template variable {{ children }}.
Only one database query is required (children are cached for the whole tree)
Usage:
<ul>
{% recursetree nodes %}
<li>
{{ node.name }}
{% if not node.is_leaf_node %}
<ul>
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
"""
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError(_('%s tag requires a queryset') % bits[0])
queryset_var = template.Variable(bits[1])
template_nodes = parser.parse(('endrecursetree',))
parser.delete_first_token()
return RecurseTreeNode(template_nodes, queryset_var)
这样又可以使用上篇文章创建的blog/comment.html
模板了
<ul class="uk-comment-list">
{% recursetree post.comments %}
<li class="uk-margin-small-top">
{% include 'blog/comment.html' with comment=node counter=revcounter level=node.level %}
{% if not node.is_leaf_node %}
<ul class="uk-comment-list uk-margin-small-top" style="padding-left: 4%">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>