嵌套的Django模板

13 投票
3 回答
17303 浏览
提问于 2025-04-16 17:09

这看起来是个很基础的事情,但尽管我已经使用Django大约一年了,我还是没遇到过这种情况。

在很多模板或网页框架中,模板继承的工作方式有点不同,通常它们更像是包装器。如果你有一个子模板 `childtemplate.html`、一个父模板 `parenttemplate.html` 和一个祖父模板 `grandparenttemplate.html`,那么最终渲染出来的内容通常看起来像这样:

grandparent header
    parent header
        child header
        child content
    parent content
    parent footer
grandparent content
grandparent footer

在Django中并不是完全这样,但我想知道如何实现它。

具体来说,我有一个“子”模板,假设它是 foo.htmlfoo.html 可以选择性地接收一个变量 parent_template,如果没有提供则默认使用 "base.html"。

{% extends parent_template|default:"base.html" %}

{% block content %}
I am a foo and my title is {{ foo.title }}
{% endblock content %}

所以,这就是我遇到的问题。如果 parent_template 是一个模板,那么这个模板应该把 foo.html 的内容包裹起来,然后把结果放到 base.html 中:

{% extends "base.html" %}

{% something_magical_here %}
<div>parent header</div>
# content of foo.html
<div>parent footer</div>
{% end_something_magical_here %}

然后在 base.html 中:

<html>
... snip ...
<div id="content">
{% something_else_magical %}
# content of parent_template rendering, or just foo.html if no parent_template given
{% end_something_else_magical %}

这应该渲染成

<html>
... snip ...
<div id="content">
<div>parent header</div> 
I am a foo and my title is Bar
<div>parent footer</div>

如果设置了 parent_template,并且

<html>
... snip ...
<div id="content">
I am a foo and my title is Bar

如果没有设置。

我希望我的问题能说清楚:我需要(可选地)把一个模板包裹在父模板中,然后把结果发送到 base.html 模板。

通常,像这样的代码可能会有效:

#foo.html
{% extends "parent.html" %}
{% block child_content %}
I am a foo and my title is {{ foo.title }}
{% endblock child_content %}

#parent.html
{% extends "base.html" %}

{% block content %}
parent header
{% block child_content %}{% endblock child_content %}
parent content
parent footer

#base.html
base header
{% block content %}{% endblock content %}
base content
base footer

然而,由于 parent_template 可能是空的,有时候 base.html 只会得到 child_content 这个块,而不是 content 这个块。

另外,我希望能做到这一点,而不需要创建一堆子块(如果我决定 foo 应用应该有自己的 /foo/base.html,然后再调用 /base.html 呢)?

有什么想法吗?

3 个回答

0

我有三个模板:gparent(祖父),parent(父亲)和gchild(孩子)。然后我测试了gparent和gchild,测试方法如下:

gparent里有一个hn标签的块,gchild继承了gparent。这里没有提到parent。

{% block hn %}              
Can you see me?     # 1  naked text in the html, treated as a string.
{{  "No, I cant see you." }}    # 2 A string literal in the variable template brace.
{% endblock hn %}

这两个标签之间的内容在孩子的页面上都显示出来了。当后面的内容不是字符串时,Django会报解析错误。这是因为在双大括号里面,Django期待的是一个变量,或者是它可以计算的字符串。

当我在gchild里添加了一个hn块和一些内容后,我看到的只是gchild的hn块内容。

{% block hn %}
Now I have my own hn block                      
{% endblock hn %}
                            

然后我重复了这个测试,保留了gchild的hn块,但去掉了内容:

{% block hn %}          
{% endblock hn %}                                   

一个空的存在的hn块在gchild里覆盖了gparent的hn块内容。

接下来我在gparent和gchild之间插入了parent。现在parent继承了gparent,而gchild继承了parent。

{%  block metadata %}
{{ "You must eat your metadata to grow big and strong" }}
{% endblock metadata %}

parent现在有这个块元数据标签,而gparent和gchild里都没有这个标签。这个内容没有显示出来。

接下来我在parent的hn块里嵌套了块元数据。gparent里仍然有我们刚才测试的两个字符串,gchild有一个空的hn块。

和之前一样,什么都没有显示。

现在我将gchild里的hn块去掉:

You must eat your metadata to grow big and strong

所以,你可以添加一个在早期模板中不存在的新块标签,只要它完全嵌套在一个祖先定义的可用块里面。

中间这一代会把这个新标签和它的内容传递给孩子,孩子会显示它,前提是孩子没有覆盖它。

记住,文档里把标签之间的内容描述为一个需要填充的空洞。如果孩子没有这个标签,空洞就会被父亲填充。这就是父亲内容成为默认内容的原因。这也是为什么大多数基础模板包含你希望在整个网站上使用的头部、底部、导航和样式。但如果孩子有相同的标签,孩子就自己填充了这个空洞。这也是为什么孩子可以有自己的块标题标签,即使它嵌套在html里面,通常是在父块头部里面,或者你选择称之为的任何东西。

当我第一次学习Django时,我对此感到很困惑,因为我总是想做一些疯狂的事情,比如

{% block first %}
{% block second %}
{% endblock first %}
{% endblock second %}

这在任何地方都行不通。

2
  1. base.html
{% block content %}
  <p>Grand Parent file</p>
{% endblock %}
  1. parent.html
{% extends 'base.html' %}

{% block content %}
    {% include 'grandchild1.html' %}
    {% include 'grandchild2.html' %}
{% endblock %}

3.1. grandchild1.html

<div>Hello I'm grandchild 1</div>

3.2 grandchild2.html

<div>Hello I'm grandchild 2</div>

这样你可以把文件嵌套到任何层级。把每个部分写在不同的 .html 文件中。
然后把它们包含到 parent.html 或其他文件中。

15

这个 extends 模板标签可以接受一个可变的参数。

所以:

base.html
    {% block content %}
        <p>BASE</p>
    {% endblock %}

parent.html
    {% extends "base.html" %}

    {% block content %}
        {{ block.super }}
        <p>PARENT</p>
    {% endblock %}

foo.html
    {% extends ext_templ %}

    {% block content %}
        {{ block.super }}
        <p>FOO</p>
    {% endblock %}

使用 base:

return render_to_response('foo.html', {'ext_templ':'base.html'})

会给你:

<p>BASE</p>
<p>FOO</p>

使用 parent:

return render_to_response('foo.html', {'ext_templ':'parent.html'})

会给你:

<p>BASE</p>
<p>PARENT</p>
<p>FOO</p>

补充:

解决这个问题以获得嵌套块的一种方法是:

base.html
    {% block content %}
        {% block top %}
            <p>BASE START</p>
        {% endblock %}

        {% block bot %}
            <p>BASE END</p>
        {% endblock %}
    {% endblock %}


parent.html
    {% extends "base.html" %}

    {% block top %}
        {{ block.super }}
        <p>PARENT</p>
    {% endblock %}

    {% block bot %}
        <p>PARENT</p>
        {{ block.super }}
    {% endblock %}

foo.html
    {% extends ext_templ %}

    {% block top %}
        {{ block.super }}
        <p>FOO</p>
    {% endblock %}

    {% block bot %}
        <p>FOO END</p>
        {{ block.super }}
    {% endblock %}

我想到的另一种方法是用一个 {% if ext_templ == 'parent.html' %} 标签来包裹这些块,但这似乎不太简洁。我也很想知道其他人对嵌套块的看法。

撰写回答