一、Jinja2模板引擎
1.1 变量
默认情况下,Flask 在应用目录中的 template 子目录中寻找模板。
在模板中使用 {{}}
结构来表示一个变量,是一种特殊的占位符,告诉模板引擎这个位置的值从渲染模板时使用的数据中获取。
index.html:
<h1>Hello World!</h1>
user.html:
<h1>Hello, {{ name }}!</h1>
hello.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=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>
变量的值可以使用过滤器修改。过滤器添加在变量名之后,二者之间以竖线分隔。
Jinjia2变量过滤器:
过滤器名 | 说明 |
---|---|
safe | 渲染值时不转义 |
capitalize | 把值的首字母转换成大写,其他字母转换成小写 |
lower | 把值转换成小写形式 |
upper | 把值转换成大写形式 |
title | 把值中每个单词的首字母都转换成大写 |
trim | 把值的首尾空格删掉 |
striptags | 渲染之前把值中所有的 HTML 标签都删掉 |
1.3 控制结构
(1)条件判断语句
{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}
(2)for循环语句
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
(3)宏
Jinja2支持宏,类似于函数的作用,例如:
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
还可以把宏保存在单独文件中,然后重复使用,如
macors.html:
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
然后再需要用到宏的模板中使用 import
语句导入:
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}
{% endfor %}
</ul>
(4)模板
需要在多处重复使用的模板代码片段可以写入单独的文件,再引入所有模板中,以以避免重复:
{% include 'common.html' %}
模板也是可以继承的,先创建一个基模板 base.html
如下:
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock%}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
上面 block
和endblock
定义的区块中内容可以在衍生模板中覆盖,如下:
{% extends "base.html" %}
{% block title%}Index{% endblock %}
{% block head %}
{{ super() }}
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}
extends
声明此模板衍生自 base.html
,然后将基模板中定义的3个区块重新覆盖,在衍生模板的区块里可以调用 super()
来使用基模板中的内容。
二、使用Flask-Bootstrap集成Bootstrap
使用前需要先初始化Flask-Bootstrap,如下
from flask_bootstrap import Bootstrap
# ...
bootstrap = Bootstrap(app)
然后可以直接继承提供的 bootstrap/base.html
模板,其是一个包含了Bootstrap 文件和一般结构的基模板,衍生模板 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">
<div class="page-header">
<h1>Hello, {{ name }}!</h1>
</div>
</div>
{% endblock %}
完整hello.py如下:
from flask import Flask, render_template
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
Flask-Bootstrap基模板中定义的区块如下:
区块名 | 说明 |
---|---|
doc | 整个 HTML 文档 |
html_attribs | <html> 标签的属性 |
html | <html> 标签中的内容 |
head | <head> 标签中的内容 |
title | <title> 标签中的内容 |
metas | 一组 <meta> 标签 |
styles | CSS 声明 |
body_attribs | <body> 标签的属性 |
body | <body> 标签中的内容 |
navbar | 用户定义的导航栏 |
content | 用户定义的页面内容 |
scripts | 文档底部的 JavaScript 声明 |
若要在衍生模块中添加新的JavaScript文件,为了防止覆盖掉原有的引入Bootstrap的内容,需要使用 super()
函数:
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}
三、自定义错误页面
通常的错误页面有如下两种:
- 404:客户端请求未知页面或路由时显示
- 500:有未处理的异常时显示
使用 app.errorhandler
装饰器为这两个错误提供自定义的处理函数:
@app.errorhandler(404)
def pate_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
为了减少代码重复,我们可以在上面的基础上改进一下 base.html
,使其成为继承了 bootstrap/base.html
的二级基模板,也可以被其他模板继承。
二级模板 templates/base.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 %}
这样,应用中的模板继承自这个模板即可。
templates/404.html
{% 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 也可以继承改模板来简化:
{% extends "base.html" %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {{ name }}!</h1>
</div>
{% endblock %}
四、链接
有时需要用动态路由链接多个不同的页面,此时就可以使用 url_for()
辅助函数,它使用应用的 URL 映射中保存的信息生成URL。
用法如下:
(1)以视图名为参数,返回对应的URL
url_for('index')
返回根URL /
(2)使用参数 _external
可返回绝对地址
url_for('index', _external=True)
返回绝对地址 http://localhost:5000/
(3)生成动态URL,将动态部分作为关键词参数传入
url_for('user', name='john', _external=True)
返回结果是 http://localhost:5000/user/john
(4)还可以添加非动态参数到查询字符串中
url_for('user', name='john', pate=2, version=1)
返回结果是 /user/john?page=2&version=1
五、静态文件
Flask路由中有一个特殊路由 static 路由:/static/<filename>
例如调用:
url_for('static', filename='css/styles.css', _external=True)
返回的结果是 http://localhost:5000/static/css/styles.css
Flask默认在根目录中名为 static 的子目录中寻找静态文件。
下例说明了在应用的基模板中引入 favicon.ico 图标:
templates/base.html
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}", type="image/x-icon">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}", type="image/x-icon">
{% endblock %}
六、使用Flask-Moment本地化日期和时间
服务器一般使用协调世界时(UTC)来统一时间,但是把时间发送个Web浏览器时需要转换成当地时间,然后用Javascript渲染。
Flask-Moment是一个Flask扩展,能简化把Moment.js 集成到 Jinja2 模板中的过程。
使用pip在虚拟环境中安装 flask-moment 之后同样需要进行初始化
from flask_moment import Moment
moment = Moment(app)
除了Moment.js, Flask-Moment 还依赖 jQuery.js。因此要在 HTML 文档的某个地方引入这两个库。Bootstrap 已经引入了 jQuery.js,因此只需引入 Moment.js 即可。
在基模板中的 scripts 块中引入这个库,同时保留原始内容。
在 templates/base.html 的任何位置引入:
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
为了处理时间戳,Flask-Moment 向模板开放了 moment 对象,下例把变量 current_time
传入模板进行渲染:
hello.py:
from flask import Flask, render_template
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from datetime import datetime
app = Flask(__name__)
bootstrap = Bootstrap(app)
moment = Moment(app)
@app.route('/')
def index():
return render_template('index.html', current_time=datetime.utcnow())
然后在index.html 中渲染模板变量 current_time
templates/index.html
{% extends "base.html" %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello World!</h1>
</div>
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
{% endblock %}
format('LLL')
函数根据客户端计算机中的时区和区域设渲染日期和时间。参数决定了渲染的方式,从 'L'
到 'LLLL'
分别对应不同的复杂度。format() 函数还可接受很多自定义的格式说明符。
第二行中的 fromNow()
渲染相对时间戳,而且会随着时间的推移自动刷新显示的时间。这个时间戳最开始显示为 a few seconds ago
,但设定 refresh=True
参数后,其内容会随着时间的推移而更新。
Moment.js文档:http://momentjs.com/docs/#/displaying/
效果如下:
另外,Flask-Moment渲染的时间还可以实现多种语言的本地化,方法时在引入 Moment.js 后, 立即把两个字母的语言代码传给 locale()
函数,如西班牙语方式:
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{{ moment.locale('es') }}
{% endblock %}