详解Python的Django框架中的模版继承


Posted in Python onJuly 16, 2015

在实际应用中,你将用 Django 模板系统来创建整个 HTML 页面。 这就带来一个常见的 Web 开发问题: 在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?

解决该问题的传统做法是使用 服务器端的 includes ,你可以在 HTML 页面中使用该指令将一个网页嵌入到另一个中。 事实上, Django 通过刚才讲述的 {% include %} 支持了这种方法。 但是用 Django 解决此类问题的首选方法是使用更加优雅的策略—— 模板继承 。

本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载。

让我们通过修改 current_datetime.html 文件,为 current_datetime 创建一个更加完整的模板来体会一下这种做法:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
  <title>The current time</title>
</head>
<body>
  <h1>My helpful timestamp site</h1>
  <p>It is now {{ current_date }}.</p>

  <hr>
  <p>Thanks for visiting my site.</p>
</body>
</html>

这看起来很棒,但如果我们要为第三章的 hours_ahead 视图创建另一个模板会发生什么事情呢?

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
  <title>Future time</title>
</head>
<body>
  <h1>My helpful timestamp site</h1>
  <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>

  <hr>
  <p>Thanks for visiting my site.</p>
</body>
</html>

很明显,我们刚才重复了大量的 HTML 代码。 想象一下,如果有一个更典型的网站,它有导航条、样式表,可能还有一些 JavaScript 代码,事情必将以向每个模板填充各种冗余的 HTML 而告终。

解决这个问题的服务器端 include 方案是找出两个模板中的共同部分,将其保存为不同的模板片段,然后在每个模板中进行 include。 也许你会把模板头部的一些代码保存为 header.html 文件:

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

你可能会把底部保存到文件 footer.html :

<hr>
  <p>Thanks for visiting my site.</p>
</body>
</html>

对基于 include 的策略,头部和底部的包含很简单。 麻烦的是中间部分。 在此范例中,每个页面都有一个 <h1>My helpful timestamp site</h1> 标题,但是这个标题不能放在 header.html 中,因为每个页面的 <title> 是不同的。 如果我们将 <h1> 包含在头部,我们就不得不包含 <title> ,但这样又不允许在每个页面对它进行定制。 何去何从呢?

Django 的模板继承系统解决了这些问题。 你可以将其视为服务器端 include 的逆向思维版本。 你可以对那些 不同 的代码段进行定义,而不是 共同 代码段。

第一步是定义 基础模板 , 该框架之后将由 子模板 所继承。 以下是我们目前所讲述范例的基础模板:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
  <title>{% block title %}{% endblock %}</title>
</head>
<body>
  <h1>My helpful timestamp site</h1>
  {% block content %}{% endblock %}
  {% block footer %}
  <hr>
  <p>Thanks for visiting my site.</p>
  {% endblock %}
</body>
</html>

这个叫做 base.html 的模板定义了一个简单的 HTML 框架文档,我们将在本站点的所有页面中使用。 子模板的作用就是重载、添加或保留那些块的内容。 (如果你一直按顺序学习到这里,保存这个文件到你的template目录下,命名为 base.html .)

我们使用一个以前已经见过的模板标签: {% block %} 。 所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。 每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。

现在我们已经有了一个基本模板,我们可以修改 current_datetime.html 模板来 使用它:

{% extends "base.html" %}

{% block title %}The current time{% endblock %}

{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}

再为 hours_ahead 视图创建一个模板,看起来是这样的:

{% extends "base.html" %}

{% block title %}Future time{% endblock %}

{% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}

看起来很漂亮是不是? 每个模板只包含对自己而言 独一无二 的代码。 无需多余的部分。 如果想进行站点级的设计修改,仅需修改 base.html ,所有其它模板会立即反映出所作修改。

以下是其工作方式。 在加载 current_datetime.html 模板时,模板引擎发现了 {% extends %} 标签, 注意到该模板是一个子模板。 模板引擎立即装载其父模板,即本例中的 base.html 。

此时,模板引擎注意到 base.html 中的三个 {% block %} 标签,并用子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 中定义的标题,对 {% block content %} 也是如此。 所以,网页标题一块将由 {% block title %}替换,同样地,网页的内容一块将由 {% block content %}替换。

注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。

继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。

你可以根据需要使用任意多的继承次数。 使用继承的一种常见方式是下面的三层法:

  •     创建 base.html 模板,在其中定义站点的主要外观感受。 这些都是不常修改甚至从不修改的部分。
  •     为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。这些模板对 base.html 进行拓展,并包含区域特定的风格与设计。
  •     为每种类型的页面创建独立的模板,例如论坛页面或者图片库。 这些模板拓展相应的区域模板。

这个方法可最大限度地重用代码,并使得向公共区域(如区域级的导航)添加内容成为一件轻松的工作。

以下是使用模板继承的一些诀窍:

  •     如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作用。
  •     一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义父模板中所有的代码块,因此你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。 俗话说,钩子越多越好。
  •     如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。
  •     如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一个魔法变量将会表现出父模板中的内容。 如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。
  •     不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block 标签的工作方式是双向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容。如果模板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。
  •     {% extends %} 对所传入模板名称使用的加载方法和 get_template() 相同。 也就是说,会将模板名称被添加到 TEMPLATE_DIRS 设置之后。
  •     多数情况下, {% extends %} 的参数应该是字符串,但是如果直到运行时方能确定父模板名,这个参数也可以是个变量。 这使得你能够实现一些很酷的动态功能。
Python 相关文章推荐
低版本中Python除法运算小技巧
Apr 05 Python
python函数局部变量用法实例分析
Aug 04 Python
Python爬取APP下载链接的实现方法
Sep 30 Python
Python实现字典去除重复的方法示例
Jul 31 Python
Python字符串拼接六种方法介绍
Dec 18 Python
Python学习_几种存取xls/xlsx文件的方法总结
May 03 Python
Linux(Redhat)安装python3.6虚拟环境(推荐)
May 05 Python
python实现搜索文本文件内容脚本
Jun 22 Python
python3+selenium自动化测试框架详解
Mar 17 Python
Python 元组拆包示例(Tuple Unpacking)
Dec 24 Python
PyQt5 closeEvent关闭事件退出提示框原理解析
Jan 08 Python
用PYTHON去计算88键钢琴的琴键频率和音高
Apr 10 Python
Django中模版的子目录与include标签的使用方法
Jul 16 #Python
Django中使用locals()函数的技巧
Jul 16 #Python
Django框架中render_to_response()函数的使用方法
Jul 16 #Python
在Python的Django框架中加载模版的方法
Jul 16 #Python
Django框架下在视图中使用模版的方法
Jul 16 #Python
详解Django中的过滤器
Jul 16 #Python
在Python的Django框架中为代码添加注释的方法
Jul 16 #Python
You might like
PHP 中的一些经验积累
2006/10/09 PHP
PHP把小数转成整数3种方法
2014/06/30 PHP
php数组索引的Key加引号和不加引号的区别
2014/08/19 PHP
PHP图像裁剪缩略裁切类源码及使用方法
2016/01/07 PHP
浅谈Yii乐观锁的使用及原理
2017/07/25 PHP
Yii框架 session 数据库存储操作方法示例
2019/11/18 PHP
Nigma vs Alliance BO5 第四场2.14
2021/03/10 DOTA
javascript document.images实例
2008/05/27 Javascript
js chrome浏览器判断代码
2010/03/28 Javascript
jQuery实现菜单感应鼠标滑动动画效果的方法
2015/02/28 Javascript
JavaScript中的Math.LOG2E属性使用详解
2015/06/14 Javascript
JS加载iFrame出现空白问题的解决办法
2016/05/13 Javascript
JavaScript设计模式之单体模式全面解析
2016/09/09 Javascript
JS变量中有var定义和无var定义的区别以及es6中let命令和const命令
2017/02/19 Javascript
Nodejs读取文件时相对路径的正确写法(使用fs模块)
2017/04/27 NodeJs
js实现水平滚动菜单导航
2017/07/21 Javascript
基于iScroll实现内容滚动效果
2018/03/21 Javascript
Vue中Table组件Select的勾选和取消勾选事件详解
2019/03/19 Javascript
JS获取当前时间的年月日时分秒及时间的格式化的方法
2019/12/18 Javascript
Python获取脚本所在目录的正确方法
2014/04/15 Python
Python cookbook(数据结构与算法)让字典保持有序的方法
2018/02/18 Python
基于python绘制科赫雪花
2018/06/22 Python
python中pip的安装与使用教程
2018/08/10 Python
Python中类的创建和实例化操作示例
2019/02/27 Python
python自动化测试之DDT数据驱动的实现代码
2019/07/23 Python
Python字典底层实现原理详解
2019/12/18 Python
详解Python中字符串前“b”,“r”,“u”,“f”的作用
2019/12/18 Python
解决django 向mysql中写入中文字符出错的问题
2020/05/18 Python
Python制作一个仿QQ办公版的图形登录界面
2020/09/22 Python
澳大利亚足球鞋和服装购物网站:Ultra Football
2018/10/11 全球购物
德国、奥地利和瑞士最大的旅行和度假门户网站:HolidayCheck
2019/11/14 全球购物
长曲棍球装备:Lacrosse Monkey
2020/12/02 全球购物
描述一下JVM加载class文件的原理机制
2013/12/08 面试题
金融专业大学生自我评价
2014/01/09 职场文书
竞选部长演讲稿
2014/04/26 职场文书
Python自动化测试PO模型封装过程详解
2021/06/22 Python