目录
Flask入门
运行方式
URL与函数的映射(动态路由)
PostMan的使用
查询参数的获取
上传文件
其它参数
url_for 函数
响应-重定向
响应-响应内容
响应-自定义响应
Flask模板
模板介绍
模板的使用
模板-传参
模板使用url_for函数
过滤器介绍
Jinja模板自带过滤器
流程控制-选择结构
流程控制-循环结构
导入模板include
set与with标签
静态文件
模板继承
Flask视图
add_url_rule与app.route
类视图
基于调度方法的类视图
装饰器
蓝图介绍
蓝图的目录结构
蓝图中模版文件
蓝图中静态文件
蓝图url_for函数
子域名实现
Flask高级
Flask设置Cookie
Flask中使用Session
Session实战
Local对象
Flask_app上下文
Flask_线程隔离的g对象
Flask_钩子函数介绍
Flask_信号机制
Flask_内置信号
WTForms介绍和基本使用
WTForms常用验证器
WTForms自定义验证器
Flask安全上传文件
利用flask-wtf验证上传的文件
Restful介绍
Restful的基本使用
Flask_RESTful参数验证
Flask_SQLAlchemy
SQLAlchemy的使用
SQLAlchemy操作数据库
ORM模型映射到数据库中
SQLAlchemy对数据的增删改查操作
SQLAlchemy常用数据类型
query函数的使用
filter过滤数据
表关系
ORM关系之一对多
ORM关系之一对一
ORM关系之多对多
排序
limit、offset、slice使用
懒加载
分组group_by和过滤分组having
Flask-SQLAlchemy的使用
Flask-Migrate
Flask项目结构重构
Flask入门
Flask的介绍
Flask 是一款发布于2010年非常流行的 Python Web 框架。
特点
1 微框架、简洁,给开发者提供了很大的扩展性。
2 Flask和相应的插件写得很好,用起来很爽。
3 开发效率非常高,比如使用 SQLAlchemy 的 ORM 操作数据库可以节省开发者大量书写 sql 的时 间。
Flask 的灵活度非常之高,他不会帮你做太多的决策,很多都可以按 照自己的意愿进行更改。
比如:
使用 Flask 开发数据库的时候,具体是使用 SQLAlchemy 还是 MongoEngine,选择权完全掌握在你自己的手中。 Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail ,用户认证 Flask-Login ,数据库 Flask-SQLAlchemy ),都需要 用第三方的扩展来实现。
Flask 没有默认使用的数据库,你可以选择 MySQL ,也可以用 NoSQL 。 其 WSGI 工具箱采用 Werkzeug (路由模块),模板引擎则使用 Jinja2 。这两个也是 Flask 框架的核心。
扩展列表:http://flask.pocoo.org/extensions/
Flask-SQLalchemy:操作数据库;
Flask-script:插入脚本;
Flask-migrate:管理迁移数据库;
Flask-Session:Session存储方式指定;
Flask-WTF:表单;
Flask-Mail:邮件;
Flask-Bable:提供国际化和本地化支持,翻译;
Flask-Login:认证用户状态;
Flask-OpenID:认证;
Flask-RESTful:开发REST API的工具;
Flask-Bootstrap:集成前端Twitter Bootstrap框架;
Flask-Moment:本地化日期和时间;
Flask-Admin:简单而可扩展的管理接口的框架
文档地址
1 中文文档(http://docs.jinkan.org/docs/flask/)
2 英文文档(http://flask.pocoo.org/docs/1.0/)
Flask的安装
pip install flask
第一个Flask程序
#从flask包中导入Flask类 from flask import Flask #创建一个Flask对象 app = Flask(__name__) #@app.route:是一个装饰器 #@app.route('/')就是将url中 / 映射到hello_world设个视图函数上面 #以后你访问我这个网站的 / 目录的时候 会执行hello_world这个函数,然后将这个函数的返回值返回给浏览器 @app.route('/') def hello_world(): return 'hello Flask!' #启动这个WEB服务 if __name__ == '__main__': #默认为5000端口 app.run() #app.run(port=8000)
运行方式
通过对象运行
运行程序时,可以指定运行的主机IP地址,端口
app.run(host="0.0.0.0", port=5000) # 127.0.0.1
参数解释
host
主机IP地址,可以不传 默认localhost
port
端口号,可以不传 默认5000
通过Python运行方式运行
app = Flask(__name__) @app.route("/") def index(): return "hello world" if __name__ == '__main__': app.run()
提示
如果想在同一个局域网下的其他电脑访问自己电脑上的Flask网 站,需要设置 host='0.0.0.0' 才能访问得到
通过Flask自带命令运行
app = Flask(__name__) @app.route("/") def index(): return "hello world" # 程序中不用再写app.run()
$ export FLASK_APP=helloworld $ flask run * Running on http://127.0.0.1:5000/
举例
flask run -h 0.0.0.0 -p 8000
注意 命令行下,可以使用使用简写 可以通过 flask run --help 获取帮助
Debug模式与配置参数加载
控制台倒是给出了错误提示信息,但是我们希望在浏览器也能有相 应的提示信息
开启Debug模式 运行时传递参数
app.run(debug = True)
通过修改配置参数 config
1. app.config.update(DEBUG=True) 2. # app.config['DEBUG'] = True 3. app.run()
通过配置文件设置 config
URL与函数的映射(动态路由)
URL路径参数
比如,有一个请求访问的接口地址为 /users/11001 ,其中 11001 实际上为 具体的请求参数,表明请求 11001 号用户的信息。 此时如何从url中提取出 11001 的数据?
@app.route('/users/<user_id>') def user_info(user_id): print(type(user_id)) return 'hello user{}'.format(user_id)
其中 ,尖括号是固定写法,语法为 , variable 默认的 数据类型是字符串。 如果需要指定类型,则要写成 converter:variable ,其中 converter 就是类型名称,可以有以下几种:
1 string:如果没有指定具体的数据类型,那么默认就是使用 string 数据类型。
2 int:数据类型只能传递 int 类型。
3 float:数据类型只能传递 float 类型。
4 path:数据类型和 string 有点类似,都是可以接收任意的字符串,但是 path 可以接收路径,也就 是说可以包含斜杠。
5 uuid:数据类型只能接收符合 uuid 的字符串。 uuid 是一个全宇宙都唯一的字符串,一般可以用来 作为表的主键。
6 any:数据类型可以在一个 url 中指定多个路径。
将上面的例子以整型匹配数据,可以如下使用:
@app.route('/users/<int:user_id>') def user_info(user_id): print(type(user_id)) return f'正在获取 ID {user_id} 的用户信息' @app.route('/users/<int(min=1):user_id>') def user_info(user_id): print(type(user_id)) return f'hello user {user_id}'
注意 若是数据与设置的类型不能匹配,则会返回 Not Found
PostMan的使用
Postman一款非常流行的API调试工具。其实,开发人员用的更多。 因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。 不过,对于开发过程中去调试接口,Postman确实足够的简单方 便,而且功能强大。 官方网站:https://www.getpostman.com/
安装
1 Postman最早是作用chrome浏览器插件存在的,所以,你可以 到chrome商店搜索下载安装,因为部分原因,所以,大家都会 找别人共享的postman插件文件来安装。
2 Postman提供了独立的安装包,不再依赖于Chrome浏览器了。 同时支持MAC、Windows和Linux,推荐你使用这种方式安装。 https://www.postman.com/downloads/
查询参数的获取
例如要获取 http://127.0.0.1:5000/test/?wd=python&ie=ok的参数
from flask import Flask,request app = Flask(__name__) @app.route('/') def index(): wd = request.args.get('wd') ie = request.values.get('ie') return f"Hello! {wd} == {ie}" if __name__ == '__main__': app.run(debug=True)
使用总结
如果你的这个页面的想要做 SEO 优化,就是被搜索引擎搜索到, 那么推荐使用第一种形式(path的形式)。 如果不在乎搜索引擎优化,那么就可以使用第二种(查询字符 串的形式)。
请求体参数
from flask import Flask,request app = Flask(__name__) @app.route('/',methods=['POST']) def index(): uname = request.form.get('uname') pwd = request.values.get('pwd') age = request.form.get('age') return f"Hello! {uname} == {pwd} == {age}" if __name__ == '__main__': app.run(debug=True)
上传文件
客户端上传图片到服务器,并保存到服务器中
from flask import request @app.route('/upload', methods=['POST']) def upload_file(): f = request.files['pic'] # with open('./demo.png', 'wb') as new_file: # new_file.write(f.read()) f.save('./demo.png') return '上传成功!'
其它参数
如果想要获取其他地方传递的参数,可以通过Flask提供的request 对象来读取。 不同位置的参数都存放在request的不同属性中
from flask import Flask,request app = Flask(__name__) @app.route('/args') def args(): cookies = request.cookies.get('uid') headers = request.headers.get('ContentType') url = request.url method = request.method return f'上传成功!! {cookies} == {headers} =={url} == {method}' if __name__ =='__main__': app.run(debug=True)
url_for 函数
一般我们通过一个 URL就可以执行到某一个函数。 如果反过来,我们知道一个函数,怎么去获得这个 URL呢? url_for 函数就可以帮我们实现这个功能。
注意
url_for 函数可以接收1个及以上的参数,他接收函数名作为第 一个参数 如果还出现其他的参数,则会添加到 URL 的后面作为查询参 数。
@app.route('/post/list/<page>/') def my_list(page): return 'my list' @app.route('/') def hello_world(): return url_for('my_list',page=2,num=8) # return "/post/list/2?num=8"
使用url_for函数原因
问题 为什么选择 url_for 而不选择直接在代码中拼 URL 的原因有两点:
回答 1 将来如果修改了 URL ,但没有修改该 URL 对应的函数名,就不用到处去替换URL 了 2 url_for() 函数会转义一些特殊字符和 unicode 字符串,这些事情 url_for 会自动的帮我们
@app.route('/login/') def login(): return 'login' @app.route('/') def hello_world(): return url_for('login', next='/') # /login/?next=/ # 会自动的将/编码,不需要手动去处理。 # url=/login/?next=%2F
技巧
在定义url的时候,一定要记得在最后加一个斜杠。 1. 如果不加斜杠,那么在浏览器中访问这个url的时候,如果最 后加了斜杠,那么就访问不到。这样用户体验不太好。 2. 搜索引擎会将不加斜杠的和加斜杠的视为两个不同的url。而 其实加和不加斜杠的都是同一个url,那么就会给搜索引擎造成 一个误解。加了斜杠,就不会出现没有斜杠的情况。
响应-重定向
永久性重定向:
http 的状态码是 301,多用于旧网址被废弃了要转到一个新的网 址确保用户的访问 比如:你输入 www.jingdong.com 的时候,会被重定向到 ww w.jd.com , 因为 jingdong.com 这个网址已经被废弃了,被改成 jd.com 所以这种情况下应该用永久重定向
暂时性重定向:
http 的状态码是 302,表示页面的暂时性跳转。 比如:访问一个需要权限的网址,如果当前用户没有登录,应 该重定向到登录页面, 这种情况下,应该用暂时性重定向。
flask中重定向
重定向是通过 redirect(location,code=302) 这个函数来实现的, location表示 需要重定向到的 URL, 应该配合之前讲的 url_for() 函数来使用, code 表示采用哪个重定向,默认是 302 也即 暂时性重定向, 可以 修改成 301 来实现永久性重定向
from flask import Flask,request,url_for,redirect app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' @app.route('/login/') def login(): return '这是登录页面' #falsk中重定向 @app.route('/profile/') def proflie(): if request.args.get('name'): return '个人中心页面' else: # return redirect(url_for('login')) return redirect(url_for('login'),code=302) if __name__ == '__main__': app.run(debug=True)
响应-响应内容
from flask import redirectd @app.route('/return_str') def return_str(): return "你好,少年"
返回JSON
from flask import jsonify app.config['JSON_AS_ASCII'] = False @app.route('/return_json1') def return_json1(): json_dict = { "msg_int": 10, "msg_str": "你好,少年" } return jsonify(json_dict) @app.route('/return_json2') def return_json2(): json_dict = { "msg_int": 10, "msg_str": "你好,少年" } return json_dict
元组方式
可以返回一个元组,元组中必须至少包含一个项目,且项目应当由 (response, status) 、 (response, headers) 或者 (response, status, headers) 组成。 status 的值会重载状态代码, headers 是一个由额外头部值组成的列表 或字 典 status 值会覆盖状态代码, headers 可以是一个列表或字典,作 为额外的消息标头值。
@app.route('/demo1') def demo1(): # return '状态码为 666', 666 # return '状态码为 666', 666,[('itbaizhan', 'Python')] return '状态码为 666', 666, {'itbaizhan': 'Python'}
响应-自定义响应
创建Response
from flask import Response @app.route('/return_str') def return_str(): return Response("你好,少年")
make_response方式
@app.route('/demo2') def demo2(): resp = make_response('make response测试') resp.headers['itbaizhan'] = 'Python' resp.status = '404 not found' return resp
Flask模板
模板介绍
思考 : 网站如何向客户端返回一个漂亮的页面呢?
提示 :
漂亮的页面需要 html 、 css 、 js .
可以把这一堆字段串全都写到视图中, 作为 HttpResponse() 的参数,响应给客户端
问题
视图部分代码臃肿, 耦合度高
这样定义的字符串是不会出任何效果和错误的
效果无法及时查看.有错也不容易及时发现
解决问题
模板 Template
MVT 设计模式中的 T , Template
M全拼为Model,与MVC中的M功能相同,负责和数据库交 互,进行数据处理。
V全拼为View,与MVC中的C功能相同,接收请求,进行业务处 理,返回应答。
T全拼为Template,与MVC中的V功能相同,负责封装构造要返 回的html。
模板的使用
在 Flask中,配套的模板是 Jinja2,Jinja2的作者也是Flask的作者。 这个模板非常的强大,并且执行效率高。
使用步骤
创建模板
在 应用 同级目录下创建模板文件夹 templates . 文件夹名称固定写法.
在 templates 文件夹下, 创建 应用 同名文件夹. 例, Book
在 应用 同名文件夹下创建 网页模板 文件. 例 : index.html
设置模板查找路径
模板处理数据
from flask import Flask,render_template app = Flask(__name__) @app.route('/test') def index(): return render_template('index.html')
模板-传参
在使用 render_template 渲染模版的时候,可以传递关键字参数(命名参 数)。
from flask import Flask,render_template app = Flask(__name__) @app.route('/') def hello_world(): return render_template('index.html',uname='sxt')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SXT</title> </head> <body> 从模版中渲染的数据 <br> {{ uname}} </body> </html>
小技巧
如果你的参数项过多,那么可以将所有的参数放到一个字典 中, 然后在传这个字典参数的时候,使用两个星号,将字典打散成 关键字参数(也叫命名参数)
@app.route('/') def hello_world(): context = { 'uname': 'momo', 'age': 18, 'country': 'china', 'childrens': { 'name': 'mjz', 'height': '62cm' } } returnrender_template('index.html',**context)
获取方式是: {{childrens.name}} 或者 {{childrens['name']}}
模板使用url_for函数
模版中也可使用 url_for ,和后台视图函数中的 url_for 使用起来基本是一 模一样的。
提示 在模板中使用函数,需要在函数 左右两边加上2个 {} 例如: {{ url_for(func) }}
@app.route('/accounts/login/<name>/') def login(name): print(name) return '通过URL_FOR定位过来的!!!'
<a href="{{url_for('login',p1='abc',p2='ddd',name='zhangsan') }}">登录</a>
注意 无论是 路径参数 还是 查询式参数 都可以直接传递
过滤器介绍
有时候我们想要在模版中对一些变量进行处理,那么就必须需要类 似于Python中的函数一样,可以将这个值传到函数中,然后做一些 操作。 在模版中,过滤器相当于是一个函数,把当前的变量传入到过滤器 中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果 渲染到页面中
@app.route('/') def hello_world(): return render_template('index.html',postion=-1)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SXT</title> </head> <body> <h3>过滤器的基本使用</h3> <p>位置的绝对值为[未使用过滤器]:{{ postion}} </p> <p>位置的绝对值为[使用过滤器]:{{ postion|abs}}</p> </body> </html>
Jinja模板自带过滤器
过滤器是通过管道符号 | 使用的,例如: { name|length }} 将返回name的 长度。 过滤器相当于是一个函数,把当前的变量传入到过滤器中,然后过 滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面 中。 Jinja2中内置了许多过滤器 https://jinja.palletsprojects.com/en/3.0.x/templates/#filters
defalut过滤器
<body> <h1>default过滤器</h1> 过滤前的昵称数据是:{{nick_name}}<br> 过滤后的昵称数据是:{{nick_name | default('用户1',boolean=true)}}<br> 过滤后的昵称数据是:{{nick_name or '用户2'}} <br> </body>
转义字符
<body> <h1>转义字符过滤器</h1> <!-- 模板中默认 做了转义字符的效果 --> 转义前的数据是:{{ info | safe }} <!-- 不转义:不将特殊字符转换成 <类似的数据 --> {% autoescape true %} <!-- false代表不再转义特殊字符 / true 转义特殊字符 <--> {{info }} <!-- 转义:将特殊字符转换成 <类似的数据 --> {% endautoescape %} </body>
其它过滤器
<body> <h1>其它过滤器</h1> 绝对值:{{ -6 | abs }}<br> 小数: {{ 6 | float }}<br> 字符串:{{ 6 | string }}<br> 格式化:{{'%s--%s' | format('我' , '你')}} <br>长度:{{'我是九,你是三,除了你,还是你'|length}}<br> 最后一个:{{'我是九,你是三,除了你,还是你'|last}}<br> 第一个:{{'我是九,你是三,除了你,还是你'|first}}<br> 统计次数: {{'我是九,你是三,除了你,还是你' | wordcount }}<br> 替换:{{'===我是九,你是三,除了你,还是你 ====' |replace('我是九,你是三,除了你,还是 你' ,'拿着,这个无限额度的黑卡,随便刷')}} </body>
小提示
jinja2模板 默认全局开启了自动转义功能
safe 过滤器:可以关闭一个字符串的自动转义
escape 过滤器:对某一个字符串进行转义
autoescape 标签,可以对他包含的代码块关闭或开启自动转义
{% autoescape true/false %} 代码块 {% endautoescape %
自定义过滤器
只有当系统提供的过滤器不符合需求后,才须自定义过滤器 过滤器本质上就是一个函数。 如果在模版中调用这个过滤器,那么就会将这个变量的值作为第一 个参数传给过滤器这个函数, 然后函数的返回值会作为这个过滤器的返回值。 需要使用到一个装饰器: @app.template_filter('过滤器名称')
自定义数据替换过滤器
例如:将新闻中出现的 所有“ 我是九你是三,除了你还是你” 换成 "你不用多好,我喜欢就好"
#将模版设置为自动加载模式 app.config['TEMPLATES_AUTO_RELOAD']=True @app.template_filter('cut') def cut(value): value=value.replace("我是九你是三,除了你还是你",'你不用多好,我喜欢就好') return value
<p>使用自定义过滤器:{{新闻内容值|cut}}</p>
自定义时间过滤器
例如:操作发布新闻 与现在的时间间隔
from datetime import datetime #需求:操作发布新闻 与现在的时间间隔 @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. 否则就是显示具体的时间 2030/10/2016: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 <p>发布时间:{{新闻创建时间|handle_time}}</p>