Flask 模板

144 阅读8分钟

Flask 中集成了 模板引擎 jinja2


app.py


from flask import Flask,render_template

user  = {
  "username":"Wu han",
   "bio":"A boy 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 = Flask(__name__)

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


templates/watchlist.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{user.username}}</title> # 调用获取变量属性
</head>
<body>
  <a href="{{url_for('index')}}">&larr; Return</a>
  <h2>{{user.username}}</h2>
  {% if user.bio %}  # if循环
    <i> {{user.bio}}</i>
  {% endif %}
  {# 下面是电影清单(这是注释) #}
  <h5>{{ user.username }}'s Watchlist ({{ movies|length }})</h5>
  <ul>
    {% for movie in movies %} # for 循环
      <li>{{ movie.name }} - {{ movie.year }}</li>
    {% endfor %}
  </ul>
</body>
</html>
  1. 语句

{% ... %} : if 、for 循环等

 {% if user.bio %}  # if循环
    <i> {{user.bio}}</i>
 {% else %} 
    <i> This user has not provided a bio.</i>
 {% endif %}
  {% for movie in movies %} # for 循环
    <li>{{ movie.name }} - {{ movie.year }}</li>
  {% endfor %}

  1. 表达式

{{ ... }} : 字符串,变量,函数调用等

  1. 注释

{# #}

变量名说明
loop.index当前迭代数(从1开始计数)
loop.index()当前迭代数(从0开始计数)
loop.revindex当前反向迭代数(从1开始计数)
loop.revindex()当前反向迭代数(从0开始计数)
loop.first如果是第一个元素,则为True
loop.last如果是最后一个元素,则为True
loop.previtem上一个迭代得条目
loop.nextitem下一个迭代得条目
loop.length序列包含得元素数量

渲染模板

render_template() 函数来渲染
@app.route('/')
@app.route('/hello')
def index():
    return render_template('watchlist.html', user=user, movies=movies)

render_template_string() 函数用来渲染模板字符串
定义模板上下文
  • 在模板中定义变量,使用set标签
  {% set navigation = [('/','Home'), ('/about','About')] %}
  
  
  {% set navigation %} 
    <li>
      <a href="/">Home</a> 
    <li>
      <a href="/about">About</a> 
  {% endset %}
标准模板全局变量
  • config:当前得配置对象
  • request:当前的请求对象,在已激活得请求环境下可用
  • session:当前得回话对象,在已激活得请求环境下可用
  • g:与请求绑定得全局变量,在已激活得请求环境下可用
自定义上下文

使用app.context_processor 装饰器来注册模板上下文处理函数。模板上下文函数需要返回一个包含变量键值对得字典。

方式1@app.context_processor
def inject_foo():
  foo  = "I am foo."
  return dict(foo=foo) # 等同于return {'foo':foo}

方式2def inject_foo():
  foo = "I am foo."
  return dict(foo=foo)
app.context_processor(inject_foo)

方式3:
app.context_processor(lambda : dict(foo="I am foo."))

↑:当我们调用 render_template函数渲染得任意一个模板时候,所有使用app.context_processor装饰器注册得模板上下文处理函数都会被执行,这些函数得返回值会添加到模板中,因为我们可以在 模板中直接使用foo变量。

全局对象

  1. 内置全局函数(常用)
  2. range([start,]stop[,step]) :和Python中的range()用法相同
  3. lipsum(n=5, html=True,min=20,max=100):生成随机文本(lorem ipsum),可以在测试时用来填充页面,默认生成5端HTML文本,每段包含20~100个单词
  4. dict(**items):和Python中dict()用法相同
  5. Flask内置模板全局函数
  6. url_for():用于生成URL得函数
<a href='{{ url_for('index') }}'>&larr; Return</a>
  1. get_flashed_messages:用于获取flask消息得函数
  2. 自定义全局函数

@app.template_global([name=])

@app.template_global()
def bar():
  return "I am bar."
  

app.add_template_global(your_global_function)

过滤器

标题过滤器
{{ name|title }} # 将name得字符串转换为标题格式得 跟 python中 name.title()一样
长度过滤器
{{ movies|length }} # 获取movies列表得长度
标签过滤器
{% filter upper %} # 将下面这段文字全部转换成大写
  This text becomes uppercase.
{% endfilter %}

过滤器可以叠加使用

<h1>Hello, {{ name|default('陌生人')|title }}!</h1>
内置过滤器:
过滤器说明
default(value, default_value=u"", boolean=False)设置默认值,默认值作为参数传入,别名为d
escape(s)转义HTML文本,别名为e
first(seq)返回序列得第一个元素
last(seq)返回序列得最后一个元素
length(object)返回变量得长度
| random(seq) | 返回序列初中的随机元素 | 
	| safe(value) | 将变量值标记为安全,避免转义 | 
	| trim(value) | 消除变量值前后的空格 |
	| max(value, case_sensitive=False, attribute=None) | 返回序列中的最大值 |

| min(value, case_sensitive=False, attribute=None) | 返回序列中最小值 | | unique(value, case_sensitive=False, attribute=None) | 返回序列中的不重复的值 | | striptage(value) | 消除变量值内的HTML标签 | | urlize(value, trim_url_limit=None, onfollow=False, target=None,rel=None) | 将url文本转换为可单击的HTML链接| | wordcount(s) | 计算单词数量| | tojson(value, indent=None) | 将变量值转换为JSON格式| | truncate(s, length=255, killwords=False, end="...", leeway=None) | 截断字符串,常用语显示文章摘要,length参数设置截断的长度,killwords参数设置是否截断单词,end参数设置结尾符号 |


注:我们介绍了XSS攻击的主要防范措施,其中最主要的是对用户输入的文本进行转义。根据Flask的设置,Jinja2会自动对模板中的变量进行转义,所以我们不用手动使用escape过滤器或调用escape()函数对变量进行转义。

将文本标记为安全的方法:

因为jinja2会对文本默认进行转义,所以如果不想进行转义得话 那么需要一个操作

方式1:
{{ sanitieze_text | safe }}


方式2from flask import Markup

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

自定义过滤器:

app.template_filter()装饰器

from flask import Markup

@app.template_filter()
def musical(s):
  return s + Markup(' &#9835;')
  
{{ name|musical }}
测试器:

测试器(Test)是一些用来测试变量或者本次奥大师,返回布尔值(True或者False)得特殊函数。

{% if age is number %}
  {{ age * 365}}
{% else %}
  无效得数字
{% endif %}

内置测试器:

测试器说明
callacble(object)判断对象是否可被调用
defined(value)判断变量是否已定义
undefined(value)判断变量是否未定义
none(value)判断变量是为None
number(value)判断变量是否是数字
string(value)判断变量是否是字符串
sequence(value)判断变量是否是序列,比如字符串,列表,元祖
iterable(value)判断变量是否可迭代
mapping(value)判断变量是否是匹配对象,比如字典
sameas(value, other)判断变量与other是否指向相同的内存地址

用法:

{%if foo is sameas(bar) %}
↑↓ 效果一样
{%if foo is sameas bar %}

自定义测试器

app.template_test()装饰器

@app.template_test()
def baz(n):
  if n == "baz":
    return True
   return False

模板环境对象

在程序中,可以使用app.jinja_env更改jinja2设置。

app = Flask(__name__)
# 修改定界符
app.jinja_env.variable_start_string = '[['
app.jinjaenv.variable_end_string = ']]'

添加自定义全局对象


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

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


添加自定义过滤器


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

添加自定义测试器

def baz(n): 
    if n == 'baz': 
        return True 
     return False 
app.jinja_env.tests['baz'] = baz

模板结构组织

局部模板
{% include '_bannner.html' %}  # 局部模板命名一般以 ‘_’ 开始
macros.html / _macros.html # 最好把宏单独存放,方便管理。

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

引用:

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

模板继承
{% block head %} {% endblock head %}

{% block styles %}

{% block content %}

{% block footer %}

{% block scripts %}

用法:

{% extends "greeter.html"%}
{% block times %}
	# 这是子模板的内容
{% endblock times %}

应用定义好的块

# 引用定义好的块
{{ self.块名() }}

============================
----base.html
{% bclock title %}
    <h1> This is base.html </h1>
{% endblock %}
----page.html
{% bclock body%}
  {{ self.title() }}
    <h1> This is body </h1>
{% endblock %}
  =====
  # 输出:
    This is base.html
    This is body

super() 用法

{% bclock title %}
  {{ super() }} # 用来继承父类已经存在的内容 , 如果不加super(), 那么这个块的内容会被新的覆盖
{% endblock %}

===========================
# 用法
----base.html
{% block title %}
  <h1> This is base.html </h1>
{% endblock %}
----page1.html
{% extends "base.html" %}
  {% block title %}
    {{ super() }}
    <h1> This is Page1 html<h1/>
  {% endblock %}
 ======
 # 输出:
 This is base.html
 This is Page1 html
 
----page2.html
{% extends "base.html" %}
  {% block title %}
     <h1> This is Page2 html<h1/>
  {% endblock %}
 ======
 # 输出:
 This is Page2 html

空白控制

默认jinja2 会在命令前后添加空行,所以需要一些控制对他来进行删除

{% if True -%}

{%- if True%}

环境对象删除

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

注: 宏内的空白不可进行环境对象的配置,只能进行手动删除。


加载静态文件

一个Web项目不仅需要HTML模板,还需要许多静态文件,比如CSS、JavaScript文件、图片以及音频等。在Flask程序中,默认我们需要将静态文件存储在与主脚本(包含程序实例的脚本)同级目录的static文件夹中。

默认路径如下:

url_for('static',filename='')

<img src='{{ url_for("static", filename="avatar.jpg") }}'width='50'/>

引入Bootstrap

{% 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链接直接引用

{% block styles %}
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
{% endblock %}
...
{% block scripts %}
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
{% endblock %}

使用宏加载静态资源


{% 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_files('css',css/bootstrap.min.css)

static_file('css', 'https://maxcdn.../css/bootstrap.min.css', local=False)


消息闪现(Flash)

使用功能flash()函数发送的消息会存储在session中,我们需要在模板中使用全局函数get_flashed_messages()获取消息并将其显示出来。

需要程序密钥

app.secret_key = "secret key"

.env 
SECRET_KEY = "secret key"
@app.route("/flash")
def flash1():
    flash("欢迎光临")
    return render_template('flash.html')
----------------------------
html页面:
{% for message in get_flashed_messages() %}
    {{  message }}
{% endfor %}

🌞当get_flashed_messages()函数被调用时,session中存储的所有消息都会被移除。如果你这时刷新页面,会发现重载后的页面不再出现这条消息。


自定义错误页面

templates/errors/404.html

{% 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(code=,name=,description=)装饰器

  • code:状态码
  • name:原因短语
  • description:错误描述,另外使用get_description()还可以获取HTML格式错误代码
from flask import Flask, render_template

@app.errorhandler(404)
def page_not(e):
  return render_template('error/404.htmll'), 404

可以注册其他类型的异常

app.errorhandler(NameError)