想要开发出易于维护的程序,关键在于编写形式简洁且结构良好的代码。到目前为止我们接触到的示例都太简单,无法说明这一点,但Flask视图函数的两个完全独立的作用却被融合在了一起,这就会产生一个问题。

视图函数的作用很简单,即生成请求的响应。 对于简单的请求来说,这就足够了,但是一般而言,请求会改变程序的状态,而这种变化也会在视图函数中产生。

例如,用户在网站注册了一个新账户。用户在表单中输入电子邮件地址和密码,然后点击提交按钮。服务器接收到包含用户输入数据的请求,然后Flask把请求分发到处理注册请求的视图函数。这个视图函数需要访问数据库,添加新用户,然后生成响应回送浏览器。这两个过程分别称为业务逻辑和表现逻辑。

把业务逻辑和表现逻辑混在一起会导致代码难以理解和维护。假设要为一个大型表格构建一个HTML代码,表格中的数据由数据库中读取的数据以及必要的HTML字符串连接在一起。把表现逻辑移到模板中能够提升程序的可维护性。

模板是一个包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为渲染。为了渲染模板,Flask使用了一个名为Jinja2的强大模板引擎。

3.1 Jinja2模板引擎

形式最简单的Jinja2模板就是一个包含响应文本的文件。下面的例子是一个Jinja2模板,它和第二章helloworld示例中的index()视图函数的响应一样。Jinja2模板要放在整个Flask Web 工程的templates文件夹里。

<h1>Hello world!</h1>

如果视图函数返回的响应中包含一个使用变量表示的动态部分,利用Jinja2也可以实现。

<h1>Hello,{{ name }}!</h1>

3.1.1 渲染模板

默认情况下,Flask在程序文件夹的templates文件夹中寻找模板。在示例程序的下一个版本中,要把前面定义的两个模板放在templates文件夹下,分别命名为index.html和user.html。然后,我们需要改一下程序中的视图函数,以便渲染这些模板。

from flask import render_template
@app.route('/')
def index():return render_template('index.html')@app.route('/user/<name>')
def user(name):return render_template('user.html',name = name)if __name__ == '__main__':app.run(debug=True)

Flask提供的render_template函数把Jinja2模板引擎集成到了程序中,render_template函数的第一个参数是模板的文件名,随后的参数都是键值对,表示模板中的变量对应的真实值。在这段代码中第二个模板收到名为name的变量。其中左边的name表示参数名,就是模板中使用的占位符;右边是当前作用域中的变量,表示同名参数的值。

3.1.2 变量

上面的代码在模板中使用的{{ name }}结构表示一个变量,它是一种特殊的占位符,告诉模板引擎这个位置的值从渲染模板时使用的数据中获取。

Jinja2能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象。在模板中使用变量的一些示例如下:

<p>A value from a dictionary:{{ mydict['key'] }}.</p>
<p>A value from a list:{{ mylist[3] }}.</p>
<p>A value from a list,with a variable index:{{ mylist[myintvar] }}.</p>
<p>A value from an object's method:{{ myobj.somemethod }}.</p>

可以使用过滤器修改变量,过滤器名添加在变量名之后,中间使用竖线分割。例如,下面模板使用首字母大写形式显示变量name的值

<h1>Hello,{{ name|capitalize }}!</h1>

Jinja2部分常用的过滤器:

  • safe:渲染值时不进行转义
  • capitalize:把值的首字母转换成大写,其他字母转换成小写
  • lower:把值转换成小写形式
  • upper:把值转换成大写形式
  • title:把值中的每个单词的首字母都转换成大写
  • trim:把值的首尾空格去掉
  • striptags:渲染之前把值中所有的HTML标签都删掉
safe过滤器值得特别说明一下,默认情况下,出于安全考虑,Jinja2会转义所有变量。例如,如果一个变量的值为"<h1>Hello</h1>",Jinja2会将其渲染成"&lt;h1&gt;Hello&lt;/h1&gt;",浏览器能够显示这个h1元素,但不会进行解释。很多情况下需要变量中存储的HTML代码,这时可使用safe过滤器。 
千万不要在不可信的值上使用safe过滤器,例如用户在表单中输入的文本,否则会造成安全隐患,如CSRF。

3.1.3 控制结构

Jinja2提供了多种控制结构,可用来改变模板的渲染流程。本节使用简单的例子介绍其中最有用的控制结构。下面的例子展示了如何在模板中使用条件控制语句:

{% if user %}Hello ,{{ user }}!
{% else %}Hello,Stranger!
{% endif %}

另一种常见需求是再模板中渲染一组元素。可以用for 循环实现这一需求:

<ul>{% for comment in comments %}<li>{{ comment }}</li>{% endfor %}
</ul>

Jinja2还支持宏。宏类似于Python代码中的函数,例如

<ul>{% for comment in comments %}{{ render_comment(comment)}}{% endfor %}
</ul>

为了重复使用宏,我们可以将它保存在单独的文件夹中,然后在需要使用的模板中导入:

{% import 'macros.html' as macros %}
<ul>{% for comment in comments %}{{ macros.render_comment(comment) }}
</ul>

需要在多处重复使用的模板代码片段中写入单独的文件,再包含再所有模板中,以避免重复

{% include 'comment.html' %}

另一种重复使用代码的强大方式是模板继承,它类似于Python代码中的类继承。首先创建一个名为base.html的基模板:

<html>
<head>{% block head %}<title>{% block title %}{% endblock %}  - My Application </title>{% endblock %}
</head>
<body>{% block body %}{% endblock %}
</body>
</html>

block标签定义的元素可以在衍生模板中进行修改。在本例中我们定义了一个名为head、title和body的块。注意,title包含在head中。我们可以使用基模板衍生一个模板:

{% extends 'base.html' %}
{% block title %}Index{% endblock %}
{% block head %}{{ super() }}<style></style>
{% endblock %}
{% block body %}
<h1>Hello,World!</h1>
{% endblock %}

extends指令声明这个模板衍生自base.html。在该指令之后,基模板中的三个块被重新定义,模板引擎会将其插入适当的位置。注意新定义的head块,在基模板中其内容不是空的,所以使用super()来获取原来的内容

3.2 使用Flask-Bootstrap集成Twitter Bootstrap

Bootstrap是Twitter开发的一个开源框架,它提供的用户界面组件可以用于创建整洁且具有吸引力的网页,而且这些网页还能兼容所有现代Web浏览器。
Bootstrap是客户端框架,因此不会直接涉及服务器。服务器需要做的只是提供引用了Bootstrap层叠样式表(CSS)和JavaScript文件的HTML响应,并在HTML、CSS和JavaScript代码中实例化所需组件。这些操作最理想的执行场所就是模板。
要想在程序中集成Bootstrap,显然要对模板做必要的改动。不过更简单的方法是使用一个名为Flask-Bootstrap的Flask扩展,简化集成的过程。Flask-Bootstrap使用pip安装:
pip install flask-bootstrap
Flask扩展一般都在创建程序实例时初始化。下面是Flask-Bootstrap的初始化方法。

from flask import Flask
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
先导入Flask扩展包,然后把程序实例传入构造方法进行初始化。
初始化Flask-Bootstrap之后,就可以在程序中使用一个包含所有Boostrap文件的基模板。这个模板利用Jinja2的模板继承机制,让程序扩展一个具有基本页面结构的基模板,其中就有用来引入Bootstrap的元素。下面是把user.html改写为衍生模板的新版本。
我们在程序中构造了一个Bootstrap,句柄为boostrap,那么我们在引用的时候,文件夹的名字也是bootstrap(见下面的第一行)
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation"><div class="container"><div class="navbar-header"><button type = "button" class="navbar-toggle"data-toggle="collapse" data-target=".navbar-collapse"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="/">Flasky</a></div><div class="navbar-collapse collapse"><ul class="nav navbar-nav"><li><a href="/">Home</a> </li></ul></div></div>
</div>
{% endblock %}{% block content %}
<div class="container"><div class="page-header"><h1>hello ,{{ name }}!</h1></div>
</div>
{% endblock %}

Jinja2中的extends指令从Flask-Bootstrap中导入bootstrap/base.html,从而实现模板继承。Flask-Bootstrap中的基模板提供了一个网页框架,引入了Bootstrap中的所有CSS和JavaScript 文件。基模板中定义了可在衍生模板中进行重定义的块。block和endblock指令定义的块中的内容可添加到基模板中。

上面这个user.html模板定义了3个块,分别是title、navbar和content。这些块都是基模板提供的,可在衍生模板中重新定义。title块的作用很明显,其中的内容会出现在渲染后的HTML文档头部,放在<title>标签中。navbar和content这两个块分别表示页面中的导航条和主题内容。
在这个模板中,navbar块使用Bootstrap组件定义了一个简单的导航条。content块中有个<div>容器,其中包含一个页面头部。之前版本的模板中的Hello消息,现在就放在这个页面头部,改动之后的程序:
Flask-Bootstrap的base.html还定义了许多块,都可以在衍生模板中使用。
块名 说明
doc 整个HTML文档
html.attribs <html>标签的属性
html <html>标签中的内容
head <head>标签中的内容
title <title>标签中的内容
metas 一组<meta>标签
styles 层叠样式表定义
body_aattribs <body>标签的属性
body <body>标签中的内容
navbar 用户定义的导航条
content 用户定义的页面内容
scripts 文档底部的JavaScript声明

该表中的很多块都是Flask-Bootstrap自用的,如果直接进行重定义可能会出现一些问题。例如,Bootstrap所需的文件在styles和scripts块中声明。如果程序需要向已经有内容的块中添加新内容,必须使用Jinja2提供的Super()函数。例如,如果要在衍生模板中添加新的JavaScript文件,需要这么定义scripts块。

{% block scripts %}
{{ super() }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}

3.3 自定义错误页面

如果你在浏览器的地址栏输入了不可用的路由,那么会显示一个状态码为404的错误页面。现在这个错误页面太简陋、平庸,而且样式也和使用了Bootstrap的页面不一致。
像常规路由一样,Flask允许程序使用基于模板的自定义错误页面。最常见的错误代码有两个:404,表示客户端请求了未知页面或路由;500,表示有未处理的异常。为这两个错误代码指定自定义处理程序的方式如下所示:

@app.errorhandler(404)
def page_not_found(e):return render_template('404.html'),404@app.errorhandler(500)
def internal_server_error(e):return render_template('500.html'),500

和视图函数一样,错误处理程序也会返回响应。它们还返回与该错误对应的数字状态码。

错误处理程序中引用的模板也需要编写。这些模板应该和常规页面使用相同的布局,因此要有一个导航条和显示错误消息的页面头部。
编写这些模板最直观的方法就是复制templates/user.html,然后分别创建tmplates/404.html和500.html,然后把两个文件的页面头部元素改为相应的错误消息。但这种方法会带来很多重复劳动。
Jinja2的模板继承机制可以帮助我们解决这一问题。Flask-Bootstrap提供了一个具有页面基本布局的基模板,同样程序可以定义一个具有更完整页面布局的基模板,其中包含导航条,而页面内容则可留到衍生模板中定义。下面的例子展示了templates/base.html的内容,该html文件是继承自bootstrap/base.html的新模板,其中定义了导航条。这个模板本身也可作为其他模板的基模板,如templates/user.html等。如下

{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation"><div class="container"><div class="navbar-header"><button type = "button" class="navbar-toggle"data-toggle="collapse" data-target=".navbar-collapse"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="/">Flasky</a></div><div class="navbar-collapse collapse"><ul class="nav navbar-nav"><li><a href="/">Home</a> </li></ul></div></div>
</div>
{% endblock %}{% block content %}
<div class="container">{% block page_content %}{% endblock %}
</div>
{% endblock %}

这个模板的整个content块中只有一个<div>容器,其中包含了一个名为page_content的新的空块,块中的内容由衍生模板定义。

现在,程序使用的模板继承自这个base.html模板,而不是直接继承自bootstrap/base.html的基模板。通过继承现在这个新的base模板编写自定义的404错误页面很简单:

{% extends 'base.html' %}
{% block title %}Flasky - Page Not Found{% endblock %}
{% block page_content %}
<div class = 'page-header'><h1>   Not Found    .</h1>
</div>
{% endblock %}

同样的,现在我们的user.html也可以通过继承新的base.html来简化:

{% extends 'base.html' %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class = 'page-header'><h1>Hello,{{ name }}!</h1>
</div>
{% endblock %}

3.4 链接

任何具有多个路由的程序都需要可以连接不同页面的链接,例如导航条。
在模板中直接编写简单路由的URL链接不难,但对于包含可变部分的动态路由,在模板中构建正URL就很难。而且,直接编写URL会对代码中定义的路由产生不必要的依赖关系。如果重新定义路由,模板中的链接可能会失效。
为了避免这些问题,Flask提供了url_for() 函数,它可以使用程序URL映射中保存的信息生成URL。
url_for()函数最简单的方法是以视图函数名(或者app.add_url_route()定义路由时使用的端点名)作为参数,返回对应的URL。例如,在当前版本的hello.py程序中调用url_for('index')得到的结果是'/'。调用url_for('index',_external=True)返回的则是绝对地址,在这个示例中是 http://localhost:5000/。
生成连接程序内不同路由的链接时,使用相对地址就足够了。如果要生成在浏览器之外使用的链接,则必须使用绝对地址,例如在电子邮件中发送的链接。
使用url_for()生成动态地址时,将动态部分作为关键字参数传入。例如,url_for(‘user’,name ='john',_external = True)的返回结果是http://localhost:5000/user/john。其中,user是我们之前在程序中定义的用于处理不同用户的函数,name是该函数的传入参数。
传入url_for()的关键字参数不仅限于动态路由中的参数,函数能将任何额外参数添加到查询字符串中,例如url_for('index',page=2)的返回结果是 /?page=2。

3.5 静态文件

Web程序不是仅由Python代码和模板组成。大多数程序还会使用静态文件,例如HTML中引用的图片、JavaScript源码文件和CSS。
我们记得在第二章中检查hello.py程序的URL映射时,其中有一个static路由。这是因为对静态文件的引用被当成一个特殊的路由,即/static/<filename> 。例如,调用url_for('static',filename=‘css/styles.css’,_external =True)得到的结果是http://localhost:5000/static/css/styles.css。
默认设置下,Flask在程序根目录中名为static的子目录中寻找静态文件。如果需要,可在static文件夹中使用子文件夹存放文件。服务器接收到前面那个URL后,会生成一个响应,包含文件系统中static/css/style.css文件中的内容。
下面一个程序展示了如何在程序的基模板中放置favicon.ico图标。这个图标会显示在浏览器的地址栏中。
{% block head %}{{ super() }}<link rel="short icon" href="{{ url_for('static',filename ='favicon.icon') }}" type="image/x-icon"/><link rel="icon" href="{{ url_for('static',filename ='favicon.icon') }}" type="image/x-icon"/>
{% endblock %}

图标的声明会插入head块的末尾。注意如何使用super()保留基模板中定义的块的原始内容。

3.6 使用Flask-Moment本地化日期和时间

如果Web程序的用户来自世界各地,处理时间和日期并不简单。
我们知道,服务器需要统一的时间单位,这和用户处在的地理位置无关,所以一般使用协调世界时间(UTC),不过用户看到UTC格式的时间会感到困惑,他们更希望看到当地时间,而且采用当地惯用的格式。
想要在服务器上只只使用UTC时间,一个优雅的解决方案是,把时间单位发送给Web浏览器,转换成当地时间,然后渲染。Web浏览器可以更好地完成这一任务,因为它能获取用户电脑的时区和区域设置。
有一个使用JavaScript开发的优秀客户端开源代码库,名为moment.js,它可以在浏览器中渲染日期和时间,Flask-Moment是一个Flask程序扩展,能把moment.js集成到Jinja2模板中。可以使用pip安装:pip install flask-moment,它的初始化方法如下面的hello.py片段所示:

from flask_moment import Moment
app = Flask(__name__)
moment = Moment(app)

除了moment.js,Flask-Moment还依赖jquery.js。要在HTML文档的某个地方引入这两个库,可以直接引入,这样可以选择使用哪个版本,也可以使用扩展提供的辅助函数,从内容分发网络(CDN)中引入通过测试的版本。Bootstrap已经引入了jquery.js,因此只需引入moment.js即可。下面展示了如何在基模板的scripts块中引入这个库。

示例  templates/base.html :引入moment.js库

{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}

为了处理时间戳,Flask-Moment向模板开放了moment类。下面的代码把变量current_time传入模板进行渲染

示例 hello.py:加入一个datetime变量

from datetime import datetime
@app.route('/')
def index():return render_template('index.html',curren_time = datetime.utcnow())

示例 templates/index.html:使用Flask-Moment渲染时间戳    (代码运行没有效果,暂不清楚原因)

<p>The local date and time is {{ moment(curren_time).format('LLL') }}.</p>
<p>That was {{ moment(curren_time).fromNow(refresh=True) }}</p>

format('LLL')根据客户端电脑中的时区和区域设置渲染日期和时间。参数决定了渲染的方式,‘L’到‘LLLL’分别对应不同的复杂度。format()函数还可接受自定义的格式说明符。

第二行中的fromNow()渲染相对时间戳,而且会随着时间的推移自动刷新显示的时间。这个时间戳最开始显示为“a few seconds ago”,但指定refresh参数后,其内容会随着时间的推移而更新。如果一直待在这个页面,几分钟后,会看到显示的文本变成“a minute ago”、“2 minutes ago”等。
运行无效果:
Flask-Moment实现了moment.js中的format()、fromNow、fromTime、calendar、valueOf和unix方法。 Flask-Moment假定服务器端程序处理的时间戳是纯正的datetime对象,且使用UTC表示。Flask-Moment渲染的时间戳可实现多种语言的本地化。语言可在模板中选择,把语言代码传给lang()函数即可。{{ moment.lang(es) }}
使用本章介绍的技术,我们可以为程序编写出现代化且用户友好的页面。

Flask Web开发 3.0 模板相关推荐

  1. Flask Web开发-1.2模板及网页设计基础

    目录: 前言: 一,Jinja2模板引擎 1.渲染模板 2.变量 3.控制结构 二,使用Flask-Bootstrap集成Twitter Bootstrap 三,自定义错误页面 四,链接 五,静态文件 ...

  2. python flask html模板,python flask web开发实战 Jinja2模板

    templates/index.html Hello World! templates/user.html Hello, {{ name }}! 渲染模板: from flask import Fla ...

  3. Flask Web开发基础实战-1.0用户认证与注册模块

    目录: 前言: 一,账户密码安全性 使用Werkzeug实现密码散列 二,创建登录的认证蓝本 三,Flask-Login认证用户 1.用于登录的用户数据库模型 2.保护路由 3.添加登录表单 4.登入 ...

  4. flask web开发是前端还是后端_Flask Web开发实战:入门、进阶与原理解析 PDF 全格式版...

    给大家带来的一篇关于Flask相关的电子书资源,介绍了关于Flask.Web.开发实战方面的内容,本书是由机械工业出版社出版,格式为PDF,资源大小12.2M,李辉编写,目前豆瓣.亚马逊.当当.京东等 ...

  5. 《Flask Web开发:基于Python的Web应用开发实战》笔记(原创)

    内容提要 在学习"狗书"<Flask Web开发:基于Python的Web应用开发实战>的过程中,一直遇到各种各样的坑.该书的第一部分是"Flask简介&qu ...

  6. Flask Web开发:基于Python的Web应用开发实战

    <Flask Web开发:基于Python的Web应用开发实战> 虽然简单的网站(Flask+Python+SAE)已经上线,但只是入门.开发大型网站,系统地学习一遍还是有必要的. 201 ...

  7. 《Flask Web开发实战:入门、进阶与原理解析》读书笔记

    写在前面 学docker编排,有一个用Flask框架的Demo,感觉挺方便,所以学习下 基于<Flask Web开发实战:入门.进阶与原理解析>做的读书笔记 个人还是比较喜欢看书,看书的话 ...

  8. Flask Web开发入门

    Flask Web开发入门(八)之文件上传 https://blog.csdn.net/kangkanglou/article/details/79027425 前端:详情见上面的链接/也可以直接用f ...

  9. 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)

    目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构 前言 学习Python也有一个半月时间了,学到现在感觉还是 ...

最新文章

  1. NFV — 系统架构
  2. accp8.0转换教材第11章Ajax交互扩展理解与练习
  3. 一个简单的插件框架示例
  4. 计算机网络基础学测,《计算机网络技术基础》第二1章单元学习测习题-20210622072616.docx-原创力文档...
  5. File stdin , line 1
  6. 分布式搜索elasticsearch集群监控工具bigdesk
  7. 二十八种未授权访问漏洞合集(暂时最全)
  8. MogDB大对象LargeObject存取测试
  9. 红橙Darren视频笔记 旋转加载界面
  10. FeatureLayer.FeatureClass.Feature --以及图层最容易理解的讲解;如有巧合,一定是别人抄袭(Arcgis辅助理解)
  11. 牛市买基金好还是股票好?买基金会翻倍吗?
  12. c51单片机小车代码解释
  13. VSCode 中怎样快速切换多个项目
  14. 人力资源管理-输入、输出、工具和技术
  15. CDH5: 使用parcels配置lzo
  16. 和互联网公司服务器有关的一些情况
  17. 主流的第三方直播SDK对比(腾讯云、即构、阿里云、声网、网易云信、网宿)
  18. Linux下socket编程:TCP连接
  19. Java中hasNext()的作用
  20. 关于Context的理解(转)

热门文章

  1. 趣味编程故事|java进程占用cpu过高怎么办,别急我来帮你
  2. MMRotate 全面升级,新增 BoxType 设计
  3. 【正点原子sys、delay、usart文件夹介绍】
  4. python 股票指标库talib_talib金融库怎么用?
  5. win10设置网络打印机
  6. 手电筒安卓_安卓实用小工具!智能工具箱特别版本安卓软件
  7. 集合去重,取交集并集差值
  8. 台式机下成功在windows10的基础上安装Ubuntu 18.04 LTS 系统(详细教程)
  9. 没学历的IT人生没那么悲催,献给程序员们
  10. android 4.4 源码编译,Android 4.4源码编译过程