[TOC]

0x00 前言简述

描述:Flask 官方介绍Web Develoment one drop at a time,实际上它是一个基于Python开发的Web轻量级框架; 通过Flask和各种插件的配合使用,以新的框架实现Web前后端联合开发。

Flask 核心特性就是”微”,而微框架中的“微”字表示 Flask 的目标是保持核心简单而又可扩展(从零开始由你做主),所有并不是说它不适用于大型项目;

Flask 官方网站: http://flask.pocoo.org
Flask 依赖内置的 Jinja 模板引擎和 Werkzeug WSGI 套件(WSGI 工具集)以及itsdangerous基于Django签名模块下面列出其帮助文档:

Flask 特点:

  • 1.当前最流行的Python-Web框架,已经超越Django排名第一了;
  • 2.官方文档齐全,方便入手;
  • 3.非常好的扩展机制和第三方的扩展环境;
  • 4.社区活跃度非常高;
  • 5.微型框架提供给开发者更大的选择空间;

Flask VS Django 对比区别:

  • (1) 相同点:
    • 都是基于MVC设计模式的Web框架;
  • (2) 区别点:
    • 前者轻量级开发框架用户自定义多轻捷便利(6行代码实现一个Web服务器),而Django较于前者比较重但是功能完善;

软件架构设设计风格:层次清晰/便于维护
Q: MVC(Model、View、Controller) 设计模式一句话解释

它是一种软件设计规范使得业务逻辑、后台数据、前台界面进行分离,其核心思想是解耦合,优点是降低了模块之间的耦合性,方便变更以及更容易重构代码并且最大程度实现代码重用;
比如:需要更换为其他数据库存储只需要调整Models模型即可
比如: 需要更换页面显示时候只需要修改view视图即可

Q: MVT(Model、View、Template)设计模式与MVC本质无什么差别,各组件之间为了保持松耦合关系,只是定义上有些许不同;

1.负责业务对象与数据库(ORM)的对象;
2.负责业务逻辑并在适当的时候调用Model和Template;
3.负责将页面展示给用户;

学习关键点:
掌握URL、Jinjia2模板语法、标准类视图、ORM、Flask会话、Restful、权限和角色模型、Celery异步机制等技能知识。


0x01 环境安装

描述: 在进行Flask开发建议使用最新版本的Python3版本以及采用Pycharm进行快速Python Flask项目开发,并且建议在开发环境和生产环境下都使用虚拟环境来管理项目的依赖。

(1) venv 虚拟环境

Q:为什么要使用虚拟环境?

随着你的 Python 项目越来越多,你会发现不同的项目会需要不同的版本的 Python 库,同一个 Python 库的不同版本可能不兼容。
虚拟环境可以为每一个项目安装独立的 Python 库,这样就可以隔离不同项目之间的 Python 库,也可以隔离项目与操作系统之间的 Python 库

Python 3 内置了用于创建虚拟环境的 venv 模块,我们可以采用其创建一个虚拟环境流程如下:

1
2
3
4
5
6
7
8
# Linux / Windows
# 创建虚拟目录
mkdir project && cd ./project && python3 -m venv venv
# 激活虚拟环境
$ . venv/bin/activate # Linux
> venv\Scripts\activate # Windows

# Tips:激活后终端提示符会显示虚拟环境的名称。

补充Python2.x下虚拟环境安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# pip 与 virtualenv 安装
apt install python-pip && pip install virtualenv

# 虚拟环境配置
apt install virtualenvwrapper
export WORKON_HOME=~/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh

# 创建虚拟环境名称
mkvirtualenv
# 删除虚拟环境名称
rmvirtualenv
# 进入虚拟环境名称
workon
# 退出
deactivate

项目变量定义:

1
2
3
4
5
6
7
8
# 环境变量从dotenv中读取
pip install python-dotenv

.env
.flaskenv

# 环境变量从virtualenv中
.flaskenv


(2) Flask与扩展安装
1
2
3
4
5
6
7
8
# 依赖安装
cat requirement.txt
flask
flask-script
Flask-RESTful

# 在已激活的虚拟环境中可以使用如下命令安装 Flask:
pip install -r requirement.txt

Flask(__name__).run() 参数配选项

1
2
3
4
5
6
7
debug    # 调试模式默认为True
threaded # 是否开启多线程
port # 指定服务器的端口 (5000)
host # 绑定的主机地址(127.0.0.1 / 0.0.0.0)

# 简单示例
app.run(debug=Ture,host='0.0.0.0',port=8000)

Flask 命令行界面支持的环境变量:

1
2
3
4
5
6
7
8
9
10
11
12
# 应用发现
FLASK_APP=hello
# 环境变量
FLASK_ENV=development
# 看附加文件与Reloader
FLASK_RUN_EXTRA_FILES=file1:dirA/file2:dirB/
# 调试模式(在开发者模式自动开启)
FLASK_DEBUG=1
# 启动端口设置
FLASK_RUN_PORT=8000
# 禁用dotenv
FLASK_SKIP_DOTENV=1

Flask 命令:

1
2
3
4
5
# 运行开发服务器(启动参数指定)
flask run --port 8000 --extra-files file1:dirA/file2:dirB/

# 运行开发服务器(从环境变量中读取启动端口)
flask shell


0x02 基础尝试

描述:一个简单Flask项目创建流程如下:

  • 1.导入flask包中的Flask模块
  • 2.创建Flask对象
  • 3.使用对象实例进行路由注册
  • 4.在路由下编写路由函数并返回响应字符串
  • 5.通过对象实例的run()方法启动Flask项目
(1) 小试牛刀

示例1.初始化Flask项目之hello_world.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python3
from flask import Flask
from datetime import datetime

app = Flask(__name__)

@app.route('/')
def hello_world():
a = "Flask"
b = " - Hello world"
time = datetime.now()
return "<h4 style='text-algin:center'>Project %s %s %s</h4>" % (a,b,time)

if __name__ == '__main__':
app.run(debug=Ture,host='0.0.0.0',port=8000)

启动配置指定启动环境模式:

1
2
3
4
5
6
7
8
9
10
11
12
# 方式1
# bat
set FLASK_ENV=development
# powershell
$env:FLASK_ENV="development"
# linux
FLASK_ENV=production
python Flask-hello_world.py

# 方式2
env FLASK_APP=.\Flask-hello_world.py flask run
$env:FLASK_APP=.\Flask-hello_world.py flask run

执行结果:

1
2
3
4
5
6
7
8
 * Serving Flask app "Flask-hello_world" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat # 修改自动重启
* Debugger is active! # debuger
* Debugger PIN: 113-873-865 # 唯一身份标识
* Running on http://0.0.0.0:8000/ (Press CTRL+C to quit)

总结:

  • Dubugger 相关功能在Flask中调速器拥有保护的功能,采用PIN作为当前调试的身份认证,常常在开发环境中使用生产环境中不建议开启;


示例2:环境变量与启动参数
描述:我们可以采用Flask的flask-Script扩展库在启动flask动态指定启动参数或者自身自带参数;
文档地址:https://flask.palletsprojects.com/en/1.1.x/cli/?highlight=flask%20script

方式1.Flask-Script方式(在1.0版本前使用现在已丢弃),使用其前安装它pip install flask-script

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask
from flask_script import Manager
# 初始化操作
app = Flask(__name__)
# 使用app构建Manager对象
manager = Manager(app)

@app.route('/')
def param():
return f'Flask - script,host port param'

if __name__ == '__main__':
manager.run()

flask-script 帮助命令:

1
2
3
4
5
6
7
8
9
10
11
$python .\Day1\flask-scritp.py     
usage: flask-scritp.py [-?] {shell,runserver} ...
positional arguments:
{shell,runserver}
shell Runs a Python shell inside Flask application context.
runserver Runs the Flask development server i.e. app.run()

usage: flask-scritp.py runserver [-?] [-h HOST] [-p PORT] [--threaded]
[--processes PROCESSES]
[--passthrough-errors] [-d] [-D] [-r] [-R]
[--ssl-crt SSL_CRT] [--ssl-key SSL_KEY]

执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Linux
python3 http.py runserver -p 8000 - h 0.0.0.0 -d -r --threaded

# windows
python .\Day1\flask-scritp.py runserver -h 0.0.0.0 -p 8000 -r -d --threaded
* Serving Flask app "flask-scritp" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 113-873-865
* Running on http://0.0.0.0:8000/ (Press CTRL+C to quit)


方式2:采用命令行或者环境变量指定端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Powershell(Linux 不多说了)
$env:FLASK_ENV="development"
$env:FLASK_APP="./Flask-param.py"
$env:FLASK_RUN_PORT="8080";
flask run
# * Serving Flask app "./Flask-param.py" (lazy loading)
# * Environment: development
# * Debug mode: on
# * Restarting with stat
# * Debugger is active!
# * Debugger PIN: 292-194-254
# * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)

# 也可以在运行flask run指定host-port参数注意其优先级高于环境变量
$env:FLASK_APP="./Flask-param.py"
flask run --port 8000 --host 0.0.0.0
* Debugger is active!
* Debugger PIN: 292-194-254
* Running on http://0.0.0.0:8000/ (Press CTRL+C to quit)

路由管理

在看路由前我们先大致了解一下用户请求流程:

1
2
3
# Flask 请求与响应返回
浏览器 -> Route -> Views -> Views -> Templates -> 浏览器
-> modesl /

Route(路由): 将从可客端发送过来的请求分发到指定函数上;
规则语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# (1)路由参数获取
<converter:variable_name>
# converter 类型
* String: 接收任何没有斜杠('/')的文件(默认);
* Int: 接收整型;
* Float: 接收浮点型;
* Path: 接收路径可接收斜线('/')不以其作为分割阶段,即其从`aa/bb/cc`;
* Uuid: 只接受uuid字符串,唯一码一种生成规则;
* Any: 可以同时指定多种路径进行限定;
# Example
@app.route('/default/<string:id/')
@app.route('/<int:id>/')
@app.route('/<uuid:id>/')


# (2) 请求方法
methods=[GET,POST,HEAD,PUT,DELETE,OPTION]
# Example
@app.route('/api-Rsetful/',methods=['GET','POST'])

# (3) 反向解析:根据函数名字获取反向路径
url_for('函数名称',参数名=value)

实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@app.route('/')
def index():
return '<b> Hello World! This is Flask Index </b>'

@app.route('/getstring/<id>/')
def get_str(id):
print(id,":",type(id)) # str
return '字符串: Hello {} Username!'.format(id)

@app.route('/getint/<int:id>/')
def get_int(id):
print(id,":",type(id)) # int
return '数值: {} '.format(id)

@app.route('/getpath/<path:id>/')
def get_path(id):
print(id,":",type(id)) # str 注意与 string 的不同
return '路径: {} '.format(id)

@app.route('/getuuid/<uuid:uuid>/')
def get_uuid(uuid):
print(uuid,":",type(uuid)) # 277cd8ed-041d-42f7-980c-a0f305288255 : <class 'uuid.UUID'>
return 'uuid: {} '.format(uuid)

@app.route('/getany/<any(a,b):id>/')
def get_any(id):
print(id,":",type(id)) # a : <class 'str'>
return 'id: {} '.format(id)

# 请求类型限制
@app.route('/method/',methods=['GET','POST','DELETE'])
def get_method():
return '该请求类型成功!'

# 重定向与反向解析
@app.route('/redirect/')
def get_redirect():
# 硬编码方式
#return redirect('/')
# 软编码方式(动态获取) 蓝图名称.函数
#return redirect(url_for('app.index'))
return redirect(url_for('app.get_any',id='a'))

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# http://127.0.0.1:8000/getpath/weiyigeek/api/
weiyigeek/api : <class 'str'>
路径: weiyigeek/api

# http://127.0.0.1:8000/getany/a/
a : <class 'str'>
id: a

# http://127.0.0.1:8000/getuuid/277cd8ed-041d-42f7-980c-a0f305288255/
277cd8ed-041d-42f7-980c-a0f305288255 : <class 'uuid.UUID'>
uuid: 277cd8ed-041d-42f7-980c-a0f305288255 、

# 重定向与反向解析
127.0.0.1:8000/redirect/ ==> http://127.0.0.1:8000/getany/a/

注意事项:

  • 1.可以多个路由指向一个函数,在实际开发中利用数据类型进行处理分类;
  • 2.Flask视图函数默认支持GET、HEAD、OPTION等请求,如需支持其他请求方式请手动注册即可;
  • 3.使用重定向与反向解析时候需要导入flask包中的redirect模块即from flask import redirect,url_for;

Q:使用时候容器出现循环引用的问题?
解决办法:

  • 懒加载: 使用函数调用的形式进行加载
  • 蓝图: 对路由进行规划(采用flask-buleprint扩展实现)


懒加载

插件以及数据库迁移都是需要使用懒加载方法;

项目概况:

1
2
3
4
5
6
7
8
9
10
11
$tree ./
./App/
├── __init__.py
├── __pycache__ # Flask 运行时候缓存
│ ├── __init__.cpython-37.pyc
│ └── views.cpython-37.pyc
├── moudels.py
├── static
├── templates
└── views.py
setup.py

  • Step1.Flask 启动入口: setup.py

    1
    2
    3
    4
    5
    6
    7
    8
    #!/usr/bin/python3
    # 导入 App 类中模块方法
    from App import create_app
    # 调用App类__init__模块中的方法
    app = create_app();

    if __name__ == "__main__":
    app.run()
  • Step2.默认包App项目包:./App/__init__.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from flask import Flask
    # 导入App类View模块中的init_route方法
    from App.views import init_route

    # 生成app对象并将其传递给init_route方法
    def create_app():
    app = Flask(__name__)
    init_route(app)
    return app
  • Step3.视图路由:./App/view.py

    1
    2
    3
    4
    5
    6
    7
    8
    def init_route(app):
    @app.route('/')
    def hello_world():
    return f'Request / <br> Hello World'

    @app.route('/hello')
    def hello():
    return f'Request /hello <br> Hello Flask, WeiyiGeek'

执行结果:

1
2
3
4
5
#  / 
Request / <br> Hello World

# /hello
Request /hello <br> Hello Flask, WeiyiGeek


Blueprint

描述:动态路由依赖于 Blueprint 蓝图在使用前必须进行安装该模块pip install flask-buleprint,并且在使用的时候进行初始化即创建蓝图对象;

使用和Flash对象差不多,可直接作为装饰器来注册路由

项目结构:

1
2
3
4
5
6
7
8
9
10
$tree Buleroute/
Buleroute/
├── __init__.py
├── templates
│ └── index.html
└── view
├── __init__.py
├── index.py
└── user.py
setup.py #与上面差不多只是调用的是Buleroute包__init__模块中的create_app()方法下面就不重复展示

Buleroute包的初始化模块:Buleroute/__init__.py

1
2
3
4
5
6
from flask import Flask
from Buleroute.view import init_view #view模块下init_view方法
def create_app():
app = Flask(__name__)
init_view(app=app)
return app

view包中初始化模块:Buleroute/view/__init__.py

1
2
3
4
5
6
7
from .index import index  
from .user import user

def init_view(app):
# app 蓝图注册
app.register_blueprint(index) #传入一个名称为index的蓝图
app.register_blueprint(user)

蓝图(Blueprint):

1
2
3
4
5
6
7
8
9
10
# index.py
from flask import Blueprint,render_template
# 蓝图对象 = 蓝图名称
index = Blueprint('index',__name__) # 建立一个名称为index的蓝图

@index.route('/')
@index.route('/index')
def index_bule():
#return f'request / <b>这是蓝图的首页</b>'
return render_template('index.html',msg="基础入门(模板参数传递)") # 将jiani模板渲染成为HTML并向其传递参数

Flask模板:Buleroute/templates/index.html

1
2
3
4
5
6
7
<h1>Flask 入门学习 - Demo  </h1>
<ul>
<!-- 接收来自render_template()方法的参数-->
<li>First : {{msg}}</li>
<li>Second</li>
<li>There</li>
</ul>

执行结果:

WeiyiGeek.结果

WeiyiGeek.结果


MVC 架构实践

描述:三阶改装项目结构

1
2
3
4
5
6
7
8
9
- Setup.py 项目管理及入口文件
- App 应用项目目录(MVC)
- __init__ 初始化文件
- ext extension扩展库除了和路由相关
- setting config以及全局项目配置
- view apis和路由视图函数
- models 定制模型与数据库相关
- static 静态资源文件
- template 网页模板文件

setup.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python3
# from App import create_app
# from Buleroute import create_app
import os
from SQLAlchemy import create_app
from flask_script import Manager
from flask_migrate import MigrateCommand


# 系统获取环境变量来进行切换数据库
env = os.environ.get("FLASK_ENV","develop")

app = create_app(env)
manager = Manager(app=app)
manager.add_command('db',MigrateCommand)

if __name__ == "__main__":
manager.run()

App/__init__.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# MVC
from flask import Flask
from SQLAlchemy.view import init_view
from .ext import init_ext
from .setting import envs
from .models import User

def create_app(env):
app = Flask(__name__)
# 未采用环境切换时候
# init_param(app)

# 采用环境变量进行开发和生产环境切换
app.config.from_object(envs.get(env))
# 初始化扩展
init_ext(app)
# 初始化路由
init_view(app=app)
return app

ext.py

1
2
3
4
5
6
7
8
9
10
11
# 第三方扩展包初始化加载
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()

# 后初始化化懒加载
def init_ext(app):
db.init_app(app)
migrate.init_app(app, db)

Day2\SQLAlchemy\setting.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 第三方库所需参数设置
# def init_param(app):
# # 数据库连接字符串通用: 数据库+驱动://用户名:密码@主机:端口/具体库?参数
# app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///sqlite.db"
# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

def get_db_uri(dbinfo):
engine = dbinfo.get("ENGINE") or "sqlite"
driver = dbinfo.get("DRIVER") or "sqlite"
user = dbinfo.get("USER") or ""
password = dbinfo.get("PASSWORD") or ""
host = dbinfo.get("HOST") or ""
port = dbinfo.get("PORT") or ""
name = dbinfo.get("NAME") or ""

if engine == "sqlite":
return "{}:///{}".format(engine,name)
else:
return "{}+{}://{}:{}@{}:{}/{}".format(engine,driver,user,password,host,port,name)


class DevelopConfig:
# SQLALCHEMY 设置环境变量
DEBUG = Ture
SQLALCHEMY_TRACK_MODIFICATIONS = False
DBINFO = {
"ENGINE": "sqlite",
"NAME": "sqlite.db"
}
SQLALCHEMY_DATABASE_URI = get_db_uri(DBINFO)


class ProductConfig:
DEBUG = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
DBINFO = {
"ENGINE": "mysql",
"DRIVER": "pymysql",
"USER": "root",
"PASSWORD": "Pass#2020",
"HOST": "127.0.0.1",
"PORT": "3306",
"NAME": "FlaskTest"
}
SQLALCHEMY_DATABASE_URI = get_db_uri(DBINFO)


# 可以直接向app.config传递类对象达到切换测试环境与生产环境
envs = {
"develop": DevelopConfig,
"product": ProductConfig
}

Day2\SQLAlchemy\models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3
# 第三方模块自定类声明
from SQLAlchemy.ext import db

def save(obj):
db.session.add(obj)
db.session.commit()

# User 类
class User(db.Model):
__tablename__ = 'user' #表名
id = db.Column(db.Integer, primary_key=Ture)
username = db.Column(db.String(16))

def commit(self):
save(self)

class Member(db.Model):
id = db.Column(db.Integer, primary_key=Ture)
subname = db.Column(db.String(16))
def commit(self,db):
save(delf,db)

Day2\SQLAlchemy\view\db.py: 数据库模型交互与蓝图此处体现了MVT思想,此处的View表示了控制器接受请求处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from flask import Blueprint
from SQLAlchemy.models import db,User
database = Blueprint('database',__name__)

@database.route('/createdb/')
def create_db():
db.create_all();
return '创建成功'

@database.route('/adduser/')
def user_add():
user = User()
user.username = "WeiyiGeek"

# # 官方
# db.session.add(user)
# db.session.commit()

# 在类中自定义方法(实际是对上面的两个方法的调用)
user.commit()
return "username %s Insert Successful!" %(user.username)

@database.route('/dropdb/')
def drop_db():
db.drop_all()
return '删除成功'

补充说明:
Flask 扩展调用图示(二阶拆分):

1
2
3
                             |-----------------------> Ext
Control Manager ---> __init__ /
|----> Views ---> Models

WeiyiGeek.基础结构(三阶拆分)

WeiyiGeek.基础结构(三阶拆分)

内置对象

Flask四大内置对象如下所示:

  • Request: request
  • Session: session
  • G: g
  • Config: 在模板中采用config而在Python代码中是app.config;
Request

描述:request是服务器在接收到客户端请求后会自动创建Request对象(注意由Flask框架创建并且Request对象不可修改);
导入格式:from flask import request

对象属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- url: 完整请求地址
- url_root: 主机与端口号的URL
- path: 路由中的路径
- host_url: 主机与端口号的URL
- base_url: 去掉GET参数的URL
- method: 请求方法
- remote_addr: 请求的客户端地址
- args: GET请求参数
- form: POST请求参数
- values:返回请求中的参数和form
- date: 请求的数据
- files: 请求上传的文件
- headers: 请求头
- cookies: 请求中的cookie
- session: 请求中的session

# 语法说明
return request.method #POST
return json.dumps(request.form) #{"username": "123", "password": "1234"}
return json.dumps(request.args) #url:http://192.168.1.183:5000/login?a=1&b=2 返回值:{"a": "1", "b": "2"}
return str(request.values) #CombinedMultiDict([ImmutableMultiDict([('a', '1'), ('b', '2')]), ImmutableMultiDict([('username', '123'), ('password', '1234')])])
return json.dumps(request.cookies) #cookies信息
return str(request.headers) #headers信息
return request.headers.get('User-Agent') #获取User-Agent信息
return 'url: %s , script_root: %s , path: %s , base_url: %s , url_root : %s' % (request.url,request.script_root, request.path,request.base_url,request.url_root)
# url: http://192.168.1.183:5000/testrequest?a&b ,
# script_root: ,
# path: /testrequest ,
# base_url: http://192.168.1.183:5000/testrequest ,
# url_root : http://192.168.1.183:5000/


ImmutableMutiltiDict 类似于字典的数据类型与字典的区别就是可以存在相同的键以列表的方式存放比如[(key,value)];

1
2
3
4
5
6
# 常用获取方式
dict = ImmutableMutiltiDict
# 获取单个key的值
dict.get('key')
# 获取指定key对应的所有值
dict.getlist('key')

实际案例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@demo2.route('/request/<path:url>',methods=['GET','POST'])
def req(url):
req_method = ''
if request.method.upper() == 'GET':
req_method = 'GET Successful!'
elif request.method.upper() == 'POST':
req_method = 'POST Successful!'
else:
req_method = request.method.upper()+'Not Support!'

# request 属性演示
return " Current Time: {}<br>Header:<pre>{}</pre><br> HOST: {} <br>URL: {} <br>Method: {} <br>Client IP: {} <br>".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),request.headers, request.host_url, request.base_url, req_method, request.remote_addr)

# 执行结果:http://127.0.0.1:8000/request/user/weiyigeek
# Current Time: 2020-09-07 17:52:50

# Header:
# Host: 127.0.0.1:8000
# User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
# Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
# Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
# Accept-Encoding: gzip, deflate
# Connection: keep-alive
# Upgrade-Insecure-Requests: 1

# HOST: http://127.0.0.1:8000/
# URL: http://127.0.0.1:8000/request/user/weiyigeek
# Method: GET Successful!
# Client IP: 127.0.0.1

基础示例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@demo2.route('/login/',methods=['GET','POST'])
def send_rep():
# GET
print(request.args,"\n",type(request.args));
print(request.args.getlist('a'))

# POST
print(request.form,"\n",type(request.form))

# 登录示例
if request.method.upper() == 'POST':
if request.form['username'] == 'weiyigeek' and request.form['password'] == 'pass':
return 'Ture'
else:
#当form中的两个字段内容不一致时,返回我们所需要的测试信息需要替换的部分
return str(request.headers)
else:
return render_template('login.html')

return 'successful!'

# 执行结果
# GET
ImmutableMultiDict([])
<class 'werkzeug.datastructures.ImmutableMultiDict'>
[]
# POST
ImmutableMultiDict([('username', 'weiyigeek'), ('password', 'pass')])
<class 'werkzeug.datastructures.ImmutableMultiDict'

基础示例3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from werkzeug.utils import secure_filename
from flask import Blueprint,request,render_template
# date是请求的数据,files随请求上传的文件
@demo2.route('/upload',methods=['GET','POST'])
def upload():
if request.method == 'POST':
print(request.date," : ", type(request.date))
print(request.files," : ", type(request.files))
f = request.files['file']
filename = secure_filename(f.filename)
#f.save(os.path.join('app/static',filename))
f.save('App/static/'+str(filename))
return 'ok'
else:
return render_template('upload.html')

# html
<!DOCTYPE html>
<html>
<body>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="text" name="flag" value="weiyigeek">
<input type="file" name="file" /><br />
<input type="submit" value="Upload" />
</form>
</body>
</html>

# 注意:上传文件需求管理员权限
# powershell start-process cmd -verb runas
# cd /d E:\githubProject\Study-Promgram\Python3\Flask\Day3
# python .\Setup.py runserver -h 0.0.0.0 -p 8000 -r -d --threaded

# 执行结果
None : <class 'NoneType'>
ImmutableMultiDict([('file', <FileStorage: 'QQ截图20200907112813.jpg' ('image/jpeg')>)]) : <class 'werkzeug.datastructures.ImmutableMultiDict'>
127.0.0.1 - - [07/Sep/2020 18:23:23] "POST /upload HTTP/1.1" 200 -


Response

描述: 服务器返回给客户端的数据,有程序开发者创建返回Reponse对象;

1
2
3
4
5
6
7
8
1.通过直接返回字符串与状态、也可采用Reponse对象或者通过make_response(data,code)函数使传递进来的资源创建一个response,前者返回的数据内容后者返回的状态码;
2.返回的文本内容和状态码
3.利用render_template将模板渲染成为HTML
4.返回模板(实质与2一样)
5.重定向 redirect() 或者 url_for('函数名',参数=value)
6.终止信号 abort
7.钩子函数: 异常捕获或者errorhandler(app作用于全局、蓝图只能捕获本身蓝图)
8.

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 方式1.return 与 render_template() 方式
@app.route('/reponse/')
def get_reponse():
# return "Reponse Test", 201
# return render_template('hello.html'), 201 # 相差不大都是返回的字符串
return Response('我是直接返回的Reponse对象!') # 直接构建Response对象返回返回客户端状态码为201


# 方式2.make_response 方式
@app.route('/reponse/')
def get_reponse():
response = make_response(render_template('error.html'), 404)
return response

异常处理:

1
2
3
4
5
6
7
8
9
# 中止处理: 直接返回异常码响应的描述
abort(异常码)
abort(404) # 可直接向客户端抛404响应码,其数值在mapping对应的错误码否则异常抛出(其本质就是一个exception)即HttpExeception
abort(Response('404 Not Found!'))

# 捕获异常:实现页面友好化但是需要注册errorhandler;
@app.errorhandler(404):
def not_found():
return '404 , Not Found!', 404

基础实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import render_template 
....
@app.errorhandler(404)
def not_found(error):
print(error)
print(type(error))
return render_template('404.html',title="404 Not Found",msg=error) # 注意导包

#Day3\App\templates\404.html
<body>
<h1>{{title}}</h1>
<br>
<pre>{{msg}}</pre>
</body>

执行结果:

WeiyiGeek.error-404

WeiyiGeek.error-404

注意实现:

  • (1) 在FLASK中获取请求参数可以通过args属性并且支持所有请求,而form属性支持非GET请求的其他方法比如(put/patch),其获取的数据类型ImmutableMultiDict实际上是字典(Dict)的再次封装;

会话保持

描述: 我们知道学习WEB后端语言时它是我们都绕不开的话题 , 网页中采用会话保持技术进行跨请求共享数据,实际上它就是存储访问者的访问票据;

其出现原因:

  • 1) Web 开发中HTTP都是短连接(请求响应后即关闭再次请求就是全新请求)
  • 2) HTTP 请求是无状态的

实现会话保持的三种方式:

  • (1) Cookie
  • (2) Session
  • (3) Token


描述:它是客户端会话技术,其数据以key-vakye的形式存储在客户端(重要业务不建议使用会导致一定的风险),并且Flask中的Cookues默认对中文进行了处理所以可以直接使用中文;

特点:

  • 支持会话过期
  • 支持中文处理
  • 不能跨网站域名访问
  • 默认携带本站所有Cookie


基础语法:

1
2
3
4
5
6
7
8
9
# 设置Cookies
# 方式1
response = make_response("响应的字符串此处是参数 %s" % username)
# 方式2
response = Response("响应的字符串此处是参数{}".format(username))
response.set_cookie(key, value="", max_age=None, expires=None, path="/", domain=None, secure=False, httponly=False, samesite=None)
response.delete_cookie(key)
# 获取Cookies中指定的key的值
request.cookies.get('key')

简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from flask import Blueprint,request,render_template,url_for,redirect,make_response,Response

# 设置
@demo2.route('/userlogin/',methods=['GET','POST'])
def login():
if request.method.upper() == 'GET':
return render_template('login.html')
elif request.method.upper() == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username == password :
# 方式1
# response = make_response("欢迎 %s 登陆,你已经成功登陆! <a href='/userperson/'>个人主页</a>" % username )
# 方式2
response = Response("<script>alert('欢迎 %s 登陆,你已经成功登陆!,正在跳转个人主页!');window.location.href='/userperson/'</script>" % (username))
response.set_cookie('username', username)
response.set_cookie('name', '唯一极客')
#redirect(url_for('demo2.person'),302)
return response
else:
return '账号或者密码错误!'
else:
return 'ERROR! Request Method Not Allow!'

# 获取
@demo2.route('/userperson/',methods=['GET','POST'])
def person():
print(request.cookies)
if request.cookies.get('username') != None:
name = request.cookies.get('name')
username = request.cookies.get('username')
return '欢迎 <u> %s </u> 您回来, 你的登陆 <u> %s </u>用户!' % (name,username) # 易错点注意有括号
else:
return "<script>alert('用户未登录请登陆');window.location.href='/userlogin';</script>"

# 响应结果:
# Cookie
# username=weiyigeek; name="\345\224\257\344\270\200\346\236\201\345\256\242";

WeiyiGeek.Cookie

WeiyiGeek.Cookie


Session

描述: 它是一个服务端会话技术, 数据存储在服务器中(保证安全以及不可篡改)以Key-Value的形式;

特征:

  • 1.默认将session序列化后存储在cookie中(KEY->Hash->base64编码),会将机器hmac以及salt加入到其中保证session的安全性;
  • 2.可采用flask-session实现session数据持久化存储在redis中, 嵌入级的不需要修改源代码只需要配置redis即可
  • 3.默认的生命周期在31天;

注意: 必须进行FLASK的APP配置SESSION的密钥否则将会报以下错误:
"The session is unavailable because no secret " RuntimeError: The session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret.

1
2
# 在app对象中进行配置或者直接在setting进行配置然后通过类加载到app配置中
app.config['SECRET_KEY'] = 'WeiyiGeek'

在FLASK中session实现流程:

  • 1.将session存储在cookie之中;
  • 2.对数据进行序列化
  • 3.在对其进行base64编码
  • 4.之后再进行zlib压缩
  • 5.最后传递hash(验证是否被篡改)

语法参数:

1
2
3
4
5
# 1.创建 session 键值对
session['key'] = value;

# 2.值获取
session.get('key')


基础示例1.简单的FLASK内的session模块演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Day3\App\views\demo\demo2.py
from flask import Blueprint,request,render_template,url_for,redirect,make_response,Response,session
@demo2.route('/session-test/<string:name>',methods=['GET','POST'])
def sessiontest(name):
if name != None:
session['name'] = name;
session['username'] = "唯一极客";
return 'Session 创建 进入查看 session <a href="/getsession/">show</a>'
else:
return '<p style="color:red">Parameter Error!</p>'


@demo2.route('/getsession/',methods=['GET'])
def getsession():
if session.get('name') != None:
print(session)
print(type(session))
return 'session value %s , %s' % (session.get('name'),session.get('username'))
else:
return '<b>session 未设置请在 /session-test/\<string:name\> 页面上复制 </b><script language="javascript">setTimeout("location=\'/\'",3000);</script>'

执行结果:
1
2
3
4
# 结果1.默认的session将所有的键值通过序列化存储在网页的cookie中
<SecureCookieSession {'name': 'weiyigeek', 'username': '唯一极客'}>
<class 'werkzeug.local.LocalProxy'>
session=eyJuYW1lIjoid2VpeWlnZWVrIiwidXNlcm5hbWUiOiJcdTU1MmZcdTRlMDBcdTY3ODFcdTViYTIifQ.X2GdGA.MgBLw9iCDlFBMaSXmtruHjdzkGs

基础示例2.通过FLASK-Session插件将session存储到内存数据库之中即非关系型数据库(redis);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# flask-session环境安装配置
pip install flask-session
pip install redis

# 方式1
app.config['SECRET_KEY'] = "WeiyiGeek"
app.config['SESSION_COOKIE_SECURE'] = Ture
app.config['SESSION_USER_SIGNER'] = Ture
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = Redis(host='192.168.100.10', password='weyigeek',db=3)
app.config['SESSION_KEY_PREFIX'] = "product:"

# 方式2 Day3\App\setting.py
SECRET_KEY = "WeiyiGeek"
SESSION_COOKIE_SECURE = Ture
SESSION_KEY_PREFIX = "product:"
SESSION_TYPE = 'redis'
SESSION_REDIS = Redis(host='192.168.100.10', password='weyigeek',db=3)
SESSION_USER_SIGNER = Ture

# Day3\App\ext.py
from flask_session import Session

# 初始化扩展
def init_ext(app):
Session(app) # Flask session 第三方插件

执行结果:

1
2
3
<RedisSession {'_permanent': True, 'name': 'WeiyiGeek-Redis', 'username': '唯一极客'}>
<class 'werkzeug.local.LocalProxy'>
Set-Cookie:session=4a461782-840d-4a2d-8352-3622ea9102fe; Expires=Sat, 17-Oct-2020 08:11:32 GMT; Secure; HttpOnly; Path=/

WeiyiGeek.session

WeiyiGeek.session

模板引擎

描述:在学习FLASK的开发模我们首先应该了解一下模板、以及模板引擎;

Q: 什么是模板?
答: 模板就是呈现给用户的界面, 在MVT中充当了T(Templates)的角色实现VT的解耦即视图与模板;模板处理分为两个过程一是加载二是渲染;
模板代码包含两个部分:

  • 1.静态HTML
  • 2.模板语法(动态插入代码片段)

Q: 开发中VT之间的关系
答: Views 与 Templates 是多对多的关系, 即一个V可以调用任意T并且一个T可以被任意V调用;

Jinja2 模板引擎

描述: 它是由FLASK作者模仿Django的模板开发并运用在FLASK中的模板引擎,一个现代化设计和友好的Python模板语言;

特点:

  • 1.速度快广泛应用
  • 2.HTML开发和后端Python分离
  • 3.减少Python复杂度
  • 4.非常灵活快速和安全
  • 5.提供了控制继承等高级功能

模板语法:

  • 变量:
  • 标签: {\% name \%}与JAVAweb开发中jsp相似

模板中的变量作用:

  • 视图传递给模板的数据
  • 前面定义数据的调用
  • 变量不存在(默认忽略)

模板中的标签{\% tag \%}作用:

  • 1.逻辑控制
  • 2.表达式使用
  • 3.创建变量
  • 4.宏定义(较Djiago新增功能): 即利用标签实现函数功能;

常用标签一览

结构标签

注释符: 在模板引擎中的注释

1
2
3
{# ``base.html`` 
这是注释的行
#}

block: 块操作即子模板调用或者继承(父模板挖坑,子模板填坑)

1
2
3
{% block xxx %}
<p>我是等待被填充或者继承的元素</p>
{% endblock xxx %} <!-- 推荐结束时候也加上块名称 -->

extends: 继承父模板的块操作里的内容,即引用或者填充、扩充父模板中块里的元素, 其继承体系是化整为零的操作;

1
2
3
4
5
{% extends 'xxx.html' %}
{% block xxx %}
{{ super() }} // 继承
<span>我是添加的子元素</span> // 扩充
{% endblock xxx %}

include: 包含其它html文件内容到本html中体现的是由零到一的概念;

1
{% include 'xxx' %}

marco : 宏定义(其实C语言那个宏定义类型),它可以在模板中定义函数然后在其它地方进行使用;

1
2
3
4
5
6
7
8
9
{% marco hello(name) %}
{{ name }}
{% endmarco %}

# 宏调用
{{ hello("weiyigeek") }}

# 重其它模板中导入宏定义
{% from 'xxxx' import hello,func1,func2 %}

变量声明
1
2
3
4
5
6
# 变量声明
{% set index = 0 %}
{% set index = 5 * loop.index %}

# 变量调用
{{ index }}
条件结构

for: 该标签可以向Pyton一样的使用for…else..也可以获取循环信息loop对象相关方法(first/last/index/index0/revindex/reindex0)即循环器

1
2
3
4
5
{% for item in cols %}

{% else %}

{% endfor %}


过滤器

描述:Jinja2中全套模板引擎中大概有400多个过滤器;

基础语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# syntax:
{{ 变量|过滤器|过滤器 }}

# 可用过滤器函数
capitalize # 驼峰命名法
default
last
first
length
sum
sort
lower
upper
title
trim
reverse
format
safe
striptags # 渲染前将值中标签去掉

基础示例:

  • Day3\App\templates\Tag\default.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!DOCTYPE html>
    <html lang="en">
    {% block header%}
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title%} {{ title }} {% endblock %}</title>
    </head>
    {% endblock header%}
    <body>
    {% block content %}
    <p style="color: red;"> Python Flask Deveploment Study!</p>
    <h5>Name: {{ student }}</h5>
    {% endblock content %}

    <br/>
    {% block footer %}
    <p>&copy; WeiyiGeek &copysr; Python-Flask <br><a href="https://weiyigeek.top">个人主页</a></p>
    {% endblock footer %}
    <br/>
    </body>
    </html>
  • Day3\App\templates\Tag\friends.html
    1
    2
    3
    4
    5
    6
    <div id="firends">
    <ul>
    <li>https://weiyigeek.top</li>
    <li>https://weiyigeek.top</li>
    </ul>
    </div>
  • Day3\App\templates\Tag\function.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {% macro Start(name) %}
    <span> 你好, {{ name }} </span>
    {% endmacro %}

    {% macro Product(a,b,c) %}
    <p>产品列表:</p>
    <span> {{ a }} </span>
    <span> {{ b }} </span>
    <span> {{ c }} </span>
    {% endmacro %}
  • Day3\App\templates\Tag\demo3_1.html #演示文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    {% extends 'Tag/default.html' %}

    <!DOCTYPE html>
    <html lang="en">

    <!-- 示例1.块引用 -->
    {% block header %}
    {{ super() }}
    {% endblock header %}
    <body>
    {% block content %}
    <b>块引用嵌入</b>
    {{ super() }}
    {% block firends %}
    <!-- 示例2.文件包含(需要包含在父块中) -->
    <p>友情连接: </p>
    {% include 'Tag/friends.html' %}
    {% endblock firends %}

    <hr>
    <!-- 实例3.模板中定义函数含(需要包含在父块中)使用时候一般会在一个专门的文件中 -->

    <b> 标签中自定义函数: </b> <br>
    {% macro hello_tag() %}
    <u> 我是标签模板生成的函数 </u> <br>
    {% endmacro %}

    <!-- 调用几次就生成几次 -->
    {{ hello_tag() }}
    {{ hello_tag() }}

    {% from 'Tag/function.html' import Start,Product %}
    <br>
    {{ Start("WeiyiGeek") }}
    {{ Product("Python3 入门到精通","Python 可视化编程","Python - Flask Web Development") }}

    {% endblock content %}


    {% block footer %}
    <hr>
    <b>条件循环</b>
    {% for user in users %}
    {% if loop.first %}
    <li style="color:red"> {{ loop.index }} : {{ loop.index0 }} : {{ user }}</li>
    {% elif loop.last %}
    <li style="color:green"> {{ loop.index }} : {{ loop.index0 }} : {{ user }}</li>
    {% else %}
    <li style="color:blue"> {{ loop.index }} : {{ loop.index0 }} : {{ user }}</li>
    {% endif %}
    {% else %}
    <p>循环结束</p>
    {% endfor %}

    <hr>
    <b>过滤器</b>
    <p>原始字符:{{ student }}</p>
    <p>字符|capitalize:{{ student|capitalize }}</p>
    <p>字符|upper:{{ student|upper }}</p>
    <p>字符|reverse:{{ student|reverse }}</p>
    <hr>
    {{ super() }}
    {% endblock footer %}
    </body>
    </html>
  • Day3\App\views\demo\demo3.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 视图函数演示
    from flask import Blueprint,render_template

    d3 = Blueprint("demo3",__name__)

    @d3.route("/demo3_1/")
    def demo3_1():
    users = ["C++","C","Python","Go","R","JAVA","JavaScript","PHP"]
    return render_template("Tag/demo3_1.html",title="结构标签测试",student="weiyigeek",users=users)

补充知识:

ODOO 框架是一套企业资源规划(ERP)及客户关系管理(CRM)系统。以Python语言开发,数据库采用开源的PostgreSQL,系统以GNU GPL开源协议发布。
特点: Diango还重的Web框架包括ERP和OA一些模块, 以及快速生成网站;

入坑解决

问题1.使用 Visual Studio Code 开发 Flask 程序的时候,一直提示 Instance of 'SQLAlchemy' has no 'Column' member 错误,同样的代码在其它的 IDE 就没有问题;
问题原因:有pylint导致的pylint 是一个 Python 源代码检查和高亮的工具类似的还有 flake8 等;
解决办法:关闭 pylint 启用 flake8。

1
2
3
4
5
6
7
# C:\Users\WeiyiGeek\AppData\Roaming\Code\User\settings.json
"python.linting.flake8Enabled": Ture,
"python.linting.pylintEnabled": false,
"python.linting.flake8Args": [
"--disable=E1101",
"--max-line-length=120"
]


问题2.异常排查_Python.[alembic.env] No changes in schema detected?
问题原因: 未将models模块中的类加载到程序必经之路,项目并不知道models.py 的存在,所以迁移的时候项目找不到models.py。

RESTful 作用于数据序列化方便于前后端分离;

缓存Jinja2片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 语法
{% cache [timeout [,[key1, [key2, ...]]]] %}
...
{% endcache %}

# 设置超时为无没有超时而是使用自定义键:
{% cache None "key" %}...


基础实例:
# 我们有render_form_field和render_submit宏。
{% cache 60*5 %}
<div>
<form>
{% render_form_field form.username %}
{% render_submit %}
</form>
</div>
{% endcache %}