1. Jinja2简介
模板文件是包含静态的HTML文本及描述数据如何插入到HTML文本的动态内容。Flask默认使用的模板引擎是Jinja2,它是一个功能齐全的Python模板引擎,除了设置变量外,还允许我们通过控制结构添加if判断,执行for循环,调用函数等各种方式来控制模板的输出。对于Jinja2来说,模板可以是任何格式的纯文本文件,比如HTML、XML、CSV等。Jinja2的官网截图如图1所示。(https://jinja.palletsprojects.com/en/3.1.x/templates/)为其模板语法使用的介绍文档URL地址。在安装Flask框架时,会自动安装Jinja2模板,如果要在Python环境中单独安装Jinja2,pip命令的语法格式如下:
pip install Jinja2
2. Jinja2语法
Jinja2中的基本语法是:
- 控制结构 {% %}:控制结构用于定义模板中控制程序流的模块,如:判断模块(即if/elif/else)、循环模块(for循环)、宏、块(block)、继承(extends)等。控制结构定义在{%…%}模板标签内,有些控制结构还需有对应的结束标签,如: {% if 条件表达式 %} … {% endif %}。
- 变量取值 {{ }}:在Jinja2模板中,通过 {{ 变量名或表达式 }} 的语法来动态获取变量值,变量名必须由字母、数字、下划线(不能以下划线开头)和点组成。与Python中的变量类似,可以通过 “.” 来访问变量的属性,也可以使用“下标”语法([])来获取。如果变量或属性不存在,则会返回一个未定义值。如:{{ student.name }} 或 {{ student[‘name’] }}。
- 注释 {# #}:与Python中用“#”来引导注释内容类似,Jinja2规定模板中的注释内容必须包括在{# … #}的模块中。
3. 模板继承
Jinja2中最强大的部分就是模板继承。模板继承允许开发者创建一个基本(骨架)文件,其余文件从该骨架文件继承,然后只针对需要修改的地方进行修改。在Jinja2的骨架文件中,利用block关键字表示其中的内容能够被子模板进行修改,block块的定义语法如下:
{% block 标识符 %} 可被子模块修改的内容区域 {% endblock %}
在子模板中,通过{% extends 父模板名 %} 实现模板的继承,extends关键字告诉模板引擎,该模板继承于其它模板,当模板引擎渲染子模板时,会首先加载父模板。模板继承标签 {% extends 父模板名 %}应该是子模板文件的第一个出现的模板标签。在子模板中,如果需要重写父模板中的某个特定部分,可通过 {% block 标识符 %} 和 {% endblock %} 覆盖父模板的特定部分并在block块的开始标签和结束标签之间给出子模板的具体实现。
例如,图2所示为“致敬长征”项目中的根模板文件cz_base.html的代码截图,在该模板文件中,使用{% block 标识符 %} 和 {% endblock %}定义了6个可被子模板重写的块:title块用于子模块修改页面的标题,links块用于子模板添加新的静态资源链接,header块允许子模板对页面导航栏模块进行自定义设置,main块用于子模块的内容呈现,footer块允许子模板对页面底部导航模块进行自定义设置,jsinclude块用于子模板定义或导入特定的js代码或文件。cz_base.html根模板文件的页面运行效果如图3所示。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %} 长征 {% endblock %}</title>
{# Bootstrap 的 CSS 文件 #}
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-icons.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/cz_style.css') }}">
{% block links %} {% endblock %}
</head>
<body class="d-flex flex-column h-100">
{% block header %}
<header class="p-3 bg-light">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a href="#" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
<img style="width:70px; height:40px;" src="{{ url_for('static', filename='images/cz.jpg') }}">
</a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0 fs-4">
<li><a href="/" class="nav-link px-2">首页</a></li>
<li><a href="{{ url_for('video.video_list_page') }}" class="nav-link px-2">影视</a></li>
<li><a href="{{ url_for('literature.lit_list_page') }}" class="nav-link px-2">文学</a></li>
<li><a href="{{ url_for('battle.bat_list_page') }}" class="nav-link px-2">战例</a></li>
<li><a href="{{ url_for('memorial.mem_list_page') }}" class="nav-link px-2">纪念</a></li>
<li><a href="{{ url_for('hero.hero_list_page') }}" class="nav-link px-2">将领</a></li>
<li><a href="{{ url_for('history.his_list_page') }}" class="nav-link px-2">信史</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3">
<div role="search">
<input type="search" class="form-control form-control-dark"
placeholder="搜索..." aria-label="Search">
</div>
</form>
<div class="text-end">
<a href="{{ url_for('auth.register') }}" class="btn btn-outline-success me-2">注册</a>
<a href="{{ url_for('auth.login') }}" class="btn btn-outline-danger">登录</a>
</div>
</div>
</div>
</header>
{% endblock %}
<main>
{% block main %}
<div class="w-100 my-5 py-5 text-center">
<h1>这是页面的内容区域</h1>
</div>
{% endblock %}
</main>
{% block footer %}
<div class="container">
<footer class="py-3 my-4">
<ul class="nav justify-content-center border-bottom pb-3 mb-3">
<li class="nav-item">
<a href="#" class="nav-link px-2 text-muted">返回首页</a>
</li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">联系我们</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">友情链接</a></li>
<li class="nav-item"><a href="#" class="nav-link px-2 text-muted">关于我们</a></li>
</ul>
<p class="text-center text-muted">版权所有 ©2023 solute_cz</p>
</footer>
</div>
{% endblock %}
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
{% block jsinclude %} {% endblock %}
</body>
</html>
在图2所示的“致敬长征”项目首页cz_index.html中,第一行代码通过{% extends "/cz_base.html" %}声明从cz_base.html继承,并重写了父模板的title、main和jsinclude块,实现本页面的相应功能。
{% extends "/cz_base.html" %}
{% block title %} 长征 {% endblock %}
{% block main %}
<!-- 长征•图片轮播 -->
<section id="myCarousel" class="carousel slide" data-bs-ride="carousel">
<ul class="carousel-indicators">
{% for v in v_list %}
{% if loop.first %}
<li data-bs-target="#myCarousel" data-bs-slide-to="{{loop.index0}}"
class="active" aria-current="true" aria-label="Slide{{loop.index}}"></li>
{% else %}
<li data-bs-target="#myCarousel" data-bs-slide-to="{{loop.index0}}"
aria-label="Slide{{loop.index}}"></li>
{% endif %}
{% endfor %}
</ul>
<div class="carousel-inner">
{% for v in v_list %}
{% if loop.first %}
<div class="carousel-item active">
<img class="d-block w-100" src="{{ url_for('static', filename=v.img_src) }}">
</div>
{% else %}
<div class="carousel-item">
<img class="d-block w-100" src="{{ url_for('static', filename=v.img_src) }}">
</div>
{% endif %}
{% endfor %}
</div>
<ul class="m-0">
<li class="carousel-control-prev" data-bs-target="#myCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
</li>
<li class="carousel-control-next" data-bs-target="#myCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
</li>
</ul>
</section>
<!-- 长征•苦难辉煌 -->
<section class="cz-profile mt-1 bg-cz">
<div class="container py-4">
<div class="row">
<div class="block-title-w">
<h2 class="block-title">长征 • 苦难辉煌</h2>
<span class="icon-title">
<span></span>
<img src="{{ url_for('static', filename='images/star.png') }}" width="30" height="30">
</span>
<a href="{{ url_for('profile.profile_page') }}">
<span class="sub-title mt-3">
{% for line in info_list %}
{{ line }}
{% if loop.index != 1 %}
<br>
{% endif %}
{% endfor %}
</span>
</a>
</div>
</div>
</div>
</section>
<!-- 长征·人员损失 -->
<section class="cz-loss mt-1 py-4">
<div class="container">
<div class="block-title-w">
<h2 class="block-title">长征 ·人员损失</h2>
<span class="icon-title">
<span></span>
<img src="{{ url_for('static', filename='images/star.png') }}" width="30" height="30">
</span>
<span class="sub-title mt-3 text-primary">
长达两年的万里征途中,红军队伍付出了极大牺牲,人员由30余万锐减至3万余人。<br>
参考网址:https://wenku.baidu.com/view/a1ce44abb0717fd5360cdc3e.html?rec_flag=default
</span>
</div>
<div class="row">
{%for loss in cz_loss %}
<div class="col-md-3 col-sm-6 col-xs-12 text-center">
<div class="cz-loss-doughnut" id="{{loss.id}}"></div>
<h2 class="cz-loss-title">{{loss.army}}</h2>
<div class="cz-loss-decs">
<p>{{loss.description}}</p>
</div>
</div>
{% endfor %}
</div>
</div>
</section>
<!--长征·人员队伍-->
<section class="cz-army mt-1 bg-army">
<div class="container py-4">
<div class="row">
<div class="block-title-w">
<h2 class="block-title">长征·红军队伍</h2>
<span class="icon-title">
<span></span>
<img src="{{ url_for('static', filename='images/star.png') }}" width="30" height="30">
</span>
</div>
{% for army in cz_army %}
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12 p-1">
<div class="ps-relative">
<a href="{{ army.href_url }}" class="image">
<img class="army-img" src="{{ url_for('static', filename=army.img_path) }}" alt="{{ army.name }}"/>
</a>
<div class="info">
<a href="{{ army.href_url }}">{{ army.name }}</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</section>
<!--长征·红军将领-->
<section class="cz-leader mt-1 bg-cz">
<div class="container py-4">
<div class="block-title-w">
<h2 class="block-title">长征·红军将领</h2>
<span class="icon-title">
<span></span>
<img src="{{ url_for('static', filename='images/star.png') }}" width="30" height="30">
</span>
</div>
<div class="row row-wrap">
{% for leader in cz_leader %}
{% if loop.index0 % 2 == 0 %}
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12 item">
<div class="inner mb-3">
<a class="image" href="{{ leader.href_url }}">
<img src="{{ url_for('static', filename=leader.img_path) }}"
class="img-fluid w-100" alt="{{ leader.name }}">
</a>
<div class="info">
<div class="title">
<a href="{{ leader.href_url }}">{{ leader.name }}</a>
</div>
<div class="sub-title">
<p>{{ leader.profile }}</p>
</div>
<a href="{{ leader.href_url }}" class="read-more">查看详情...</a>
</div>
</div>
</div>
{% else %}
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12 item item-even">
<div class="inner mb-3">
<div class="info">
<div class="title">
<a href="{{ leader.href_url }}">{{ leader.name }}</a>
</div>
<div class="sub-title">
<p>{{ leader.profile }}</p>
</div>
<a href="{{ leader.href_url }}" class="read-more">查看详情...</a>
</div>
<a class="image" href="{{ leader.href_url }}">
<img src="{{ url_for('static', filename=leader.img_path) }}"
class="img-fluid w-100" alt="{{ leader.name }}">
</a>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</section>
<!--长征·铭记历史-->
<section class="cz-memory mt-1 py-4">
<div class="container">
<div class="row">
<div class="block-title-w">
<h2 class="block-title">长征·铭记历史</h2>
<span class="icon-title">
<span></span>
<img src="{{ url_for('static', filename='images/star.png') }}" width="30" height="30">
</span>
</div>
{% for nav in cz_navs%}
<div class="col-md-3 col-sm-6 text-center">
<div class="tr-icon"><i class="bi {{nav.icon}}" style="font-size:4rem;color:white;"></i></div>
<div class="tr-text">
<h3>{{nav.name}}</h3>
<span class="tr-line"></span>
<p>{{ nav.description }}</p>
<a href="{{ nav.href_url }}" class="btn-readmore" title="{{ nav.name }}">查看详情···</a>
</div>
</div>
{% endfor %}
</div>
</div>
</section>
<!--长征·重要阶段-->
<section class="cz-stage mt-1 py-4">
<div class="container">
<div class="row">
<div class="block-title-w">
<h2 class="block-title">长征·重要阶段</h2>
<span class="icon-title">
<span></span>
<img src="{{ url_for('static', filename='images/star.png') }}" width="30" height="30">
</span>
</div>
</div>
</div>
<div class="container">
<div class="row mt-5">
{% for stage in cz_stage %}
<div class="col-md-4 col-sm-4 p-1 {{ stage.css }}">
<div class="cz-stage-block">
<div class="stage-icon"><i class="bi bi-flag-fill" style="font-size:4rem;color:red;"></i></div>
<div class="cz-stage-step"><span>0{{ loop.index }}</span></div>
<div class="cz-stage-step-title"><a href="{{ stage.href_url }}">{{ stage.name }}</a></div>
<div class="cz-stage-step-text"><p>{{ stage.times }}</p><p>{{ stage.description }}</p></div>
</div>
</div>
{% if loop.index == 3 %}
</div>
</div>
<div class="bg-cz-xys"></div>
<div class="container">
<div class="row mt-5">
{% endif %}
{% endfor %}
</div>
</div>
</section>
{% endblock %}
{% block jsinclude %}
<script type="text/javascript">
let cz_loss={{ cz_loss | tojson | safe }}
</script>
<script src="{{ url_for('static', filename='js/echarts.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/cz_loss_daughnut.js') }}"></script>
{% endblock %}
4. if语句
在Jinja2中,可以使用if语句来控制模板的输出。下面是一个简单的例子,演示了如何在Jinja2模板中使用if语句:
{% if user %}
<p>Hello, {{ user }}!</p>
{% else %}
<p>Hello, anonymous!</p>
{% endif %}
还可以使用elif语句来添加更多的条件分支,例如:
{% if user %}
<p>Hello, {{ user }}!</p>
{% elif guest %}
<p>Hello, guest!</p>
{% else %}
<p>Hello, anonymous!</p>
{% endif %}
5. for语句
在Jinja2中,可以使用{% for %}和{% endfor %}来实现循环。例如:
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
还可以在{% endfor %}之前使用{% else %}块来处理列表为空的情况,例如下面的示例中,如果items列表为空,则会执行{% else %}块中的代码,即模板将输出在<ul>标签内输出一个内容为:“No items found.”的<li>标签:
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% else %}
<li>No items found.</li>
{% endfor %}
</ul>
在Jinja2的for循环中,提供了一些变量和函数,可以使用这些变量和函数来控制循环的行为和输出。例如,可以使用{% if loop.first %}来检查是否是第一次迭代,并执行特定的操作。常用的变量和函数如表1所示:
表1 Jinja2的for循环中常用的变量和函数
变量/函数 | 说明 |
loop.index | 当前迭代的索引(从1开始) |
loop.index0 | 当前迭代的索引(从0开始) |
loop.revindex | 迭代序列的长度减去当前迭代的索引(从1开始) |
loop.revindex0 | 迭代序列的长度减去当前迭代的索引(从0开始) |
loop.first | 如果当前迭代是第一次迭代,则为True |
loop.last | 如果当前迭代是最后一次迭代,则为True |
loop.length | 迭代序列的长度 |
loop.cycle(seq) | 返回一个生成器,按照给定的序列循环取值 |
loop.depth | 当前循环的嵌套深度 |
loop.depth0 | 当前循环的嵌套深度(从0开始) |
loop.previtem | 前一个迭代的元素 |
loop.nextitem | 下一个迭代的元素 |
loop.changed(*vals) | 检查给定的值是否在当前迭代中发生变化 |
loop.unchanged(*values) | 检查给定的值是否在当前迭代中没有发生变化 |
loop.parent | 父级循环的上下文(如果存在) |
loop.children | 子级循环的上下文列表 |
6. 模板继承实训
在接下来的实训中,将基于模板的继承,创建一个长征概述页模板文件,用于展示与长征介绍相关的内容。具体步骤如下:
- 在项目根目录下的templates/index/子目录内新建一个html文件,并命名为cz_profile.html,然后编写代码,实现从cz_base.html模板中继承,并重写main块的内容,如图5所示。在图中所示的代码中,在第1行通过{% extends "/cz_base.html" %}代码声明从该模板文件继承处cz_base.html父模板。从第2行-第38行代码重写了父模板的main块,实现了与首页相同的图片轮播模块:图片轮播使用了Bootstrap框架提供的图片轮播组件,轮播中所用到的图片信息由传递给模板的v_list变量提供,第5-15行代码根据v_list中的元素数量构造对应数目的指示器,在本示例中为一个短橫条,当前轮播到的图片对应的指示器为白色高亮突出显示;第16-28行代码逐个设置轮播用到的图片,图片的路径通过url_for()函数反转生成;第29-36行代码用于设置轮播区域两侧的手动前后切换轮播图片的按钮。
{% extends "/cz_base.html" %} {% block main %} <!-- 长征·图片轮播 --> <section id="myCarousel" class="carousel slide" data-bs-ride="carousel"> <ul class="carousel-indicators"> {% for v in v_list %} {% if loop.first %} <li data-bs-target="#myCarousel" data-bs-slide-to="{{loop.index0}}" class="active" aria-current="true" aria-label="Slide{{loop.index}}"></li> {% else %} <li data-bs-target="#myCarousel" data-bs-slide-to="{{loop.index0}}" aria-label="Slide{{loop.index}}"></li> {% endif %} {% endfor %} </ul> <div class="carousel-inner"> {% for v in v_list %} {% if loop.first %} <div class="carousel-item active"> <img class="d-block w-100" src="{{ url_for('static',filename=v.img_src) }}"> </div> {% else %} <div class="carousel-item"> <img class="d-block w-100" src="{{ url_for('static', filename=v.img_src) }}"> </div> {% endif %} {% endfor %} </div> <ul class="m-0"> <li class="carousel-control-prev" data-bs-target="#myCarousel" data-bs-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> </li> <li class="carousel-control-next" data-bs-target="#myCarousel" data-bs-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> </li> </ul> </section> {% endblock %}
- 修改cz_profile.py (位于项目根目录下的views/子目录下), 如图6所示。首先在文件的第1行代码中,导入了Flask框架下的Blueprint类及render_template()方法;第2行代码处从global_data模块导入了v_list变量,用于为图片轮播模块提供数据;第4行代码创建了一个Blueprint对象(蓝图对象,是Flask实现应用模块化的重要组件,将在后面的实训中进行学习),第8-10行代码处定义了一个函数profile_page(),在函数中利用render_template()方法渲染(1)中定义的模板cz_profile.html。在第7行代码处通过装饰器route()将路由URL“/profile”绑定到profile_page函数。
from flask import Blueprint, render_template from models.global_data import v_list profile = Blueprint('profile', __name__) @profile.route('/profile') def profile_page(): # 利用render_template方法,将相关数据渲染到模板文件中 return render_template("index/cz_profile.html", v_list=v_list)
- 修改完成后,在浏览器输入长征概述页的URL地址(),或从首页的“长征•苦难辉煌”模块的文本内容区域单击进入到长征概述页页面,应该能看到新添加的页面呈现效果,如图7所示。