Jinja2模板引擎简介
1,模板
视图函数的主要作⽤是⽣成请求的响应,这是最简单的请求。实际上,视图函数有
两个作⽤:
- 处理业务逻辑和返回响应内容。在⼤型应⽤中,把业务逻辑和表现内容放在⼀起,会增加
- 代码的复杂度和维护成本。模板的作⽤即是承担视图函数的另⼀个作⽤,即返回响应内容。
模板其实是⼀个包含响应⽂本的⽂件,其中⽤占位符(变量)表示动态部分,告诉模板引擎其具体的
值需要从使⽤的数据中获取
使⽤真实值替换变量,再返回最终得到的字符串,这个过程称为“渲染”
Flask是使⽤ Jinja2 这个模板引擎来渲染模板
使⽤模板的好处:
视图函数只负责业务逻辑和数据处理(业务逻辑⽅⾯),⽽模板则取到视图函数的数据结果进⾏展示(视图展示⽅⾯)代码结构清晰,耦合度低
Jinja2的两个概念:
- Jinja2:是 Python 下⼀个被⼴泛应⽤的模板引擎,是由Python实现的模板语⾔,他的设计思想来
源于 Django 的模板引擎,并扩展了其语法和⼀系列强⼤的功能,其是Flask内置的模板语⾔。 - 模板语⾔:是⼀种被设计来⾃动⽣成⽂档的简单⽂本格式,在模板语⾔中,⼀般都会把⼀些变量传给模板,替换模板的特定位置上预先定义好的占位变量名。
渲染模版函数
Flask提供的 render_template 函数封装了该模板引擎
render_template 函数的第⼀个参数是模板的⽂件名,后⾯的参数都是键值对,表示模板中变量
对应的真实值。
使⽤
{{}} 来表示变量名,这种 {{}} 语法叫做变量代码块
<h1>{{ post.title }}</h1>
Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 str() ⽅法转换为⼀个字符串就可以,⽐如,可以通过下⾯的⽅式显示⼀个字典或者列表中的某个元素:
{{your_dict['key']}}
{{your_list[0]}}
⽤ {%%} 定义的控制代码块,可以实现⼀些语⾔层次的功能,⽐如循环或者if语句
{% if user %}
{{ user }}
{% else %}
hello!
<ul>
{% for index in indexs %}
<li> {{ index }} </li>
{% endfor %}
</ul>
注释
使⽤ {# #} 进⾏注释,注释的内容不会在html中被渲染出来
{# {{ name }} #}
2,模板
2.1 模板变量的基本使用
模板渲染的三步操作
from flask import Flask, render_template
app = Flask(__name__)
# 模板渲染的三步操作
# 1,将模板文件放入模板文件夹中,并设置模板语言
# 2,使用render_template进行模板渲染,将要替换的内容设置为模板变量
# 3,在模板文件中使用模板变量替换内容(找到要替换内容设置为{{模板变量名}})
@app.route('/')
def index():
city_name = '北京'
htm_str = render_template('baidu.html',city=city_name)
return htm_str
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
2.2模板替换
py代码
from flask import Flask, render_template
app = Flask(__name__)
class User():
type='vip'
def is_login(self):
return True
@app.route('/')
def index():
name = 'zs'
dict = {
'name': 'ls',
'age': 18
}
list = [1, 2, 3, 4]
htm_str = render_template('render.html',name=name,dict=dict,list=list,user=User())
return htm_str
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<label>{{ name }}</label><br/>
<label>{{ dict }}</label><br/>
<label>{{ dict['name'] }}</label><br/>
<label>{{ dict.name }}</label><br/>
<label>{{ list }}</label><br/>
<label>{{ list[0] }}</label><br/>
<label>{{ user.type }}</label><br/>
<label>{{ user.is_login }}</label><br/>
</body>
</html>
2.3过滤器
py代码
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
name = 'zs'
list = [1, 6, 3, 9]
h1_tag='<h1>我是标题<h1>'
htm_str = render_template('filtering.html',name=name,list=list,h1_tag=h1_tag)
return htm_str
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<label>{{ name|lower }}</label><br/>
<label>{{ list|first }}</label><br/>
<label>{{ list|sum }}</label><br/>
<label>{{ h1_tag|safe }}</label><br/>
{#过滤块代码#}
{% filter upper %}
hello<br/>
world
{% endfilter %}
</body>
</html>
2.4自定义过滤器
1.定义过滤器函数>1定义形参接收模板变量的值,>2将转换后的结果返回
2.应用添加过滤器 add_template_filter(过滤器函数引用,过滤器名称)
py代码
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
list = [1,4,9,8,2]
htm_str = render_template('filtering1.html',list=list )
return htm_str
# 1.定义过滤器函数>1定义形参接收模板变量的值,>2将转换后的结果返回
def li_reverse(var):
return var[::-1]
# 2.应用添加过滤器 add_template_filter(过滤器函数引用,过滤器名称)
app.add_template_filter(li_reverse,'Li_reverse')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<label>{{ list|Li_reverse }}</label><br/>
</body>
</html>
3控制代码块
控制代码块主要包含两个
- if / else if / else / endif
- for / endfor
3.1 if语句
Jinja2 语法中的if语句跟 Python 中的 if 语句相似,后⾯的布尔值或返回布尔值的表达式将决定代码中的哪个流程会被执⾏:
{%if user.is_logged_in() %}
<a href='/logout'>Logout</a>
{% else %}
<a href='/login'>Login</a>
{% endif %}
过滤器可以被⽤在 if 语句中:
{% if comments | length > 0 %}
There are {{ comments | length }} comments
{% else %}
There are no comments
{% endif %}
3.2循环
我们可以在 Jinja2 中使⽤循环来迭代任何列表或者⽣成器函数
{% for post in posts %}
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.text | safe }}</p>
</div>
{% endfor %}
循环和if语句可以组合使⽤,以模拟 Python 循环中的 continue 功能,下⾯这个循环将只会渲染post.text不为None的那些post:
{% for post in posts if post.text %}
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.text | safe }}</p>
</div>
{% endfor %}
3.3实例
py代码
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
my_list = [
{
"id": 1,
"value": "我爱⼯作"
},
{
"id": 2,
"value": "⼯作使⼈快乐"
},
{
"id": 3,
"value": "沉迷于⼯作⽆法⾃拔"
},
{
"id": 4,
"value": "⽇渐消瘦"
},
{
"id": 5,
"value": "以梦为⻢,越骑越傻"
}
]
return render_template('if_for.html',my_list=my_list)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{% for item in my_list if item.id !=5 %}
{% if loop.index == 1 %}
<li style="background-color: orange">{{ item.value }}</li>
{% elif loop.index == 2 %}
<li style="background-color: green">{{ item.value }}</li>
{% elif loop.index == 3 %}
<li style="background-color: red">{{ item.value }}</li>
{% else %}
<li style="background-color: indigo">{{ item.value }}</li>
{% endif %}
{% endfor %}
</ul>
</body>
</html>
4前端渲染VS后端渲染
后端渲染:使用正则表达式在服务端直接对html进行替换
优点:有利于SEO(搜素引擎优化)
缺点:前后端不分离,开发效率很低
前端渲染:前端使用js对html内容进行更改,一般是发起ajax异步请求,后端以json形式返回数据,再进行拼接html
优点:前后端分离,开发效率高,局部动态刷新
缺点:不利于SEO
5模板继承
- 模板继承是为了重⽤模板中的公共内容。⼀般Web开发中,继承主要使⽤在⽹站的顶部菜单、底部。
- 这些内容可以定义在⽗模板中,⼦模板直接继承,⽽不需要重复书写。
标签定义的内容
{% block top %} {% endblock %}
- 相当于在⽗模板中挖个坑,当⼦模板继承⽗模板时,可以进⾏填充。
⼦模板使⽤ extends 指令声明这个模板继承⾃哪个模板 - ⽗模板中定义的块在⼦模板中被重新定义,在⼦模板中调⽤⽗模板的内容可以使⽤super()
⽗模板
base.html
{% block top %}
顶部菜单
{% endblock top %}
{% block content %}
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
⼦模板
extends指令声明这个模板继承⾃哪
{% extends 'base.html' %}
{% block content %}
需要填充的内容
{% endblock content %}
模板继承使⽤时注意点:
- 不⽀持多继承
- 为了便于阅读,在⼦模板中使⽤extends时,尽量写在模板的第⼀⾏。
- 不能在⼀个模板⽂件中定义多个相同名字的block标签。
- 当在⻚⾯中使⽤多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好
实例
py代码
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('content.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
父类模板(content_base.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>顶部内容</h1>
{% block content_block %}
我是父类
{% endblock %}
<h1>底部内容</h1>
</body>
</html>
子类模板(content.html)
{% extends 'content_base.html' %}
{% block content_block %}
<li>势如破⽵!⼈⺠币再度连闯四道关⼝ 在岸、离岸双双升破6.42</li>
<li>凛冬已⾄,还有多少银⾏⼈在假装⼲银⾏</li>
<li>⼈⺠⽇报:部分城市楼市放松限制引关注,楼市调控不会“拉抽屉”</li>
{% endblock %}
6模板中可以直接使用的变量和函数
你可以在⾃⼰的模板中访问⼀些 Flask 默认内置的函数和对象
config
你可以从模板中直接访问Flask当前的config对象:
{{config.SQLALCHEMY_DATABASE_URI}}
sqlite:///database.db
request
就是flask中代表当前请求的request对象:
{{request.url}}
http://127.0.0.1
session
为Flask的session对象
{{session.new}}
True
g变量
在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出
{{ g.name }}
url_for()
url_for会根据传⼊的路由器函数名,返回该路由对应的URL,在模板中始终使⽤url_for()就可以安全的修
改路由绑定的URL,则不⽐担⼼模板中渲染出错的链接:
{{url_for('home')}}
/
如果我们定义的路由URL是带有参数的,则可以把它们作为关键字参数传⼊url_for(),Flask会把他们填充
进最终⽣成的URL中:
{{ url_for('post', post_id=1)}}
/post/1
get_flashed_messages()
这个函数会返回之前在flask中通过flask()传⼊的消息的列表,flash函数的作⽤很简单,可以把由Python
字符串表示的消息加⼊⼀个消息队列中,再使⽤get_flashed_message()函数取出它们并消费掉:
{%for message in get_flashed_messages()%}
{{message}}
{%endfor%}
实例
py代码
from flask import Flask, session, g, render_template, flash
app = Flask(__name__)
app.secret_key = 'test'
@app.route('/')
def index():
session['name']='ls'
g.age=18
flash('登陆开始')
flash('登陆结束')
return render_template('demo5.html')
@app.route('/home')
def home():
flash('回家')
return 'home'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<label>{{ request.url }}</label><br/>
<label>{{ session.name }}</label><br/>
<label>{{ g.age }}</label><br/>
<label>{{ url_for('index') }}</label><br/>
{% for message in get_flashed_messages() %}
<script>alert("{{ message }}")</script>
{% endfor %}
</body>
</html>
7CSRF
CSRF 全拼为 Cross Site Request Forgery ,译为跨站请求伪造。
CSRF 指攻击者盗⽤了你的身份,以你的名义发送恶意请求。
包括:以你名义发送邮件,发消息,盗取你的账号,甚⾄于购买商品,虚拟货币转账…
造成的问题:个⼈隐私泄露以及财产安全。
防⽌ CSRF 攻击
步骤
- 在客户端向后端请求界⾯数据的时候,后端会往响应中的 cookie 中设置 csrf_token 的值
- 在 Form 表单中添加⼀个隐藏的的字段,值也是 csrf_token
- 在⽤户点击提交的时候,会带上这两个值向后台发起请求
- 后端接受到请求,以会以下⼏件事件:
从 cookie中取出 csrf_token
从 表单数据中取出来隐藏的 csrf_token 的值进⾏对⽐ - 如果⽐较之后两值⼀样,那么代表是正常的请求,如果没取到或者⽐较不⼀样,代表不是正常的请求,不执⾏下⼀步操作
实例
A网站
py代码
import base64
import os
from flask import Flask, request, render_template, url_for, redirect
from flask_wtf import CSRFProtect
app = Flask(__name__)
# 设置随机密钥
s_key = base64.b64encode(os.urandom(48)).decode()
app.secret_key = s_key
# 设置csrf防护
CSRFProtect(app)
@app.route('/',methods=['GET','POST'])
def index():
if request.method == 'GET':
return render_template('login.html')
username = request.form.get('username')
password = request.form.get('password')
print(username,password)
if username == 'ww' and password == '123':
return redirect(url_for('transfer'))
else:
return '用户名或密码错误'
@app.route('/transfer',methods=['GET','POST'])
def transfer():
if request.method == 'GET':
return render_template('demo2.html')
to_account = request.form.get('to_account')
money = request.form.get('money')
return '成功转了%s给%s' % (money,to_account)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
html登陆代码
<body>
<h1>欢迎来到A网站</h1>
<form action="" method="post">
<div>
<label>用户名:</label>
<input type="text" name="username" placeholder="请输入用户名">
<label>密码:</label>
<input type="password" name="password" placeholder="请输入密码">
</div>
<input type="submit" value="登陆"><br/>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
</form>
</body>
</html>
html转账代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>欢迎来到A网站</h1>
<form action="" method="post">
<div>
<label>对方账户名:</label>
<input type="text" name="to_account" placeholder="请输入对方账户">
<label>金额:</label>
<input type="text" name="money" placeholder="请输入金额">
</div>
<input type="submit" value="转帐"><br/>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
</form>
</body>
</html>
B网站
py代码
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('demo3.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
html优惠卷代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>欢迎来到B网站</h1>
<form action="http://127.0.0.1:8080/transfer" method="post">
<input type="hidden" name="to_account" value="haha">
<input type="hidden" name="money" value="8888888">
<input type="submit" value="领取优惠卷">
</form>
</body>
</html>