Django 博客

来源:互联网 发布:网络配件回收 编辑:程序博客网 时间:2024/05/22 15:30

前言

上一篇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 TreeForeignKeyfrom mptt.models import MPTTModelclass 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 makemigrationspython 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_safefrom mptt.templatetags.mptt_tags import cache_tree_childrenfrom 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.tagdef 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官方文档

原创粉丝点击