• Flask 基础


    Flask 基础

    Flask 是一个使用 Python 编写的轻量级 Web 应用框架。基于 Werkzeug WSGI 工具箱和 Jinja2 模板引擎。

    Flask 使用 BSD 授权。Flask 被称为“microframework”,因为它使用简单的核心,用 extension 增加其他功能。Flask 没有默认使用的数据库、窗体验证工具。然而,Flask 保留了扩增的弹性,可以用 Flask-extension 加入这些功能:ORM、窗体验证工具、文件上传、各种开放式身份验证技术。

    1 安装

    pip3 install flask

    2 基本使用

    2.1 werkzeug

    与 Django 类似,Flask 是基于 werkzeug 实现的。通过 werkzeug 即可简单实现最基本的返回数据。

    from werkzeug.wrappers import Request, Response
    from werkzeug.serving import run_simple
    
    def run(environ, strat_response):
        return [b"asdf"]
    
    
    if __name__ == '__main__':
    
        run_simple("localhost", 4000, run)
    
    

    亦可使用 Response 直接返回字符串,注意导入方式与 Django 不同。

    from werkzeug.wrappers import Request, Response
    from werkzeug.serving import run_simple
    
    @Request.application
    def hello(request):
        return Response('Hello World')
    
    
    if __name__ == '__main__':
    
        run_simple("localhost", 4000, hello)
    
    

    2.2 使用 Flask

    可将 Flask 看作一个类,通过 app=Flask(__name__) 进行实例化,app.run() 用以启动 Flask 。

    在函数上增加 @app.route('/index') 将函数变成视图函数,而在 route 中即可定义访问该视图函数的路由。

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/index')
    def hello():
        return "Hello World"
    
    
    if __name__ == '__main__':
    
        app.run()
    

    2.3 Flask 实现简单的登录功能

    学习 Flask 时可以类比着 Django 的学习。Django 中有的 request redirect session ,Flask 中也有,不过, 同样是实现渲染功能的 render 函数在 Flask 中变成了 render_template 。Flask 的传参方式与 Django 一样,都是直接赋值 user=user

    Flask 默认只支持 GET 方式传数据,如果想要支持更多的数据传输方式,可在 route 中加上 methods 属性来设置。@app.route('/login', methods=["GET", "POST"])。如果存在 POST 请求,则需要设置 Flask 秘钥来确保传输安全。 app.secret_key = "fasd"

    Flask 默认 HTML 文件放在 templates 文件中。

    app.py

    from flask import Flask, render_template, request, redirect, session
    
    
    app = Flask(__name__)
    # Flask的秘钥
    app.secret_key = "fasd"
    
    @app.route('/login', methods=["GET", "POST"])
    def login():
        if request.method == "GET":
            return render_template('login.html')
    
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user == 'ban' and pwd == 'ban':
            session['user'] = user
            return redirect('/index')
    
        return render_template('login.html', error="用户名或密码错误")
    
    @app.route('/index')
    def index():
        user = session.get('user')
        if not user:
            redirect('/login')
    
        return render_template('index.html', user=user)
    
    
    if __name__ == '__main__':
        app.run()
    

    3 配置文件

    3.1 复习:通过字符串找类

    给你一个路径 “settings.Foo”,可以找到类并获取去其中的大写的静态字段。

    首先,使用 rsplit 函数将字符串分成模块和类,再使用 importlib 导入模块。然后使用 getattr 函数获取模块中的类。最后,通过 dir 函数获取类中的字段。

    settings.py

    settings.py
        class Foo:
            DEBUG = True
            TEST = True
    

    xx.py

    import importlib
    
    path = "settings.Foo"
    
    p,c = path.rsplit('.',maxsplit=1)
    m = importlib.import_module(p)
    cls = getattr(m,c)
    
    # 找这个类中的字段
    for key in dir(cls):
        if key.isupper():
            print(key,getattr(cls,key))
    

    3.2 Flask 配置文件

    Flask 中的配置文件是一个 flask.config.Config 对象(继承字典),默认配置为:

    
     {
            'DEBUG':                                get_debug_flag(default=False),  是否开启Debug模式
            'TESTING':                              False,                          是否开启测试模式
            'PROPAGATE_EXCEPTIONS':                 None,                          
            'PRESERVE_CONTEXT_ON_EXCEPTION':        None,
            'SECRET_KEY':                           None,
            'PERMANENT_SESSION_LIFETIME':           timedelta(days=31),
            'USE_X_SENDFILE':                       False,
            'LOGGER_NAME':                          None,
            'LOGGER_HANDLER_POLICY':               'always',
            'SERVER_NAME':                          None,
            'APPLICATION_ROOT':                     None,
            'SESSION_COOKIE_NAME':                  'session',
            'SESSION_COOKIE_DOMAIN':                None,
            'SESSION_COOKIE_PATH':                  None,
            'SESSION_COOKIE_HTTPONLY':              True,
            'SESSION_COOKIE_SECURE':                False,
            'SESSION_REFRESH_EACH_REQUEST':         True,
            'MAX_CONTENT_LENGTH':                   None,
            'SEND_FILE_MAX_AGE_DEFAULT':            timedelta(hours=12),
            'TRAP_BAD_REQUEST_ERRORS':              False,
            'TRAP_HTTP_EXCEPTIONS':                 False,
            'EXPLAIN_TEMPLATE_LOADING':             False,
            'PREFERRED_URL_SCHEME':                 'http',
            'JSON_AS_ASCII':                        True,
            'JSON_SORT_KEYS':                       True,
            'JSONIFY_PRETTYPRINT_REGULAR':          True,
            'JSONIFY_MIMETYPE':                     'application/json',
            'TEMPLATES_AUTO_RELOAD':                None,
        }
    
    

    1. 配置方式一

    通过 app.config['属性']=值 的方式配置。app.config['DEBUG'] = True

    由于 Config 对象本质上是字典,所以还可以使用 app.config.update(...) 与更新字典一致的操作进行配置。

    2. 配置方式二

    直接将配置信息写在 Python 文件中,用 app.config.from_pyfile("python文件名称") 的方式进行配置。

    settings.py

     DEBUG = True
    
    
    app.config.from_pyfile("settings.py")
    

    或者  app.config.from_envvar("环境变量名称") ,其中环境变量的值为 Python 文件的名称。

    3. 配置方式三

    直接将配置信息写在 json 文件中,用 app.config.from_json("json文件名称") 的方式进行配置。且文件内必须是 json 格式,因为内部会执行 json.loads

    以字典格式直接配置:app.config.from_mapping({'DEBUG':True})

    4. 配置方式四(推荐)

    Flask 支持以模块中类的方式引入配置信息。即将配置信息以字段与值的方式放到另外的文件里的类中,通过 app.config.from_object("python类或类的路径") 代码进行配置。

    settings.py

    from datetime import timedelta
    class Config(object):
        DEBUG = False
        TESTING = False
        SECRET_KEY = "asdfasdfas23"
        DATABASE_URI = 'sqlite://:memory:'
    
        SESSION_COOKIE_NAME = 'session'
        SESSION_COOKIE_DOMAIN = None
        SESSION_COOKIE_PATH = None
        SESSION_COOKIE_HTTPONLY = True
        SESSION_COOKIE_SECURE = False
        SESSION_REFRESH_EACH_REQUEST = True
        PERMANENT_SESSION_LIFETIME = timedelta(hours=1)
    
    
    class ProductionConfig(Config):
        DATABASE_URI = 'mysql://user@localhost/foo'
    
    
    class DevelopmentConfig(Config):
        DEBUG = True
    
    
    class TestingConfig(Config):
        TESTING = True
    

    app.py

    app.config.from_object("settings.DevelopmentConfig")
    

    这种导入配置信息的优点有:

    1. 更改配置信息方便,只需在运行文件中改一下配置信息类即可
      例如,开发时的配置信息和上线时的配置信息有所不同,采用这种方式配置,只需在文件中改下配置类名即可。
    2. 借助类可以继承的特性,使得配置信息也可以继承,从而减少冗余。

    4 路由系统

    前面已经介绍了 Flask 的路由系统由装饰器构成。并且介绍到可以在其中设置支持的数据传输方式: @app.route('/login', methods=['GET', 'POST'])

    4.1 别名与反向获取 URL

    在 route 函数中加上 endpoint 字段设置路由别名,如果没有设置 endpoint 字段,默认别名为函数名。

    通过别名反向获取 URL 需要使用 url_for 函数。此函数形式为 url_for(endpoint, *args) 其中第一个参数为路由别名,第二个参数为要为 URL 传的值。注意,url_for 需要导入。

    from flask import url_for
    
    url_for('endpoint')
    
    url_for("index",nid=777)
    

    4.2 动态路由

    我们常常需要在 url 中进行传值操作,比如 uid 等。此时就需要用到动态路由。

    使用方式为 route('url/<类型:变量名>'),其中,类型和变量名之前的 ':' 不能有空格,如果省略类型则默认为字符串类型。

    常用路由系统有以上五种,所有的路由系统都是基于一下对应关系来处理:

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

    5 请求与响应

    5.1 请求相关信息

    # 请求相关信息
    # request.method
    # request.args
    # request.form
    # request.values
    # request.cookies
    # request.headers
    # request.path
    # request.full_path
    # request.script_root
    # request.url
    # request.base_url
    # request.url_root
    # request.host_url
    # request.host
    # request.files
    # obj = request.files['the_file_name']
    # obj.save('/var/www/uploads/' + secure_filename(f.filename))
    

    5.2 响应相关

    1. 响应体相关信息

    1. 返回字符串
      return “asdf"
    2. 返回 json 数据
    from flask import jsonify
    
    return jsonify({'k1':'v1'})
    
    1. 渲染模板
      return render_template('xxx.html')
    2. 跳转链接
      return redirect()

    2. 定制响应头

    响应由响应头和响应体组成,响应头可以先进行封装再返回。headers 字段来设置响应头中的字段值,此外还能设置 cookie 。

    from flask import make_response
    
    obj = make_response("asdf")
    obj.headers['xxxxxxx'] = '123'
    obj.set_cookie('key', 'value')
    return obj
    

    示例:

    @app.route('/index')
    def index():
        obj = make_response(render_template('index.html', stu_dic=STUDENT_DICT))
        obj.headers['xxxx'] = '123'
        return obj
    

    response_header_DIY

    6 示例程序:学生管理

    6.1 基本功能实现

    对学生信息进行详情查看或者删除。

    app.py

    #!/usr/bin/env python  
    #-*- coding:utf-8 -*-  
    
    from flask import Flask, render_template, request, redirect, session, url_for, make_response
    
    
    app = Flask(__name__)
    
    app.config.from_object("settings.DevelopmentConfig")
    # Flask的秘钥
    app.secret_key = "fasd"
    
    STUDENT_DICT = {
        1:{'name':'王龙泰','age':38,'gender':'中'},
        2:{'name':'小东北','age':73,'gender':'男'},
        3:{'name':'田硕','age':84,'gender':'男'},
    }
    
    
    @app.route('/login', methods=["GET", "POST"])
    def login():
        if request.method == "GET":
            return render_template('login.html')
    
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user == 'ban' and pwd == 'ban':
            session['user'] = user
            return redirect('/index')
    
        return render_template('login.html', error="用户名或密码错误")
    
    
    @app.route('/index')
    def index():
        # obj = make_response(render_template('index.html', stu_dic=STUDENT_DICT))
        # obj.headers['xxxx'] = '123'
        # return obj
        return render_template('index.html',stu_dic=STUDENT_DICT)
    
    
    @app.route('/detail/<int:nid>')
    def detail(nid):
        info = STUDENT_DICT[nid]
        return render_template('detail.html', info=info)
    
    
    if __name__ == '__main__':
        app.run()
    
    

    idnex.html

    <body>
        <h1>学生列表</h1>
        <table border="1">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>性别</th>
                    <th>选项</th>
                </tr>
            </thead>
            <tbody>
                {% for k,v in  stu_dic.items() %}
                    <tr>
                        <td>{{k}}</td>
                        <td>{{v.name }}</td>
                        <td>{{v.age}}</td>
                        <td>{{v.gender}}</td>
                        <td>
                            <a href="/detail/{{k}}">查看详细</a>
                            |
                            <a href="/delete/{{k}}">删除</a>
    
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    </body>
    

    detail.html

    <body>
        <h1>学生详细</h1>
        <ul>
    
            {% for item in info.values() %}
            <li>{{item}}</li>
            {% endfor %}
        </ul>
    
    

    login.html

    <body>
    <h1>欢迎登录</h1>
    <form action="" method="post">
        <input type="text" name="user">
        <input type="password" name="pwd">
        <input type="submit" value="提交">{{error}}
    </form>
    </body>
    

    6.2 添加登录认证

    1. 方式一:if 判断(不推荐)

    在每个视图函数中添加判断,如果 session 中没有登录相关信息则跳转到登录页面。

    @app.route('/index')
    def index():
        if not session.get('user'):
            return redirect(url_for('login'))
        return render_template('index.html',stu_dic=STUDENT_DICT)
    

    这种方式必须得在每一个需要登录认证的视图函数中添加,太 low ,不推荐,也别用。

    2. 方式二:装饰器

    (1) 装饰器回顾

    示例:

    def auth(func):
        def inner(*args,**kwargs):
            ret = func(*args,**kwargs)
            return ret
        return inner
    
    @auth
    def index():
        print('index')
    
    @auth
    def detail():
        print('detail')
    
    print(index.__name__)
    print(detail.__name__)
    

    此时打印两个函数名的结果全是 inner ,这是装饰器本身性质导致。

    回到用装饰器对 Flask 视图函数进行登录认证上,因为 endpoint 默认为函数名,而函数名一致会使得反向 url 失效,怎么解决呢?对 inner 函数加上内置装饰器 @functools.wraps(func)

    import functools
    
    def auth(func):
        @functools.wraps(func)
        def inner(*args,**kwargs):
            ret = func(*args,**kwargs)
            return ret
        return inner
    
    @auth
    def index():
        print('index')
    
    @auth
    def detail():
        print('detail')
    
    print(index.__name__)
    print(detail.__name__)
    

    此时输出为:

    index
    detail
    

    (2) 登录认证用的装饰器

    import functools
    def auth(func):
        @functools.wraps(func)
        def inner(*args,**kwargs):
            if not session.get('user'):
                return redirect(url_for('login'))
            ret = func(*args,**kwargs)
            return ret
        return inner
    

    此时的认证方法为:

    @app.route('/index')
    @auth
    def index():
        return render_template('index.html',stu_dic=STUDENT_DICT)
    

    注意:
    Flask 的路由系统也是以装饰器方式来实现的。那么,路由系统装饰器和登录认证装饰器哪个在前,哪个在后呢?答案是登录认证装饰器在后。因为登录装饰器用到了路由系统装饰器中的内容。

    该方法的应用场景:比较少的函数中需要额外添加功能。如果是所有函数都需要添加额外功能,那么推荐使用接下来所说明的第三种方式。

    3. 方式三:before_request

    before_request 也是一种 Flask 自带的装饰器,它用来在请求时进行一些操作,如果返回 None ,则继续进入视图函数做相应处理,如果返回其他,则作为响应返回结果。

    用在登录认证时代码如下:

    @app.before_request
    def xxxxxx():
        if request.path == '/login':
            return None
    
        if session.get('user'):
            return None
    
        return redirect('/login')
    

    7 模板渲染

    7.1 数据操作

    1. 基本数据类型

    在 Flask 的模板中,对于传过来的数据可以执行 Python 语法,例如:dict.get() list['xx']

    2. 传 HTML 代码

    由于安全性考虑,从后端传来的 HTML 代码字符串不能直接在前端编译,而是显示为字符串,想要其编译有两种方式:
    (1) 前端管道符加持: {{arg_html|safe}}
    (2) 后端安全封装:MarkUp("<input ... />")

    7.2 函数

    1. 传入函数

    def func(arg):
        return arg + 1
    
    @app.route('/tpl')
    def tpl():
        context = {
            'users':['longtai','liusong','zhoahuhu'],
            'txt':Markup("<input type='text' />"),
            'func':func
        }
    
        return render_template('tpl.html',**context)
    
    {{func(6)}}
    

    **值得注意的是: **

    1. Django 传过来的数据是自动执行的,所有不用加括号,而 Flask 不执行,所以需要加上括号。
    2. 当函数参数为多个时,前端传参数的不同。
      两个参数:{{sb(1,9)}}
      三个参数:{{ 1|db(2,3) }} 管道符前面的参数是第一个参数。

    2. 全局定义函数

    全局定义函数可以不经过任何视图函数传送即可在前端使用,需要用到 @app.template_global()@app.template_filter() 两个装饰器。

    @app.template_global()
    def sb(a1, a2):
        # {{sb(1,9)}}
        return a1 + a2
    
    @app.template_filter()
    def db(a1, a2, a3):
        # {{ 1|db(2,3) }}
        return a1 + a2 + a3
    

    7.3 模板继承

    在 Flask 中,模板的继承与 Django 中的使用方法一致。

    1. extends

    layout.html

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <h1>模板</h1>
        {% block content %}{% endblock %}
    </body>
    </html>
    

    tpl.html

    {% extends "layout.html"%}
    
    
    {% block content %}
        {{users.0}}
    
    
    {% endblock %}	
    

    2. include

    form.html

    <form>
        asdfasdf
        asdfasdf
        asdf
        asdf
    </form>
    

    在 xxx.html 调用:{% include "form.html" %}

    7.4 宏

    Flask 中的宏相当于将某些有特征功能的 HTML 代码封装起来,并能进行传值操作,使之在前端能适应不同场景的应用。

    定义:

    {% macro ccccc(name, type='text', value='') %}
        <h1>宏</h1>
        <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
        <input type="submit" value="提交">
    {% endmacro %}
    

    调用:

    {{ ccccc('n1') }}
    
    {{ ccccc('n2') }}
    

    8 Session

    当请求刚到来:Flask 读取 cookie 中 session 对应的值(如eyJrMiI6NDU2LCJ1c2VyIjoib2xkYm95),将该值解密并反序列化成字典,放入内存以便视图函数使用。

    设置 session:session['k1'] = 123
    删除 session:del session['k1']

    当请求结束时,Flask 会读取内存中字典的值,进行序列化 + 加密,写入到用户 cookie 中。

    9 闪现

    闪现可以理解为一次性的 session ,只要有一次请求获取该值,则该值就会被删除。原理为,在 session 中存储一个数据,读取时通过 pop 将数据移除。

    通过 flash 函数来添加闪现,并且可以指定闪现的类型。flash('临时数据存储','error')

    通过 get_flashed_messages 来获取闪现中的值,在该函数中可以指定 category_filter 字段来过滤闪现类型,获取自己需要类型的闪现。

    from flask import Flask,flash,get_flashed_messages
    @app.route('/page1')
    def page1():
    
        flash('临时数据存储','error')
        flash('sdfsdf234234','error')
        flash('adasdfasdf','info')
    
        return "Session"
    
    @app.route('/page2')
    def page2():
        print(get_flashed_messages(category_filter=['error']))
        return "Session"
    

    10 中间件

    Flask 也支持与 Django 类似的中间件功能。

    通过查看源码可以得知,Flask 中间件功能类似于 flask 类中的 call 方法,而实现中间件则是重写 call 方法即可。

    而 call 方法在用户发起请求时,才执行。

    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route('/index')
    def index():
        print('index')
        return "Index"
    
    
    class Middleware(object):
        def __init__(self,old):
            self.old = old
    
        def __call__(self, *args, **kwargs):
            ret = self.old(*args, **kwargs)
            return ret
    
    
    if __name__ == '__main__':
        app.wsgi_app = Middleware(app.wsgi_app)
        app.run()
    
    

    11 特殊装饰器

    1. before_request

    2. after_request

    示例:

    from flask import Flask
    app = Flask(__name__)
    
    
    @app.before_request
    def x1():
        print('before:x1')
        return '滚'
    
    @app.before_request
    def xx1():
        print('before:xx1')
    
    
    @app.after_request
    def x2(response):
        print('after:x2')
        return response
    
    @app.after_request
    def xx2(response):
        print('after:xx2')
        return response
    
    
    
    @app.route('/index')
    def index():
        print('index')
        return "Index"
    
    
    @app.route('/order')
    def order():
        print('order')
        return "order"
    
    
    if __name__ == '__main__':
    
        app.run()
    
    1. before_first_request
    from flask import Flask
    app = Flask(__name__)
    
    @app.before_first_request
    def x1():
        print('123123')
    
    
    @app.route('/index')
    def index():
        print('index')
        return "Index"
    
    
    @app.route('/order')
    def order():
        print('order')
        return "order"
    
    
    if __name__ == '__main__':
    
        app.run()
    
    1. template_global

    2. template_filter

    3. errorhandler

    出现错误时返回的内容。

    @app.errorhandler(404)
    def not_found(arg):
        print(arg)
        return "没找到"
    

  • 相关阅读:
    numpy之填充为nan的数据为该列平均值
    使用Apache HttpClient 4.5设置超时时间
    使用Apache HttpClient 4.x进行异常重试
    使用Apache HttpClient 4.x发送Json数据
    用Maven创建动态Web工程
    Eclipse查看.properties文件中文乱码
    Oracle数据库常用命令(持续更新)
    Spring@PostConstruct注解和构造方法的调用顺序
    在Java中使用Maven配置的版本信息
    Maven profile动态选择配置条件
  • 原文地址:https://www.cnblogs.com/banshaohuan/p/10346346.html
Copyright © 2020-2023  润新知