6.3.1 模板继承

我们的模板例子现在还是HTML片断,但是真实世界你将使用模板系统输出完整的HTML页面

这将导致常见的Web开发问题:怎样减少一个常见页面区域的重复和冗余(如全站导航)?

解决这个问题的经典方式是使用服务器端引入和导向,你可以在你的HTML里面嵌套另一个页面

模板系统确实也支持这种方式,上面介绍的{% include %}模板标签就是这种方案

但是解决这个问题的更好的方式是模板系统的更优雅的方式模板继承

本质上来说,模板继承使你能够构建一个“骨架”模板,里面包含你的网站的通用部分,并且在里面

定义子模板可以覆盖的“块”,让我们看看前面的例子,编辑current_datetime.tpl文件:

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
  2. <html lang="en">
  3. <head>
  4. <title>The current time</title>
  5. </head>
  6. <body>
  7. <h1>My helpful timestamp site</h1>
  8. <p>It is now {{ current_date }}.</p>
  9. <hr>
  10. <p>Thanks for visiting my site.</p>
  11. </body>
  12. </html>

看起来不错,但是当我们为另一个视图创建另一个模板时(如hours_ahead视图),如果我们想再创建

一个完整的合法的HTML模板,我们将创建下面的内容:

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
  2. <html lang="en">
  3. <head>
  4. <title>Future time</title>
  5. </head>
  6. <body>
  7. <h1>My helpful timestamp site</h1>
  8. <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
  9. <hr>
  10. <p>Thanks for visiting my site.</p>
  11. </body>
  12. </html>

显然我们重复了很多HTML内容,想象一下,如果我们在每个页面都有一些样式表,导航条,JavaScript…

我们将会在每个模板加入重复的HTML内容

这个问题的服务器端解决方案是取出模板中通用的部分然后存放在一个单独的模板中,然后被每个模板引入

可能你会把它们存放在一个叫header.tpl中:

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
  2. <html lang="en">
  3. <head>

可能还需把底下的东西存在一个叫footer.tpl的文件中:

  1. <hr>
  2. <p>Thanks for visiting my site.</p>
  3. </body>
  4. </html>

使用基于引入的策略,头和尾很容易,但是中间的东西就很混乱

例子中,每个页面有一个title

  1. <h1>My helpful timestamp site</h1>

但是title不能放到hear.tpl中,因为每个页面中的title是不同的

模板继承系统解决了这种问题,你可以认为它是服务器引入的“相反”版本

我们定义不同的部分而不是定义相同的部分

第一步是建立基本模板,即你的子模板的框架,下面是一个基本模板的例子:

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
  2. <html lang="en">
  3. <head>
  4. <title>{% block title %}{% endblock %}</title>
  5. </head>
  6. <body>
  7. <h1>My helpful timestamp site</h1>
  8. {% block content %}{% endblock %}
  9. {% block footer %}
  10. <hr>
  11. <p>Thanks for visiting my site.</p>
  12. {% endblock %}
  13. </body>
  14. </html>

我们把这个模板叫做base.tpl,它定义了我们用来在其它页面使用的基本HTML框架

现在就是子模板覆盖的工作了,要么添加内容,要么不改变块的内容

(如果你在照着例子做,把base.tpl保存到模板目录下)

这里我们使用了 {% block %} 模板标签,它告诉模板引擎一个子模板可能覆盖模板的这部分内容

既然我们有了基本模板,下面我们来编辑current_datetme.tpl来使用它:

  1. {% extends "base.tpl" %}
  2. {% block title %}The current time{% endblock %}
  3. {% block content %}
  4. <p>It is now {{ current_date }}.</p>
  5. {% endblock %}

同时我们也创建一个hours_ahead模板来使用基本模板:

  1. {% extends "base.tpl" %}
  2. {% block title %}Future time{% endblock %}
  3. {% block content %}
  4. <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
  5. {% endblock %}

这样是不是更美观一些?每个模板只包含属于自己的代码,根本没有冗余

如果你想做整个网站的改动,只需要更改base.tpl即可,其它的模板也会立即响应改动

它是这样工作的:

1、当你载入模板current_datetime.tpl,模板引擎发现 {% extends %} 标签,注意到这是一个子模板,模板引擎马上载入父模板base.tpl

2、模板引擎在base.tpl里发现了3个 {% block %} 标签,就用子模板的内容替换了这些块,于是我们定义的 {% block title %}{% block content %} 被使用

注意,既然子模板没有定义footer块,那么模板系统直接使用父模板的值父模板里 {% block %} 标签的内容一直可以作为后援方案。

你可以使用任意等级的继承,使用继承的常用方式是按以下三个步骤:

  1. 创建base.tpl模板来掌控你的网站的整体外观,它的内容很少改变
  2. 为你的网站创建base_SECTION.tpl模板,例如,base_photos.tpl,base_forum.tpl,这些模板继承base.tpl并且包括部分专有的风格和设计
  3. 为每个类别的页面创建单独的模板(例如论坛页面或者照片图库页面),这些模板继承相应的部分模板。该方法可以最大化代码重用并易于向公用区域添加东西(例如部分专有的导航)

下面是一些关于模板继承的小提示:

  1. 如果在模板里使用 {% extends %} 的话,这个标签必须在所有模板标签的最前面,否则模板继承不工作
  2. 通常基本模板里的 {% block %} 越多越好,子模板不必定义所有的父block,钩子越多越好
  3. 如果你在很多模板里复制代码,很可能你应该把这些代码移动到父模板里
  4. 如果你需要得到父模板的块内容,{{ block.super }} 变量可以帮你完成工作
    当你需要给父块添加内容而不是取代它的时候这就很有用
  5. 不能在同一模板里定义多个同名的 {% block %},因为块标签同时在两个地方工作,不仅仅在子模板中,而且在父模板中也填充内容,如果子模板有两个同名的标签,父模板将不能决定
    使用哪个块内容来使用
  6. 大部分情况下,{% extends %} 的参数是string,但是也可以是变量,如果知道运行时才知道
    父模板的名字,这可以帮助你做一些很cool的动态内容