前端开发学习Python学习web框架开发

Flask模板引擎:Jinja2常用语法整理

2020-12-24  本文已影响0人  xiaogp

摘要:FlaskJinja2HTML

Flask模板引擎Jinja2简述

模板实质上是一个静态的包含HTML语法的全部或片段的文本文件,也可以包含变量表示的动态部分使用真实值替换网页模板中的变量,生成对应数据的HTML片段,这一过程称为渲染,Falsk使用Jinja2模板引擎来渲染模板,简单而言Jinja2就是一种基于Python的在HTML代码中对动部分进行一系列操作的语法
Flask中使用render_template来渲染模板,render_template的首个入参是模板文件的名称,Falsk会从项目根目录下的templates目录下查找模版。先创建一个first.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
    <p>这是一个测试</p>
</body>
</html>

在视图函数中使用render_template渲染模板

from flask import render_template

@app.route('/template_test')
def template_test():
    return render_template('first.html')

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000)

向模板中传递参数

Flask可以将程序中的参数或变量传入指定的模板进行渲染,在HTML文件中使用{{ }}将程序中指定的参数进行传入。修改first.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
    <p>这是一个测试 {{ name }}</p>
</body>
</html>

在渲染方法render_template中传入参数,指定模板中动态变量的名字等于程序中的参数名,必须指定模板中的动态变量名

@app.route('/template_test')
def template_test():
    my_name = "你好"
    return render_template('first.html', name=my_name)

传入多个变量可以直接使用**locals()方法,此时模板中动态变量的名称要和程序中的参数名称一致,如下修改视图函数

@app.route('/template_test')
def template_test():
    name = "你好"
    return render_template('first.html', **locals())

Jinja2控制语句

Jinja2中的控制语句用来控制渲染的方向,使用iffor关键字,放在{%%}使用

if控制语句

if控制语句的基础语法

{% if condition %}
    <p>do something</p>
{% else %}
    <p>do something</p>
{% endif %}

编写一个HTML其中通过判断变量name在程序中是否存在来呈现不同的展示结果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
    {% if name %}
        <p>存在name</p>
    {% else %}
        <p>不存在name</p>
    {% endif %}
</body>
</html>

在视图函数中将name注释掉,此时不存在name程序变量,返回页面结果为"不存在name"。

@app.route('/template_test')
def template_test():
    # name = "你好"
    return render_template('first.html', **locals())

多个分支条件判断

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
    {% if age == 1 %}
        <p>age为1</p>
    {% elif age == 2 %}
        <p>age为2</p>
    {% else %}
        <p>age不为1和2</p>
    {% endif %}
</body>
</html>

组合判断逻辑,支持python and or

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
    {% if age >= 1 and age < 3 %}
        <p>age为[1, 3)</p>
    {% elif age >= 3 and age < 4 %}
        <p>age为[3, 4)</p>
    {% else %}
        <p>age为其他</p>
    {% endif %}
</body>
</html>

可以在html标签中使用jinja2控制语句,实现不同条件不同样式的按钮

{% for cate in category_list %}
    <button {% if cate[0]== category %} class="categoryButton1" {% else %} class="categoryButton2" {% endif %}
            onclick="window.location.href='rank?category={{ cate[0] }}'">{{ cate[1] }}
    </button>
{% endfor %}
按钮标签不同样式.png
for控制语句

for控制语句的基础语法

{% for 目标 in 对象 %}
    <p>do something</p>
{% endfor %}

Jinja2的for循环变量不需要{{ }}传入,不支持continue和break,但是和Python一样可以对Python的可迭代对象进行循环遍历。在一个循环代码块内部调用loop的属性可以获得循环中的状态数据,比如可以一边循环一边拿到下标,包括

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

在HTML中构建一个for循环语句,每一次循环的对象是一个字段,使用.key直接拿到value值

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
    {% for good in goods %}
        <p>{{ good.name }}</p>
        <p>{{ good.price }}</p>
    {% endfor %}
</body>
</html>

在视图函数中创建一个字典对象传入模板

@app.route('/template_test')
def template_test():
    goods = [{"name": "苹果", "price": 32}, {"name": "橘子", "price": 99}]
    return render_template('first.html', **locals())

也可以使用Python字段的语法拿到值

    {% for good in goods %}
        <p>{{ good["name"] }}</p>
        <p>{{ good["price"] }}</p>
    {% endfor %}

在代码块中使用loop关键字

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
    {% for good in goods %}
        <p>{{ good.name }}</p>
        <p>{{ good.price }}</p>
        <p>循环长度{{ loop.length }}</p>
        <p>当前索引{{ loop.index0 }}</p>
        <p>是否结束{{ loop.last }}</p>
    {% endfor %}
</body>
</html>
查看循环展示结果.png

Python中的range可以结合for循环一起使用,在jinja2中也可以直接使用

    {% for i in range(20) %}
        <p>{{ i }}</p>
    {% endfor %}

Jinja2的过滤器

过滤器的本质是一个转换函数,有时候不仅需要输出程序参数还要对参数进行修改转换才能输出,此时需要用到过滤器,过滤器写在变量后面,中间用|隔开,|左右没有空格

字符串过滤器
<p>{{ name|default('小明', true)}}</p>

这个过滤器也可以用or关键字实现,和Python语法一致,如果前项为空输出后项,无此参数和有参数但是为空都会走or逻辑

<p>{{ name or '小明'}}</p>

<p>{{ name|default('小明')|replace('小', '大')}}</p>
<p>{{ '   去除收尾空格   '|trim }}</p>
<p>{{ '我和'~name~'出去玩了'}}</p>

可迭代对象过滤器
    <p>第一个元素{{ items|first }}</p>
    <p>最后一个元素{{ items|last }}</p>

在视图函数中传递一个列表

@app.route('/template_test')
def template_test():
    items = [1, 3, 5, 7, 9]
    return render_template('first.html', **locals())

输出为"第一个元素1 最后一个元素9"

<p>列表长度{{ items|count }}</p>
<p>{{ ent_scores|max }}</p> 
<p>{{ ent_scores|min }}</p>
<p>列表转字符串{{ items|join(',') }}</p>
<p>列表升序{{ items|sort }}</p>
<p>列表降序{{ items|sort(true) }}</p>

可以指定属性排序,比如需要排序的对象是一个字典集合

items = [{"name": "苹果", "price": 23}, {"name": "西瓜", "price": 33}, {"name": "西红柿", "price": 25}]

为sort过滤器增加attribute字段指定排序属性,效果如下

    <p>根据某个属性排序{{ items|sort(attribute='price', reverse=true) }}</p>
    {% for item in items|sort(attribute='price', reverse=true) %}
        <p>{{ item.name }}</p>
    {% endfor %}
指定属性排序.png
<p>{{ '12345'|reverse }}</p>
<p>{{ [1, 2, 3, 4, 5]|reverse|join(',')}}</p>
数值对象过滤器
<p>{{ 2.222|round(2) }}</p>
<p>{{ 2.22|string + '你好'}}</p>
<p>{{ 2.222|int }}</p>
<p>{{ -2.222|abs }}</p>
<p>{{ -num + 3}}</p>

其他jinja2的内置过滤器参考http://www.pythontip.com/blog/post/5455/


过滤器的执行顺序

如果一个{{ }}表达式中既存在过滤器,又存在Python的基本语句,如运算,其他关键字等,过滤器总是和他之前紧贴的那个元素相连,即过滤器优先级最高,因此必要的时候需要使用( )来控制过滤器的范围,比如

{{ (ent_rank * 100)|int ~"%"}}

如果不加入括号,则对ent_rank int取整无效,因为他只对100有效

自定义过滤器

可以自己用Python语言实现一个自定义过滤器使用add_template_filter进行注册调用,或者使用修饰器template_filter注册

@app.template_filter('t_func')
def t_func(t):
    t2 = time.time()
    diff = t2 - t
    if diff < 60:
        return "刚刚"
    elif 60 <= diff < 60 * 60:
        return "%d分钟之前" % int(diff / 60)
    elif 3600 <= diff < 3600 * 24:
        return "%d小时之前" % int(diff / 3600)
    else:
        return "很久之前"

另一种注册方式

def t_func(t):
    t2 = time.time()
    diff = t2 - t
    if diff < 60:
        return "刚刚"
    elif 60 <= diff < 60 * 60:
        return "%d分钟之前" % int(diff / 60)
    elif 3600 <= diff < 3600 * 24:
        return "%d小时之前" % int(diff / 3600)
    else:
        return "很久之前"

app.add_template_filter(t_func, 't_func')

HTML模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
    <p>文章发表于{{ t| t_func }}</p>
</body>
</html>

最终显示结果为"文章发表于很久之前"
自定义过滤器可以带有一个或者多个参数,函数的第一个参数作为需要渲染转化的参数,后面的参数可以在过滤器括号中指定作为辅助参数,比如

def score_grade(score: int, add: str):
    if score >= 90:
        return "高风险" + add
    elif 90 > score >= 80:
        return "中度风险" + add
    elif 80 > score >= 60:
        return "低风险" + add
    elif 60 > score:
        return "良好" + add

detail.add_app_template_filter(score_grade, 'score_grade')

以上函数除了要转化的score,还增加一个可变参数,在模板中的使用,过滤器后面使用( )传递参数,如果要传递的参数也是需要渲染的变量,不需要再加{{ }},在外{{ }}内整体渲染

<p style="font-family: 楷体; font-size: 20px;">风险等级: 风险等级为{{ ent_score|score_grade('aaa') }}</p>
带有参数的过滤器.png

Jinja2编写宏和导入应用宏

宏类似函数,将一个逻辑块中变动的部分单独抽出来作为参数,其他固定的部分包装在一个程序块中,jinja2中定义宏的关键字是macro。如下在html中定义一个宏input,有name,type,value三个参数,后面两个参数有默认值,这三个参数对应h5的input的标签,实际上就是用一个宏实现了多种样式的input标签。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
{% macro input(name, type='text', value='') %}
    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}

<p>用户名:{{ input('username') }}</p>
<p>密码:{{ input('password', type='password') }}</p>
<p>用户名:{{ input('submit', type='submit', value='提交') }}</p>
</body>
</html>

定义好宏之后,使用{{ 宏(参数) }}进行调用,效果如下

宏的定义和调用.png

一个宏可以被多个h5模板导入使用,所以需要将所有的宏写在一个h5中,导入的方法类似Python的import。首先新建一个h5命名为macro.html

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

在另一个模板中导入

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
{% import 'macro.html' as macro %}
<p>用户名:{{ macro.input('username') }}</p>
<p>密码:{{ macro.input('password', type='password') }}</p>
<p>用户名:{{ macro.input('submit', type='submit', value='提交') }}</p>
</body>
</html>

也可以直接从模板中导入宏,使用from import导入

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
{% from 'macro.html' import input %}
<p>用户名:{{ input('username') }}</p>
<p>密码:{{ input('password', type='password') }}</p>
<p>用户名:{{ input('submit', type='submit', value='提交') }}</p>
</body>
</html>

include的使用

jinja2中将一个模板引入到另一个模板的指定位置使用关键字include,比如引入公共部分,header.html,footer.html等,先创建公共头部和尾部的h5,include导入的模板必须在templates目录下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>header</title>
</head>
<body>
<div class="header">
    这是网页头部
</div>

</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>footer</title>
</head>
<body>
<div class="footer">
    这是网页尾部
</div>

</body>
</html>

另外编写一个模块,使用include导入header.html和footer.html,在主模板中定义的css样式可以被include导入的模板使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
    <style type="text/css">
        .header{
            width:100%;
            height:40px;
            border: 1px solid #A52A2A;
        }
        .footer{
            width:100%;
            height:40px;
            border: 1px solid #8A2BE2;
        }
        .content{
            width:100%;
            height:40px;
            border: 1px solid #e2e2e2;
        }
    </style>
</head>
<body>
{% include "header.html" %}
<div class="content">
    这是正文
</div>
{% include "footer.html" %}
</body>
</html>

效果如下


include导入模板.png

使用set和with赋值变量

jinja2中setwith都可以对变量进行定义和赋值,set定义的在整个模板范围中都有效,with定义的变量有作用范围,with的作用就是定义这个范围,分别用set和with定义变量如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个测试</title>
</head>
<body>
{% set a='123' %}

<p>set的值{{ a }}</p>
{% with b='321' %}
    <p>with的值{{ b }}</p>
{% endwith %}
</body>
</html>

调用定义的值使用{% %},如果在with外面调用在with内部定义的值则无效
如果在一个with语句中存在需要需要渲染的变量则直接写入变量名,不需要再加渲染花括号,如果with语句中有多个变量需要定义,中间用逗号隔开,如下

{% with ent_score_diff = ent_scores[-1] - ent_scores[0], ent_max_min_diff = ent_scores|max - ent_scores|min %}

静态文件的加载 url_for

需要先创建static文件夹,再创建子文件夹cssjsimages,同时需要使用url_for函数。

(1)导入js
<script src="{{ url_for('static', filename='js/tmp.js') }}"></script>

也可以使用更通用额方式

<script type="text/javascript" src="static/js/tmp.js"></script>
(2)导入图片
 <img src="{{ url_for('static', filename='images/index.jpeg')}}" width="1700" height="350">
(3)导入css
<link rel="stylesheet" href="{{ url_for('static', filename='css/car.css') }}" />

模板的集成extends

一个系统的网站每个网页需要有统一的结构,比如每个网页都有标题,内容,底部等几个部分,模板继承的目的就是将相同的部分提取出来,形成一个base.html。在jinja2中使用extends关键字。
模板的继承包含基本模板子模板,子模板继承基本模板,基本模板包含了基本骨架,其中有一些空的不完善的块block需要子模板来填充,不同的block之间用不同的名字区分,因此每个block还需要定义一个名字,比如父模板如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block body%}
    <p>这是基础身体</p>
{% endblock %}
{% block footer %}
    <p>这是尾巴</p>
{% endblock %}
</body>
</html>

在新模板中继承,继承骨架重写title,body,footer三个block,其中{{ super() }}方法是使得基础框架中原有的block内的内容保留,默认子模板中的block代码会把基础模块中的对应的block代码覆盖。

{% extends "base.html" %}
{% block title%}这是继承的title{% endblock %}
{% block body %}
    {{ super() }}
    <p>继承的身体</p>
{% endblock %}
{% block footer %}
    <p>继承的尾巴</p>
{% endblock %}

效果如下


模板继承.png

Jinja2的空白控制

在最终输出的网页HTML中,Jinja2中的表达式会保留移除后的空行,比如如果模板代码如下

<div>
    {% if True %}
        yay
    {% endif %}
</div>

最终在浏览器看到的网页源代码是

<div>
 
        yay
 
</div>

这是由于Jinja2表达式留下的空行和jinja2的缩进导致的,但是多余的空白不影响浏览器的解析,却影响HTML文件体积大小,降低数据传输速度,因此在代码部署前需要去除空格空行,对于缩进没有强制要求,但是为了代码的可读性,建议在HTML代码中的Jinja2语句中加入缩进。
去除空格空行可以在Jinja2语句的分隔符的%旁边加入减号-,紧贴,没有空格,比如

<div id="ent-1">
    {% for industry_ent in industry_ents[:5] %}
        <a href="/compare?ent1={{ ent_1 }}&ent2={{ industry_ent }}"><p class="subTitle" style="font-size: 15px;">{{ industry_ent }}</p></a>
    {% endfor %}
</div>

渲染之后的HTML代码为


带有空白的html.png

在分隔符中加入-号

<div id="ent-1">
    {% for industry_ent in industry_ents[:5] -%}
        <a href="/compare?ent1={{ ent_1 }}&ent2={{ industry_ent }}"><p class="subTitle" style="font-size: 15px;">{{ industry_ent }}</p></a>
    {%- endfor %}
</div>

起始语句-加载右边,带有语句右边的空白去除,结束语句-加载左边,说明语句左边的空白去除,此时再看浏览器的源码如下:


去除空白的html.png

另一种方法是在app中配置jinja的环境参数,就不要在在Jinja2语法的时候时刻注意要写-号了

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

但是宏内的空白不收这两个环境参数控制,还是需要在宏分隔符中手动加入-号,例如

{% macro score_grade_img(filename, width="250", height="220") %}
    <img src="{{ url_for('static', filename=filename) }}" width={{ width }} height={{ height }}>
{% endmacro %}

手动加入-号

{% macro score_grade_img(filename, width="250", height="220") -%}
    <img src="{{ url_for('static', filename=filename) }}" width={{ width }} height={{ height }}>
{%- endmacro %}

上一篇下一篇

猜你喜欢

热点阅读