flask

一. Flask介绍

img

Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。

Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。

其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。

官方 官方文档(中文)

Flask常用第三方扩展包:

Flask-SQLalchemy:操作数据库,ORM. 号称操作数据库最快的框架SQLalchemy;

Flask-script:终端脚本工具,脚手架;

Flask-migrate:管理迁移数据库. 比Django的更加强大, 迁移数据的过程中还可以回滚;

Flask-Session:Session存储方式指定;

Flask-WTF:表单;

Flask-Mail:邮件;

Flask-Bable:提供国际化和本地化支持,翻译;

Flask-Login:认证用户状态;

Flask-OpenID:认证, OAuth;

Flask-RESTful:开发REST API的工具;

Flask JSON-RPC: 开发rpc远程服务[过程]调用

Flask-Bootstrap:集成前端Twitter Bootstrap框架

Flask-Moment:本地化日期和时间

Flask-Admin:简单而可扩展的管理接口的框架

可以通过 https://pypi.org/search/?c=Framework+%3A%3A+Flask 查看更多flask官方推荐的扩展

二. 安装

pip3 install flask==0.12.5

三. 知识储备: werkzeug

介绍: Werkzeug是一个WSGI工具包,他可以作为一个Web框架的底层库。

补充: werkzeug 不是一个web服务器,也不是一个web框架,而是一个工具包,官方的介绍说是一个 WSGI 工具包,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西,例如 Request,Response 等等

from werkzeug.wrappers import Request, Response

@Request.application
def hello(request):
    return Response('Hello World!')

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, hello)

四. 创建flask项目

与django不同,flask不会提供任何的自动操作,所以需要手动创建项目目录,需要手动创建启动项目的管理文件

例如,创建项目目录 flaskdemo,在目录中创建manage.py.在pycharm中打开项目并指定上面创建的虚拟环境

1559027006737

1. 创建flask框架主程序

名字可以是app.py/run.py/main.py/index.py

from flask import Flask
app = Flask(__name__) 

@app.route('/')
def index():
    return 'Hello World'

if __name__ == '__main__':
    # 注意: flask默认端口5000
    app.run()

2. 代码分析

# 导入Flask类
from flask import Flask

"""
import_name      Flask程序所在的包(模块),传 __name__ 就可以
                 其可以决定 Flask 在访问静态文件时查找的路径
static_path      静态文件访问路径(不推荐使用,使用 static_url_path 代替)
static_url_path  静态文件访问路径,可以不传,默认为:/ + static_folder
static_folder    静态文件存储的文件夹,可以不传,默认为 static
template_folder  模板文件存储的文件夹,可以不传,默认为 templates
"""
app = Flask(import_name=__name__)


# 编写路由视图
# flask的路由是通过给视图添加装饰器的方式进行编写的。当然也可以分离到另一个文件中。
# flask的视图函数,flask中默认允许通过return返回html格式数据给客户端。
@app.route(rule='/')
def index():
    return '<mark>Hello Word!</make>'

# 加载项目配置
class Config(object):
    # 开启调试模式
    DEBUG = True

# flask中支持多种配置方式,通过app.config来进行加载,我们会这里常用的是配置类
app.config.from_object( Config )


# 指定服务器IP和端口
if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0", port=5000)

3. 案例:登录,显示用户信息

main.py

from flask import Flask, render_template, request, redirect, session, url_for

app = Flask(__name__)
app.debug = True  # 调试模式
app.secret_key = 'xxxx-xxxx-xxxx-xxxx'  # 跟djangosetting中的秘钥一个意思

USERS = {
    1: {'name': '张三', 'age': 18, 'gender': '男', 'text': "道路千万条"},
    2: {'name': '李四', 'age': 28, 'gender': '男', 'text': "安全第一条"},
    3: {'name': '王五', 'age': 18, 'gender': '女', 'text': "行车不规范"},
}


@app.route('/detail/<int:nid>', methods=['GET'])
def detail(nid):
    user = session.get('user_info')
    if not user:
        return redirect('/login')

    info = USERS.get(nid)
    return render_template('detail.html', info=info)


@app.route('/index', methods=['GET'])
def index():
    # 从session中拿出user_info
    user = session.get('user_info')
    if not user:
        # return redirect('/login')
        url = url_for('l1')  # 反向解析
        print(url)
        return redirect(url)
    return render_template('index.html', user_dict=USERS)


@app.route('/login', methods=['GET', 'POST'], endpoint='l1')  # endpoint路由的别名,用作反向解析
def login():
    if request.method == "GET":
        return render_template('login.html')
    else:
        # request.query_string
        # post提交过来的数据放在form中
        # get请求提交过来的数据query_string中
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user == 'lqz' and pwd == '123':
            # 往session中放入key和value
            session['user_info'] = user
            return redirect('/index')
        return render_template('login.html', error='用户名或密码错误')

# 分析源码以后的路由写法
def xxx():
    return 'xxx'


app.add_url_rule('/xxx', view_func=xxx)

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

detail.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>详细信息 {{ info.name }}</h1>
<div>
    {{ info.text }}
</div>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
    {% for k,v in user_dict.items() %}
        <tr>
            <td>{{ k }}</td>
            <td>{{ v.name }}</td>
            <td>{{ v['name'] }}</td>
            <td>{{ v.get('name') }}</td>
            <td><a href="/detail/{{ k }}">查看详细</a></td>
        </tr>
    {% endfor %}
</table>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户登录</h1>
<form method="post">
    <input type="text" name="user">
    <input type="text" name="pwd">
    <input type="submit" value="登录">{{ error }}
</form>
</body>
</html>

4. 总结

# Flask三板斧:
    return '<mark>字符串</mark>'  # 提示: 支持HTML语法
    return render_template('index.html')
    return redirect('/login')

# 路由写法(路径,支持的请求方式,别名)
    # 基本的路由写法
    @app.route('/login',methods=['GET','POST'],endpoint='l1')
    # 分析源码以后的路由写法
    def xxx():
        return 'xxx'
    app.add_url_rule('/xxx', view_func=xxx)

# 模板语言渲染
    同dtl,但是比dtl强大,支持加括号执行,字典支持中括号取值和get取值

# 分组(django中的有名分组)
    @app.route('/detail/<int:nid>',methods=['GET'])
    def detail(nid):

# 反向解析
    url_for('别名')

# 获取前端传递过来的数据
    # get 请求
        request.query_string
    # post请求
        user = request.form.get('user')
        pwd = request.form.get('pwd')

5. 小练习

需求: 1.多个装饰器执行顺序. 2.反向查找的名称

# 多个装饰器执行顺序
从下到上. 因此如果是登录认证装饰器, 因该放在路由装饰器下面

# 反向查找的名称:  (注意: 别名不允许重复)
endpoint

五. 配置文件

1. 三种基本配置

# 第一种方式
# app的配置文件全在config字典中,但是有一些常用的,比如debug,会直接提到app这一层
app.debug = True
app.config['DEBUG'] = False


# 第二种方式
'''
# settings.py  注意: settings.py是与配置配置文件的加载文件同一级别
DEBUG = False
'''
app.config.from_pyfile("settings.py")


# 第三种
'''
# mysettings.py 注意: mysettings.py是与配置配置文件的加载文件同一级别
DEBUG = False
class Config(object):
    DEBUG = False
    TESTING = False
    DATABASE_URI = 'sqlite://:memory:'


class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'


class DevelopmentConfig(Config):
    DEBUG = False


class TestingConfig(Config):
    DEBUG = True
'''
app.config.from_object("mysettings.DevelopmentConfig")

2. 其它配置

# 提示: 从sys.path中已经存在路径开始写
# 提示: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录(Flask对象init方法的参数)


# 通过环境变量配置: 环境变量的值为python文件名称名称,内部调用from_pyfile方法
app.config.from_envvar("环境变量名称")
#app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])


# JSON文件名称,必须是json格式,因为内部会执行json.loads
app.config.from_json("json文件名称")


# 字典格式
app.config.from_mapping({'DEBUG': True})

3. 默认配置

# flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为:
{
    'DEBUG': False,                          # 是否开启Debug模式
    'TESTING': False,                        # 是否开启测试模式
    'PROPAGATE_EXCEPTIONS': None,            # 异常传播(是否在控制台打印LOG) 当Debug或者testing开启后,自动为True
    'PRESERVE_CONTEXT_ON_EXCEPTION': None,   # 一两句话说不清楚,一般不用它
    'SECRET_KEY': None,                      # 之前遇到过,在启用Session的时候,一定要有它
    'PERMANENT_SESSION_LIFETIME': 31,        # days , Session的生命周期(天)默认31天
    'USE_X_SENDFILE': False,                 # 是否弃用 x_sendfile
    'LOGGER_NAME': None,                     # 日志记录器的名称
    'LOGGER_HANDLER_POLICY': 'always',
    'SERVER_NAME': None,                     # 服务访问域名
    'APPLICATION_ROOT': None,                # 项目的完整路径
    'SESSION_COOKIE_NAME': 'session',        # 在cookies中存放session加密字符串的名字
    'SESSION_COOKIE_DOMAIN': None,           # 在哪个域名下会产生session记录在cookies中
    'SESSION_COOKIE_PATH': None,             # cookies的路径
    'SESSION_COOKIE_HTTPONLY': True,         # 控制 cookie 是否应被设置 httponly 的标志,
    'SESSION_COOKIE_SECURE': False,          # 控制 cookie 是否应被设置安全标志
    'SESSION_REFRESH_EACH_REQUEST': True,    # 这个标志控制永久会话如何刷新
    'MAX_CONTENT_LENGTH': None,              # 如果设置为字节数, Flask 会拒绝内容长度大于此值的请求进入,并返回一个 413 状态码
    'SEND_FILE_MAX_AGE_DEFAULT': 12,         # hours 默认缓存控制的最大期限
    'TRAP_BAD_REQUEST_ERRORS': False,        # 如果这个值被设置为 True ,Flask不会执行 HTTP 异常的错误处理,而是像对待其它异常一样, 通过异常栈让它冒泡地抛出。这对于需要找出 HTTP 异常源头的可怕调试情形是有用的。
    'TRAP_HTTP_EXCEPTIONS': False,           # Werkzeug 处理请求中的特定数据的内部数据结构会抛出同样也是“错误的请求”异常的特殊的 key errors 。同样地,为了保持一致,许多操作可以显式地抛出 BadRequest 异常。因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。
    'EXPLAIN_TEMPLATE_LOADING': False,       # 如果这个值被设置为 True ,你只会得到常规的回溯。
    'PREFERRED_URL_SCHEME': 'http',          # 生成URL的时候如果没有可用的 URL 模式话将使用这个值
    'JSON_AS_ASCII': True,                   # 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False , Flask不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。
    'JSON_SORT_KEYS': True,                  # 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。
    
    
    
    'JSONIFY_PRETTYPRINT_REGULAR': True,     # 默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。
    'JSONIFY_MIMETYPE': 'application/json',  # 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。
    'TEMPLATES_AUTO_RELOAD': None,           # 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。
} 

六. 路由

1. 路由的基本定义

注意: 路由和视图的名称必须全局唯一,不能出现重复,否则报错。

# 指定访问路径为 demo
@app.route(rule='/demo')
def demo():
    return "demo"

2. 路由参数介绍

# 提示: 即是@app.route的参数, 本质也是app.add_url_rule的参数
rule='/index'          # 符合URL规则的路由
view_func=index        # 视图函数名称
defaults=None          # 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}可以为函数提供默认参数
endpoint=None          # 名称,用于反向生成URL,即: url_for('名称')
methods=None           # 允许的请求方式,如:["GET", "post"] 大小写都可以, 内部会执行upper()

strict_slashes=None    # 对URL最后的 / 符号是否严格要求, 默认严格. False就是不严格
    @app.route('/index', strict_slashes=False)
    # 访问 http://www.xx.com/index/ 或 http://www.xx.com/index 的路由格式都可以
    
    @app.route('/index', strict_slashes=True)
    # 仅支持这种路由格式访问: http://www.xx.com/index
    
redirect_to = None      # 重定向到指定地址
    @app.route('/index/<int:nid>', redirect_to='/home/<nid>')
    
subdomain = None        # 子域名访问
    # C:\Windows\System32\drivers\etc\hosts
    127.0.0.1       www.liuqingzheng.com
    127.0.0.1       admin.liuqingzheng.com
    127.0.0.1       buy.liuqingzheng.com
    
    # 示例
    from flask import Flask, views, url_for
    
    app = Flask(import_name=__name__)
    app.config['SERVER_NAME'] = 'liuqingzheng.com:5000'
    
    @app.route("/", subdomain="admin")
    def static_index():
        return "static.your-domain.tld"
        
    # 可以传入任意的字符串,如传入的字符串为aa,显示为 aa.liuqingzheng.com
    @app.route("/dynamic", subdomain="<username>")
    def username_index(username):
        return username + ".your-domain.tld"
        
    if __name__ == '__main__':
        app.run()
        
    # 支持访问的路由需要包含如下的域名访问格式:www dynamic admin
    http://www.liuqingzheng.com:5000/dynamic
    http://admin.liuqingzheng.com:5000/dynamic
    http://buy.liuqingzheng.com:5000/dynamic

3. 路由参数接收

提示: 路由参数就是url路径的一部分。

1) 任意路由参数接收

# 不限定类型的路由参数传递
@app.route(rule='/user/<id>')
def user(id):  # 接受参数
    return "id=%s的用户中心" % id


# 路由参数理论上可以存在多个
# 参数名建议不要使用单字母,因为有些单字母在框架中默认被占用了。
@app.route(rule='/user1/<id>/<page>')
def user1(id, page):  # 接受参数
    return "id=%s,page=%s" % (id, page)

2) 限定路由参数接收: 转换器

系统自带的转换器具体使用方式在每种转换器的注释代码中有写,请留意每种转换器初始化的参数。

转换器名称 描述
string 默认类型,接受不带斜杠的任何文本
int 接受正整数
float 接受正浮点值
path 接收string但也接受斜线
uuid 接受UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx
# 限定类型的路由参数传递
# 路由格式:<类型:参数名>
# 路由参数的类型,flask支持 int整型,float浮点数,path路径,uuid唯一识别码
@app.route(rule='/user2/<int:id>')
def user2(id):
    print('type(id), id:', type(id), id)  # type(id), id: <class 'int'> 1
    return f'<mark>Hello {id}!</make>'


@app.route(rule='/user3/<string:id>')
def user3(id):
    print('type(id), id:', type(id), id)  # type(id), id: <class 'str'> zcdsb123
    return f'<mark>Hello {id}!</make>'


@app.route(rule='/user4/<float:id>')
def user4(id):
    print('type(id), id:', type(id), id)  # type(id), id: <class 'float'> 1.1
    return f'<mark>Hello {id}!</make>'


@app.route(rule='/user5/<path:id>')
def user5(id):
    print('type(id), id:', type(id), id)  # type(id), id: <class 'str'> lxdsb/zcdsb
    return f'<mark>Hello {id}!</make>'


@app.route(rule='/user6/<uuid:id>')
def user6(id):
    print('type(id), id:', type(id), id)  # type(id), id: <class 'uuid.UUID'> 95db2e6c-e7a7-11ea-9ca3-48ba4e4e6384
    return f'<mark>Hello {id}!</make>'

4. 默认转换器配置

限定路由参数的类型,flask系统自带转换器编写在werkzeug.routing.py文件中。底部可以看到以下字典:

DEFAULT_CONVERTERS = {
   "default": UnicodeConverter,
   "string": UnicodeConverter,
   "any": AnyConverter,
   "path": PathConverter,
   "int": IntegerConverter,
   "float": FloatConverter,
   "uuid": UUIDConverter,
}

5. 限定路由请求方式

from flask import Flask,request
# 限制客户端的http请求方法,注意这里与django不一样,flask并没有默认没有内置csrf攻击防范
@app.route(rule="/add", methods=["post","put","get","delete","patch"])
def add():
    # 直接从请求中取到请求方式并返回
    return request.method

6. 路由支持正则: 自定义转换器

步骤:

1.导入转换器基类:在 Flask 中,所有的路由的匹配规则都是使用转换器对象进行记录

2.自定义转换器:自定义类继承于转换器基类

3.添加转换器到默认的转换器字典中

4.使用自定义转换器实现自定义匹配规则

1) JGL模式

from flask import Flask, request

# 初始化
app = Flask(import_name=__name__)


# 编写路由视图
@app.route(rule='/')
def index():
    return "<h1>hello world!</h1>"


# 关于路由参数的限制,flask内置的类型不够具体,在开发中,我们经常接受参数,需要更加精确的限制
# 这时候,可以使用正则匹配路由参数
# 正则匹配路由参数,其实就是扩展flask内置的路由限定类型,需要完成4个步骤
# 1. 引入flask的路由转换器
from werkzeug.routing import BaseConverter


# 2. 创建自定义路由转换器
class MobileConverter(BaseConverter):
    """手机号码类型限制"""

    def __init__(self, map, *args):
        super().__init__(map)
        self.regex = "1[3-9]\d{9}"


# 3. 把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块
app.url_map.converters['mob'] = MobileConverter


# 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可
@app.route(rule='/user/<mob:mobile>')
def user(mobile):
    return mobile


# 1. 引入flask的路由转换器
from werkzeug.routing import BaseConverter


# 2. 创建自定义路由转换器
class RegexConverter(BaseConverter):
    """根据正则进行参数限制"""

    def __init__(self, map, *args):
        super().__init__(map)
        self.regex = args[0]  # args=(\w+@\w+\.\w+, )


# 3. 把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块
app.url_map.converters['re'] = RegexConverter


# 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可
@app.route(rule='/user/<re("\w+@\w+\.\w+"):email>')
def user2(email):
    print(app.url_map)  # 获取所有的路由列表
    return email


# 声明和加载配置
class Config():
    DEBUG = True


app.config.from_object(Config)

if __name__ == '__main__':
    # 运行flask
    app.run(port=8000)

2) LQZ模式

# 流程: 
"""
1. 写类,继承BaseConverter
2. 注册:app.url_map.converters['regex'] = RegexConverter
3. 使用:@app.route('/index/<regex("\d+"):nid>')  正则表达式会当作第二个参数传递到类中
"""

from flask import Flask, views, url_for
from werkzeug.routing import BaseConverter

app = Flask(import_name=__name__)


class RegexConverter(BaseConverter):
    """
    自定义URL匹配正则表达式
    """

    def __init__(self, map, regex):
        super(RegexConverter, self).__init__(map)
        self.regex = regex

    def to_python(self, value):
        """
        路由匹配时,匹配成功后传递给视图函数中参数的值
        """
        return int(value)

    def to_url(self, value):
        """
        使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
        """
        val = super(RegexConverter, self).to_url(value)
        return val


# 添加到flask中
app.url_map.converters['regex'] = RegexConverter


@app.route('/index/<regex("\d+"):nid>')
def index(nid):
    print(url_for('index', nid='888'))
    return 'Index'


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

七. CBV模式使用

from flask import Flask, request, render_template, redirect
from flask import views

app = Flask(__name__)


def auth(func):
    def inner(*args, **kwargs):
        print('before')
        result = func(*args, **kwargs)
        print('after')
        return result

    return inner

# 方式一: 继承View
'''
class IndexView(views.View):
    methods = ['GET']

    # decorators = [auth, ]
    def dispatch_request(self):
        print('Index')
        return 'Index!'
'''


# 方式二: 继承MethodView
class IndexView(views.MethodView):
    # 指定运行的请求方法
    methods = ['GET']
    # 装饰器: 加多个装饰器的执行顺序就是从上往下的效果
    decorators = [auth, ] 

    def get(self):
        return "我是get请求"

    def post(self):
        return '我是post请求'


# CBV路由注册
# IndexView.as_view('index'): 必须传name, name是该路由用于反向解析时的别名
app.add_url_rule('/index', view_func=IndexView.as_view('index'))

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

八. 模板

1. 渲染变量

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>用户列表</h1>
    <table>
        {% for k,v in user_dict.items() %}
        <tr>
            <td>{{k}}</td>
            <td>{{v.name}}</td>
            <td>{{v['name']}}</td>
            <td>{{v.get('name')}}</td>
            <td><a href="/detail/{{k}}">查看详细</a></td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

2. 变量的循环

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>用户列表</h1>
    <table>
        {% for k,v in user_dict.items() %}
        <tr>
            <td>{{k}}</td>
            <td>{{v.name}}</td>
            <td>{{v['name']}}</td>
            <td>{{v.get('name')}}</td>
            <td><a href="/detail/{{k}}">查看详细</a></td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

3. 逻辑判断

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>用户列表</h1>
    <table>
        {% if name %}
          <h1>Hello {{ name }}!</h1>
        {% else %}
          <h1>Hello World!</h1>
        {% endif %}
    </table>
</body>
</html>

4. 处理XSS攻击

# app.py
from flask import Flask,render_template,Markup,jsonify,make_response

app = Flask(__name__)

def func1(arg):
    # Markup 类似于 Django中的make_save
    return Markup("<input type='text' value='%s' />" %(arg,))

@app.route('/')
def index():
    return render_template('index.html',ff = func1)

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


# index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {{ff('六五')}}
    <!-- safe和Django中过滤器中的safe方法一致 -->
	{{ff('六五')|safe}}
</body>
</html>

九. 请求与响应对象

from flask import Flask, request, jsonify, make_response

app = Flask(__name__)
app.debug = True


@app.route('/', methods=['GET', 'POST'])
def index():
    from werkzeug.datastructures import CombinedMultiDict
    # --------------------------- 请求对象 ---------------------------
    # 获取当前请求方法
    print(request.method)  # POST

    # 获取get请求提交的数据
    print(request.args)  # ImmutableMultiDict([('name', 'yang'), ('age', '18')])
    print(type(request.args))  # <class 'werkzeug.datastructures.ImmutableMultiDict'>
    print(request.args.get('name'))  # yang
    from werkzeug.datastructures import ImmutableMultiDict

    # 获取get形式提交的数据(提示: 需要自己转)
    print(request.query_string)  # b'name=yang&age=18'

    # 获取post请求提交的数据
    print(request.form)  # ImmutableMultiDict([('name', 'yang666'), ('age', '22')])

    # 获取文件
    print(
        request.files)  # ImmutableMultiDict([('avatar', <FileStorage: 'default.jpg' ('image/jpeg')>), ('sql_file', <FileStorage: 'luffyapi.sql' ('application/x-sql')>)])
    print(request.files.get('avatar'))  # FileStorage: 'default.jpg' ('image/jpeg')>

    # 获取post和get提交的数据总和
    print(
        request.values)  # CombinedMultiDict([ImmutableMultiDict([('name', 'yang'), ('age', '18')]), ImmutableMultiDict([('name', 'yang666'), ('age', '22')])])
    print(request.values.get('name'))  # yang
    print(request.values.get('age'))  # 18
    print(request.values.getlist('name'))  # ['yang', 'yang666']

    # 获取客户端所带的cookie
    print(request.cookies)  # {'key': 'woshicookies', 'user': 'yang'}

    # 获取请求头中所携带的数据
    print(request.headers)
    '''
    Yang: xiudetianhualuanzui
    Cookie: key=woshicookies; xxx=lqz
    User-Agent: PostmanRuntime/7.26.3
    Accept: */*
    Postman-Token: 40bb7ca6-ba5b-4d1a-9732-1f49cfef954f
    Host: 127.0.0.1:8080
    Accept-Encoding: gzip, deflate, br
    Connection: keep-alive
    Content-Type: multipart/form-data; boundary=--------------------------487226677414449536798986
    Content-Length: 65491
    '''

    # 获取不带域名的请求路径
    print(request.full_path)  # /?name=yang&age=18
    # 获取不带域名只带参数的请求路径
    print(request.path)  # /

    # 获取带域名带参数的请求路径
    print(request.url)  # http://127.0.0.1:8080/?name=yang&age=18
    # 获取带域名请求路径
    print(request.base_url)  # http://www.yang1333.com:8080/
    # 获取域名
    print(request.url_root)  # http://www.yang1333.com:8080/
    # 获取域名
    print(request.host_url)  # http://www.yang1333.com:8080/
    # 获取当前请求用户IP: 127.0.0.1:500
    print(request.host)  # www.yang1333.com:8080

    # --------------------------- 响应对象 ---------------------------
    # 返回三板斧: 字符串, render_template, redirect
    return "字符串"
    return render_template('html模板路径', **{})
    return redirect('/index.html')

    # 返回JSON格式数据: 类似于Django中的JsonResponse对象
    return jsonify({'k1': 'v1'})

    # 返回response对象
    string = 'hello world'
    response = make_response(string)

    # 设置cookie操作
    response.set_cookie('key', 'value')
    response.delete_cookie('key')

    # 设置响应头
    response.headers['X-Something'] = 'A value'

    # response是flask.wrappers.Response类型
    print(type(response))
    from flask.wrappers import Response

    return response


if __name__ == '__main__':
    print(app.config)
    app.run(port=8080)

十. session

知识储备

'''
cookie:  存放在客户端的键值对
session:存放在客户端的键值对
token:   存放在客户端,通过算法来校验
'''

注意: 在使用session之前必须现在设置一下密钥

app.secret_key="session_key"  # 值随便

使用:

from flask import Flask, request, jsonify, make_response, session

app = Flask(__name__)
app.debug = True
# session实现的地址: app.session_interface
# 注意: 在使用session之前必须现在设置一下密钥
app.secret_key = 'session_key'


@app.route('/', methods=['GET', 'POST'])
def index():
    # 设置session
    session['name'] = 'lqz'
    return 'hello'


@app.route('/delete', methods=['GET', 'POST'])
def delete():
    # 删除session
    session.pop('name')
    return '删除了'


@app.route('/order', methods=['GET', 'POST'])
def order():
    print(session['name'])
    return 'order'


if __name__ == '__main__':
    print(app.config)
    app.run()

十一. 闪现

使用

# 提示: flash存值
from flask import flash, get_flashed_messages

# 简单设置
'''
添值: flash('yang-nb')
取值: res=get_flashed_messages()  # 取全部
'''

# 分类设置
'''
添值: flash('yang', category='error1')
取值: res=get_flashed_messages(category_filter=['error1'])
'''

# 作用: a页面操作出错,跳转到b页面,在b页面显示a页面的错误信息

示例

from flask import Flask, flash, get_flashed_messages, request, redirect

app = Flask(__name__)
app.debug = True
app.secret_key = 'asdfasdf'


@app.route('/index')
def index():
    # 从某个地方获取设置过的所有值,并清除。
    val = request.args.get('v')
    if val == 'oldboy':
        return 'Hello World!'
    flash('超时错误', category="x1")
    # return "ssdsdsdfsd"
    return redirect('/error')


@app.route('/error')
def error():
    """
    展示错误信息
    :return:
    如果get_flashed_messages(with_category=True)
    """
    data = get_flashed_messages(category_filter=['x1'])
    if data:
        msg = data[0]
    else:
        msg = "..."
    return "错误信息:%s" % (msg,)


if __name__ == '__main__':
    app.run(port=8080)

十二. 请求扩展

1. before_request

作用: 类比django中间件中的process_request,在请求到来执行路由函数之前先执行. 但是如果有多个顺序是从上往下执行.

应用: 基于它做用户登录认证

注意: 如果before_request的返回值不是None的清空下, 返回的而是别的值, 那么后续的请求都不会执行,本次请求直接返回, 如果定义了after_request那么会接着它执行, 最终本次请求响应结束.

@app.before_request
def process_request(*args,**kwargs):
    if request.path == '/login':
        return None
    user = session.get('user_info')
    if user:
        return None
    return redirect('/login') 

2. after_request

作用: 类比django中间件中的process_response,如果请求没有出现异常的情况下, 会在请求返回return之前执行. 但是如果有多个顺序是从下往上执行.

@app.after_request  # 后执行
def process_response1(response):
    print('process_response1 走了')
    return response

@app.after_request  # 先执行
def process_response2(response):
    print('process_response2 走了')
    return response

3. before_first_request

作用: 项目启动起来接收到第一次请求时执行.

应用: 项目初始化用来保证以后项目只要不重启就不再继续执行.

@app.before_first_request
def first():
    print('我的第一次')

4. teardown_request

作用: 在每一个路由函数执行完毕之后执行,即使遇到了异常也会执行. (提示: 返回reutrn没有任何的效果, 不能控制返回的结果)

应用: 记录日志

@app.teardown_request  
def ter(e):  # e就是上一个路由函授执行过程中出现被捕获的异常信息.
    print(e)
    print('我是teardown_request ')

5. errorhandler

作用: 绑定错误的状态码进而可以捕获服务器的错误, 并返回对应的错误页面.

@app.errorhandler(500)
def error_500(arg):
    return render_template('error.html', message='500错误')


@app.errorhandler(404)
def error_404(arg):
    return render_template('error.html', message='404错误')

6. template_global

作用: 全局的标签, 在任意的html页面中就可以直接使用, 不需要在render_template中传递参数以后才能使用.

@app.template_global()
def sb(a1, a2):
    return a1 + a2

# html页面中直接使用, 不需要传递参数.
{{ sb(1,2) }}

7. template_filter

作用: 全局的过滤器, 在任意的html页面中就可以直接使用, 不需要在render_template中传递参数以后才能使用.

@app.template_filter()
def db(a1, a2, a3):
    return a1 + a2 + a3

# html页面中直接使用, 不需要传递参数. 其中1传递给a1, 2传递给a2, 3传递给a3. (提示: Django中的过滤器最多只可以传递二个参数)
{{ 1|db(2,3) }}

十三. 中间件

class Md(object):
    def __init__(self, old_wsgi_app):
        self.old_wsgi_app = old_wsgi_app

    def __call__(self, environ, start_response):
        print('开始之前')
        ret = self.old_wsgi_app(environ, start_response)
        print('结束之后')
        return ret


if __name__ == '__main__':
    # 1. 我们发现当执行app.run方法的时候,最终执行run_simple,最后执行app(),也就是在执行app.__call__方法
    # 2. 在__call__里面,执行的是self.wsgi_app().那我们希望在执行他本身的wsgi之前做点事情。
    # 3. 所以我们先用Md类中__init__,保存之前的wsgi,然后我们用将app.wsgi转化成Md的对象。
    # 4. 那执行新的的app.wsgi_app,就是执行Md的__call__方法。
    # 5. 最后把原来的wsgi_app替换为自定义的

    app.wsgi_app = Md(app.wsgi_app)
    app.run()

十四. 蓝图(Blueprint)

1. 不使用蓝图分文件

目录结构:

image-20200830105959945

flask_project/
├── templates
    ├── __init__.py
├── views   
    ├── __init__.py
    ├── user.py 
    └── order.py     
└── app.py  

app.py

from views import app

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

views/__init__.py

from flask import Flask

app = Flask(__name__)

from . import order
from . import user

views/user.py

from . import app


@app.route('/user')
def user():
    return 'user'

views/order.py

from . import app


@app.route('/order')
def order():
    return 'order'

2. 使用蓝图分文件

目录结构:

image-20200830105959945

flask_project/
├── templates
    ├── __init__.py
├── views   
    ├── __init__.py
    ├── user.py 
    └── order.py     
└── app.py  

app.py

from views import app

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

views/__init__.py

from flask import Flask

app = Flask(__name__)

from . import order
from . import user

# 第一步在app中注册蓝图,括号里是一个蓝图对象
# 第二步,在不同文件中注册路由时,直接使用蓝图对象注册,不用使用app了,避免了循环导入的问题
app.register_blueprint(user.us)
app.register_blueprint(order.ord)

views/user.py

from flask import Blueprint

us = Blueprint('user', __name__)  # 以后再注册路由,使用蓝图对象,app对象有什么,蓝图对象就有什么


@us.route('/user')
def user():
    return 'user'

views/order.py

from flask import Blueprint

ord = Blueprint('order', __name__)


# 提示: 
'''
只有在访问该蓝图对象的路由时before_request才会触发执行
	如: https://127.0.0.1:5000/order. 
如果访问的是user中的蓝图, 或者app中的路由, 并不会触发before_request的执行
'''

@ord.before_request
def before():
    print('order-before_request')


@ord.route('/order')
def order():
    return 'order'

3. 使用蓝图之中小型系统


4. 使用蓝图之大型系统


posted @ 2020-09-08 16:19  给你加马桶唱疏通  阅读(282)  评论(0编辑  收藏  举报