文档章节

Flask框架——模板:分离数据与视图

AllenOR灵感
 AllenOR灵感
发布于 2017/09/10 01:24
字数 3741
阅读 11
收藏 0

Flask 框架学习目录


1. 概述

Flask框架的基本定位是开发服务端的动态网页应用。因此,模板是必不可少的环节。 Flask使用的是Jinja2模板引擎,因此本节课程中的模板语法,基本遵从Jinja的文档。


在本节课程中,主要从以下几个方面讲解Flask框架中的模板:

  • Flask的模板引擎

  • 两种模板渲染的方法

  • 在模板中使用变量和表达式

  • 在模板中使用全局对象

  • 自定义模板全局对象

  • 在模板中使用过滤器

  • 自定义模板过滤器

  • 模板变量的转义问题

  • 在模板中使用循环结构

  • 在模板中使用递归循环结构

  • 在模板中使用条件结构

2. 模板引擎

在Flask中,视图函数的返回值为响应的正文被送往前端浏览器。毫无疑问,一个实用 的视图函数需要在服务端根据请求的不同动态构造这个内容。然而手工拼接一段冗长 的HTML串是乏味而且相当容易出错。

这正是模板引擎发挥威力的地方,只需要将模板数据送入模板引擎,我们就告 别了那些那些拼接、转义之类的琐碎之事,轻松得到一个渲染后的字符串:


在Flask中,使用模板引擎基本就是分这三个步骤:

第一、声明数据

uname = 'WHOAMI'
`

第二、编写模板

tpl = '<h1>Welcome,{{username}}</h1>'

第三、提交模板引擎渲染

render_template_string(tpl,username=uname)

实验代码如下:

# -*- coding:utf-8 -*-
from flask import Flask,render_template_string
app = Flask(__name__)
@app.route('/')
def v_index():
    uname = 'WHOAMI'
    tpl = '<h1>Welcome,{{username}}</h1>'
    return render_template_string(tpl,username=uname)

app.run(host='0.0.0.0',port=8080)

实验页面如下:


3. 模板渲染

Flask基于Jinja2模板引擎,提供了两个渲染函数,分别使用字符串或单独的文件保存模板内容:

  • render_template_string(sourcestr,**context) - 使用sourcestr作为模板字符串

  • render_template(filename,**context) - 使用filename指定的文件内容作为模板字符串

render_template_string :下面的示例使用一个相当简单的模板,向不同的用户回 送个性化的欢迎信息:

@app.route('/user/<uname>')
def show_user_profile(uname): 
  return render_template_string('<h1>Welcome,{{ uname }}</h1>',uname=uname)

在Jinja2的语法中,{{varibale}}表示一个输出命令,每当渲染引擎发现一个输出命令,它就 在渲染结果中,使用模板数据上下文中变量variable的值替换原始的输出命令。

render_template :在生产环境中,在代码里写模板字符串不是什么好主意。正经的方法是将模板写在 单独的模板文件里,使用render_template()函数进行渲染:

@app.route('/user/<username>')
def v_user(username): 
 return render_template('user.html',username=username)

默认情况下,Flask使用当前模块文件夹下的templates子目录作为模板目录,user.html文件 应当放置在这个文件夹下:

/app
 /web.py
 /templates
   /user.html

user.html的内容如下:

<body>
   <h1>Welcome, {{ username }}</h1>
</body>

4. 变量与表达式

模板变量在模板被渲染时通过上下文字典传递给模板。下面的示例中,在模板中使用了 变量nameage,当调用render_template_string()渲染模板时,通过关键字参数 将两个变量的值传递进来:

tpl = 'name : {{ name }} age : {{age}} '
print render_template_string(tpl,name='Marion5',age=12)

变量成员 :如果传入模板的变量是不是Python简单类型,而是比如字典或对象类型, 那么在模板中可以向Python中一样的方式访问其成员属性或方法。

稍有不同的是,对于字典变量,除了可以使用[]方式访问其成员,还可以使用.

tpl = 'name: {{u["name"]}} name again:{{u.name}}'
print render_template_string(tpl,u={'name':'Marion5','age':12})

同样的,对于对象变量,除了使用.访问属性值,还可以使用[]

class User:
 def __init__(self,name,age):
   self.name = name
   self.age = age

tpl = 'name : {{u.name}} name again:{{u["name"]}}'
print render_template_string(tpl,u=User('Mary',20))

表达式 :变量还可以应用表达式,比如进行数学运算,那些常用的数学 操作符( + - / // % * )都是有效的:

data = {'x':12,'y':13}
tpl = '{{x}} + {{y}} = {{ x+y }}'
print render_template_string(tpl,**data)

或者进行比较或逻辑运算( == != > >= < <= and or not):

data = {'x':12,'y':13,'z':11}
tpl = '{{x}} > {{y}} : {{ x>y }}'
print render_template_string(tpl,**data)

函数调用 :在输出命令中,可以对变量或常量进行函数调用:

tpl = '{{ range(10) }} '
print render_template_string(tpl)

需要注意的是,模板有自己的全局域/globals,因此这里的range()函数并不是Python 应用中的函数。

5. 全局对象

Jinja2内置的全局对象包括:

  • range([start, ]stop[, step])

  • lipsum(n=5, html=True, min=20, max=100)

  • dict(**items)

  • class cycler(*items)

  • class joiner(sep=', ')

Flask向Jinja2模板注入了以下全局对象,可以在模板中直接访问:

  • config - 当前Flask应用实例的配置对象

  • request - 当前HTTP请求对象

  • session - 当前HTTP请求的会话对象

  • g - 当前HTTP请求周期内的全局对象

  • url_for() - URL生成函数

  • get_flashed_messages() - 闪信函数

下面的示例中,从session中提取当前用户名:

@app.route('/')
def v_index():
 tpl = 'welcome back, {{ session.username }}'
 return render_template_string(tpl)

6. 自定义全局对象

可以使用应用对象的context_processor装饰器向引擎注入额外的全局对象。 下面的示例向模板全局域中注入vendor变量,其值为hubwiz

@app.context_processor
def vendor_processor():
 return dict(vendor='hubwiz')

这时我们可以在模板中直接使用vendor变量了:

@app.route('/')
def v_index():
 tpl = 'powered by {{vendor}}'
 return render_template_string(tpl)

当然,同样的方法可以用于注入全局函数。下面的示例向模板全局域中注入format_price 函数:

@app.context_processor
def utility_processor():
 def format_price(amount, currency=u'€'):
   return u'{0:.2f}{1}'.format(amount, currency)
 return dict(format_price=format_price)

7. 过滤器

模板中可以使用过滤器|来修改变量的值。下面的示例使用内置的title过滤器 将name变量中每个单词的首字母转换为大写:

tpl = '{{ name|title }}'
print render_template_string(tpl,name='jimi hendrix') #Jimi Hendrix

过滤器级联 :可以将多个过滤器串联起来,构成过滤流水线。下面的示例对name 变量依次使用了两个过滤器,scriptags过滤器用来除去name变量中的HTML标签, title过滤器将传入的字符串中每个单词的首字母转换为大写:

tpl = '{{ name|striptags|title }}'
print render_template_string(tpl,name='<h1>jimi hendrix</h1>') #Jimi Hendrix

过滤器参数 :可以使用小括号为过滤器传入额外的参数。下面的示例将列表型变量 的多个成员使用join过滤器连接起来:

tpl = '{{ seq | join("-") }}' 
print render_template_string(tpl,seq=[1,2,3]) # 1-2-3

在Jinja2中,一个过滤器其实就是一个函数,第一个参数用来接收前序环节传入的值,而 返回值则作为后续环节过滤器函数的第一个参数:


Jinja2内置了很多过滤器,在其官网文档页 可以了解详细情况。

8. 定制过滤器

我们已经知道,过滤器其实就是一个函数。在Flask中,可以使用Flask.template_filter 装饰器创建自己的过滤器。下面的示例创建了一个名为reverse的串反转过滤器,它总是 将输入的字符串逆向重排:

@app.template_filter('reverse')
def reverse_filter(s):
 return s[::-1]

下面的示例演示了如何调用我们自制的过滤器:

@app.route('/')
def index():
 return render_template_string('{{ greeting | reverse }}',greeting='Hello, Jinja2' )

另一种等价地创建定制过滤器的方法是将过滤器函数添加到Flask应用实例的jinja_env字典中:

def reverse_filter(s):
 return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter

9. 块过滤器

块过滤器可以在整块内容上应用指定的过滤器:

{% filter [filtername] %}
...
{% endfilter %}

下面的示例将filter块内的模板内容使用upper过滤器全部转换成大写:

tpl = '''
 {% filter upper %}
 <p>Filter sections allow you to apply regular Jinja2 filters on a block 
 of template data. Just wrap the code in the special filter section</p>
 {% endfilter %}
 '''
render_template_string(tpl)

在块过滤器上也可以使用多个过滤器进行级联。下面的示例首先对filter块内容使用escape 过滤器进行转义,然后使用upper过滤器将其转换为大写:

tpl = '''
 {% filter escape | upper %}
 <p>Filter sections allow you to apply regular Jinja2 filters on a block 
 of template data. Just wrap the code in the special filter section</p>
 {% endfilter %}
 '''
render_template_string(tpl)

10. 模板中的变量风险

模板引擎的基本工作是依据给出的未知的数据上下文,结合模板生成HTML串。 考虑到结果HTML串将要在客户端的浏览器中运行,这里面存在着诸多的隐患。

XSS :如果模板变量来自于用户输入,那么存在被恶意访问者注入用于跨站攻击脚本的风险。

在下面的示例中,数据上下文user的内容来自于数据库中,而昵称/nickname 的值是允许用户自己修改的。一个恶意的访问者在自己的昵称中掺杂了脚本,当 任意用户访问该用户的个人页面时,都将被弹窗:

user = {'id':123,'nickname':'haha<script>alert("xss vulnerable!")</script>'}
tpl = '<h1>homepage of {{nickname}}</h1>'
render_template_string(tpl,**user)

页面变形 : 另一种轻微一些但更常见的问题是用户提交的数据中包含具有 特殊意义的HTML字符,比如<>&等。下面的示例中,用户 的昵称恰好看起来恰好是一个HTML标签,导致其个人页面中不能显示昵称:

user = {'id':123,'nickname':'< IAMKING>'}
tpl = '<h1>homepage of {{nickname}}</h1>'
render_template_string(tpl,**user)

11. 变量转义

解决这些问题的办法就是对变量执行转义操作,将变量中的具有特殊含义的HTML字符 使用HTML实体码表示。例如:<IAMKING>将被转换为<IAMKING>

自动转义 : 在模板中使用autoescape标签可以开启或关闭模板引擎的自动转义 功能。在开启自动转义功能时,模板引擎将对转义块内的所有变量自动执行转义操作。

下面的示例中,使用autoescape标签开启了自动转义:

user = {'id':123,'nickname':'< IAMKING>'}
tpl = '''
 {% autoescape true %}
 <h1>homepage of <a href="/user/{{id}}">{{nickname}}</a></h1>
 {% endautoescape %}
 '''
render_template_string(tpl,**user)

但是自动转义开启的时候,会对转义块内所有的变量执行转义操作,即是这些变量压根 不可能包含HTML字符,或者其内容可控。当变量数量很多时,这将造成不必要的性能损失。

我们可以使用safe过滤器将这些可控的变量标记为安全的,渲染引擎将不再对其进行转义。 下面的示例中,使用safe标签取消id变量的转义操作:

user = {'id':123,'nickname':'< IAMKING>'}
tpl = '''
 {% autoescape true %} 
 <h1>homepage of <a href="/user/{{id | safe}}">{{nickname}}</a></h1>
 {% endautoescape %}
 '''
render_template_string(tpl,**user)

手动转义 :和自动转义对应的就是手动的对变量执行转义操作。方法是使用escape 过滤器,可以简写为e

下面的示例中,对模板中的nickname变量执行手动转义:

user = {'id':123,'nickname':'< IAMKING>'}
tpl = '<h1>homepage of <a href="/user/{{id}}">{{nickname | e }}</a></h1>'
render_template_string(tpl,**user)

12. 循环结构

假设我们有一组用户数据如下:

data = [
 {'name' : 'John','age' : 20,},
 {'name' : 'Linda','age' : 21},
 {'name' : 'Mary','age' : 30},
 {'name' : 'Cook','age' : 40}
]

可以使用循环结构,对一组数据使用单一模板进行渲染:

{% for [loop condition] %}
...
{% endfor%}

下面的示例对列表中的每一个对象生成一个

  • 标签:

    tpl = '''
     <ul>
     {{% for user in users %}}
     <li>{{ user.name }}</li>
     {{% endfor %}}
     </ul>
     '''
    render_template_string(tpl,users=data)

    迭代过滤 :Jinja2的for循环不能像Python一样中途退出/break跳过/continue, 但是它支持在迭代时进行条件过滤。下面的示例模板只为年龄大于25的用户生成列表项:

    tpl = '''
     <ul>
     {{% for user in users if user.age > 25 %}}
     <li>{{ user.name }}</li>
     {{% endfor %}}
     </ul>
     '''
    render_template_string(tpl,users=data)

    默认输出块 :如果没有执行至少一次循环(比如列表为空,或者被过滤了), 可以使用else块生成默认的输出。下面的示例模板将在没有用户匹配时输出not found

    tpl = '''
     <ul>
     {{% for user in users if user.age > 50 %}}
     <li>{{ user.name }}</li>
     {{% else %}}
     <li>not found!</li>
     {{% endfor %}}
     </ul>
     '''
    render_template_string(tpl,users=data)

    13. 递归循环

    有些数据是具有不确定层次的递归数据,比如文件系统,目录里还有目录:

    /application       ------ 目录 
     /app.py           ------ 文件
     /static           ------ 目录
       /main.css       ------ 文件
       /jquery.min.css ------ 文件
     /templates        ------ 目录
       /user.html      ------ 文件

    其对应的数据表达参见示例中的tree对象。Jinja2的循环结构支持递归调用。使用方法如下:

    13.1 使用recursive关键字声明循环为递归循环

    {% for item in data recursive}
    ...
    {% endfor %}

    13.2 在循环内部,使用loop()函数调用子节点

    {{ loop(item.children) }}

    14. 循环块中的特殊变量

    在for循环块中,Jinja2提供了关于循环的一些特殊变量:

    loop.index :当次执行的循环序号,从1开始。下面的示例将输出1至10:

    {% for i in range(10) %}
    {{ loop.index }}
    {% endfor %}

    loop.index0 :当前执行的循环序号,从0开始。

    loop.revindex :当前执行的循环反序序号,从1开始。下面的示例将输出10至1:

    {% for i in range(10) %}
    {{ loop.revindex }}
    {% endfor %}

    loop.revindex0 :当前执行的循环反序序号,从0开始

    loop.first :如果当次执行是循环中的首次,则值为True。下面的示例将输出True、False、False....

    {% for i in range(10) %}
    {{ loop.index }}
    {% endfor %}

    loop.last :如果档次执行时循环中的最后一次,则值为True

    loop.length :列表中的元素数量

    loop.cycle(*args) :从一个列表中循环取值。下面的示例将循环输出c1、c2、c3、c1、c2、c3...

    {% for i in range(10) %}
    {{ loop.cycle('c1','c2','c3') }}
    {% endfor %}

    loop.depth :递归循环的层深,从1开始

    loop.depth0 :递归循环的层深,从0开始

    15. 条件结构

    在Jinja2中,可以使用条件块设置模板内容的输出条件。只有当指定的条件 满足时,条件块内的模板内容才会被渲染输出:

    {% if [condition] %}
    ...
    {% endif %}

    下面的示例中,只有当用户的年龄不小于18岁时,才输出适合成人观看的内容:

    data = {'name':'Obama',age:62}
    tpl = '''
     {% if user.age >= 18 %}
     <div>some adult content...</div>
     {% endif %}
     '''
    render_template_string(tpl,user=data)

    elif :可以为条件块添加使用elif添加多重条件判断分支:

    {% if [condition] %}
    ...
    {% elif [condition2] %}
    ...
    {% elif [condition3] %}
    ...
    {% endif%}

    下面的示例中,当用户的年龄大于60岁时,输出养生节目,大于18岁而小于60岁时,输出成人节目:

    data = {'name':'Obama',age:62}
    tpl = '''
     {% if user.age >= 60%}
     <div>some health preserving content...</div>
     {% elif user.age >= 18 %}
     <div>some adult content...</div>
     {% endif %}
     '''
    render_template_string(tpl,user=data)

    else :当条件块中的所有条件都不满足时,可以使用else添加默认输出块:

    {% if [condition] %}
    ...
    {% elif [condition2] %}
    ...
    {% else %}
    ...
    {% endif %}

    下面的示例中,给未成年人输出卡通节目:

    data = {'name':'Obama',age:62}
    tpl = '''
     {% if user.age >= 60 %}
     <div>some health preserving content...</div>
     {% elif user.age >= 18 %}
     <div>some adult content...</div>
     {% else %}
     <div>some cartoon content...</div>
     {% endif %}
     '''
    render_template_string(tpl,user=data)

    Reference:

    flask框架

  • 本文转载自:http://www.jianshu.com/p/4370d9740284

    共有 人打赏支持
    AllenOR灵感
    粉丝 10
    博文 2634
    码字总数 82983
    作品 0
    程序员
    Flask框架 —— 从入门到精通

    更新日期 :2016 - 2 - 26 有开源网友提醒,故为了可阅读性更新排版。 Hello World 作者背景 应用程序简介 要求 安装 Flask 在 Flask 中的 “Hello, World” 下一步? 模板 回顾 为什么我们需...

    水果糖
    2016/02/26
    402
    0
    从零开始搭建论坛(三):Flask框架简单介绍

    前面两篇文章中我们已经了解 Web(HTTP)服务器,Web应用程序,Web框架,WSGI这些 Python Web 开发中的概念。我们知道,Web框架通过将不同Web应用程序中的共性部分给抽象出来,提供一系列通用的...

    selfboot
    2016/10/30
    0
    0
    基于Linux环境的Web.py框架介绍

    前言 客户端和Web服务器的交互过程可以概括为:Web服务器接收客户端的请求后,由Web应用服务器对浏览器的请求进行处理,将生成的响应传递给Web服务器,再由Web服务器返回给客户端。为了简化W...

    元宵大师
    07/27
    0
    0
    (二)Flask 学习 —— 模板

    模板 回顾 如果你依照 Hello World 这一章的话,你应当有一个完全工作的简单的 web 应用程序,它有着如下的文件结构: microblogflask <virtual environment files>app static templates init...

    水果糖
    2016/02/25
    239
    0
    Python全栈 Web(Flask框架、安装、应用)

    Flask 轻量级WEB框架 静态网页: 不能与服务器交互的网页都是静态网页 动态网页: 能够与服务器进行交互的网页 WEB:浏览器 网页(前端三剑客) 服务器: 能够给用户提供服务的机器就是服务器...

    巴黎香榭
    09/20
    0
    0

    没有更多内容

    加载失败,请刷新页面

    加载更多

    03-《Apache Tomcat 9 User Guide》之安装

    1.Introduction There are several ways to set up Tomcat for running on different platforms. The main documentation for this is a file called RUNNING.txt. We encourage you to refe......

    飞鱼说编程
    22分钟前
    1
    0
    Hbase 概述及特点

    1、Hbase概述 HBase是一种构建在HDFS之上的分布式、面向列的存储系统。在需要实时读写、随机访问超大规模数据集时,可以使用HBase。 尽管已经有许多数据存储和访问的策略和实现方法,但事实上...

    PeakFang-BOK
    49分钟前
    0
    0
    TortoiseGit(乌龟git)保存用户名密码的方法

    windows下比较比较好用的git客户端有2种: 1. msysgit + TortoiseGit(乌龟git) 2. GitHub for Windows github的windows版也用过一段时间,但还是不太习惯。所以目前仍然青睐与msysgit+乌龟g...

    simpower
    今天
    1
    0
    Java并发编程:volatile关键字解析

    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在Java 5之后,volatile关键字才得以重获生...

    engeue
    今天
    2
    0
    通过ajax访问远程天气预报服务

    http://www.webxml.com.cn/zh_cn/index.aspx 更改wsdl文件 打开文件将15行,51行,101行去掉 然后把文件复制到c盘 然后在桌面上面就生成了文件 将文件打成jar包 package cn.it.ws.weather;...

    江戸川
    今天
    1
    0

    没有更多内容

    加载失败,请刷新页面

    加载更多

    返回顶部
    顶部