Web开发之Flask框架学习笔记(三)—— 模板

Ⅰ. 基础知识

模板(template):包含固定内容和动态部分的可重用文件
模板引擎(template engine):使得我们可以在HTML文件中使用特殊语法来标记出变量,作用就是读取并执行模板中的特殊语法标记,并根据传入的数据将变量替换为实际值,输出最终的HTML页面,这个过程被称为渲染(rendering)
在这里插入图片描述
Flask默认的模板引擎是Jinja2

Ⅱ. template基本用法

1.创建模板

需在程序文件所在的根目录下创建templates文件用于存放模板文件

watchlist.html:电影清单模板

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>{{ user.username }}'s Watchlist</title>
</head>
<body>
<a href="{{ url_for('index') }}">&larr; Return</a>
<h2>{{ user.username }}</h2>
{% if user.bio %}
	<i>{{ user.bio }}</i>
{% else %}
	<i>This user has not provided a bio.</i>
{% endif %}
{# 下面是电影清单(这是注释) #}
<h5>{{ user.username }}'s Watchlist ({{ movies|length }})</h5>
<ul>
	{% for movie in movies %}
		<li>{{ movie.name }} - {{ movie.year }}</li>
	{% endfor %}
</ul>
</body>
</html>

tips: &larr为HTML实体,显示为左箭头

2.模板语法

在模板中,Jinja2支持使用“.”获取变量的属性,比如user字典中的username键值通过“.”获取,即user.username,在效果上等同于user[‘username’]

Jinjia2中的界定符:

(1)语句 比如if判断、for循环等:

{% … %}

在语句结束的地方必须添加结束标签 例如:

{% if user.bio %}

{% else %}

{% endif %}

末尾的{% endif %}不能省略

(2)表达式 比如字符串、变量、函数调用等:

{{ … }}

(3)注释

{# … #}

3.渲染模板

from flask import Flask, render_template
app = Flask(__name__)
user = {
    'username': 'Grey Li',
    'bio': 'A boy who loves movies and music.',
}
movies = [
    {'name': 'My Neighbor Totoro', 'year': '1988'},
    {'name': 'Three Colours trilogy', 'year': '1993'},
    {'name': 'Forrest Gump', 'year': '1994'},
    {'name': 'Perfect Blue', 'year': '1997'},
    {'name': 'The Matrix', 'year': '1999'},
    {'name': 'Memento', 'year': '2000'},
    {'name': 'The Bucket list', 'year': '2007'},
    {'name': 'Black Swan', 'year': '2010'},
    {'name': 'Gone Girl', 'year': '2014'},
    {'name': 'CoCo', 'year': '2017'},
]

@app.route('/')
def index():
	return '<h1>Index Page</h1>'

@app.route('/watchlist')
def watchlist():
	return render_template('watchlist.html', user=user, movies=movies)

Ⅲ. 模板辅助工具

1.上下文

模板上下文包含很多变量,其中包括我们调用render_template()函数时手动传入的变量以及Flask默认传入的变量。

除了渲染时传入变量,也可以在模板中定义变量,使用set标签:

{% set navigation = [('/', 'Home'), ('/about', 'About')] %}

也可以将将一部分模板数据定义为变量,使用set和endset标签声明开始和结束:

{% set navigation %}
	<li><a href="/">Home</a>
	<li><a href="/about">About</a>
{% endset %}

Flask在模板上下文中提供了一些内置变量,可在模板中直接使用:
在这里插入图片描述
若多个模板需用同一变量,比较好的方法是设置一个模板全局变量
Flask提供了app.context_processor装饰器,可用来注册模板上下文处理函数,
它可以帮我们完成统一传入变量的工作,该函数需返回一个包含变量键值对的字典

@app.context_processor
def inject_foo():
	foo = 'I am foo.'
	return dict(foo=foo)    # equal to: return {'foo': foo}

调用render_template()函数渲染任意一个模板时,所有使用app.context_processor装饰器注册的模板上下文处理函数(包括Flask内置的上下文处理函数)都会被执行,这些函数的返回值会被添加到模板中,因此我们可以在模板中直接使用foo变量。

另一个方法:

def inject_foo():
	foo = 'I am foo'
	return dict(foo=foo)

app.context_processor(inject_foo)

使用lambda可简化为:

app.context_processor(lambda: dict(foo='I am foo.'))

2.全局对象

指在所有模板中都可以直接使用的对象

Jinjia2内置的全局函数(点击查看)

Flask在模板中内置的全局函数:url_for() get_flashed_messages()
全局变量:g, session, config, request

注册模板全局函数:

@app.template_global()     # 可使用name参数自定义名称
def bar():                 # 默认使用函数原名传入模板
	return 'I am bar'

也可用app.add_template_global()方法注册
传入函数对象和可选的自定义名称(name)

3.过滤器(filter)

用来修改和过滤变量值的特殊函数

用length获取movies列表的长度,对movies变量使用length过滤器:

{{  movies|length }}

将过滤器作用于一部分模板数据:

{% filter upper %}
	This text becomes uppercase.
{% endfilter %}

过滤器可叠加使用,下面的示例为name变量设置默认值,并将其标题化:

<h1>Hello, {{ name|default('Joseph')|title }}!</h1>

Jinja2内置过滤器(点击查看)

避免转义,可对变量使用safe过滤器
(不要直接对用户输入内容使用,容易被植入恶意代码)

将文本标记为安全(避免转义)

from flask import Markup
@app.route('/hello')
def hello():
	text = Markup('<h1>Hello, Flask!</h1>')
	return render_template('index.html', text=text)

注册自定义过滤器:

from flask import Markup

@app.template_filter()
def musical(s):
	return s + Markup('&#9835;')    # 加上音符图标

可使用app.add_template_filter()方法

4.测试器(Test)

用来测试变量或表达式,返回bool值的特殊函数

{% if age is number %}
	{{ age * 365 }}
{% else %}
	Invalid number
{% endif %}

Jinja2内置测试器(点击查看)

{% if foo is sameas(bar) %}...
equal to
{% if foo is sameas bar %}...

注册自定义测试器:

@app.template_test()     # name参数自定义名称
def baz(n):
	if n == 'baz':
	return True
return False

app.add_template_test()

5.模板环境对象

可使用app.jinja_env更改Jinjia2设置

# 自定义变量定界符的开始和结束符号
app = Flask(__name__)
app.jinja_env.variable_start_string = '[['
app.jinja_env.variable_end_string = ']]'

模板环境中的全局函数、过滤器和测试器分别存储在Enviroment对象的globals、filters和tests属性中,这三个属性都是字典对象。除了使用Flask提供的装饰器和方法注册自定义函数,我们也可以直接操作这三个字典来添加相应的函数或变量,这通过向对应的字典属性中添加一个键值对实现,传入模板的名称作为键,对应的函数对象或变量作为值。

1.添加自定义全局变量
和app.template_global()装饰器不同,直接操作globals字典允许我们传入任意Python对象,而不仅仅是函数

def bar():
	return 'I am bar.'
foo = 'I am foo.'

app.jinja_env.globals['bar'] = bar
app.jinja_env.globals['foo'] = foo

2.添加自定义过滤器

def smiling(s):
	return s + ' :)'
	
app.jinja_env.filters['smiling'] = smiling

3.添加自定义测试器

def baz(n):
	if n == 'baz':
		return True
	return False

app.jinja_env.tests['baz'] = baz

Ⅳ. 模板结构组织

局部模板

独立模板
非独立模板 局部模板/次模板(AJAX请求)

使用include标签插入一个局部模板

{% include '_banner.html' %}

宏(marco):

使用宏可以把一部分模板代码封装到宏里,使用传递的参数来构建内容,最后返回构建后的内容。功能上与局部模板类似,都是为了方便代码块的重用

通常把宏存储在单独文件中,该文件一般命名为macros.html/_macors.html
创建宏时,使用macro和endmacro标签声明宏的开始和结束

{% macro qux(amount=1) %}
	{% if amount == 1 %}
		I am qux.
	{% elif amount > 1 %}
		We are quxs.
	{% endif %}
{% endmacro %}

使用时,用import语句导入

{% from 'macros.html' import qux %}
...
{{ qux(amount=5) }}

在使用宏时我们需要注意上下文问题。在Jinja2中,出于性能的考虑,并且为了让这一切保持显式,默认情况下包含(include)一个局部模板会传递当前上下文到局部模板中,但导入(import)却不会

导入时显式地使用with context声明传入当前模板的上下文:

{% from "marcros.html" import foo with context %}

模板继承

1.编写基模板

基模板通常命名为base.html/layout.html

<!DOCTYPE html>
<html>
<head>
	{% block head %}
		<meta charset="utf-8">
		<title>{% block title %}Template - HelloFlask{% endblock %}</title>
		{% block styles %}{% endblock %}
	{% endblock %}
</head>
<body>
<nav>
	<ul><li><a href="{{ url_for('index') }}">Home</a></li></ul>
</nav>
<main>
	{% block content %}{% endblock %}
</main>
<footer>
	{% block footer %}
		...
	{% endblock %}
</footer>
{% block scripts %}{% endblock %}
</body>
</html>

2.编写子模版

子模版只需对特定块进行修改

{% extends 'base.html' %}
{% from 'macros.html' import qux %}

{% block content %}
{% set name='baz'%}
<h1>Template</h1>
<ul>
	<li><a href="{{ url_for('watchlist') }}">Watchlist</a></li>
	<li>Filter: {{ foo|musical }}</li>
	<li>Global: {{ bar() }}<li>
	<li>Test: {% if name is baz %}I am baz.{% endif %}</li>
	<li>Macro: {{ qux(amount=5) }}</li>
</ul>
{% endblock %}

tips: extends必须时子模版的第一个标签,使用extends标签声明扩展基模板,它告诉模板引擎当前模板派生自base.html

(1)覆盖内容: 当在子模板里创建同名的块时,会使用子块的内容覆盖父块的内容
(2)追加内容: 使用Jinja2提供的super()函数进行声明

<!--向基模板的styles块追究一行<style>样式定义-->
{% block styles %}
{{ super() }}
<style>
	.foo{
		color: red;
	}
</style>
{% endblock %}

Ⅴ. 进阶实践

空白控制

实际输出的HTML文件中,模板中的Jinja2语句、表达式和注释会保留移除后的空行

<body>
{% set age=23 %}
{% if age > 20 %}
    <i>{{ user.username }} can play the game only 10 minutes!</i>
{% endif %}
</body>

实际输出:

<body>


    <i>Grey Li can play the game only 10 minutes!</i>

</body>

若想在渲染时自动去掉这些空行,可以在定界符内侧添加减号。如,{%-endfor%}会移除该语句前的空白,同理,在右边的定界符内侧添加减号将移除该语句后的空白

<body>
{% set age=23 -%}
{% if age > 20 -%}
    <i>{{ user.username }} can play the game only 10 minutes!</i>
{%- endif %}
</body>

输出:

<body>
<i>Grey Li can play the game only 10 minutes!</i>
</body>

除了在模板中使用 - 来控制空白,也可使用模版环境对象提供的trim_blocks(删除Jinja2语句后的第一个空行)和lstrip_blocks(删除Jinja2语句所在行之前的空格和制表符(tabs))属性设置:

app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True

trim_blocks中的blocks指的是使用{%…%}定界符的代码块

宏内的空白控制行为不受trim_blocks和lstrip_blocks属性控制,我们需手动设置

{% macro qux(amount=1) %}
	{% if amount == 1 -%}
		I am qux.
	{% elif amount > 1 -%}
		We are quxs.
	{%- endif %}
{% endmacro %}

不必严格控制HTML输出,多余空白不影响浏览器的解析

加载静态文件

在Flask程序中,默认我们需要将静态文件存储在与主脚本(包含程序实例的脚本)同级目录的static文件夹中。

使用url_for()获取静态文件URL,Flask内置了用于获取静态文件的视图函数,endpoint为static,默认URL规则为/static/<path: filename>

若想使用其他文件夹来存储静态文件,可以在实例化Flask类时使用static_folder参数指定,静态文件的URL路径中的static也会自动跟随文件夹名称变化。在实例化Flask类时使static_url_path参数则可以自定义静态文件的URL路径。

<img src="{{ url_for('static', filename='avatar.jpg') }}" width="50">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename= 'styles.css' ) }}">

CSS与JavaScript Bootstrap

Bootstrap所依赖的jQueryPopper.js需要单独下载,这三个JavaScript文件在引入时要按照jQuery→Popper.js→Boostrap的顺序引入。

...
{% block styles %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
{% endblock %}
...
{% block scripts %}
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/popper.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
{% endblock %}
...

本地加载或CDN加载(更简,URL替换为CDN提供的资源URL即可)

为方便加载静态资源,可创建一个专门用于加载静态资源的宏:

{% macro static_file(type, filename_or_url, local=True) %}
	{% if local %}
		{% set filename_or_url = url_for('static', filename=filename_or_url) %}
	{% endif %}
	{% if type == 'css' %}
		<link rel="stylesheet" href="{{ filename_or_url }}" type="text/css">
	{% elif type == 'js' %}
		<script type="text/javascript" src="{{ filename_or_url }}"></script>
	{% elif type == 'icon' %}
		<link rel="icon" href="{{ filename_or_url }}">
	{% endif %}
{% endmacro %}

使用方法:

<!--本地-->
static_file('css', 'css/bootstrap.min.css')

<!--CDN-->
static_file('css', 'https://maxcdn.../css/bootstrap.min.css', local=False)

消息闪现

flash()函数

from flask import Flask, render_template, flash

app = Flask(__name__)
app.secret_key = 'secret string'

@app.route('/flash')
def just_flash():
	flash('I am flash,ha ha ha ha ha ha!')
	return redirect(url_for('index'))

发送的消息存储在session对象中,使用get_flashed_message()函数在模板里获取

<!--渲染flash消息-->
<main>
	{% for message in get_flashed_messages() %}
		<div class="alert">{{ message }}</div>
	{% endfor %}
	{% block content %}{% endblock %}
</main>

自定义错误页面

<!--404页面模板  base的子模版-->
{% extends 'base.html' %}

{% block title %}404 - Page Not Found{% endblock %}

{% block content %}
<h1>Page Not Found</h1>
<p>You are lost...</p>
{% endblock %}
# 错误处理函数需要附加app.errorhandler()装饰器,并传入错误状态码作为参数
# 404错误处理器
@app.errorhandler(404)
def page_not_found(e):
	return render_template('errors/404.html'), 404
# 错误处理函数接收异常对象作为参数,内置的异常对象提供了下列常用属性
# code   name    description

只有使用render_template()传入的模板文件才会被渲染,如果把Jinja2代码写在单独的JavaScript或是CSS文件中,尽管在HTML中引入了它们,但它们包含的Jinja2代码永远也不会被执行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值