【问题标题】:Represent a tree of objects in Django template在 Django 模板中表示对象树
【发布时间】:2013-10-12 03:56:22
【问题描述】:

我有一个 Django 模型,它具有同一个类的 ForeignKey,有效地制作了一棵树:

class Tag(models.Model):
    name = models.CharField(max_length=50)
    parent = models.ForeignKey('self', blank=True, null=True)

在 Django shell (./manage.py shell) 中使用递归,我可以轻松地将树表示为纯文本:

def nodes(parent, level):
    children = Tag.objects.filter(parent=parent)
    for c in children:
        spaces = ""
        for i in xrange(0,level):
            spaces+="  "
        print "%s%s" % (spaces,c.name)
        nodes(c.pk,level+1)

nodes(None,0)

我不确定如何将整个树放入 Django 模板。我创建了一个自定义模板标记来简化此操作,但我不知道如何将数据传递给模板以轻松遍历树以在模板中显示。这是基本的模板标签。

@register.inclusion_tag("core/tags.html")
def render_tags(**kwargs):
    tags = Tag.objects.all()
    return {"tags":tags}

我知道以上内容非常基本,我只是不知道从哪里开始。我想如果 Tag 类有一个函数来获取它的孩子可能会更容易,所以我也有这个类:

    def children(self):
        return Tag.objects.filter(parent=self.pk)

我在那里使用self.pk,那么树的根就是rootTag=Tag(),因为它没有保存,所以没有pk,rootTag.children()会找到任何没有父标签的标签,以及任何然后这些标签中的一些可以继续调用它们的children() 函数。但就像我说的,我不知道如何将其转换为某种单一的数据结构以传递给我的模板。

想法?我想我可能想建立一种字典,我只是无法在这里完成。

【问题讨论】:

  • 为什么传递根目录还不够好?您仍然可以循环模板中的每个节点子节点。您可能只需要以某种方式重构您的 children 方法,这样它就不会每次都进行新的数据库调用。
  • 有一个数据库调用这一事实并不重要,它需要递归,而不是迭代,AFAIK 并不是模板真正可以做到的。
  • 啊。我现在明白你的问题了。 django-mptt (github.com/django-mptt/django-mptt) 有帮助吗?我自己从来没有用过,但我听说它对这种东西有好处

标签: python django templates tree


【解决方案1】:

我搜索得越多,我就越意识到尝试使用模板递归来做到这一点并不是要走的路,但我现在仍然想避免使用 django-mptt,因为它对我来说似乎太多了感觉是一个简单的问题,我不打算拥有非常大的树。

An answerthis related question 指出,确实不应该使用模板递归,而是在视图中处理序列化。正如我在原始问题中提到的那样,我认为这会更好,假设我最有可能需要构建一个字典。

This answer 基本上就是我想要的。 serializable_object 是我可以传递给模板的东西,如果我决定稍后使用 json ,那么按照该答案中的说明很容易做到。

我将选择 Eliot 的答案作为正确的答案,因为 django-mptt 可能是在这里做的“最正确”的事情,但对于任何希望避免使用另一个第三方应用程序的人来说,这是一个替代解决方案。

class Tag(models.Model):
    name = models.CharField(max_length=50)
    description = models.CharField(max_length=100, blank=True)
    parent = models.ForeignKey('self', blank=True, null=True)

    # Not necessary, can use Tag.tag_set.all()
    # But this function uses pk which allows
    #    rootTag=Tag() 
    #    rootTag.children()
    # Because rootTag has no pk
    def children(self):
        return Tag.objects.filter(parent=self.pk)

    def serializable_object(self):
        obj = {'name': self.name, 'children': []}
        for child in self.children():
            obj['children'].append(child.serializable_object())
        return obj

使用该模型,您可以:

rootTag=Tag()
rootTag.serializable_object()

或者:

tags = []
for t in Tag.objects.filter(parent=None):
    tags.append(t.serializable_object())

# And if you need JSON, 
from django.utils import simplejson
simplejson.dumps(tags)

【讨论】:

  • 我从没想过模板递归会导致问题,所以感谢您分享您的解决方案。
【解决方案2】:

正如 Jproffitt 提到的那样,Django MPTT 是实现目标的好方法 你要。

使用它,您可以在模板中递归地访问子实例, 像这样:

{% load mptt_tags %}
<h1>Tags</h1>
<ul>
{% recursetree tags %}
    <li>{{ node.name }}
        {% if not node.is_leaf_node %}
            <ul>
                {{ children }}
            </ul>
        {% endif %}
    </li>
{% endrecursetree %}
</ul>

我将它用于我的一个项目,它易于设置和使用。

无需第三方应用的替代解决方案

如果您不想使用专用应用程序,我认为这些解决方案可能有效 (未经测试):

# yourapp/tags_list.html
<ul>
{% for tag in tags %}
    <li>{{ tag.name }}</li>
    {% if tag.children.exists %}
       {% with tag.children.all as tags %}
            {% include "yourapp/tags_list.html" %}
        {% endwith %}    
    {% endif %}
{% endfor %}
</ul>

这样,模板应该只是递归调用自己,直到没有 更多儿童标签。此解决方案需要您在 Tag 模型中指定相关名称:

parent = models.ForeignKey('self', blank=True, null=True, related_name="children")

但是,请注意,这些解决方案将涉及比使用 MPTT 更多的数据库查询。

【讨论】:

  • 我去看看,谢谢。似乎整个应用程序对于我正在尝试做的事情来说太过分了,但随着这个项目的进行,它可能会继续有用。再次感谢。我暂时不解决这个问题,但如果没有人插话,我会回来的。
  • 我更新了我的答案以提供替代方案,你能告诉我它是否有效吗?
  • 似乎给我递归错误,超出了最大深度。
  • 我发现即使没有孩子,tag.children.exists 也是 True 因为关系管理器确实存在,所以我试图将其调整为基于长度.
  • 我能够通过使用两个“with”模板标签来解决这个问题,第一个指定模板名称,第二个为包含的模板定义as tags。我已提议对您的解决方案进行修改,但需要进行审核才能生效。
猜你喜欢
  • 2013-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-05
  • 2012-09-12
  • 1970-01-01
  • 2023-03-07
  • 2020-06-13
相关资源
最近更新 更多