jinja2模板
在之前的章节中,视图函数只是直接返回文本,而在实际生产环境中其实很少这样用,因为实际的页面大多是带有样式和复杂逻辑的HTML代码,这可以让浏览器渲染出非常漂亮的页面。目前市面上有非常多的模板系统,其中最知名最好用的就是Jinja2
和Mako
,我们先来看一下这两个模板的特点和不同:
-
Jinja2:
Jinja
是日本寺庙的意思,并且寺庙的英文是temple
和模板的英文template
的发音类似。Jinja2
是默认的仿Django
模板的一个模板引擎,由Flask
的作者开发。它速度快,被广泛使用,并且提供了可选的沙箱模板来保证执行环境的安全,它有以下优点:- 让前端开发者和后端开发者工作分离。
- 减少
Flask
项目代码的耦合性,页面逻辑放在模板中,业务逻辑放在视图函数中,将页面逻辑和业务逻辑解耦有利于代码的维护。 - 提供了控制语句、继承等高级功能,减少开发的复杂度。
-
Marko:
Marko
是另一个知名的模板。他从Django
、Jinja2
等模板借鉴了很多语法和API,他有以下优点:- 性能和
Jinja2
相近,在这里可以看到。 - 有大型网站在使用,有成功的案例。
Reddit
和豆瓣都在使用。 - 有知名的web框架支持。
Pylons
和Pyramid
这两个web框架内置模板就是Mako
。 - 支持在模板中写几乎原生的Python语法的代码,对Python工程师比较友好,开发效率高。
- 自带完整的缓存系统。当然也提供了非常好的扩展借口,很容易切换成其他的缓存系统。
- 性能和
1.1 Flask模板简介
- 在渲染模版的时候,默认会从项目根目录下的
templates
目录下查找模版。 - 如果不想把模版文件放在
templates
目录下,那么可以在Flask
初始化的时候指定template_folder
来指定模版的路径。
1.2 模版参数
- 在使用
render_template
渲染模版的时候,可以传递关键字参数。以后直接在模版中使用就可以了。 - 如果你的参数过多,那么可以将所有的参数放到一个字典中,然后在传这个字典参数的时候,使用两个星号,将字典打散成关键参数。
flask代码:
# 先导render_template模块
from flask import Flask,render_template
app = Flask(__name__)
# 第一种方式
@app.route("/")
def hello_world():
content = {
"name":"张三",
"age":18
}
return render_template("index.html",**content)
# 第二种方式
@app.route("/list/")
def my_list():
return render_template("list.html",name="张三",age=18)
# 第三种方式
@app.route("/detail/")
def goods_detail():
content = {
"name":"张三",
"age":18
}
return render_template("detail.html",content=content)
模板中的代码:
3种方式对应的模板文件:
第一种方式模板文件格式
<p>{{name}}</p>
<p>{{age}}</p>
第二种方式模板文件格式
<p>{{name}}</p>
<p>{{age}}</p>
第三种方式模板文件格式
<p>{{content.name}}</p>
<p>{{content.age}}</p>
字典格式
<p>{{content["name"]}}</p>
<p>{{content["age"]}}</p>
1.3 模板中使用url_for
模版中的url_for
跟我们后台视图函数中的url_for
使用起来基本是一模一样的。也是传递视图函数的名字,也可以传递参数。
使用的时候,需要在url_for
左右两边加上一个{{ url_for('func') }}
flask代码:
from flask import Flask,render_template,url_for
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/accounts/login/<id>/')
def login(id):
return render_template('login.html')
if __name__ == '__main__':
app.run(debug=True)
模板中的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>知了课堂首页</title>
</head>
<body>
这是从模版中渲染的
{# {{ 用来存放变量 }}#}
{# {% 用来执行函数或者逻辑代码 %}#}
<p><a href="{{ url_for('login',ref='/',id='1') }}">登录</a></p>
</body>
</html>
1.4 过滤器的基本使用
1.4.1 什么是过滤器,语法是什么
- 有时候我们想要在模版中对一些变量进行处理,那么就必须需要类似于Python中的函数一样,可以将这个值传到函数中,然后做一些操作。在模版中,过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。
- 基本语法:
{{ variable|过滤器名字 }}
。使用管道符号|
进行组合。
1.4.2 常用过滤器
1. default
过滤器:
使用方式{{ value|default('默认值') }}
。如果value这个key
不存在,那么就会使用default
过滤器提供的默认值。如果你想使用类似于python
中判断一个值是否为False(例如:None、空字符串、空列表、空字典等),那么就必须要传递另外一个参数{{ value|default('默认值',boolean=True) }}
。
可以使用or
来替代default('默认值',boolean=True)
。例如:{{ signature or '此人很懒,没有留下任何说明' }}
。
2. 自动转义过滤器:
-
safe
过滤器:可以关闭一个字符串的自动转义。 -
escape
过滤器:对某一个字符串进行转义。 -
autoescape
标签,可以对他里面的代码块关闭或开启自动转义。{% autoescape off/on %} ...代码块 {% endautoescape %}
3. 常用过滤器
1.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)
:计算一个长字符串中单词的个数。
1.4.3 自定义模版过滤器
过滤器本质上就是一个函数。如果在模版中调用这个过滤器,那么就会将这个变量的值作为第一个参数传给过滤器这个函数,然后函数的返回值会作为这个过滤器的返回值。需要使用到一个装饰器:@app.template_filter('cut')
@app.template_filter('cut')
def cut(value):
value = value.replace("hello",'')
return value
<p>{{ article|cut }}</p>
flask代码:
from flask import Flask,render_template
from datetime import datetime
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
@app.route('/')
def index():
context = {
'position': -9,
'signature': '<script>alert("hello")</script>',
'persons':['python','ketang'],
'age': "18",
'article': 'hello pythonworld hello',
'create_time': datetime(2017,10,20,16,19,0)
}
return render_template('index.html',**context)
@app.template_filter('cut')
def cut(value):
value = value.replace("hello",'')
return value
@app.template_filter('handle_time')
def handle_time(time):
"""
time距离现在的时间间隔
1. 如果时间间隔小于1分钟以内,那么就显示“刚刚”
2. 如果是大于1分钟小于1小时,那么就显示“xx分钟前”
3. 如果是大于1小时小于24小时,那么就显示“xx小时前”
4. 如果是大于24小时小于30天以内,那么就显示“xx天前”
5. 否则就是显示具体的时间 2017/10/20 16:15
"""
if isinstance(time,datetime):
now = datetime.now()
timestamp = (now - time).total_seconds()
if timestamp < 60:
return "刚刚"
elif timestamp>=60 and timestamp < 60*60:
minutes = timestamp / 60
return "%s分钟前" % int(minutes)
elif timestamp >= 60*60 and timestamp < 60*60*24:
hours = timestamp / (60*60)
return '%s小时前' % int(hours)
elif timestamp >= 60*60*24 and timestamp < 60*60*24*30:
days = timestamp / (60*60*24)
return "%s天前" % int(days)
else:
return time.strftime('%Y/%m/%d %H:%M')
else:
return time
if __name__ == '__main__':
app.run(debug=True)
模板中的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>python课堂</title>
</head>
<body>
{# <p>位置的绝对值是:{{ position|abs }}</p>#}
{# <p>个性签名:{{ signature or '此人很懒,没有留下任何说明' }}</p>#}
{# <p>个性签名:{{ signature|safe }}</p>#}
{# {% autoescape off %}#}
{# {% endautoescape %}#}
{# <p>{{ persons[0] }}</p>#}
{# <p>{{ "我的名字是:%s"|format('xiaoming') }}</p>#}
{# <p>人数{{ persons|length }}</p>#}
{# {% if age|int == 18 %}#}
{# <p>年龄是18岁</p>#}
{# {% else %}#}
{# <p>年龄不是18岁</p>#}
{# {% endif %}#}
{# <p>{{ article|replace('hello','aaa') }}</p>#}
{# <p>{{ article|truncate(length=5) }}</p>#}
{# <p>{{ signature|striptags }}</p>#}
{# <p>{{ article|cut }}</p>#}
<p>发表时间:{{ create_time|handle_time }}</p>
</body>
</html>
1.5 模板中的语句
1.5.1 if条件判断语句
if
条件判断语句必须放在{% if statement %}
中间,并且还必须有结束的标签{% endif %}
。和python
中的类似,可以使用>,<,<=,>=,==,!=
来进行判断,也可以通过and,or,not,()
来进行逻辑合并操作。
flask代码:
from flask import Flask,render_template
app = Flask(__name__)
app.config.update({
'DEBUG': True,
'TEMPLATES_AUTO_RELOAD': True
})
@app.route('/')
def index():
context = {
'username': 'abc',
'age': 17
}
return render_template('index.html',**context)
if __name__ == '__main__':
app.run()
模板中的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
{% if username == 'xiaoming' %}
<p>首页</p>
{% else %}
<p>我不是首页</p>
{% endif %}
{% if age >= 18 %}
<p>你可以进入网吧了</p>
{% else %}
<p>未成年人禁止进入网吧</p>
{% endif %}
</body>
</html>
1.5.2 for循环语句
在jinja2
中的for
循环,跟python
中的for
循环基本上是一模一样的。也是for...in...
的形式。并且也可以遍历所有的序列以及迭代器。但是唯一不同的是,jinja2
中的for
循环没有break
和continue
语句。
flask代码:
from flask import Flask,render_template
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
@app.route('/')
def index():
context = {
'users':['xiaoming1','xiaoming2','xiaoming3'],
'person': {
'username': 'xiaoming',
'age': 18,
'country': 'china'
},
'books':[
{
'name': '三国演义',
'author':'罗贯中',
'price': 110
},{
'name': '西游记',
'author':'吴承恩',
'price': 109
},{
'name': '红楼梦',
'author':'曹雪芹',
'price': 120
},{
'name': '水浒传',
'author':'施耐庵',
'price': 119
}
]
}
return render_template('index.html',**context)
if __name__ == '__main__':
app.run(debug=True)
模板中的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>for循环</title>
</head>
<body>
<ul>
{% for user in users|reverse %}
<li>{{ user }}</li>
{% else %}
<li>沒有任何值</li>
{% endfor %}
</ul>
<table>
<thead>
<tr>
<th>用户名</th>
<th>年龄</th>
<th>国家</th>
</tr>
</thead>
<tbody>
<tr>
{% for key in person.keys() %}
<td>{{ key }}</td>
{% endfor %}
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>序号</th>
<th>书名</th>
<th>作者</th>
<th>价格</th>
<th>总数</th>
</tr>
</thead>
<tbody>
{% for book in books %}
{% if loop.first %}
<tr style="background: red;">
{% elif loop.last %}
<tr style="background: pink;">
{% else %}
<tr>
{% endif %}
<td>{{ loop.index0 }}</td>
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.price }}</td>
<td>{{ loop.length }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table border="1">
<tbody>
{% for x in range(1,10) %}
<tr>
{% for y in range(1,10) if y <= x %}
<td>{{ y }}*{{ x }}={{ x*y }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
1.5.3 set语句
在模版中,可以使用set
语句来定义变量。示例如下:
{% set username='python课堂' %}
<p>用户名:{{ username }}</p>
一旦定义了这个变量,那么在后面的代码中,都可以使用这个变量,就类似于Python
的变量定义是一样的。
1.5.4 with语句
with
语句定义的变量,只能在with
语句块中使用,超过了这个代码块,就不能再使用了。示例代码如下:
{% with classroom = 'python1班' %}
<p>班级:{{ classroom }}</p>
{% endwith %}
with
语句也不一定要跟一个变量,可以定义一个空的with
语句,以后在with
块中通过set
定义的变量,就只能在这个with
块中使用了:
{% with %}
{% set classroom = 'python1班' %}
<p>班级:{{ classroom }}</p>
{% endwith %}
1.6 宏详解
1.6.1 宏的基本使用
模板中的宏跟python
中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量。
使用宏的时候,参数可以为默认值。相关示例代码如下:
- 定义宏:
{% macro input(name, value='', type='text') %} <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> {% endmacro %}
- 使用宏:
<p>{{ input('username') }}</p> <p>{{ input('password', type='password') }}</p>
1.6.2 导入宏
-
import "宏文件的路径" as xxx
。 -
from '宏文件的路径' import 宏的名字 [as xxx]
。 - 宏文件路径,不要以相对路径去寻找,都要以
templates
作为绝对路径去找。 - 如果想要在导入宏的时候,就把当前模版的一些参数传给宏所在的模版,那么就应该在导入的时候使用
with context
。示例:from 'xxx.html' import input with context
。
flask代码:
from flask import Flask,render_template
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
@app.route('/')
def hello_world():
return render_template('index/index.html',username='xiaoming')
if __name__ == '__main__':
app.run(debug=True)
模板文件中的代码:
{#{% from "macros.html" import input as input_field %}#}
{% import "macros/macros.html" as macros with context %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>python宏</title>
</head>
<body>
<h1>python登录</h1>
<table>
<tbody>
<tr>
<td>用户名:</td>
<td>{{ macros.input('username') }}</td>
</tr>
<tr>
<td>密码:</td>
<td>{{ macros.input("password",type="password") }}</td>
</tr>
<tr>
<td></td>
<td>{{ macros.input(value="提交",type="submit") }}</td>
</tr>
</tbody>
</table>
<p>{{ username }}</p>
</body>
</html>
宏文件:
{% macro input(name="",value="",type="text") %}
<input type="{{ type }}" name="{{ name }}" value="{{ username }}">
{% endmacro %}
1.7 include详解
1.7.1 include标签
- 这个标签相当于是直接将指定的模版中的代码复制粘贴到当前位置。
-
include
标签,如果想要使用父模版中的变量,直接用就可以了,不需要使用with context
。 -
include
的路径,也是跟import
一样,直接从templates
根目录下去找,不要以相对路径去找。
flask代码:
from flask import Flask,render_template
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
@app.route('/')
def hello_world():
return render_template('index.html',username='xiaoming')
@app.route('/detail/')
def detail():
return render_template('course_detail.html')
if __name__ == '__main__':
app.run(debug=True)
模板中的文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>python课堂</title>
</head>
<body>
{% include "common/header.html" %}
<div class="content">
中间的
</div>
{% include "common/footer.html" %}
</body>
</html>
footer.html
中的代码:
<footer>
这是底部
</footer>
header.html
中的代码:
<style>
.nav ul{
overflow: hidden;
}
.nav ul li{
float: left;
margin: 0 20px;
}
</style>
<nav class="nav">
<ul>
<li>首页</li>
<li>课程详情</li>
<li>视频教程</li>
<li>关于我们</li>
<li>{{ username }}</li>
</ul>
</nav>
1.8 静态文件
加载静态文件使用的是url_for
函数。然后第一个参数需要为static
,第二个参数需要为一个关键字参数filename='路径'
。示例:
{{ url_for("static",filename='xxx') }}
路径查找,要以当前项目的static
目录作为根目录。
模板中的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>python课堂</title>
<link rel="stylesheet" href="{{ url_for('static',filename="css/index.css") }}">
<script src="{{ url_for("static",filename='js/index.js') }}"></script>
</head>
<body>
<img src="{{ url_for("static",filename='imgs/buyudaren.jpg') }}" alt="">
</body>
</html>
1.9 模版继承
1.9.1 为什么需要模版继承
模版继承可以把一些公用的代码单独抽取出来放到一个父模板中。以后子模板直接继承就可以使用了。这样可以重复性的代码,并且以后修改起来也比较方便。
1.9.2 模版继承语法
使用extends
语句,来指明继承的父模板。父模板的路径,也是相对于templates
文件夹下的绝对路径。示例代码如下:
{% extends "base.html" %}
1.9.3 block语法
一般在父模版中,定义一些公共的代码。子模板可能要根据具体的需求实现不同的代码。这时候父模版就应该有能力提供一个接口,让父模板来实现。从而实现具体业务需求的功能。
在父模板中:
{% block block的名字 %}
{% endblock %}
在子模板中:
{% block block的名字 %}
子模板中的代码
{% endblock %}
1.9.4 调用父模版代码block中的代码
默认情况下,子模板如果实现了父模版定义的block
。那么子模板block
中的代码就会覆盖掉父模板中的代码。如果想要在子模板中仍然保持父模板中的代码,那么可以使用{{ super() }}
来实现。示例如下:
父模板:
{% block body_block %}
<p style="background: red;">这是父模板中的代码</p>
{% endblock %}
子模板:
{% block body_block %}
{{ super() }}
<p style="background: green;">我是子模板中的代码</p>
{% endblock %}
1.9.5 调用另外一个block中的代码
如果想要在另外一个模版中使用其他模版中的代码。那么可以通过{{ self.其他block名字() }}
就可以了。示例代码如下:
{% block title %}
python课堂首页
{% endblock %}
{% block body_block %}
{{ self.title() }}
<p style="background: green;">我是子模板中的代码</p>
{% endblock %}
1.9.6 其他注意事项
- 子模板中的代码,第一行,应该是
extends
。 - 子模板中,如果要实现自己的代码,应该放到
block
中。如果放到其他地方,那么就不会被渲染。
网友评论