业务(business logic 比如插入数据库)和展示逻辑(presentation logic, 比如生成返回)最好分开,展示逻辑可以放置在模板中。模板是一个包含响应文本的文件,用占位符变量表示动态部分。rendering(渲染):把占位符用实际值代替,并返回最终响应字符串。Flask采用模板引擎Jinja2。
模板引擎Jinja2
简单的模板:templates/index.html
<h1>Hello World!</h1>
带参数的模板:templates/user.html
<h1>Hello, {{ name }}!</h1>
渲染模板
默认Flask查找应用的templates子目录寻找模板,现在我们对上一章的hello.py基于模板进行改写:
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()
变量
更多变量示例:
<p>A value from a dictionary: {{ mydict[‘key‘] }}.</p> <p>A value froma list: {{ mylist[3] }}.</p> <p>A value froma list, with a variable index: {{ mylist[myintvar] }}.</p> <p>A value froman object‘s method: {{ myobj.somemethod() }}.</p>
注意上面的列表 {{ mylist[myintvar] }},有名字索引。变量后面还可以添加过滤器,比如首字母转为大写其他的转为小写:Hello, {{ name|capitalize }}。Jinja2中常见的变量过滤器如下:
Filter name | Description |
safe | Renders the value without applying escaping |
capitalize | Converts the first character of the value to uppercase and the rest to lowercase |
lower | Converts the value to lowercase characters |
upper | Converts the value to uppercase characters |
title | Capitalizes each word in the value |
trim | Removes leading and trailing whitespace from the value |
striptags | Removes any HTML tags from the value before rendering |
默认Jinja2因为安全原因转义所有变量,比如变量的值为‘<h1>Hello</h1>‘,Jinja2就会渲染成‘<h1>Hello</h1>‘,这样就不会被浏览器解释。注意不要对用户表单输入等可能不安全的代码使用safe。完整的内置过滤器参考
控制结构
- if语句:
{% if user %} Hello, {{ user }}!{% else %} Hello, Stranger!{% endif %}
- for语句:
<ul> {% for comment in comments %} <li>{{ comment }}</li> {% endfor %}</ul>
- macro
{% 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‘ %}
- 继承
基类: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三个可变部分。下面有个继承的子模板:
{% extends "base.html" %}{% block title %}Index{% endblock %}{% block head %} {{ super() }} <style> </style>{% endblock %}{% block body %}<h1>Hello, World!</h1>{% endblock %}
使用Flask-Bootstrap集成Twitter Bootstrap
Bootstrap是来自Twitter的开源框架,它提供了用户界面组件以创造简洁并有吸引力的网页并兼容所有现在的网络浏览器。参考资料:维基百科中文Bootstrap介绍, 维基百科英文Bootstrap介绍
Bootstrap是客户端框架,服务器只需要提供HTML响应,引用Bootstrap的CSS和JavaScript文件并通过HTML、CSS和JavaScript实例化所需的组件。这些事情很适合在模板中做。需要安装:pip install flask-bootstrap。
下面我们flask-bootstrap基于flask-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 %}
上面模板定义了title , navbar和content三个block。Flask-Bootstrap的base.html提供的block如下:
Block name | Description |
doc | The entire HTML document |
html_attribs | Attributes inside the <html> tag |
html | The contents of the <html> tag |
head | The contents of the <head> tag |
title | The contents of the <title> tag |
metas | The list of <meta> tags |
styles | Cascading stylesheet definitions |
body_attribs | Attributes inside the <body> tag |
body | The contents of the <body> tag |
navbar | User-defined navigation bar |
content | User-defined page content |
scripts | JavaScript declarations at the bottom of the document |
上面的block很多Flask-Bootstrap自己也在使用,通常不能简单重载,需要调用super():
{% block scripts %} {{ super() }} <script type="text/javascript" src="my-script.js"></script> {% endblock %}
hello.py 修改如下:
from flask import Flask, render_template from flask.ext.script import Manager from flask.ext.bootstrap import Bootstrap app = Flask(__name__) manager = Manager(app) 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) if __name__ == ‘__main__‘: manager.run()
现在访问根目录,返回的是普通的文本页面。但是访问/user/username已经展示有初步菜单的bootstrap页面了。
自定义错误页
两种最常见的错误码是404 (找不到页面)和500(有未处理异常)。我们再修改下:hello.py:
from flask import Flask, render_template from flask.ext.script import Manager from flask.ext.bootstrap import Bootstrap app = Flask(__name__) manager = Manager(app) bootstrap = Bootstrap(app) @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 @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()
响应的模板也基于继承进行了调整:
父模板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 %}
子模板:templates/500.html
{% extends "base.html" %} {% block title %}Flasky - Internal Server Error{% endblock %} {% block page_content %} <div class="page-header"> <h1>Internal Server Error</h1> </div> {% endblock %}
子模板:templates/index.html
{% extends "base.html" %} {% block title %}Flasky{% endblock %} {% block page_content %} <div class="page-header"> <h1>Hello World!</h1> </div> {% endblock %}
子模板:templates/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_for(‘index‘, _external=True)生成 http://localhost:5000/。动态内容比如:url_for(‘user‘, name=‘john‘, _external=True)生成http://localhost:5000/user/john。url_for(‘index‘, page=2)生成/?page=2。
静态文件
通常会有图片、js文件、css等文件。static表示静态文件,url_for(‘static‘, filename=‘css/styles.css‘, _external=True)对应http://localhost:5000/static/css/styles.css。下面我们在templates/base.html的头部增加了一个图标。
{% extends "bootstrap/base.html" %}{% block title %}Flasky{% endblock %}{% block head %}{{ super() }}<link rel="shortcut icon" href="{{ url_for(‘static‘, filename=‘favicon.ico‘) }}" type="image/x-icon"><link rel="icon" href="{{ url_for(‘static‘, filename=‘favicon.ico‘) }}" type="image/x-icon">{% 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 %}
使用Flask-Moment本地化日期和时间
服务器的时间通常以UTC的格式存在,但是浏览器需要显示本地时间,为此需要一个转换,这个工作可以由浏览器来完成。
安装:pip install flask-moment
Flask-Moment依赖jquery.js和moment.js。Bootstrap已经包含了Bootstrap,moment.js需要在模板中加载,修改templates/base.html:
{% extends "bootstrap/base.html" %}{% block title %}Flasky{% endblock %}{% block head %}{{ super() }}<link rel="shortcut icon" href="{{ url_for(‘static‘, filename=‘favicon.ico‘) }}" type="image/x-icon"><link rel="icon" href="{{ url_for(‘static‘, filename=‘favicon.ico‘) }}" type="image/x-icon">{% 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 %}{% block scripts %}{{ super() }}{{ moment.include_moment() }}{% endblock %}
修改hello.py:
from datetime import datetime from flask import Flask, render_template from flask.ext.script import Manager from flask.ext.bootstrap import Bootstrap from flask.ext.moment import Moment app = Flask(__name__) manager = Manager(app) bootstrap = Bootstrap(app) moment = Moment(app) @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 @app.route(‘/‘) def index(): return render_template(‘index.html‘, current_time=datetime.utcnow()) @app.route(‘/user/<name>‘) def user(name): return render_template(‘user.html‘, name=name) if __name__ == ‘__main__‘: manager.run()
再修改: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表示越来越详细。fromNow()会定期刷新,表示到现在的时间有多久。moment.js中实现了format() , fromNow() , fromTime() , calendar() ,valueOf()和unix(),文档参见[momentjs.com/docs/#/displaying/]。
语言选择:{{ moment.lang(‘es‘) }}。