模板
@(Flask)
视图函数
作用:生成请求的响应。
一般情况下,请求会改变程序的状态,这种改变相应的需要在视图函数中产生。
两种逻辑的处理
- 业务逻辑
- 表现逻辑
两种逻辑不分离将会使得代码难以理解和维护。
Jinja2模板引擎
模板是包含响应文本的文件。
动态部分用占位变量表示,具体的值在请求的上下文中才能知道。
渲染:使用真实值替代变量,返回最终的响应字符串。
Flask用的是强大的Jinja2模板引擎。
最简单的模板
- 只包含响应文本的文件。
#templates/index.html : Jinja2模板
<h1>Hello World!</h1>
- 添加动态变量
#templates/index.html : Jinja2模板
<h1>Hello,{{ name }}!</h1>
可以看到:Jinja2模板的动态部分的替代和视图函数中的不同。
#视图函数
return '<h1>Hello, %s!</h1>' %name
#Jinja2模板
<h1>Hello,{{ name }}!</h1>
渲染模板
在程序中新建templates文件夹。
新建模板(xxx.html文档)
#index.html
<h1>Hello World!</h1>
#user.html
<h1>Hello, {{ name }}!</h1>
在hello.py中渲染:
#!/usr/bin/python
# coding=utf-8
from flask import Flask, render_template
from flask.ext.script import Manager
app = Flask(__name__)
manager = Manager(app)
@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__':
manager.run() #服务器运行要依赖manager来管理,有了新的boss
注意引入render_template
包,然后视图函数的返回值用render_template()
函数来渲染模板,看到渲染时动态传入参数的方法。
render_template()函数
第一个参数是模板的文件名,随后参数都是键值对。
name=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 a object's method: {{ myobj.somemethod() }}</p>
过滤器
使用过滤器修改变量,过滤器名在变量名后,中间用竖线分隔:
Hello, {{ name|capitalize }}
这样将会以首字母大写形式显示name的值。
常用的过滤器:
- safe : 渲染时不转义
- capitalize : 值的首字母转换为大写,其他字母小写
- lower : 值转换为小写形式
- upper : 值转换为大写形式
- title : 值中的每个单词的首字母转换为大写
- trim : 值的首尾空格去掉
- striptags : 渲染之前把值中所有的HTML标签删掉
特别注意safe
这个的使用场景,默认情况下,Jinja2会转义所有的变量。
我们有时需要不转义的HTML代码,此时需要safe过滤器。
不要在不可信的值上使用safe过滤器。
完整过滤器列表.
控制结构
渲染流程需要引入控制结构。
条件控制:
{% if user %}
Hello, {{ user }}!
{% else %}
Hello, stranger!
{% endif %}
渲染一组元素:
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
宏:
{% macro render_comment(comment) %}
<li> {{ comment }} </li>
{% endmacro %}
<!--利用宏-->
<ul>
{% for comment in comments %}
{{ render_comment(comment)}}
{% endfor %}
</ul>
可以将宏单独保存,需要使用的模板中引入即可。
<% import 'macros.html' as macros %>
<ul>
<% for comment in comments %>
{{ macros.render_comment(comment) }}
<% endfor %>
</ul>
多处重复使用时,可以用包含来做。
<% include 'common.html' %>
模板继承
<!DOCTYPE html>
<html>
<head>
<% block head %>
<title>{% block title %} {% endblock %}</title>
<% endblock %>
</head>
<body>
<% block body %>
<% endblock %>
</body>
</html>
其中block标签定义的元素可以在衍生模板中修改。
扩展语法:
<% extends "base.html" %>
<% block title %>Index<% endblock %>
<% block head %>
{{ super() }}
<style>
</style>
<% endblock %>
<% block body %>
<h1>Hello World! </h1>
<% endblock %>
通过extends
可以达到重复利用模板。
使用Flask-Bootstrap集成Twitter Bootstrap
Bootstrap是客户端框架,主要提供CSS,JS文件,在JS代码宏实例化所需要的组件,这些操作的理想场所是模板。
程序中集成Bootstrap,需要对模板做必要的改动。
更简单的做法是:使用Flask-Bootstrap扩展。
$ pip install flask-bootstrap
修正: 新版的Flask导入包是:
from flask_script import Manager
from flask_bootstrap import Bootstrap
而以前的flask.ext.xx
被弃用了,新版的命名空间是flask_xx
使用步骤
基于模板继承机制,使用一个包含所有Bootstrap文件的基模板。
{% extends "bootstrap/base.html" %}
Jinja2的extends指令从Flask-Bootstrap中导入bootstrap/base.html
,实现了模板继承。
而base.html
提供了一个网页框架,引入了Bootstrap中的所有CSS和JS文件。
此外,基模板中定义了可在衍生模板中重新定义的块。
bootstrap/base.html中的块
- doc : 整个HTML文档
- html_attribs :
<html>
标签的属性 - html :
<html>
标签中的内容 - head :
<head>
标签中的内容 - title :
<title>
标签中的内容 - metas : 一组
<meta>
标签 - styles : CSS定义
- body_attribs :
<body>
标签的属性 - body :
<body>
标签中的内容 - navbar : 用户定义的导航条
- content : 用户定义的页面内容
- scripts : 文档底部的JS声明
直接重定义块会导致问题,因此,如果程序需要向已有内容的块中添加新内容,必须使用super()
函数。
示例:添加新的jS文件
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="my-script.js"></script>
{% endblock %}
补充:bootstrap/base.html概览:
{% block doc -%}
<!DOCTYPE html>
<html{% block html_attribs %}{% endblock html_attribs %}>
{%- block html %}
<head>
{%- block head %}
<title>{% block title %}{% endblock title %}</title>
{%- block metas %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{%- endblock metas %}
{%- block styles %}
<!-- Bootstrap -->
<link href="{{bootstrap_find_resource('css/bootstrap.css', cdn='bootstrap')}}" rel="stylesheet">
{%- endblock styles %}
{%- endblock head %}
</head>
<body{% block body_attribs %}{% endblock body_attribs %}>
{% block body -%}
{% block navbar %}
{%- endblock navbar %}
{% block content -%}
{%- endblock content %}
{% block scripts %}
<script src="{{bootstrap_find_resource('jquery.js', cdn='jquery')}}"></script>
<script src="{{bootstrap_find_resource('js/bootstrap.js', cdn='bootstrap')}}"></script>
{%- endblock scripts %}
{%- endblock body %}
</body>
{%- endblock html %}
</html>
{% endblock doc -%}
自定义错误页面
场景:在浏览器地址栏输入了不可用的路由,则会有一个状态码为404的错误页面。为了风格一致,需要自定义一个界面。
两个错误代码返回值:
+ 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/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 %}
异常渲染界面基于这个基模板来写,而不是基于bootstrap的基模板来做。
于是,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 %}
链接
任何具有多个路由的程序需要可以连接不同页面的链接。
模板中直接编写简单路由的URL链接不难,但是对于包含可变部分的动态路由,在模板中构建正确的URL就困难。此外,直接编写URL会对代码中定义的路由产生不必要的依赖关系。重新定义路由,模板中的连接就会失效。
这个更通俗的解释是:在html中写链接是很容易做到的,但是,这是一种硬编码的做法,更好的做法是通过辅助函数来构造,能够解构出层次来,模块化编写程序。
url_for()
辅助函数,使用程序URL映射中保存的信息生成URL。
url_for()函数的用法
最简单的用法是:url_for()
以视图函数名或者app.add_url_route()
定义路由时使用的端点名作为参数,返回的是对应的URL。
e.g.
url_for('index')
返回的是'/'
.
url_for('index',_external=True)
返回的是绝对地址,即地址栏中的全部地址字符串。
两种运用场景:
- 程序内部使用相对地址即可
- 程序外部使用绝对地址
注意拿到相对地址是相对于根目录的。
url_for()使用动态参数
将动态部分作为关键字参数传入,返回的是值替换以后的字符串。
url_for()传递额外的参数
此函数的参数关键字不仅限于动态路由中的参数,还能将任何额外参数添加到查询字符串中。这是个很有用的特性。
url_for('index',page=2)
返回结果就是/?page=2
.
静态文件
Web程序不仅仅由Python代码和模板组成,大多数场景下会用到静态文件。
如:
- 图片
- JS源代码
- CSS
URL映射中有一个static路由,对静态文件的引用当做一个特殊的路由。
/static/<filename>
利用:
url_for('static', filename='css/style.css',_external=True)
得到的结果是:http://localhost:5000/static/css/styles.css
默认设置下,Flask在程序根目录中名为static的子目录中寻找静态文件。
Flask-Moment本地化日期和时间
服务器可以使用UTC时间来统一显示,但是UTC显示时间的格式不够友好,会让人感到困惑,更好的方式是采用当地惯用时间格式。
解决方案是:把时间单位发送给Web浏览器转换为当地时间,服务器上只用UTC时间。
moment.js是一个很棒的库,在Flask中可以用Flask-Moment来集成到Jinja2中。
安装
pip install flask-moment
使用语法
from flask_moment import Moment
moment = Moment(app)
除了moment.js库,Flask-Moment 还依赖jquery库。
因此需要在HTML文档的某个地方引入这两个库,可直接引入:
<script src="{{bootstrap_find_resource('js/bootstrap.js', cdn='bootstrap')}}"></script>
。
也可使用扩展提供的辅助函数:
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
在Bootstrap框架下,已经引入了JQuery,所以只需要再引入moment.js即可。
Flask-Moment向模板开放了moment类,所以在模板中可以调用moment的方法进行渲染。
<!--templates/index.html-->
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
在hello.py中:
from datetime import datetime
@app.route('/')
def index():
return render_template('index.html',current_time=datetime.utcnow())
但是实际测试未通过。
函数的使用:
- format()
- fromNow()
- fromTime()
- calender()
- valueOf()
- unix()
具体可查阅文档来学习moment.js的格式化选项。