文档章节

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

AllenOR灵感
 AllenOR灵感
发布于 2017/09/10 01:24
字数 3741
阅读 9
收藏 0
点赞 0
评论 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 学习 —— 模板

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

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

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

    selfboot
    2016/10/30
    0
    0
    看完这篇文章还能不懂Flask这种Web框架吗?

    Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基...

    lemonwater
    05/14
    0
    0
    (三)Flask 学习 —— web 表单

    web 表单 回顾 在上一章节中,我们定义了一个简单的模板,使用占位符来虚拟了暂未实现的部分,比如用户以及文章等。 在本章我们将要讲述应用程序的特性之一–表单,我们将会详细讨论如何使用...

    水果糖
    2016/02/25
    1K
    0
    (十一)Flask 学习 —— 邮件支持

    邮件支持 回顾 在近来的几篇教程中,我们一直在与数据库打交道。 今天我们打算让数据库休息下,相反我们今天准备完成网页应用程序中一项重要的功能:能够给用户发送邮件。 在我们小型 microb...

    水果糖
    2016/02/26
    164
    0
    (一)Flask 学习 —— Hello World

    Hello World 作者背景 作者是一个使用多种语言开发复杂程序并且拥有十多年经验的软件工程师。作者第一次学习 Python 是在为一个 C++ 库创建绑定的时候。 除了 Python,作者曾经用 PHP, Ruby,...

    水果糖
    2016/02/25
    242
    0
    Python Web 应用:WSGI基础

    在Django,Flask,Bottle和其他一切Python web 框架底层的是Web Server Gateway Interface,简称WSGI。WSGI对Python来说就像 Servlets对Java一样——一种用于web服务器并允许不同web服务器和...

    局长
    2016/09/08
    3.4K
    12
    python-复盘-Web开发

    一、Web开发 Browser/Server模式目前最流行,简称BS架构。在BS架构下,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web页面,并把Web页面展示给...

    时间之友
    2017/11/23
    0
    0
    Flask從入門到入土(三)——模板

      模板是一個包含響應文本的文件,其中包含佔位變量表示的動態部分,其具體值只是請求上下文中才能知道。使用真實值替換變量,再返回最終得到的響應字符串,這一過程稱爲渲染。爲了渲染模板...

    奶berber
    01/21
    0
    0

    没有更多内容

    加载失败,请刷新页面

    加载更多

    下一页

    Java设计模式学习之工厂模式

    在Java(或者叫做面向对象语言)的世界中,工厂模式被广泛应用于项目中,也许你并没有听说过,不过也许你已经在使用了。 简单来说,工厂模式的出现源于增加程序序的可扩展性,降低耦合度。之...

    路小磊
    13分钟前
    0
    0
    npm profile 新功能介绍

    转载地址 npm profile 新功能介绍 npm新版本新推来一个功能,npm profile,这个可以更改自己简介信息的命令,以后可以不用去登录网站来修改自己的简介了 具体的这个功能的支持大概是在6这个版...

    durban
    25分钟前
    0
    0
    Serial2Ethernet Bi-redirection

    Serial Tool Serial Tool is a utility for developing serial communications, custom protocols or device testing. You can set up bytes to send accordingly to your protocol and save......

    zungyiu
    30分钟前
    0
    0
    python里求解物理学上的双弹簧质能系统

    物理的模型如下: 在这个系统里有两个物体,它们的质量分别是m1和m2,被两个弹簧连接在一起,伸缩系统为k1和k2,左端固定。假定没有外力时,两个弹簧的长度为L1和L2。 由于两物体有重力,那么...

    wangxuwei
    45分钟前
    0
    0
    apolloxlua 介绍

    ##项目介绍 apolloxlua 目前支持javascript到lua的翻译。可以在openresty和luajit里使用。这个工具分为两种模式, 一种是web模式,可以通过网页使用。另外一种是tool模式, 通常作为大规模翻...

    钟元OSS
    52分钟前
    0
    0
    Mybatis入门

    简介: 定义:Mybatis是一个支持普通SQL查询、存储过程和高级映射的持久层框架。 途径:MyBatis通过XML文件或者注解的形式配置映射,实现数据库查询。 特性:动态SQL语句。 文件结构:Mybat...

    霍淇滨
    今天
    0
    0
    开发技术瓶颈期,如何突破

    前言 读书、学习的那些事情,以前我也陆续叨叨了不少,但总觉得 “学习方法” 就是一个永远在路上的话题。个人的能力、经验积累与习惯方法不尽相同,而且一篇文章甚至一本书都很难将学习方法...

    _小迷糊
    今天
    0
    0
    安装tensorflow-XXX报错

    报错: tensorflow-0.5.0-cp27-none-linux_x86_64.whl is not a supported wheel on this platform. 解决: wget https://bootstrap.pypa.io/get-pip.py sudo python2.7 get-pip.py sudo p......

    Yao--靠自己
    今天
    0
    0
    JVM学习手册(一):JVM模型

    一直从事JAVA开发,天天和JVM打交道,仔细想想对JVM还真的不是特别了解,实在是不应该.周六看了许多资料,也算有点心得,记录一下。 JVM内存模型分为5个区域:方法区,堆,虚拟机栈,本地方法栈,程序计...

    勤奋的蚂蚁
    今天
    0
    0
    转行零基础该如何学Python?这些一定要明白!

    转行零基础学Python编程开发难度大吗?从哪学起?近期很多小伙伴问我,如果自己转行学习Python,完全0基础能否学会呢?Python的难度到底有多大?今天,小编就来为大家详细解读一下这个问题。...

    猫咪编程
    今天
    2
    0

    没有更多内容

    加载失败,请刷新页面

    加载更多

    下一页

    返回顶部
    顶部