Django 博客 - 10 基于MPTT的评论

前言

上一篇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, rghttree_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')

PostViewget_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标签里,会添加两个上下文变量nodechildren

  • 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>

参考文档

django-mptt官方文档

猜你喜欢

转载自blog.csdn.net/abc_1234d/article/details/78360006