1. jinja2简介
- Jinja2:是 Python 下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。
- 模板语言:是一种被设计来自动生成文档的简单文本格式,在模板语言中,一般都会把一些变量传给模板,替换模板的特定位置上预先定义好的占位变量名。
- {{}} 来表示变量名,这种 {{}} 语法叫做变量代码块
- 用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句
- 使用 {# #} 进行注释,注释的内容不会在html中被渲染出来
2. 过滤器
- 过滤器的使用方式为:变量名 | 过滤器。
{{variable | filter_name(*args)}}
- 如果没有任何参数传给过滤器,则可以把括号省略掉
{{variable | filter_name}}
常见内建过滤器
字符串操作
safe:禁用转义
<p>{{ '<em>hello</em>' | safe }}</p>
capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{{ 'hello' | capitalize }}</p>
lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>
upper:把值转成大写
<p>{{ 'hello' | upper }}</p>
title:把值中的每个单词的首字母都转成大写
<p>{{ 'hello' | title }}</p>
reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>
format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>
striptags:渲染之前把值中所有的HTML标签都删掉
<p>{{ '<em>hello</em>' | striptags }}</p>
truncate: 字符串截断
<p>{{ 'hello every one' | truncate(9)}}</p>
列表操作
first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>
last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>
length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>
sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>
sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>
3. 控制代码块
控制代码块主要包含两个:
- if/else if /else / endif
- for / endfor if语句
Jinja2 语法中的if语句跟 Python 中的 if 语句相似,后面的布尔值或返回布尔值的表达式将决定代码中的哪个流程会被执行:
{%if user.is_logged_in() %}
<a href='/logout'>Logout</a>
{% else %}
<a href='/login'>Login</a>
{% endif %}
# 我们可以在 Jinja2 中使用循环来迭代任何列表或者生成器函数
{% for post in posts %}
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.text | safe }}</p>
</div>
{% endfor %}
复制代码
- 在一个 for 循环块中你可以访问这些特殊的变量:
4. 模板代码复用
4.1 宏
- 把它看作 Jinja2 中的一个函数,它会返回一个模板或者 HTML 字符串
- 为了避免反复地编写同样的模板代码,出现代码冗余,可以把他们写成函数以进行重用
- 需要在多处重复使用的模板代码片段可以写入单独的文件,再包含在所有模板中,以避免重复
# 定义宏
{% macro input(name,value='',type='text') %}
<input type="{{type}}" name="{{name}}"
value="{{value}}" class="form-control">
{% endmacro %}
# 调用宏
{{ input('name' value='zs')}}
# 输出
<input type="text" name="name"
value="zs" class="form-control">
复制代码
4.2 继承
# 父模板
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嵌套时,阅读性更好。
4.3 包含
代码重用的功能,包含(Include)。它的功能是将另一个模板整个加载到当前模板中,并直接渲染。
# include的使用
{% include 'hello.html' %}
包含在使用时,如果包含的模板文件不存在时,程序会抛出TemplateNotFound异常,可以加上 ignore missing 关键字。如果包含的模板文件不存在,会忽略这条include语句。
# include 的使用加上关键字ignore missing
{% include 'hello.html' ignore missing %}
复制代码
4.4 小结
- 宏(Macro)、继承(Block)、包含(include)均能实现代码的复用。
- 继承(Block)的本质是代码替换,一般用来实现多个页面中重复不变的区域。
- 宏(Macro)的功能类似函数,可以传入参数,需要定义、调用。
- 包含(include)是直接将目标模板文件整个渲染出来。
5. 特有函数和变量
- 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%}
复制代码
6. Flask-WTF表单
-
使用 Flask-WTF 实现表单
-
配置参数,关闭 CSRF 校验
app.config['WTF_CSRF_ENABLED'] = False
- CSRF:跨站请求伪造
模板页面:
<form method="post">
{{ form.username.label }} {{ form.username }}<br/>
{{ form.password.label }} {{ form.password }}<br/>
{{ form.password2.label }} {{ form.password2 }}<br/>
{{ form.submit }}
</form>
复制代码
视图函数:
from flask import Flask,render_template, flash
#导入wtf扩展的表单类
from flask_wtf import FlaskForm
#导入自定义表单需要的字段
from wtforms import SubmitField,StringField,PasswordField
#导入wtf扩展提供的表单验证器
from wtforms.validators import DataRequired,EqualTo
app = Flask(__name__)
app.config['SECRET_KEY']='SECRET_KEY'
#自定义表单类,文本字段、密码字段、提交按钮
class RegisterForm(FlaskForm):
username = StringField("用户名:", validators=[DataRequired("请输入用户名")], render_kw={"placeholder": "请输入用户名"})
password = PasswordField("密码:", validators=[DataRequired("请输入密码")])
password2 = PasswordField("确认密码:", validators=[DataRequired("请输入确认密码"), EqualTo("password", "两次密码不一致")])
submit = SubmitField("注册")
#定义根路由视图函数,生成表单对象,获取表单数据,进行表单数据验证
@app.route('/demo2', methods=["get", "post"])
def demo2():
register_form = RegisterForm()
# 验证表单
if register_form.validate_on_submit():
# 如果代码能走到这个地方,那么就代码表单中所有的数据都能验证成功
username = request.form.get("username")
password = request.form.get("password")
password2 = request.form.get("password2")
# 假装做注册操作
print(username, password, password2)
return "success"
else:
if request.method == "POST":
flash("参数有误或者不完整")
return render_template('temp_register.html', form=register_form)
if __name__ == '__main__':
app.run(debug=True)
复制代码
7. CSRF
CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造。
- 即用户访问了一个存在漏洞的网站A,在未退出登录的同时访问B攻击网站,B攻击网站会诱导用户做一些操作,提交这些操作的同时,B网站会携带cookie跳转访问A网站,A网站会认为用户访问,同意B网站的请求,B网站攻击成功。
8. 蓝图
蓝图/Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效
使用蓝图可以分为三个步骤:
# 1.创建一个蓝图对象
admin=Blueprint('admin',__name__)
# 2.在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
@admin.route('/')
def admin_home():
return 'admin_home'
# 3.在应用对象上注册这个蓝图对象
app.register_blueprint(admin,url\_prefix='/admin')
当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数
复制代码
蓝图的url前缀
-
当我们在应用对象上注册一个蓝图时,可以指定一个url_prefix关键字参数(这个参数默认是/)
-
在应用最终的路由表url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的URL规则而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可
-
url_for
url_for('admin.index') # /admin/
9. 单元测试
测试从软件开发过程可以分为:
- 单元测试: 对单独的代码块(例如函数)分别进行测试,以保证它们的正确性
- 集成测试: 对大量的程序单元的协同工作情况做测试
- 系统测试: 同时对整个系统的正确性进行检查,而不是针对独立的片段
断言语句类似于:
raise AssertionError
AssertionError
复制代码
常用的断言方法:
assertEqual 如果两个值相等,则pass
assertNotEqual 如果两个值不相等,则pass
assertTrue 判断bool值为True,则pass
assertFalse 判断bool值为False,则pass
assertIsNone 不存在,则pass
assertIsNotNone 存在,则pass
复制代码