flask 系列教程( 2)Jinja2 模版 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
CicadaMan
V2EX    Flask

flask 系列教程( 2)Jinja2 模版

  •  
  •   CicadaMan 2017 年 7 月 20 日 4965 次点击
    这是一个创建于 3120 天前的主题,其中的信息可能已经有所发展或是发生改变。

    模板:

    在之前的章节中,视图函数只是直接返回文本,而在实际生产环境中其实很少这样用,因为实际的页面大多是带有样式和复杂逻辑的 HTML 代码,这可以让浏览器渲染出非常漂亮的页面。目前市面上有非常多的模板系统,其中最知名最好用的就是Jinja2Mako,我们先来看一下这两个模板的特点和不同:

    1. Jinja2:Jinja是日本寺庙的意思,并且寺庙的英文是temple和模板的英文template的发音类似。Jinja2是默认的仿Django模板的一个模板引擎,由Flask的作者开发。它速度快,被广泛使用,并且提供了可选的沙箱模板来保证执行环境的安全,它有以下优点:
    • 让前端开发者和后端开发者工作分离。
    • 减少Flask项目代码的耦合性,页面逻辑放在模板中,业务逻辑放在视图函数中,将页面逻辑和业务逻辑解耦有利于代码的维护。
    • 提供了控制语句、继承等高级功能,减少开发的复杂度。
    1. Marko:Marko是另一个知名的模板。他从DjangoJinja2等模板借鉴了很多语法和 API,他有以下优点:
    • 性能和Jinja2相近,在这里可以看到。
    • 有大型网站在使用,有成功的案例。Reddit和豆瓣都在使用。
    • 有知名的 web 框架支持。PylonsPyramid这两个 web 框架内置模板就是Mako
    • 支持在模板中写几乎原生的 Python 语法的代码,对 Python 工程师比较友好,开发效率高。
    • 自带完整的缓存系统。当然也提供了非常好的扩展借口,很容易切换成其他的缓存系统。
    Flask渲染Jinja模板:

    要渲染一个模板,通过render_template方法即可,以下将用一个简单的例子进行讲解:

    from flask import Flask,render_template app = Flask(__name__) @app.route('/about/') def about(): return render_template('about.html') 

    当访问/about/的时候,about()函数会在当前目录下的templates文件夹下寻找about.html模板文件。如果想更改模板文件地址,应该在创建app的时候,给Flask传递一个关键字参数template_folder,指定具体的路径,再看以下例子:

    from flask import Flask,render_template app = Flask(__name__,template_folder=r'C:\templates') @app.route('/about/') def about(): return render_template('about.html') 

    以上例子将会在 C 盘的 templates 文件夹中寻找模板文件。还有最后一点是,如果模板文件中有参数需要传递,应该怎么传呢,我们再来看一个例子:

    from flask import Flask,render_template app = Flask(__name__) @app.route('/about/') def about(): # return render_template('about.html',user='xiaotuo') return render_template('about.html',**{'user':'xiaotuo}) 

    以上例子介绍了两种传递参数的方式,因为render_template需要传递的是一个关键字参数,所以第一种方式是顺其自然的。但是当你的模板中要传递的参数过多的时候,把所有参数放在一个函数中显然不是一个好的选择,因此我们使用字典进行包装,并且加两个*号,来转换成关键字参数。

    Jinja2:

    Jinja2默认已经跟着Flask进行安装了,如果没有被安装到,可以通过pip install Jinja2来进行安装。

    概要:

    Jinja模板是简单的一个纯文本文件( html/xml/csv...),不仅仅是用来产生html文件,后缀名也依照你自己的心情而定。当然,尽量命名为模板正确的文件格式,增加可读性。先看一个简单例子:

    1. <html lang="en"> 2. <head> 3. <title>My Webpage</title> 4. </head> 5. <body> 6. <ul id="navigation"> 7. {% for item in navigation %} 8. <li><a href="{{ item.href }}">{{ item.caption }}</a></li> 9. {% endfor %} 10. </ul> 11. 12. {{ a_variable }} 13. {{ user.name }} 14. {{ user['name'] }} 15. 16. {# a comment #} 17. </body> 18.</html> 

    以上示例有需要进行解释:

    • 第 12~14 行的{{ ... }}:用来装载一个变量,模板渲染的时候,会把这个变量代表的值替换掉。并且可以间接访问一个变量的属性或者一个字典的key。关于点.号访问和[]中括号访问,没有任何区别,都可以访问属性和字典的值。
    • 第 7~9 行的{% ... %}:用来装载一个控制语句,以上装载的是for循环,以后只要是要用到控制语句的,就用{% ... %}
    • 第 14 行的{# ... #}:用来装载一个注释,模板渲染的时候会忽视这中间的值。
    属性访问规则:
    1. 比如在模板中有一个变量这样使用:foo.bar,那么在Jinja2中是这样进行访问的:
    • 先去查找foobar这个属性,也即通过getattr(foo,'bar')
    • 如果没有,就去通过foo.__getitem__('bar')的方式进行查找。
    • 如果以上两种方式都没有找到,返回一个undefined
    1. 在模板中有一个变量这样使用:foo['bar'],那么在Jinja2中是这样进行访问:
    • 通过foo.__getitem__('bar')的方式进行查找。
    • 如果没有,就通过getattr(foo,'bar')的方式进行查找。
    • 如果以上没有找到,则返回一个undefined
    过滤器:

    过滤器是通过管道符号(|)进行使用的,例如:{{ name|length }},将返回 name 的长度。过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。Jinja2中内置了许多过滤器,在这里可以看到所有的过滤器,现对一些常用的过滤器进行讲解:

    • abs(value):返回一个数值的绝对值。示例:-1|abs
    • default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。示例:name|default('xiaotuo')如果 name 不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。
    • escape(value)或 e:转义字符,会将<>等符号转义成 HTML 中的符号。示例:content|escapecontent|e
    • first(value):返回一个序列的第一个元素。示例:names|first
    • format(value,*arags,**kwargs):格式化字符串。比如:
    {{ "%s" - "%s"|format('Hello?',"Foo!") }} 将输出:Helloo? - Foo! 
    • last(value):返回一个序列的最后一个元素。示例:names|last
    • length(value):返回一个序列或者字典的长度。示例:names|length
    • join(value,d=u''):将一个序列用d这个参数的值拼接成字符串。
    • safe(value):如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例:content_html|safe
    • int(value):将值转换为int类型。
    • float(value):将值转换为float类型。
    • lower(value):将字符串转换为小写。
    • upper(value):将字符串转换为小写。
    • replace(value,old,new): 替换将old替换为new的字符串。
    • truncate(value,length=255,killwords=False):截取length长度的字符串。
    • striptags(value):删除字符串中所有的 HTML 标签,如果出现多个空格,将替换成一个空格。
    • trim:截取字符串前面和后面的空白字符。
    • string(value):将变量转换成字符串。
    • wordcount(s):计算一个长字符串中单词的个数。
    控制语句:

    所有的控制语句都是放在{% ... %}中,并且有一个语句{% endxxx %}来进行结束,Jinja中常用的控制语句有if/for..in..,现对他们进行讲解:

    • if:if 语句和python中的类似,可以使用>,<,<=,>=,==,!=来进行判断,也可以通过and,or,not,()来进行逻辑合并操作,以下看例子:
    {% if kenny.sick %} Kenny is sick. {% elif kenny.dead %} You killed Kenny! You bastard!!! {% else %} Kenny looks okay --- so far {% endif %} 
    • for...in...for循环可以遍历任何一个序列包括数组、字典、元组。并且可以进行反向遍历,以下将用几个例子进行解释:
    1. 普通的遍历:
    <ul> {% for user in users %} <li>{{ user.username|e }}</li> {% endfor %} </ul> 
    1. 遍历字典:
    <dl> {% for key, value in my_dict.iteritems() %} <dt>{{ key|e }}</dt> <dd>{{ value|e }}</dd> {% endfor %} </dl> 
    1. 如果序列中没有值的时候,进入else
    <ul> {% for user in users %} <li>{{ user.username|e }}</li> {% else %} <li><em>no users found</em></li> {% endfor %} </ul> 

    并且Jinja中的for循环还包含以下变量,可以用来获取当前的遍历状态:

    | 变量 | 描述 | | --- | --- | | loop.index | 当前迭代的索引(从 1 开始) | | loop.index0 | 当前迭代的索引(从 0 开始) | | loop.first | 是否是第一次迭代,返回 True/False | | loop.last | 是否是最后一次迭代,返回 True/False | | loop.length | 序列的长度 |

    另外,不可以使用continuebreak表达式来控制循环的执行

    测试器:

    测试器主要用来判断一个值是否满足某种类型,并且这种类型一般通过普通的if判断是有很大的挑战的。语法是:if...is...,先来简单的看个例子:

    {% if variable is escaped%} value of variable: {{ escaped }} {% else %} variable is not escaped {% endif %} 

    以上判断variable这个变量是否已经被转义了,Jinja中内置了许多的测试器,看以下列表:

    • callable(object):是否可调用。
    • defined(object):是否已经被定义了。
    • escaped(object):是否已经被转义了。
    • upper(object):是否全是大写。
    • lower(object):是否全是小写。
    • string(object):是否是一个字符串。
    • sequence(object):是否是一个序列。
    • number(object):是否是一个数字。
    • odd(object):是否是奇数。
    • even(object):是否是偶数。
    宏:

    模板中的宏跟 python 中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,以下将用一个例子来进行解释:

    {% macro input(name, value='', type='text') %} <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}"> {% endmacro %} 

    以上例子可以抽取出了一个 input 标签,指定了一些默认参数。那么我们以后创建input标签的时候,可以通过他快速的创建:

    <p>{{ input('username') }}</p> <p>{{ input('password', type='password') }}</p> 
    import 语句:

    在真实的开发中,会将一些常用的宏单独放在一个文件中,在需要使用的时候,再从这个文件中进行导入。import语句的用法跟python中的import类似,可以直接import...as...,也可以from...import...或者from...import...as...,假设现在有一个文件,叫做forms.html,里面有两个宏分别为inputtextarea,如下:

    forms.html: {% macro input(name, value='', type='text') %} <input type="{{ type }}" value="{{ value|e }}" name="{{ name }}"> {% endmacro %} {% macro textarea(name, value='', rows=10, cols=40) %} <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">{{ value|e }}</textarea> {% endmacro %} 

    看以下导入宏的例子:

    1. import...as...形式:
    {% import 'forms.html' as forms %} <dl> <dt>Username</dt> <dd>{{ forms.input('username') }}</dd> <dt>Password</dt> <dd>{{ forms.input('password', type='password') }}</dd> </dl> <p>{{ forms.textarea('comment') }}</p> 
    1. from...import...as.../from...import...形式:
    {% from 'forms.html' import input as input_field, textarea %} <dl> <dt>Username</dt> <dd>{{ input_field('username') }}</dd> <dt>Password</dt> <dd>{{ input_field('password', type='password') }}</dd> </dl> <p>{{ textarea('comment') }}</p> 

    另外需要注意的是,导入模板并不会把当前上下文中的变量添加到被导入的模板中,如果你想要导入一个需要访问当前上下文变量的宏,有两种可能的方法:

    • 显式地传入请求或请求对象的属性作为宏的参数。
    • 与上下文一起( with context )导入宏。

    与上下文中一起( with context )导入的方式如下:

    {% from '_helpers.html' import my_macro with context %} 
    include 语句:

    include语句可以把一个模板引入到另外一个模板中,类似于把一个模板的代码 copy 到另外一个模板的指定位置,看以下例子:

    {% include 'header.html' %} Body {% include 'footer.html' %} 
    赋值( set )语句:

    有时候我们想在在模板中添加变量,这时候赋值语句( set )就派上用场了,先看以下例子:

    {% set name='xiaotuo' %} 

    那么以后就可以使用name来代替xiaotuo这个值了,同时,也可以给他赋值为列表和元组:

    {% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %} 

    赋值语句创建的变量在其之后都是有效的,如果不想让一个变量污染全局环境,可以使用with语句来创建一个内部的作用域,将set语句放在其中,这样创建的变量只在with代码块中才有效,看以下示例:

    {% with %} {% set foo = 42 %} {{ foo }} foo is 42 here {% endwith %} 

    也可以在with的后面直接添加变量,比如以上的写法可以修改成这样:

    {% with foo = 42 %} {{ foo }} {% endwith %} 

    这两种方式都是等价的,一旦超出with代码块,就不能再使用foo这个变量了。

    模板继承:

    Flask中的模板可以继承,通过继承可以把模板中许多重复出现的元素抽取出来,放在父模板中,并且父模板通过定义block给子模板开一个口,子模板根据需要,再实现这个block,假设现在有一个base.html这个父模板,代码如下:

    <!DOCTYPE html> <html lang="en"> <head> {% block head %} <link rel="stylesheet" href="style.css" /> <title>{% block title %}{% endblock %} - My Webpage</title> {% endblock %} </head> <body> <div id="content">{% block content %}{% endblock %}</div> <div id="footer"> {% block footer %} Copyright 2008 by <a href="http://domain.invalid/">you</a>. {% endblock %} </div> </body> </html> 

    以上父模板中,抽取了所有模板都需要用到的元素htmlbody等,并且对于一些所有模板都要用到的样式文件style.css也进行了抽取,同时对于一些子模板需要重写的地方,比如titleheadcontent都定义成了block,然后子模板可以根据自己的需要,再具体的实现。以下再来看子模板的代码:

    {% extends "base.html" %} {% block title %}Index{% endblock %} {% block head %} {{ super() }} <style type="text/css"> .important { color: #336699; } </style> {% endblock %} {% block content %} <h1>Index</h1> <p class="important"> Welcome to my awesome homepage. </p> {% endblock %} 

    首先第一行就定义了子模板继承的父模板,并且可以看到子模板实现了title这个block,并填充了自己的内容,再看head这个block,里面调用了super()这个函数,这个函数的目的是执行父模板中的代码,把父模板中的内容添加到子模板中,如果没有这一句,则父模板中处在head这个block中的代码将会被子模板中的代码给覆盖掉。

    另外,模板中不能出现重名的block,如果一个地方需要用到另外一个block中的内容,可以使用self.blockname的方式进行引用,比如以下示例:

    <title>{% block title %}{% endblock %}</title> <h1>{{ self.title() }}</h1> {% block body %}{% endblock %} 

    以上示例中h1标签重用了title这个block中的内容,子模板实现了title这个blockh1标签也能拥有这个值。

    另外,在子模板中,所有的文本标签和代码都要添加到从父模板中继承的block中。否则,这些文本和标签将不会被渲染。

    转义:

    转义的概念是,在模板渲染字符串的时候,字符串有可能包括一些非常危险的字符比如<>等,这些字符会破坏掉原来HTML标签的结构,更严重的可能会发生XSS跨域脚本攻击,因此如果碰到<>这些字符的时候,应该转义成HTML能正确表示这些字符的写法,比如>HTML中应该用<来表示等。

    但是Flask中默认没有开启全局自动转义,针对那些以.html.htm.xml.xhtml结尾的文件,如果采用render_template函数进行渲染的,则会开启自动转义。并且当用render_template_string函数的时候,会将所有的字符串进行转义后再渲染。而对于Jinja2默认没有开启全局自动转义,作者有自己的原因:

    1. 渲染到模板中的字符串并不是所有都是危险的,大部分还是没有问题的,如果开启自动转义,那么将会带来大量的不必要的开销。
    2. Jinja2很难获取当前的字符串是否已经被转义过了,因此如果开启自动转义,将对一些已经被转义过的字符串发生二次转义,在渲染后会破坏原来的字符串。

    在没有开启自动转义的模式下(比如以.conf结尾的文件),对于一些不信任的字符串,可以通过{{ content_html|e }}或者是{{ content_html|escape }}的方式进行转义。在开启了自动转义的模式下,如果想关闭自动转义,可以通过{{ content_html|safe }}的方式关闭自动转义。而{%autoescape true/false%}...{%endautoescape%}可以将一段代码块放在中间,来关闭或开启自动转义,例如以下代码关闭了自动转义:

    {% autoescape false %} <p>autoescaping is disabled here <p>{{ will_not_be_escaped }} {% endautoescape %} 
    数据类型:

    Jinja支持许多数据类型,包括:字符串、整型、浮点型、列表、元组、字典、true/false。注意其中的布尔类型是true/false,都是小写,不是Python中的True/False

    运算符:
    • +号运算符:可以完成数字相加,字符串相加,列表相加。但是并不推荐使用+运算符来操作字符串,字符串相加应该使用~运算符。
    • -号运算符:只能针对两个数字相减。
    • /号运算符:对两个数进行相除。
    • %号运算符:取余运算。
    • *号运算符:乘号运算符,并且可以对字符进行相乘。
    • **号运算符:次幂运算符,比如 2**3=8。
    • in操作符:跟 python 中的in一样使用,比如{{1 in [1,2,3]}}返回true
    • ~号运算符:拼接多个字符串,比如{{"Hello" ~ "World"}}将返回HelloWorld

    静态文件配置:

    Web应用中会出现大量的静态文件来使得网页更加生动美观。静态文件主要包括有CSS样式文件、Javascript脚本文件、图片文件、字体文件等静态资源。在Jinja中加载静态文件非常简单,只需要通过url_for全局函数就可以实现,看以下代码:

    <link href="{{ url_for('static',filename='about.css') }}"> 

    url_for函数默认会在项目根目录下的static文件夹中寻找about.css文件,如果找到了,会生成一个相对于项目根目录下的/static/about.css路径。当然我们也可以把静态文件不放在static文件夹中,此时就需要具体指定了,看以下代码:

    app = Flask(__name__,static_folder='/tmp') 

    那么访问静态文件的时候,将会到/tmp这个文件夹下寻找。

    对 Flask 和 Python 感兴趣的,可以加群:482869582。一起讨论和学哦~~

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5006 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 02:11 PVG 10:11 LAX 18:11 JFK 21:11
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86