Flask 极致细节:2. 模板(参数传递,表格展示,模板继承,宏)

Flask 极致细节:2. 模板(参数传递,表格展示,模板继承,宏)

提示:此博客包含如下概念的介绍:模板的介绍,参数传递,列表与表格的制作,(自定义)过滤器,模板继承,引入样式表,引入图片,宏(macro)的介绍与使用。
此章节可能会比较枯燥,之后我们会跟新真实的案例,但打好基础总是必须的。另外,我们会一如既往地分享所有代码,并附有非常详细的注解。

喜欢的朋友点个赞哦:)
代码链接:https://pan.baidu.com/s/1sjshcJPgsYvP_Pb_zw8sIA
密码:yu34



0. 准备工作

当你下载并打开代码后,里面的结构应该是这样的:

--项目名
	|---static (静态)
	|---templates (模板)
	|---app.py (运行/启动)
	|---venv1 (虚拟环境)
	|---requirements.txt (所有安装包以及版本)
	|---config.py (参数配置文件)
	|---readme.md (说明文档)

我们可以创建虚拟环境:virtualenv [venv],或直接使用已经存在的虚拟环境venv1。接下来进入虚拟环境:.\[venv]\Scripts\activate。如是自己创建的新虚拟环境,还需要安装依赖:pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。相关的配置以及解释请参见前一篇博文:Flask 极致细节:0. VS Code 手动新建Flask项目

1. 参数的传递

1.1 案例

第一个问题是,我们在app.py中的一些参数,如何传递/显示到html网页中。

这里,我们首先考虑最基础的参数传递,包含以下几种:string,int,list中的元素,dictionary中的元素,以及创建的对象。

app.py中的代码:

class Attendance:
    def __init__(self, name, address) -> None:
        self.name = name
        self.address = address
        self.gender = 'Male'
    def __str__(self) -> str:
        return self.name

@app.route('/show')
def show():
    name = 'Alex'                                                   # 将string传入html
    age = 52                                                        # 将int传入html
    fruits = ['apple','banana','pineapple','orange']                # 将list中的元素传入html
    dict1 = {'Marry':59,'Beny':63,'Eric':75,'Peter':100}            # 将dictionary中的元素传入html
    attendance1 = Attendance(name='Peter', address='Berlin')        # 将类的实例传入html
    return render_template('app_templateEx.html', names=name, \
        ages=age, fruits=fruits, dict=dict1, attends=attendance1)

app_template.html中的代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Template例子</title>
</head>
<body>

<h1>这是关于模板的例子</h1>
<p>段落1</p>
<div>用户信息展示</div>
<p>
    <!--stringint传入html -->
    用户名是:Name {{ names }}, with age {{ ages }}. 
    <br>
    <!-- 将list中的元素传入html -->
    选择的水果是:{{ fruits.1 }}
    <br>
    <!-- 将dictionary中的元素传入html -->
    Marry获得的分数:{{ dict.get('Marry') }};Eric获得的分数:{{ dict.Eric }}
    <br>
    <!-- 将对象中的元素传入html,这里attends会直接引用__str__里面的内容,所以attends返回的就是attends.name的值 -->
    参会者:{{ attends }};性别:{{ attends.gender }};地址:{{ attends.address }}</p>
</body>
</html>

1.2 小节

在模板中获得view中传递的变量的格式:{{ 变量名key }}

app.py中,我们通过render_template('模板名称', key=value, key=value)来实现参数的传递。如果我们点进去render_template这个函数,我们就可以明确地看到一个参数context,它的注释是:the variables that should be available in the context of the template

render_template的源代码:

def render_template(
    template_name_or_list: t.Union[str, t.List[str]], **context: t.Any
) -> str:
    """Renders a template from the template folder with the given
    context.

    :param template_name_or_list: the name of the template to be
                                  rendered, or an iterable with template names
                                  the first one existing will be rendered
    :param context: the variables that should be available in the
                    context of the template.
    """
    ctx = _app_ctx_stack.top
    ctx.app.update_template_context(context)
    return _render(
        ctx.app.jinja_env.get_or_select_template(template_name_or_list),
        context,
        ctx.app,
    )

在模板中的取值方式:

- string a -> {{ a }}
- int/float a -> {{ a }}
- list a=[aa,ab,ac,ad] -> 取第一个元素:{{ a.0 }} 同 {{ a[0] }}
- dictionary a={‘aa’:1,‘ab’:2,‘ac’:3,‘ad’:4} -> 取第一个元素:{{ a.aa }} 同 {{ a.get(‘aa’) }}
- 对象 a=Attendance(name=‘Peter’, address=‘Berlin’) -> 取对象中的地址属性:{{ a.address }} 同 {{ 对象.属性 }}

2. 表格的展示

此章节中,我们会先解释如何在html中书写iffor循环,然后进入表格的制作。在此之前,关于html中出现的关键字,可以参考此链接,也可以在VSCode中把鼠标移至关键字上方,会出现相关的英文提示。

2.1 iffor循环

控制块的书写逻辑如下:

{% if 条件 %}
...
{% else %}
...
{% endif %}

{% for 变量 in 可迭代对象 %}
...
{% endfor %}

app.py中,添加:

girlnames = ['Marry','Linda','Alexi','Mia','Bessie','Adam','韩梅梅','安吉丽娜','王刚']
users = [
    {'username': 'AABB', 'password': 'abab', 'addr': 'shanghai', 'phone': '4321'},
    {'username': 'AABB2', 'password': 'abab2', 'addr': 'shanghai2', 'phone': '43212'},
    {'username': 'AABB3', 'password': 'abab3', 'addr': 'shanghai3', 'phone': '43213'},
    {'username': 'AABB4', 'password': 'abab4', 'addr': 'shanghai4', 'phone': '43214'},
    {'username': 'AABB5', 'password': 'abab5', 'addr': 'shanghai5', 'phone': '43215'},
    {'username': 'AABB6', 'password': 'abab6', 'addr': 'shanghai6', 'phone': '43216'},
    {'username': 'AABB7', 'password': 'abab7', 'addr': 'shanghai7', 'phone': '43217'}
]
return render_template('app_templateEx.html', names=name, \
    ages=age, fruits=fruits, dict=dict1, attends=attendance1,\
    girls=girlnames, usersTable = users)

app_templateEx.html中:

<hr>
<ul>
    {% for girl in girls %}
        {% if girl|length >=4 %}                <!-- 当字符串长度大于等于4,这条字符串的颜色会变成红色 -->
            <li class="a"> {{ girl }} </li>     <!-- 注意页面最顶端的 style -->
        {% else %}
            <li> {{ girl }} </li>
        {% endif %}
    {% endfor %}
</ul>
<hr>
<table border="1" cellpadding="0" cellspacing="0" width="80%">
    {% for user in usersTable %}
        <tr {% if loop.index == 3 %} class="a" {% endif %}>     <!-- loop.first, loop.last 第一行和最后一行 -->
            <td> {{ loop.index }} </td>     <!-- 如果loop.index0,那么序号从0开始,否则从1开始 -->
            <td> {{ user.username }} </td>  <!-- 当index为3的时候,对应的这行都变成红色,注意style对class a的描述 -->
            <td> {{ user.password }} </td>
            <td> {{ user.addr }} </td>
            <td> {{ user.phone }} </td>
        </tr>
    {% endfor %}

对应的样式:

<style>
    .a{
        color: red;
        font-weight: bold;
    }
</style>

这里,第一部分(<ul>...</ul>)通过for循环,将girls里面的每一个字符串作为bullet罗列出来。并且,如果字符串长度大于4,则该字符串显示红色。效果如下:
在这里插入图片描述
第二部分是表格的制作。我们将users这个包含着dict的list作为变量导入进来,然后同样用for循环去遍历整个列表。列表中的每一个元素是一个字典user,字典的每一个元素表达为字典.元素。当index为3的时候,对应的这行都变成红色。效果如下:
在这里插入图片描述

2.2 过滤器

过滤器本质就是一个函数,只是换了一种方式去使用而已。

{{ 变量名 | 过滤器(*args) }} # 需要传参的话
{{ 变量名 | 过滤器 }}

常见过滤器:

  1. safe 禁用转义
  2. capitalize 首字母大写,其余小写
  3. lower和upper
  4. title 一句话中的每个首字母大写
  5. reverse 反转
  6. format 格式化输出
  7. 列表过滤器:first, last, length, sum, sort等等
  8. 字典过滤器:.keys, .values, .items用于获取值,键,键值等等。

例子见app_templateEx.html

<p>过滤器的使用</p>
<p>
    当前用户共:{{ girls | length }}人。
    <br>
    {{ msg | safe }}            <!-- 如果没有safe这个禁止转义,这里就会直接显示<p>这是一条测试信息</p>,因为<这个符号会被转义成&lt -->
    <br>
    {{ fruits.1 }}。首字母大写:{{ fruits.1 | capitalize }}
    <br>
    {{ '%s is in %d days.' | format(fruits.1, ages) }}
    <br>
    <!-- 列表过滤器的例子 -->
    {{ fruits | first }}<br>
    {{ fruits | last }}<br>
    {{ fruits | length }}<br>
    {{ fruits | sort }}<br>
    <!-- 字典过滤器的例子 -->
    <p>获取字典过滤器的值</p>
    {% for v in usersTable.0.values() %}
        <p> {{v}} </p>
    {% endfor %}
    <p>获取字典过滤器的健</p>
    {% for k in usersTable.0.keys() %}
        <p> {{k}} </p>
    {% endfor %}
    <p>获取字典过滤器的健值</p>
    {% for k,v in usersTable.0.items() %}
        <p> {{k}} -- {{v}} </p>
    {% endfor %}
</p>

2.3 自定义过滤器

两种方式:

1. 通过flask模块中的add_template_filter方法

- 定义函数,带有参数和返回值(例如`app_template`中的`filter_replace`函数)
- 添加过滤器:`app.add_template_filter(function,name='')`
- 在模板中使用:{{ 变量 | 自定义过滤器 }}

app.py中,我们举了一个例子:

# 自定义过滤器,用法1
def filter_replace(value):
    print('---->',value)
    value = value.replace('hello','')
    print('====>',value)
    return value.strip()    # 去除首尾的空格
app.add_template_filter(filter_replace, 'replace_')

2. 使用修饰器完成

- 定义函数,带有参数和返回值(例如`app_template`中的`filter_reverseList`函数)
- 通过装饰器完成:`@app.template_filter('过滤器名称')`装饰上面的函数
- 在模板中使用:{{ 变量 | 自定义过滤器 }}

app.py中,我们也举了一个例子:

# 自定义过滤器,用法2,使用装饰器
@app.template_filter('reverse_')
def filter_reverseList(li):
    temp_li = list(li)
    temp_li.reverse()
    return temp_li

app_templateEx.html中,

<p>
    自定义过滤器<br>
    {{ filtermsg | replace_ }}<br>      <!-- 第一种自定义过滤器的书写方式 -->
    Original list: {{ fruits }}<br>
    List after reverse: {{ fruits | reverse_ }}<br>         <!-- 第二种自定义过滤器的书写方式 -->
</p>

3. 模板继承

3.1 类的继承

这里我们先大概描述一下类的继承,因为模板的继承与类的继承相似。

一个类继承另一个类时,会自动获得另一个类的所有属性和方法,被继承的类称之为父类,新类称为子类。子类拥有父类所有的属性和方法,并且可以定义自己的属性和方法。具体的例子可以参见链接1以及链接2

3.2 模板继承大体步骤

父模板:

  1. 定义一个父模板,一般名字起base.ml
  2. 分析这个模板中哪些是变化的,然后对需要变化的地方进行挖坑(对变化的部分用block进行预留位置)。{% block 名字 %} {% endblock %}
  3. 注意:关于样式和脚本。一般来说,子模板都会自己定制一部分的样式和脚本,所以需要在父模板中提前挖坑预留出来。预留的模块可以是空的,比如 {% block myjs %} {% endblock %},因为预留的目的是子模板需要用来进行填充。

子模板使用父模板:

  1. {% extends '父模板的名称' %} 将父模板继承过来。
  2. 找到对应的block(坑)进行填充,每一个block都有名字。

3.3 模板继承的一个例子:结果

我们先来看最终呈现的效果。当我们运行python app.py后,我们会看到主界面如下:

在这里插入图片描述

如果我们点击了类的继承界面:base界面,界面变成:

在这里插入图片描述

如果我们点击了类的继承界面:继承界面,界面变成:

在这里插入图片描述

如果我们点击一个按钮按钮:

在这里插入图片描述

如果我们点击两个返回主页按钮,我们就会看到主界面。

3.4 模板继承的一个例子:代码

我们首先在app.py中定义了两个路由,分别导向父模板和子模板的页面:

@app.route('/inherit',methods = ['GET','POST'])
def inherit():
    # 在子模板中有一个按钮,点击后会回到主界面。
    if request.method == "POST":
        return redirect(location=url_for('mainpage'))
    # 进入子模板的界面
    return render_template('inherit_test.html')

@app.route('/base',methods = ['GET','POST'])
def base():
    # 在父模板中有一个按钮,点击后会回到主界面。
    if request.method == "POST":
        return redirect(location=url_for('mainpage'))
    # 进入父模板的界面
    return render_template("base.html")

接下来,我们将对父模板index.html以及子模板inherit_test.html进行比对,看看模板的继承究竟是怎么用的。

(1) 我们为title挖了个坑,这样父模板和子模板的标题可以不同;

父模板:

<title>
    {% block title %}       <!-- 需要被改变的部分都可以用block来做一个标记  -->
    基页
    {% endblock %}
</title>

子模板:

{% block title %}
新的一页
{% endblock %}

这里我们就能看到,子模板不再需要填写<title></title>框架了,只需要把父模板的坑填好。如果不填的话,子模板对应的内容会和父模板一样。

(2) 样式

父模块中,我们将整个版面分成了三个部分:head, middle 以及foot。其中,headfoot我们都定死了:

head

<div id="head"> 
    <ul>
        <li>首页</li>
        <li>第一排</li>
        <li>第二排</li>
        <li>第三排</li>
        <li>第四排</li>
    </ul>
</div>

foot

<div id="foot">
    底部填充
</div>

样式style中,有一部分也是固定的:

<style>
    #head{
        height: 50px;
        background-color: bisque;
    }
    #head ul{
        list-style: none;
        height: 50px;
    }
    #head ul li{
        float: left;
        width: 100px;
        text-align: center;
        font-size: 18;
        height: 50px;
        line-height: 50px;
    }
    #foot{
        height: 50px;
        line-height: 50px;
    }
</style>

但是和middle相关的部分是变化的:

父模板:

{% block mycss %}
<style>
    #middle {
        height: 600px;
        background-color: cadetblue;
    }
</style>
{% endblock %}

子模板:

{% block mycss %}
<style>
    #middle {
        height: 600px;
        background-color: blueviolet;
        color: white;
        font-weight: bold;
    }
    .div1{
        width: 30%;
        float: left;
        border: 1px solid red;
        height: 300px;
    }
</style>

(3) middle部分

父模板:

<div id="middle"> 
    这一行在两个页面是一致的。<br>
    {% block middle %}  
        基页的中间行
        <form action="/base" method="post">
            <p><button>从基页返回主页</button></p>      <!-- 为这个按钮写一个脚本(点下它之后会发生什么) -->
        </form>
    {% endblock %}
</div>

这一行在两个页面是一致的。这部分是固定的,但下面的部分,包括middle部分的样式都是可变的。在base页面中,我们定义了一个按钮,点击后,可以直接回到主界面(因为重定向的原因)。

子模板:

{% block middle %}
继承页的中间行
<p><button id="buttonBase">一个按钮</button></p>
<form action="/inherit" method="post">
    <p><button>从继承页返回主页</button></p>
</form>
<div class="div1"></div>
<div class="div1"></div>
<div class="div1"></div>
{% endblock %}

除了一个从继承页返回主页的按钮外,这里我们还加了一个一个按钮按钮,点击后会跳出弹窗,显示ok

对应的,脚本位置我们也增加了一个坑:

父模块:

{% block myjs %}  
{% endblock %}

子模块:

{% block myjs %}
    <script>
        btn = document.getElementById('buttonBase')
        btn.onclick = function(){
            alert('ok')
        }
    </script>
{% endblock %}

我们看到,父模块里只是挖了坑,里面没有任何内容。这也是允许的。

3.5 引入样式表

通常,我们不会把脚本和样式都放在一个html文件里,而是单独作为各自的.css.js文件放在static目录下,文件结构可以是:

--statics
  |--css
    |--style.css
  |--js
    |--index.js
  |--images

在这里,我们做一个简单的例子。

我们先给inherit_test.html中的一个div1加一个id,如下所示:

{% block middle %}
继承页的中间行
<p><button id="buttonBase">一个按钮</button></p>
<form action="/inherit" method="post">
    <p><button>从继承页返回主页</button></p>
</form>
<div class="div1" id="d1"></div>
<div class="div1"></div>
<div class="div1"></div>
{% endblock %}

style.css(static/css路径下)中,我们只是给id为d1的模块加了一个绿色的背景色。

#d1{
    background-color: green;
}

inherit_test.html中,添加

<link rel="stylesheet" href="{{url_for('static',filename='css/style.css')}}">

3.6 引入图片

现在,我们试试将图片导入到inherit_test.html页面中。图片保存于statics/images路径下。

inherit_test.html中添加:

<img src="{{url_for('static',filename='images/view3.png')}}" >

4. 宏:Macro

把它看作jinjia2这个模板引擎的一个函数。这个函数可以返回一个html的字符串。
目的:为了避免重复定义相同代码,以代码复用。

4.1 结论

两种定义方式

  1. 在模板中直接定义
  2. 将所有宏提取到一个模板中,谁想用就直接导入
{% import 'macro/macro.html' as XXX %}
{{ XXX.form(...) }}

4.2 代码

我们在macro/路径下新建macro1.html,宏的定义写法如下:

{% macro form(action,value='登录',method='POST') %}
    <form action="{{ action }}" method="{{ method }}">
        <input type="text" placeholder="用户名" name="username">
        <br>
        <input type="password" placeholder="密码" name="password">
        <br>
        <input type="submit" placeholder="{{ value }}">
    </form>
{% endmacro %}

如果我们不用宏,那么就是这么写:

<form action="/test7" method="post">
    <p><input type="text" name="username" placeholder="请输入用户名"></p>
    <p><input type="text" name="address" placeholder="请输入地址"></p>
    <p><input type="submit" placeholder="提交"></p>
</form>

所以,宏就像是一个函数一样。我们如果要调用宏,在同一文件内:

{{ form('/') }}

如果我们要在另一个html文件中调用这个宏,我们可以先把这段宏代码放在一个独立的html文件中,比如/macro/macro.html,然后在其他的html文件内导入:

{% import 'macro/macro.html' as macroTemp %}
{{ macroTemp.form('/inherit', value='注册') }}

5. 总结

变量:{{ 变量 }}
{{ url_for(‘static’,filename=’’) }}
{{ hongname(xxx) }} 宏的调用
块:{% %}
{% if 条件 %} … {% endif %}
{% for 条件 %} … {% endfor %}
{% block 条件 %} … {% endblock %}
{% macro 条件 %} … {% endmacro %}
{% include ‘’ %} 包含
{% import ‘’ %} 导入宏
{% extends %} 模板继承

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破浪会有时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值