Flask框架介绍
- 作者: Armin ronacher
- 诞生时间: 2010年
- 组成: werkzueg + jinja2
- werkzueg: 一个包含WSGI、路由、调试的工具集;专门用来处理请求相关的内容的,比如: 地址。
- jinja2: 默认的模板引擎,用来做页面渲染处理的。
- 额外的扩展包: 可以处理数据库的链接, 站点管理,flask-cache做缓存处理。
Hello Flask
路由和视图
与路由有关的知识点
app.url_map:查看哪些路由(地址)可以访问
- 格式: 使用app.url_map, 返回的是app装饰的所有的路由和路径之间的映射关系
- 注意点: 只有被app.url_map包含进来的路由(地址)才能被访问
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "This is index."
# 判断是否直接使用当前模块运行程序
if __name__ == '__main__':
print(app.url_map)
app.run()
app.run()参数讲解
- 参数1: host, 如果我们不指定,默认值是127.0.0.1
- 参数2: port, 如果我们不指定,默认的值是5000
- 参数3: debug,调试模式,如果不指定,默认的值是False
- 如果设置为True,两个好处
- 1.如果在运行的过程中,直接改动代码了,不需要重新启动程序,只需要ctrl+s保存即可部署程序
- 2.如果程序报错了,会有友情提示
通常会将debug设置为true,如:
app.run(debug=True)
动态URL:在访问路由的时候指定参数
- 格式: @app.route("/<类型:变量名>")
- 常见的参数类型
- 整数 int
- 小数 float
- 字符串 path(未指定类型时,默认为path)
自定义参数类型(自定义转换器)
- 背景:
- 如果系统提供的int、float等参数类型满足不了需求的时候,我们需要自定义。之所以int、float、path可以接收不同的数据类型,是因为系统已经提供好对应的转换器了。
- 自定义转换器格式
- 1.定义一个继承自BaseConverter的类
- 2.重写init()(类似于构造函数)
- 3.初始化父类成员变量和子类自己的规则
- 4.将转换器类添加到系统默认的转换器列表中
from flask import Flask
from werkzeug.routing import BaseConverter
app = Flask(__name__)
# 1 定义一个继承自BaseConverter的类
class MyRegexConverter(BaseConverter):
# 事实上,只写regex正则表达式也可以算作自定义,但这样不够灵活。
# regex = "\d{3}"
# 2 重写init()(类似与构造函数)
def __init__(self, map, regex):
# 3 初始化父类成员变量和子类自己的规则
super(MyRegexConverter, self).__init__(map)
self.regex = regex # self.regex是实例类的regex,通过参数regex确定下来
# 4 将转换器类添加到系统默认的转换器列表中
app.url_map.converters['re'] = MyRegexConverter
# 可以打印输出所有的转换器,包括系统定义的和自己定义的
print(app.url_map.converters)
# 接收3位参数
@app.route('/<re("\d{3}"):number>') # 使用了自定义参数类型re,并传入规则
def test(number):
return "三位数:%s" % number
# 接收4位参数
@app.route('/<re("\d{4}"):number>') # 使用了自定义参数类型re,并传入规则
def test1(number):
return "四位数:%s" % number
# 接收一个手机号
@app.route('/<re("1[3-9]\d{9}"):tel>') # 使用了自定义参数类型re,并传入规则
def get_phone_number(tel):
return "手机号:%s" % tel
if __name__ == '__main__':
app.run(debug=True)
- ctrl+r :批量替换
- 引号问题:
@app.route('/<re("\d{4}"):number>')
该语句最外层为单引号,内层为双引号,不能单引号内还是单引号。只能单引号内为双引号,或双引号内为单引号。
给路由增加其他的访问方式
- 格式: @app.route(‘路径’,methods=[‘请求方式1’,‘请求方式2’,…])
- 常见的请求方式:
- GET, POST, PUT, DELETE
- 注意点: 如果不指定请求方式,那么默认支持的是GET
from flask import Flask
app = Flask(__name__)
# 接收一个手机号
@app.route('/', methods=["post"]) # 使用了自定义参数类型re,并传入规则
def test():
return "test"
if __name__ == '__main__':
app.run(debug=True)
Postman应用软件用来模拟post请求。
与响应有关的知识点
所谓“响应”即视图函数的返回值,返回什么就是对请求者作出了什么回应。
返回字符串
- 1.直接返回响应体数据
- return ‘字符串’
- 2.直接返回响应体数据+状态码
- return ‘字符串’, 状态码
- 3.直接返回响应体数据+状态码+响应头信息
- return ‘字符串’, 状态码, {‘key’:‘value’}
通过jsonify返回json数据
- 格式: jsonify(dict)
- 简化格式: jsonify(key=value,key2=value2)
@app.route('/')
def test():
# 1 定义一個字典
dict = {
"name":"Jenny",
"age":18
}
# 2 返回json数据
# return jsonify(dict)
# 3 返回json数据-简化格式
return jsonify(name="Lisa", age=18)
返回一个重定向
redirect
- 格式: redirect(‘地址’), 地址既可以是本地服务器的地址,也可以是其他服务器的地址
- 注意点: 重定向的代号是302
- 特点: 重定向是两次请求
@app.route('/')
def taobao():
return redirect("http://taobao.com")
url_for
- 解释: 称为反向解析——根据路由函数名转向该视图函数(如果该视图函数的路由有变量,则需要传入),即返回的是视图函数对应的路由地址
- 格式: url_for(‘视图函数’,key=value)
key是视图函数中的参数名,value是传入的实参。
- 注意点: url_for经常配合redirect使用,传递参数
@app.route('/jingdong')
def jingdong():
# 下面语句打印出来的结果就是/taobao/0
# print(url_for("taobao", token = 0))
# 因此,可以将其作为redirect的参数
response = redirect(url_for("taobao", token=0))
return response
@app.route('/yamaxun')
def yamaxun():
response = redirect(url_for("taobao", token=1))
return response
@app.route('/taobao/<int:token>')
def taobao(token):
if token == 0:
return "欢迎京东用户,您将享受7折优惠"
elif token == 1:
return "欢迎亚马逊用户,您将享受9折优惠"
else:
return "欢迎"
abort & errorhandler
- 使用场景: 当访问服务器资源的时候,如果找不到该资源,可以报出异常信息,可以自定义此异常信息的内容;使用errorhandler捕捉。
- 格式: abort(错误代号)
- 格式: @app.errorhandler(错误代号)
@app.route('/')
def test():
# abort(404) # 直接使用abort()使直接产生404错误
return "hello"
# 只要请求了未设置的路由地址,就会产生此效果
@app.errorhandler(404)
def page_not_found(e):
print(e)
return "页面找不到o(╥﹏╥)o"
其他
request对象
-
介绍:request就是flask中代表当前请求的request对象,其中一个请求上下文变量(理解成全局变量,在视图函数中直接使用时可以取到当前本次请求)。
-
参数:
- request.data: 获取的是非表单(ajax)以post提交的数据
- request.form: 获取的是表单以post方式提交的数据
- request.args: 获取的是问号后面的查询参数。如
www.taobao.com?name=Lisa&age=18
。 - request.method: 获取的请求方式
- request.url: 获取请求的地址
- request.files: 获取的是input标签中type类型为file的文件
@app.route('/')
def test():
print(request.method)
print(request.url)
print(request.args)
# 使用字典的取值方法取到对应的值。但这种方式太死板,倘若并未出现?及后面应该有的内容,那么就会报错:Bad Request
# print(request.args['name'])
# 推荐使用以下方法。即使未出现?及后面应该有的内容,也不会报错,将返回None
print(request.args.get("name"))
# 如果获取不到age,则赋默认值0
print(request.args.get("age", 0))
return "hello"
输入http://127.0.0.1:5000/
后控制台的打印结果为:
且页面可以正常显示:
如果print(request.args['name'])
,则结果为:
输入http://127.0.0.1:5000/?name=Lisa&age=18
:
加载app程序运行参数
app程序运行所有的参数信息存储在app.config中。打印结果:<Config {‘ENV’: ‘production’, ‘DEBUG’: True, ‘TESTING’: False, ‘PROPAGATE_EXCEPTIONS’: None, ‘PRESERVE_CONTEXT_ON_EXCEPTION’: None, ‘SECRET_KEY’: None, ‘PERMANENT_SESSION_LIFETIME’: datetime.timedelta(days=31), ‘USE_X_SENDFILE’: False, ‘SERVER_NAME’: None, ‘APPLICATION_ROOT’: ‘/’, ‘SESSION_COOKIE_NAME’: ‘session’, ‘SESSION_COOKIE_DOMAIN’: None, ‘SESSION_COOKIE_PATH’: None, ‘SESSION_COOKIE_HTTPONLY’: True, ‘SESSION_COOKIE_SECURE’: False, ‘SESSION_COOKIE_SAMESITE’: None, ‘SESSION_REFRESH_EACH_REQUEST’: True, ‘MAX_CONTENT_LENGTH’: None, ‘SEND_FILE_MAX_AGE_DEFAULT’: datetime.timedelta(seconds=43200), ‘TRAP_BAD_REQUEST_ERRORS’: None, ‘TRAP_HTTP_EXCEPTIONS’: False, ‘EXPLAIN_TEMPLATE_LOADING’: False, ‘PREFERRED_URL_SCHEME’: ‘http’, ‘JSON_AS_ASCII’: True, ‘JSON_SORT_KEYS’: True, ‘JSONIFY_PRETTYPRINT_REGULAR’: False, ‘JSONIFY_MIMETYPE’: ‘application/json’, ‘TEMPLATES_AUTO_RELOAD’: None, ‘MAX_COOKIE_SIZE’: 4093}>
- 1.从配置类(对象)中加载
- app.config.from_object(obj)
- 2.从配置文件中加载
- app.config.from_pyfile(file)
- 3.从环境变量中加载
- app.config.from_envvar(环境变量)
# 1 从配置类(对象)中加载
class MyConfig(object):
# 配置调试模式
DEBUG = True
app.config.from_object(MyConfig)
# 先创建Config.ini文件,文件内容此处只写了 DEBUG = True
# 2 从配置文件中加载
app.config.from_pyfile("Config.ini")
请求钩子
-
解释: 访问正常视图函数时顺带执行的方法。在客户端和服务器交互的过程中,有些准备工作需要处理,比如在请求开始时,建立数据库连接、根据需求进行权限校验,在请求结束时,指定数据的交互格式。为了让每个视图函数避免编写重复功能的代码,flask体用了通用设施的功能,即请求钩子。
-
常见的请求钩子有四种:
-
1.before_first_request: 在处理第一个请求前执行
-
[记住] 2.before_request: 在每次请求前执行,在该装饰函数中,一旦return,视图函数不再执行
-
[记住] 3.after_request: 如果没有抛出错误,在每次请求后执行。接受一个参数:视图函数作出的响应。 在此函数中可以对响应值,在返回之前做最后一步处理后再返回。
-
4.teardown_request: 在每次请求后执行。接受一个参数: 用来接收错误信息。
-
# before_first_request装饰的函数在第一次访问的时候执行,里面适合做初始化操作,比如,io文件读写
@app.before_first_request
def before_first_request():
print("before_first_request") # step1
# before_request在每次请求前都执行,适合对请求参数做校验、访问统计
@app.before_request
def before_request():
print("before_request") # step2
# after_request在视图函数执行之后执行,适合对视图函数的返回值做统一处理,比如: 返回统一的json数据格式
@app.after_request
def after_request(resp): # resp就是相应类Response的实例对象,其包含了很多内容,这些内容就是与视图函数有关的
print("after_request") # step4
# 对视图函数的返回值做统一处理,比如: 返回统一的json数据格式
resp.headers["Content-Type"] = "application/json"
return resp
# teardown_request在请求销毁之后执行,适合做异常信息统计
@app.teardown_request
def teardown_request(e):
print(e)
print("teardown_request") # step5
@app.route('/')
def test():
# print(app.config)
return "hello" # step3
利用断点调试法,可以看到以上几个print语句的执行流程,已标注在上面(step1,2,3,4,5)。
视图内容和模板
状态保持
实现状态保持(保持服务器和浏览器交互的状态)的主要方式:
- 在客户端存储信息——Cookie
- 在服务器端存储信息——Session
Cookie
- 解释: 由服务器设置,存储在浏览器。
- 作用: 用来做广告推送
- cookie的设置和获取
- 设置cookie: response.set_cookie(key,value,max_age)
- max_age: 表示cookie在浏览器的存储时间,单位是秒
- 获取cookie: request.cookies.get(“key”)
- 设置cookie: response.set_cookie(key,value,max_age)
from flask import Flask, make_response, request
app = Flask(__name__)
@app.route('/set_cookie')
def set_cookie():
# 调用make_response(),获取响应体对象
response = make_response("set cookie")
# 设置cookie
response.set_cookie("computer", "mac")
response.set_cookie("price", "4000", 10) # cookie存在的时间设置为10秒
return response
# 获取cookie
@app.route('/get_cookie')
def get_cookie():
name = request.cookies.get("computer")
price = request.cookies.get("price")
return "name is %s, price is %s" % (name, price)
if __name__ == "__main__":
app.run(debug=True)
Session
- 解释: 里面存储的是敏感信息(比如身份证、登陆信息),由服务器设置,并存储在服务器。(存储在浏览器就危险了,都能看到了)
- 作用: 用来做用户的登陆状态保持
- session的设置和获取
- 设置session: sessioin[key] = value
- 获取session: value = session.get(key)
- 注意点:
- 1.session的存储依赖于cookie
- 2.存储在cookie中的sessionID需要加密,需要设置秘钥(SECRET_KEY)(只要需要把变量存储到session中,就需要设置秘钥)
from flask import Flask, make_response, request, session
app = Flask(__name__)
# 设置SECRET_KEY
app.config["SECRET_KEY"] = "argbshnjyrikymjfnhtbrge"
# 设置session
@app.route('/set_session/<name>')
def set_session(name):
session["name"] = name
return "set session"
# 获取session
@app.route('/get_session')
def get_session():
value = session.get("name")
return "name is %s" % value
if __name__ == "__main__":
app.run(debug=True)
上下文
- 解释: 就是一个容器,保存了flask程序运行过程中的一些信息。
请求上下文(request context):
在flask中,可以直接在视图函数中使用request这个对象获取相关数据,而request就是请求上下文的对象,保存了当前本次请求的相关数据,如地址、cookie等。请求上下文对象有两种:
- request: 封装的是HTTP请求相关的数据。如,user = request.args.get(‘user’)。
- session: 封装的是和用户相关的敏感信息。如,session[‘name’] = user.id或session.get(‘name’)。
应用上下文(application context)
应用上下文有以下两种:
- current_app: 是app的一个代理对象,可以通过它获取app身上设置的各种属性,主要用在模块化开发中
- g: 一个局部的全局变量,主要用在装饰器中,可以通过它传递一些数据。g保存的是当前请求的全局变量。
假设你来到一家自助餐餐厅,餐厅老板就是app,餐厅服务员就是curren_app,老板雇佣服务员,让其作为自己的代理对象,给客人服务。你需要拿起盘子选菜,“你”作为一个客户端,这个盘子就是g对象。当你吃完后离开餐厅,盘子就需要进行回收,服务员也要清除自己的当前服务状态,开始为其他人服务。
flask_script扩展
flask_XX或Flask-XX都是flaks的扩展。
-
解释: 属于flaks的扩展
-
作用: 用来动态运行程序,配合flask_migrate做数据库迁移
-
使用步骤:
-
1.安装
- pip install flask_script
-
2.导入Manager类
-
3.创建manager对象,管理app
-
4.使用manager启动程序
- 启动命令: python xxx.py runserver -h -p -d
- -h:即host,表示IP地址
- -p:端口号
- -d:调试模式
- 启动命令: python xxx.py runserver -h -p -d
-
from flask import Flask, make_response, request, session
from flask_script import Manager
app = Flask(__name__)
# 1 创建对象manager,管理app
manager = Manager(app)
@app.route('/')
def test():
return "hello"
if __name__ == "__main__":
# 2 使用manager启动程序
manager.run()
右键Run无法启动,但并非只能用上面的命令行方式启动。只需做以下修改:
jinja2模板概念&render_template函数
- 解释: jinja2模板用来展示数据的HTML页面,这个过程通常称为渲染。render_template函数属于jinja2的模板函数。
- 好处:
- 1.以后的视图函数,只负责业务逻辑的处理,比如: 数据库的增删改查
- 2.以后数据的展示,全部都由jinja2的模板负责
- 使用格式:
- response = render_template(‘模板文件’)
@app.route('/')
def test():
# t1.html存放在templates文件夹下,必须命名为templates,因为Flask类已经这样设置好了
response = render_template('t1.html')
return response
一个设置:
右键templates文件夹,选择
模板语法
-
解释: 在模板中获取视图函数的变量
-
模板语法的种类
-
获取变量:
{{ 变量 }}
-
分支格式:
{% if 条件 %} 语句1 {% else %} 语句2 {% endif %}
-
循环语句格式:
{% for 变量 in 容器 %} {% endfor %}
-
注释:
{# 这里是注释的内容 #}
-
示例
@app.route('/')
def test():
# 1 定义各种类型的变量
number = 10
str = "Lisa"
tuple = (1, 2, 3, 4, 5)
list = [6, 7, 8, 9, 10]
dict = {
"name": "Lisa",
"age": 18
}
# 2 携带变量到模板中展示,=前表示模板中的变量名,=后表示此处(上面设置的)的变量名
return render_template("t1.html", number=number, str=str, tuple=tuple, list=list, dict=dict)
<body>
<h1>获取各种变量的值</h1>
<h2>整数:{{number}}</h2>
<h2>字符串:{{str}}</h2>
<h2>元组:{{tuple}}</h2>
<h2>元组-获取具体值:方法①tuple[0]={{tuple[0]}} 方法②tuple.1={{ tuple.1 }}</h2>
<h2>列表:{{list}}</h2>
<h2>列表-获取具体值:方法①list[0]={{list[0]}} 方法②list.1={{ list.1 }}</h2>
<h2>字典:{{dict}}</h2>
{# dict["age"]表示字典里age属性,而dict[age]中的age代表的是定义的一个age的变量 #}
<h2>字典-获取具体值:方法①dict.name={{dict.name}} 方法②dict["age"]={{ dict["age"] }}</h2>
<h1>1.遍历元祖中的偶数</h1>
{% for item in tuple %}
{% if item %2 == 0 %}
<h3>{{ item }}</h3>
{% endif %}
{% endfor %}
<h1>2.遍历字典</h1>
{% for key in dict %}
{# dict.key那么这个key会当成字典中的一个键, dict[key],那么这个key当成一个变量 #}
<h3>{{ key }} = {{ dict[key] }}</h3>
{% endfor %}
</body>
模板自带过滤器
系统字符串过滤器[理解]
- 解释: 过滤器,用来过滤想要的数据
- 格式: {{ 字符串 | 字符串过滤器 }}
- 常见的字符串过滤器有:
- title: 将每个单词的首字母都大写
- lower: 将每个单词都小写
- upper: 将每个单词都大写
- reverse: 反转
- …
示例
<body>
{# 使用格式:{{ 字符串 | 字符串过滤器 }}#}
1.safe:禁用转义,让标签生效,否则就直接打印的是字符串<em>hello</em>了
<p>{{ '<em>hello</em>' | safe }}</p>
2.capitalize:把字符串变量的首字母转成大写,其余字母转小写
<p>{{ 'hello PYTHON' | capitalize }}</p>
3.lower:把字符串的所有字符都转成小写(中文没有大小写)
<p>{{ 'HELLO PYthON 你好' | lower }}</p>
4.upper:把字符串的所有字符都转成大写
<p>{{ 'hello python 你好' | upper }}</p>
5.title:把字符串中的每个单词的首字母都转成大写
<p>{{ 'hello world python java' | title }}</p>
6.reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>
<p>{{ '我爱你' | reverse }}</p>
7.format:格式化输出
<p>{{ '%s is %d' | format('age',17) }}</p>
8.striptags:渲染之前就把值中所有的HTML标签都删掉,之后才进行渲染处理
<p>{{ '<em>hello</em>' | striptags }}</p>
</body>
系统列表过滤器[理解]
- 解释: 过滤器,用来过滤想要的数据
- 格式: {{ 列表 | 列表过滤器 }}
- 常见的列表过滤器有:
- first: 获取列表第一个元素
- last: 最后一个元素
- sum: 列表和
- length: 列表长度
- sort: 列表排序,默认为升序
- …
自定义过滤器[掌握]
-
解释: 当系统提供的过滤器满足不了需求的时候,需要自定义
-
自定义过滤器有两种格式:
-
1.先定义好函数,再将函数添加到系统默认的过滤器列表中
- def 函数名: pass
- app.add_template_filter(函数名,‘过滤器名’)
-
2.定义函数的时候,直接使用系统过滤器进行装饰
@app.template_filter('过滤器名') def 函数名(): pass
-
示例
from flask import Flask, make_response, request, session, render_template
app = Flask(__name__)
# -----------示例①:求列表中所有偶数的和---------
# 1.定义好函数
def get_even_num(list):
sum = 0
for i in list:
if i % 2 == 0:
sum += i
return sum
# 2. 将函数添加到系统默认的过滤器列表中【app.add_template_filter(函数名,‘过滤器名’)】
app.add_template_filter(get_even_num, "even_num")
# ------------示例②:将列表中的元素反转----------
# 定义函数的时候,直接使用系统过滤器进行装饰
@app.template_filter("reverse")
def list_reverse(list):
list.reverse()
return list
@app.route('/')
def test():
return render_template("t1.html")
if __name__ == "__main__":
app.run(debug=True)
<body>
<h2>原列表:{{ [1, 2, 7, 4, 5] }}</h2>
<h2>原列表中偶数之和:{{ [1, 2, 7, 4, 5] | even_num }}</h2>
{# even_num是我们自定义的过滤器的名称,这样做实际是将[1, 2, 3, 4, 5]当做实参传递给even_num对应的函数#}
<h2>反转列表:{{ [1, 2, 7, 4, 5] | reverse }}</h2>
<h2>降序列表:{{ [1, 2, 7, 4, 5] | sort | reverse}}</h2>
</body>
模板代码复用
宏
-
解释: 相当于python中的函数,定义好一段功能,在需要的时候进行调用即可
-
定义格式:
{% macro 宏名(参数) %} {% endmacro %}
-
使用格式:
// 使用当前文件定义好的宏 {{ 宏名(参数) }} //使用其他文件定义好的宏 {% import '文件' as 别名%} {{ 别名.宏名(参数) }}
继承 [掌握]
-
解释: 一个子模板继承自父模板
-
作用: 共性抽取,代码复用
-
父模板
- 1.所有子类都具有的相同的内容的, 在父模板中直接写死
- 2.每个子类的模板中不一样的内容,使用block模板定义好
-
子模板
- 1.根据子类自己的需求,去重写父类中的block对应的内容
- 2.如果重写之后,还想保留父类的内容,那么使用{{super()}}
- 3.继承格式: {% extends ‘父文件名’%}, 写在页面的顶部
-
注意点:
-
定义block的格式
{% block 名称 %} {% endblock %}
-
示例
包含
-
解释: 在一个文件中完全拥有另外一个文件,不够灵活,没法扩展
-
格式:
方式一:
{% include '文件' %}
方式二:
{% include '文件' ignore missing %}
{# ignore missing的作用:如果包含的文件不存在,也不会报错#}
模板使用练习
loop.index0()
loop.index()
from flask import Flask, make_response, request, session, render_template
app = Flask(__name__)
@app.route('/')
def hello_world():
# 1.定义5条数据
my_list = [
{
"id": 1,
"value": "我爱工作"
},
{
"id": 2,
"value": "工作使人快乐"
},
{
"id": 3,
"value": "沉迷于工作无法自拔"
},
{
"id": 4,
"value": "日渐消瘦"
},
{
"id": 5,
"value": "以梦为马,越骑越傻"
}
]
# 2.在模板中显示4条
return render_template("t1.html", list=my_list)
if __name__ == "__main__":
app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{# 如果dict.id 不等于5才遍历 #}
{% for dict in list if dict.id !=5 %}
{# 方式一 #}
{# {% if dict.id == 1 %}#}
{# <li style="background: yellow;"> {{ dict.value }} </li>#}
{# {% elif dict.id == 2 %}#}
{# <li style="background: green;"> {{ dict.value }} </li>#}
{# {% elif dict.id == 3 %}#}
{# <li style="background: red;"> {{ dict.value }} </li>#}
{# {% else %}#}
{# <li style="background: purple;"> {{ dict.value }} </li>#}
{# {% endif %}#}
{# 遍历的时候可以获取到从0开始的索引 #}
{# <h3>{{ loop.index0 }}</h3>#}
{# 遍历的时候可以获取到从1开始的索引 #}
{# <h3>{{ loop.index }}</h3>#}
{# 方式二 #}
{% if loop.index == 1 %}
<li style="background: yellow;"> {{ dict.value }} </li>
{% elif loop.index == 2 %}
<li style="background: green;"> {{ dict.value }} </li>
{% elif loop.index == 3 %}
<li style="background: red;"> {{ dict.value }} </li>
{% else %}
<li style="background: purple;"> {{ dict.value }} </li>
{% endif %}
</ul>
</body>
</html>
模板特有变量
- 解释: 模板特有变量指的是不需要通过python程序传递就可以直接使用的变量
- 常见的特有变量如下:
- config: 就是flask中的app.config, 表示应用程序中的所有配置信息
- request: 表示请求上下文对象,封装的是请求相关的数据
- g: 局部的全局变量(了解)
- url_for(): 反解析,通过函数的名字,解析到视图函数的路径
- get_flashed_messsages(): 用来消耗flash方法中存储的消息。
- 场景: 登陆出错,可以显示
- 注意点:
- 1.使用flash存储消息的时候需要设置SECRET_KEY
- 2.因为flash内部的消息存储,依赖于了session(查看flash方法的源代码可以看到相关内容)
csrf攻击
简介
攻击流程
-
示意图展示
-
代码演示
(略)
解决攻击
- 示意图
- 在cookie增加一个csrf_token
- 在表单中增加一个csrf_token
- 校验: 取出cookie和表单中的csrf_token比较如果二者一致那么是正常请求
-
CSRFProtect解决攻击
- 使用流程:
- 1.安装扩展包:pip install flask-wtf
- 2.导入包:from flask_wtf.csrf import CSRFProtect
- 3.创建CSRFProtect对象,保护app对象
- 4.设置SECRET_KEY便于csrf_token加密
- 5.在表单中设置csrf_token隐藏字段即可
- 注意点: .CSRFProtect一旦保护了app之后, 会对’POST’, ‘PUT’, ‘PATCH’, 'DELETE’做校验.
- 使用流程:
例子:用户注册案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{# 只要使用了CSEFProtect类保护app,就可以直接使用csrf_token()获取csrf_token值 #}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
用户名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
确认密码:<input type="text" name="repassword"><br>
<input type="submit" value="注册">
</form>
</body>
</html>
from flask import Flask, make_response, request, session, render_template
from flask_wtf import CSRFProtect
app = Flask(__name__)
# 设置秘钥
app.config["SECRET_KEY"] = "fesfva"
# 保护app
CSRFProtect(app)
@app.route('/', methods=["GET", "POST"])
def hello_world():
# 1 如果是GET请求,则直接返回注册页面
if request.method == "GET":
return render_template("t1.html")
# 2 如果是POST请求,说明点击了“注册”,故处理注册业务
else:
# 2.1 获取参数
username = request.form.get("username")
password = request.form.get("password")
repassword = request.form.get("repassword")
# 2.2 校验参数,判断是否为空
if not all([username, password, repassword]):
return "参数填写不全!"
# 2.3 两次密码是否一致
if password != repassword:
return "两次密码输入不一致!"
# 2.4 返回注册成功
return "注册成功!"
if __name__ == "__main__":
app.run(debug=True)