Flask初识

一、Flask初识

1、Flask介绍

Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug服务 ,模板引擎则使用 Jinja2 。
Flask使用 BSD 授权。

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

对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,
开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,
需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。

下载:
pip install flask

 

2、werkzeug

复制代码
werkzeug类比django的wsgiref模块,封装了sorket

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', 5000, hello)
复制代码

 

二、基本的使用

1、FlaskDemo

复制代码
from flask import Flask

# 实例化一个Flask对象 给它指定一个名字
app = Flask(__name__)  # 名字可以任意起,但是一般我们使用__name__


@app.route("/")  # 这是路由(http://127.0.0.1:5000/),就是用户访问的url(默认端口是5000)
def hello_world():  # 路由对应的处理函数
    return "hello World!"


if __name__ == '__main__':
    app.run()  # 启动Flask
复制代码

 

2、登录Demo

复制代码
1. Flask代码
from flask import Flask, render_template, request, redirect


# template_folder就是指定你去哪个文件夹找你要渲染的html页面
# 默认是templates,可以修改,修改后你项目中存放html页面的文件夹名要对应更改
app = Flask(__name__, template_folder='templates')


@app.route("/login", methods=["GET", "POST"])  # method:指定允许请求的方式,不写-->默认只有GET
def login():
    if request.method == 'GET':
        return render_template('login.html')
    if request.method == 'POST':
        username = request.form.get('username')  # request.form:从表单获取数据
        pwd = request.form.get('pwd')
        if username == 'xiaoming' and pwd == '123':
            return redirect('/index')  # redirect重定向
        else:
            return render_template('login.html', error='用户名或者密码错误')
            # 参数可以返回多个,用字典的形式返回,但是要解耦 **{}
            # return render_template("login.html", **{"error": "用户名或密码错误"})


@app.route('/index')
def index():
    return render_template("index.html")


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


2. login.html
<body>
<form action="" method="post">

    <input type="text" name="username">
    <input type="text" name="pwd">
    <input type="submit">
    <p>{{error}}</p>
</form>
</body>


3. index.html
<body>
<h1>这是首页</h1>

<img src="/static/1.JPG" alt="">

</body>


4. 注意tempaltes以及static的配置
app = Flask(__name__, template_folder='templates', static_folder='static')
tempaltes是存放html页面的文件夹,可以在实例化Flask的时候通过参数template_folder指定文件夹的名字。
默认是tempaltes。

static是存放静态文件(图片、css、js等)的文件夹,可以在实例化Flask的时候通过参数static_folder指定文件夹的名字。
默认是static。

static_url_path='/xxx' 它是一个路径,指向static的路径,也就是static路径的别名(不是真正的static路径),
如果设置了static_url_path,那么静态文件有两种方式可以访问到:
    "/项目路径/static/静态文件"
    "/xxx/静态文件"

app = Flask(__name__,template_folder='templates', static_folder='static', static_url_path='/xxx')
复制代码

 

小结

从上面的demo可以看出,flask的响应对象:

Django Flask
return HttpResponse("字符串") return "字符串"
return render("login.html") return render_template("login.html")
return redirect("/index") return redirect("/index")
  return send_file("文件")
  return jsonify(dict)

 

补充

1.send_file

复制代码
from flask import Flask, send_file

app = Flask(__name__)

@app.route("/get_file")
def get_file():
    return send_file('j.JPG')  # 返回文件类型的(这里的j.JPG是一张图片)

"""
send_file会把文件打开,并把文件流发送到客户端,
send_file还会自动识别文件的类型(图片、视频、文本等),把文件类型声明在响应的content-Type,
最后浏览器会从content-Type里识别出文件的类型(图片、视频、文本等)并显示在页面上
"""

if __name__ == '__main__':
    app.run()
复制代码

 

2.jsonify

复制代码
from flask import Flask, jsonify

app = Flask(__name__)


@app.route("/get_json")
def get_json():
    import json
    dic = {"name": "zbj", "age": 17}
    jdic = json.dumps(dic)
    return jdic

@app.route("/get_jsonify")
def get_jsonify():
    dic = {"name": "zbj", "age": 17}
    return jsonify(dic)

"""
上面两种方式有什么不同呢?
1.使用json.dumps序列化后的字典,再返回的字符串,
它其实就是字符串,它的Content-Type: text/html; 
浏览器并不知道你发的是json格式的字符串,
那么在前端就会认为你是普通的字符串,不能使用前端字典(Object)的方法(比如用 . 取值等)。

2.使用jsonify返回的字典,它返回的是标准的json格式的字符串,
它的Content-Type: application/json; 
浏览器现在是知道你发的是json格式的字符串,
那么在前端就会帮你进行处理成为前端的Object,可以使用前端Object的所有方法
"""


if __name__ == '__main__':
    app.run()
复制代码

 

3.request.xxx

复制代码
from flask import Flask, request

app = Flask(__name__)


"""
flask中的request.args == Django中的request.GET
都是接收url中的参数
"""

@app.route("/get_args")
def get_args():
    # 浏览器访问:http://127.0.0.1:5000/get_args?id=1&name=zbj
    print(request.method)  # GET
    print(request.url)  # http://127.0.0.1:5000/get_args?id=1&name=zbj
    print(request.cookies)  # {}
    print(request.args)  # ImmutableMultiDict([('id', '1'), ('name', 'zbj')])
    print(request.args.get("id"))  # args本质上是一个字典,这里显示:1
    print(request.args["name"])  # zbj
    print(request.args.to_dict())  # {'id': '1', 'name': 'zbj'}
    return "hello"

if __name__ == '__main__':
    app.run()
复制代码

 

4.request.form

复制代码
from flask import Flask, request

app = Flask(__name__)


@app.route("/get_form")
def get_form():
    # 当前端页面提交了一个表单,表单内容是:username: zzbj
    # 使用request.form接收表单中提交的数据
    print(request.form)  # ImmutableMultiDict([('username', 'zzbj')])
    print(request.form.get("username"))  # zzbj
    print(request.form.to_dict())  # {"username": "zzbj"}
    return render_template("login.html")

if __name__ == '__main__':
    app.run()
复制代码

 

5.request.data

复制代码
from flask import Flask, request

app = Flask(__name__)


@app.route("/get_data")
def get_data():
    """request.form和request.data有什么区别?
    首先使用这两个方法的前提是post或者put请求
    当我们发送请求过来时,请求头的Content-Type是application/x-www-form-urlencoded或者multipart/form-data的时候,
    也就是我们表单提交,访问request.form会返回一个ImmutableMultiDict对象,而request.data是空的。
    当我们发送请求过来时,请求头的Content-Type是application/json的时候,
    也就是json请求,访问request.form会返回一个ImmutableMultiDict空对象,而request.data是字节类型的json串。
    """

    print(request.form)  # ImmutableMultiDict([])
    print(request.data)  # b'{"username": "zzbj"}'  注意这里是 bytes类型
    return render_template("login.html")


if __name__ == '__main__':
    app.run()
复制代码

 

6.request.json

复制代码
from flask import Flask, request

app = Flask(__name__)


@app.route("/get_json")
def get_json():
    """
    request.json就是json.loads(request.data)
    """
    print(request.data)  # b'{"username": "zzbj"}'  注意这里是 bytes类型
    print(request.json)  # {"username": "zzbj"}  注意这里是dict类型
    return render_template("login.html")


if __name__ == '__main__':
    app.run()
复制代码

 

3、配置文件

复制代码
1. 配置文件信息
flask中的配置文件是一个flask.config.Config对象(继承字典)
默认配置为:
{
    'ENV':                                  production生产环境
    'DEBUG':                                get_debug_flag(default=False),  是否开启Debug模式
    'TESTING':                              False,                          是否开启测试模式
    'PROPAGATE_EXCEPTIONS':                 None,                          
    'PRESERVE_CONTEXT_ON_EXCEPTION':        None,
    'SECRET_KEY':                           None, session是否加盐
    '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,
}


2. 修改flask配置信息的方式
Flask的配置信息是存在app.config里面的,
我们可以在实例化Flask对象之后,打印一下app.config来查看,
app.config是所有配置信息组成的字典。

方式一
既然是字典,就可以通过字典的方式进行修改:
通过app.config["xxx"] = xxx来更改配置信息,注意:config里面的键是大写的!!!
例如:
app.config["DEBUG"] = True
注意:不建议这么使用


方式二
可以直接通过Flask的实例化对象对配置进行修改,注意:这里是小写的
app.debug = True


方式三
通过app.config.from_object("python类或类的路径")来配置

1.flask项目代码
from flask import Flask


app = Flask(__name__)
app.config.from_object('settings.DEVConfig')
# app.config["DEBUG"] = True
# app.debug = True

@app.route('/')
def index():
    print(app.config)
    return "主页"


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


2.settings.py
class DEVConfig(object):
    DEBUG = True


class TestingConfig(object):
    TESTING = True
复制代码

 

4、路由系统

复制代码
1. 路由参数
@app.route('/user/<username>')  # 参数是字符串类型
@app.route('/book/<int:id>')  # 指定参数是int类型
@app.route('/book/<float:id>')  # 指定参数是float类型
@app.route('/post/<path:path>')
注意:指定参数类型(int、float等)的时候,冒号两边不能有空格,即int:id,不能是int: id


# 路由默认支持的参数
DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,  # 字符串
    'string':           UnicodeConverter,  # 字符串
    'any':              AnyConverter,  # 任意
    'path':             PathConverter,  # 路径
    'int':              IntegerConverter,  # 整数
    'float':            FloatConverter,  # 小数
    'uuid':             UUIDConverter,  # uuid
}


2. 路由的命名
通过给route设置endpoint参数,可以给路由进行命名,如果不设置endpoint,默认的路由命名是函数名
@app.route('/book/<int:id>', endpoint='book') # 默认是函数名字


3. 命名路由的反向解析
from flask import Flask, url_for
url_for("路由的名字", nid=32425)


4. 例子
from flask import Flask, redirect, url_for


app = Flask(__name__)


@app.route('/book/<int:id>', endpoint='book')
def book(id):
    print(id, type(id))
    return "BOOK"


@app.route('/index', endpoint='index')
def index():
    return redirect(url_for('book', id=1))


if __name__ == '__main__':
    app.run()
复制代码

 

5、模板

1. 跟Django比较,相同点与不同点
相同点:
  Flask模板的使用的是JinJa2模板,语法基本上跟Django无差别,只是在一些细节上有些不同。
  Django模板的基本使用可参考:
    https://www.cnblogs.com/Zzbj/p/9892169.html
    https://www.cnblogs.com/Zzbj/p/9898526.html

不同点:
  flask模板中函数执行要加括号,Django的模板使用函数是不需要括号的
  flask模板中字典的取值可以使用 . 点,也使用get,Django模板中字典取值只能使用 . 点
  flask模板中列表的取值可以使用 . 点,也使用[索引],Django模板中列表取值只能使用 . 点
  支持创建一个函数通过render_template页面的形式传递到模板中


2. 示例

复制代码
from flask import Flask, render_template


app = Flask(__name__)

book_list = [
    {"id": 1, "title": "三国演义"},
    {"id": 2, "title": "水浒传"},
    {"id": 3, "title": "西游记"},
    {"id": 4, "title": "红楼梦"},
]

name = 'ZBJ的图书'
my_dict = {"age": 18}
my_list = [1, 2]

def my_func():
    return '<h2>把自定义函数传到模板</h2>'


@app.route('/book',)
def book():
    return render_template('book.html', **{'book_list': book_list, 'name': name, 'my_dict': my_dict, 'my_list': my_list , 'my_func': my_func})


if __name__ == '__main__':
    app.run()
python代码
复制代码
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>{{name}}</h1>
    <table>
        <thead>
            <tr>
                <th>title</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            {% for book in book_list %}
                <tr>
                    <td>{{book.title}}</td>
                    <td>删除 | 编辑</td>
                </tr>
            {% endfor %}
        </tbody>

    </table>
    <hr>
    {{my_func()|safe}}
    <hr>
    字典get取值:{{my_dict.get("age", 0)}}
    <br>
    字典 . 点取值:{{my_dict.age}}
    <br>
    <hr>
    列表索引取值:{{my_list[0]}}
    <br>
    列表 . 点取值:{{my_list.1}}
    <hr>
</body>
</html>
模板代码
复制代码

 

6、请求和响应

复制代码
from flask import Flask
from flask import request
from flask import render_template
from flask import redirect
from flask import make_response

app = Flask(__name__)


@app.route('/login', methods=['GET', "POST"])
def login():
    """
    1、请求相关信息
    request.method            请求的方式
    request.args            请求的url的参数 ?
    request.form            form表单的数据
    request.values
    request.cookies            cookies
    request.headers            请求头
    request.path            请求的url路径(不含参数 ?)
    request.full_path        请求的url路径(包含参数 ?)
    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('/uploads/' + secure_filename(f.filename))  把文件数据保存在服务器

    2、响应相关信息
    1. 这几种是把响应直接放在响应体里面,然后直接返回
    return "字符串"
    return render_template('html模板路径',**{})
    return redirect('/index.html')

    2. 下面这种是先封装响应对象,然后对响应对象做一些操作,再把响应对象返回
    response = make_response(render_template('index.html'))  response是flask.wrappers.Response类型
    response.delete_cookie('key')  删除cookie
    response.set_cookie('key', 'value')  设置cookie
    response.headers['X-Something'] = 'value'  设置响应头
    return response  最后返回
    """
    return "看注释吧!"


if __name__ == '__main__':
    app.run()
复制代码

 

7、session

session对象,它允许你在不同请求间存储特定用户的信息。
它是在 Cookies 的基础上实现的,并且对 Cookies 进行加密,你需要设置一个密钥。

session的加密默认用的是md5的加密方式,它规定了必须加盐,这个盐secret_key在配置信息中可以设置。
通过配置信息的secret_key设置盐:
  app.secret_key = '嘿嘿嘿'
  app.config['SECRET_KEY'] = '嘿嘿嘿'


设置session
  session['xxx'] = 'xxx'

取session值
  session.get('xxx', '')

删除session
  session.pop('xxx', None)


示例
1. python代码

复制代码
# python代码
from flask import Flask, session, redirect, url_for, request, render_template
# 注意:导入的时候session跟sessions是不同的,注意 s


app = Flask(__name__)
# session必须设置session加密的盐
app.secret_key = '嘿嘿嘿'


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == "POST":
        username = request.form.get('username')
        pwd = request.form.get('pwd')
        if username == 'xiaoming' and pwd == '123':
            session['username'] = username
            return redirect(url_for('index'))
        return render_template('login.html', error="用户名或者密码错误")
    return render_template('login.html')


@app.route('/index')
def index():
    if session.get('username', ''):
        return "用户已登录,这是首页"
    return "用户未登录,请去登录页面"


@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))


if __name__ == '__main__':
    app.run()
复制代码

 

2. login.html

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="" method="post">

    <input type="text" name="username">
    <input type="text" name="pwd">
    <input type="submit">
    <p>{{error}}</p>
</form>

</body>
</html>
复制代码

 

8、flash

1、flash原理
flash是一个基于session实现的用于保存数据的集合,特点是使用一次就删除.一次的意思是一次请求的开始,请求的结束,
在这段时间是可以随便使用的,但是请求结束后,第二次请求进来的时候,就取不到值了。
它的原理:
  设置值的时候 session["xxx"] = value
  取值 session.pop("xxx")

注意:因为flash是基于session的,因此也要设置SECRET_KEY

设置方式一:
直接设置值
  flash(值1)
  flash(值2)

设置值:
  flash('小明')
  flash('小东')

取值:
  name = get_flashed_messages()   # ['小明', '小东']
注意:
  flash是一个列表,多次flash设置值,都是append进一个列表的,
  取值的时候,就把那个列表取出来,然后把值清空,
  再次取值的时候,就只能取到一个空列表。

设置方式二:
给flash分类,flash有一个参数category,就是分类的意思,
不设置的话,所有flash都是message这个分类,我们可以根据需要给flash的值设置分类。

  flash(值1, 分类名1)
  flash(值2, 分类名2)

设置值:
  flash('小明', 'ming')
  flash('小东')

取值:
  name1 = get_flashed_messages()   # ['小明', '小东']
  name2 = get_flashed_messages(category_filter=['ming'])   # ['小明']


2、Demo
1. flash原理Demo

复制代码
from flask import Flask, session


app = Flask(__name__)
app.secret_key = '雅蠛蝶'


@app.route('/set')
def set():
    session['name'] = '小明'
    return "SET"


@app.route('/get')
def get():
    name = session.pop('name')
    print(name)
    return "GET"


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

取了一次值后,下次去get,就取不到值了
复制代码

 

2. flashDemo

复制代码
from flask import Flask, flash, get_flashed_messages


app = Flask(__name__)
app.config['SECRET_KEY'] = '雅蠛蝶'


@app.route('/set')
def set_flash():
    flash('小明', 'ming')
    flash('小东')
    return "SET_FLASH"


@app.route('/get')
def get_flash():
    name1 = get_flashed_messages()
    print(name1)  # ['小明', '小东']
    name2 = get_flashed_messages(category_filter=['ming'])
    print(name2)  # ['小明']
    return "GET_FLASH"


if __name__ == '__main__':
    app.run()
复制代码

 

9、中间件

9-1、小预习
Flask的中间件跟Django的不太一样,我们需要从源码去理解,
需要用到类的一些内置方法(双下方法):__init__、__call__

1. 类

复制代码
class A():
    def __init__(self):
        print("init")

    def __call__(self, *args, **kwargs):
        print("call")

a = A()  # init
a()  # call
类加括号:实例化一个对象-->调用__init__方法
对象加括号:调用__call__方法
复制代码

 

2. werkzeug启动flask项目的原理

复制代码
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', 5000, hello)
    
最后启动项目的是执行了run_simple这个方法,
这个方法有三个参数:
    localhost:域名
    5000:端口
    hello:函数名

意思就是:在这个域名这个端口,调用hello这个函数
复制代码

 

9-2、app.run的源码基本流程
(werkzeug是我们封装sorket的地方~源码是从run_simple方法开始走的)

app.run():
  调用了werkzueg的run_simple方法 run_simple(host, port, self, **options)

  这个方法中是self是我们的app, 我们知道werkzeug会执行app()

  这是我们程序的入口,会执行我们app.__call__方法

  那现在如果我想在请求进来之前做一些事情,以及请求结束以后做一些事情

  那么就可以实现中间件的效果

app.run() -->
  run_simple(host, port, self, **options) -->
    self() --> app() --> app是Flask的实例化对象,因此会调用Flask这个类的__call__ -->
      __call__把app.wsgi_app执行后的结果返回了,return self.wsgi_app(environ, start_response)[self就是app],
      environ是请求来的最原生的数据-->
        也就是说,flask所有请求进来时,都会从Flask的__call__开始执行,
        我们只需在__call__这个方式的执行前后做一些事情,就可以实现中间件的功能了。

9-3、实现方式
1. 改源码(不建议使用)

复制代码
def __call__(self, environ, start_response):
    """The WSGI server calls the Flask application object as the
    WSGI application. This calls :meth:`wsgi_app` which can be
    wrapped to applying middleware."""
    # return self.wsgi_app(environ, start_response)
    print("开始之前")
    ret = self.wsgi_app(environ, start_response)
    print("请求之后")
    return ret
    
复制代码

 

2. 中间件类

复制代码
from flask import Flask


app = Flask(__name__)


class Middleware(object):
    def __init__(self, old_call):
        self.old_call = old_call

    def __call__(self, *args, **kwargs):
        print('请求来之前做的事情')
        ret = self.old_call(*args, **kwargs)
        print('请求来到之后做的事情')
        return ret


app.wsgi_app = Middleware(app.wsgi_app)


if __name__ == '__main__':
    app.run()
    # run_simple(host, port, self, **options)
    # run_simple执行了 self()
    # self = app
    # app() 执行Flask的__call__
    # __call__返回app.wsgi_app()的执行结果
    # 我重写了app.wsgi_app--> Middleware(app.wsgi_app)()

注意:通常我们不会这样去实现中间件,因为Flask中有一些特殊的装饰器能够帮我们实现中间件的功能,哈哈哈哈~~~
复制代码

 

10、特殊的装饰器

需求:
  我们很多访问都是需要进行登录认证,那么在flask中如何实现登录认证的功能

1. 小预习

复制代码
# python的装饰器
from functools import wraps
# 被装饰器装饰的函数名字会变成内部的函数名
# 我们需要给inner函数加个装饰器来修改inner函数的信息


def wrapper(func):
    @wraps(func)
    def inner(*args, **kwargs):
        ret = func(*args, **kwargs)
        return ret
    return inner


@wrapper
def index(x, y):
    return x + y


res = index(1, 2)
print(res)
print(index.__name__)
复制代码

 

2. 使用python装饰器实现认证功能

复制代码
给每个需要认证的函数都加上自定义的认证装饰器

from flask import Flask, session, redirect, url_for
from functools import wraps


app = Flask(__name__)
app.secret_key = '哈哈'

# 实现登录 我们不可能每个视图函数都去获取session然后判断
# 我们可以利用装饰器
# 注意装饰器装饰完函数名的问题
# 注意装饰器执行顺序问题


# 实现认证的装饰器
def auth(func):
    @wraps(func)
    def inner(*args, **kwargs):
        if not session.get('username'):
            return redirect(url_for('login'))
        ret = func(*args, **kwargs)
        return ret
    return inner


@app.route('/login')
def login():
    session['username'] = '小明'
    return "登录成功"


@app.route('/logout')
def logout():
    session.pop('username')
    return "退出登录"


# 注意:app.route和auth装饰器的顺序不能乱,必须是先认证了成功后才放行
@app.route('/user')
@auth
def user():
    return "用户页面"


@app.route('/book')
@auth
def book():
    return "图书页面"


if __name__ == '__main__':
    app.run()
复制代码

 

3. Flask提供的特殊装饰器before_request
任何请求进来,都要先执行被before_request装饰的函数,
在这个函数进行认证,其他函数就不需要每个都加认证装饰器了。

复制代码
from flask import Flask, session, redirect, url_for, request


app = Flask(__name__)
app.secret_key = '哈哈'

# 任何请求进来,都要先执行这个函数
@app.before_request
def auth():
    if request.path == '/login' or request.path == '/':
        # 如果是登录请求,直接放行
        return None
    if session.get('username', ''):
        # 如果携带了session的请求,证明已经登录,放行
        return None
    # 否则,都跳转到登录页面
    return redirect(url_for('login'))


@app.route('/login')
def login():
    session['username'] = '小明'
    return "登录成功"


@app.route('/logout')
def logout():
    session.pop('username')
    return "退出登录"


# 不需要加其他装饰器了
@app.route('/user')
def user():
    return "用户页面"


@app.route('/book')
def book():
    return "图书页面"


if __name__ == '__main__':
    app.run()
复制代码

 

11、flask常用的特殊装饰器

复制代码
1. 请求第一次进来的时候执行
@app.before_first_request
第二次请求就不会执行了

2. 请求每次进来都执行
@app.before_request

3. 每次响应的时候执行
@app.after_request

4. 发生404的时候执行
@app.errorhandler(404)

5. 全局的函数,在所有html模板中都可以使用的函数,并且不需要传
@app.template_global()

6.模板的过滤器,在所有html模板中都可以使用的过滤器,并且不需要传
@app.template_filter()
复制代码

 

7. Demo

复制代码
from flask import Flask, Request, render_template

app = Flask(__name__)
app.debug = True


@app.before_first_request
def before_first_request1():
    print('before_first_request1')


@app.before_first_request
def before_first_request2():
    print('before_first_request2')


@app.before_request
def before_request1():
    print('before_request1')


@app.before_request
def before_request2():
    print('before_request2')


@app.after_request
def after_request1(response):
    print('after_request1', response)
    return response


@app.after_request
def after_request2(response):
    print('after_request2', response)
    return response


@app.errorhandler(404)
def page_not_found(error):
    return 'This page does not exist', 404


@app.template_global()
def jiafa(a1, a2):
    # 在所有模板在都可以这样使用 {{ jiafa(1, 2) }}
    return a1 + a2


@app.template_filter()
def db(data):
    # 在所有模板在都可以这样使用 {{ "hello"|db() }}
    # 这里data就是hello
    return data[::2]


@app.route('/')
def hello_world():
    return render_template('hello.html')


if __name__ == '__main__':
    app.run()
Demo
复制代码

 

8. 注意
这几个装饰器跟Django的中间件很像,也有执行顺序的
request请求进来,顺序执行
response响应出去,倒序执行

超级注意
  !!! before_request有返回值的时候还会按顺序执行after_request
  !!! Django<=1.9的版本 当process_request有返回值的时候跟flask是一样的

 

9. 请求流程图

 

11.2、通用的装饰器

1.FBV直接使用普通的python装饰器即可

2.CBV需要在类里面定义 decorators 类属性去显示调用装饰器

复制代码
from flask import Flask, views, request
from functools import wraps

# 实例化一个Flask对象 给它指定一个名字
app = Flask(__name__)  # 名字可以任意起,但是一般我们使用__name__


# 定义装饰器方法
def flask_exception(func):
    @wraps(func)
    def wrapper(*args, **wargs):
        try:
            return func(*args, **wargs)  # 带参数返回
        except Exception as ex:
            print("ex: ", ex)
            return "ex"

    return wrapper


@app.route("/", methods=["GET"])
@flask_exception  # FBV直接使用python装饰器
def index():
    if request.method == "GET":
        a
        return "index!"


class TaskCreate(views.MethodView):
    methods = ["GET"]
    decorators = [flask_exception]  # CBV使用类属性 decorators 去显示调用装饰器

    def get(self):
        b
        return "hello World!"


if __name__ == '__main__':
    app.add_url_rule('/create',
                     view_func=TaskCreate.as_view('task_create'))
    app.run(host="127.0.0.1", port=5000)
复制代码

 

12、CBV视图

1. route原理

复制代码
之前我们的视图都是FBV,那么我们的CBV要怎么实现呢?
实现CBV之前我们要看一下路由的实现原理
我们看下@app.route("/xx")做了什么:

    首先这是一个带参数的装饰器,那么带参数的装饰器执行顺序是什么样的
        1,先去掉@ 执行route("/xx")得到返回值
        2,再拿返回值加上@符号去装饰接下来的函数

route源码
    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

从源码中可以看出:
    route函数返回了decorator函数,也就是说实际上是用decorator这个函数去装饰我们的视图函数的
    f就是我们的视图函数,
    decorator调用了add_url_rule方法
    endpoint默认取函数名!
    两个函数不能用一个endpoint !


那么实际上路由的实现原理:
from flask import Flask


app = Flask(__name__)


def index():
    return "index"


app.add_url_rule("/", endpoint="index", view_func=index)


if __name__ == '__main__':
    app.run()
复制代码

 

2. CBV

复制代码
# CBV需要导入views
from flask import Flask, views, session, redirect, url_for
from functools import wraps


app = Flask(__name__)


def auth(func):
    @wraps(func)
    def inner(*args, **kwargs):
        if not session.get("userinfo"):
            return "你还没登录,滚"
        ret = func(*args, **kwargs)
        return ret
    return inner


class MyView(views.MethodView):
    methods = ["GET", "POST"]
    # 设置decorators相当于给这个CBV的所有请求方法都加了指定的装饰器
    # decorators = [auth, ]

    def get(self):
        return "GET"

    def post(self):
        return "POST"


# 第一个参数:路由
# 第二个参数:视图
# name == endpoint 命名路由
app.add_url_rule("/index", view_func=MyView.as_view(name="index"))


if __name__ == '__main__':
    app.run()
复制代码

 

13、自定义路由的正则匹配

复制代码
from flask import Flask, url_for
from werkzeug.routing import BaseConverter

app = Flask(__name__)


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

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

    def to_python(self, value):
        """
        路由匹配时,匹配成功后传递给视图函数中参数的值
        value默认的类型是str,我们可以设置成我们想要的类型
        :param value:
        :return:
        """
        return int(value)

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


# 新加一个匹配规则regex到flask中
# 原本只有 int string等,现在还多了一个regex
app.url_map.converters['regex'] = RegexConverter


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


if __name__ == '__main__':
    app.run()
复制代码

 

14、蓝图

1. 作用
为我们提供目录的划分,就是解耦用的。


2. 目录

 

3. 实现步骤

复制代码
1,新建一个项目目录 项目目录下建一个同名的python包
2,在这个包同级下建manager.py
3, 在包的__init__ 实例化Flask对象
4,在manager.py 导入Flask的实例化对象app  app.run()
5, 在Python包里建立views文件夹
    任何一个文件都都可以生成蓝图对象
    from flask import BluePrint
    # 实例化蓝图对象
    userBlue = BluePrint("userBlue", __name__)
    @userBlue.route("/user")
    def user():
        return "xxx"
6, 把蓝图对象注册到app中
    app.register_blueprint(userBlue, **options)
复制代码

 

4. 注意
每个蓝图可以指定自己的模板以及静态文件~

还可以在app中注册蓝图的时候指定路由前缀~~

还可以给蓝图加before_request~~

蓝图 before_request 的两种方法

方法一:

CBV中,需要使用 蓝图对象.before_request(方法名),就是把 before_request 指定到对应的函数去执行

复制代码
"""方法一:CBV before_request"""

import json
from flask import Flask, Blueprint
from flask import request, views, jsonify, make_response


class Common(object):
    """存放公共方法的类"""

    @staticmethod
    def parse_request_data():
        try:
            data = json.loads(request.data)
            # 处理逻辑自己定义
            # 有需要的话,可以给request.__dict__增加属性
            request.__dict__["origin_data"] = data
            print("CBV before request")
        except Exception as ex:
            print(ex)


class TaskCreate(views.MethodView):
    methods = ["POST"]

    def post(self):
        # before_request函数处理后,可以在这里取
        data = request.__dict__["origin_data"]
        # data = json.loads(request.data)
        print("create data: %s" % data)
        """中间代码逻辑"""

        response = {"result": "success"}
        resp = make_response(jsonify(response))
        return resp


if __name__ == '__main__':
    # 定义蓝图
    taskBlue = Blueprint('taskBlue', __name__)
    # 路由
    taskBlue.add_url_rule('/task/create',
                          view_func=TaskCreate.as_view('task_create'))
    # 请求进来时统一走before_request指定的函数,相等于中间件
    taskBlue.before_request(Common.parse_request_data)

    app = Flask(__name__)
    app.register_blueprint(taskBlue)  # 把蓝图对象注册到app中
    app.run()
复制代码

 

方法二:

FBC中,直接使用装饰器,@蓝图对象.before_request 装饰某个指定的函数即可

复制代码
"""方法二:FBV before_request"""
import json
from flask import Flask, Blueprint
from flask import request, jsonify, make_response

# 定义蓝图
taskBlue = Blueprint('taskBlue', __name__)


@taskBlue.before_request
def blue_before_request():
    data = json.loads(request.data)
    # 处理逻辑自己定义
    # 有需要的话,可以给request.__dict__增加属性
    request.__dict__["origin_data"] = data
    print("FBV before request")


@taskBlue.route('/task/update', methods=["POST"])
def task_update():
    data = request.__dict__["origin_data"]
    # data = json.loads(request.data)
    print("update data: %s" % data)
    """中间代码逻辑"""
    response = {"result": "success"}
    resp = make_response(jsonify(response))
    return resp


if __name__ == '__main__':
    app = Flask(__name__)
    app.register_blueprint(taskBlue)  # 把蓝图对象注册到app中
    app.run()
复制代码

 

5. 示例代码

复制代码
from flask import Flask
from .views.book import bookBlue
from .views.user import userBlue


# 创建Flask的实例化对象app
def create_app():
    app = Flask(__name__, template_folder='templates')  # 指定templates
    app.register_blueprint(userBlue)  # 把蓝图对象注册到app
    app.register_blueprint(bookBlue, url_prefix='/book')  # 还可以指定路由的前缀
    return app
__init__.py
复制代码
复制代码
from BlueDemo import create_app


# 导入Flask的实例化对象app
app = create_app()

if __name__ == '__main__':
    # 启动Flask
    app.run()
manage.py
复制代码
复制代码
from flask import Blueprint, render_template


# 实例化一个蓝图对象
userBlue = Blueprint('userBlue', __name__)


@userBlue.route('/user')
def user():
    return render_template('user.html')
views/user.py
复制代码
复制代码
from flask import Blueprint


# 实例化一个蓝图对象
bookBlue = Blueprint('bookBlue', __name__)


# bookBlue蓝图对象,注册到app的时候,指定了url_prefix,相当于做了路由的分发
# 这个视图的路由:/book/list
@bookBlue.route('/list')
def list():
    return "Book_list"


# 这个视图的路由:/book/create
@bookBlue.route('/create')
def create():
    return "Book_create"
views/book.py
复制代码

 

15、CBV配合蓝图

复制代码
# CBV需要导入views
from flask import Flask, Blueprint, views, url_for, session
from functools import wraps


app = Flask(__name__)  # Flask实例
userBlue = Blueprint("userBlue", __name__)  # 蓝图实例


# 登录验证的装饰器
def auth(func):
    @wraps(func)
    def inner(*args, **kwargs):
        if not session.get("userinfo"):
            return "你还没登录,滚"
        ret = func(*args, **kwargs)
        return ret
    return inner

# CBV类
class Home(views.MethodView):
    # 默认写出来的请求方法都被允许,如果还是想重定制,可使用methods属性来重新定义允许的请求
    methods = ["GET", "POST"]

    # 设置decorators相当于给这个CBV的所有请求方法都加了指定的装饰器,装饰器按索引顺序执行
    decorators = [auth, ]

    def dispatch_request(self, *args, **kwargs):
        """可以写一些自己的逻辑"""
        return super(Home, self).dispatch_request(*args, **kwargs)

    def get(self):
        return "GET"

    def post(self):
        return "POST"

# dispatch_request方法在父类views.MethodView是已经定义了的
# 是用于根据请求的不同进入不同的方法,例如我们对上面的CBV进行get请求,那么dispatch_request方法就会把请求分配进入get()方法
# 而不需要我们自己手动进行 request==GET 的判断。
# 因此可以重写dispatch_request方法可以在里面先实现一些我们的逻辑,再调用父类的dispatch,当然也可以不重写

# 第一个参数:路由
# 第二个参数:视图(类.as_view())
# name == endpoint 命名路由
userBlue.add_url_rule("/index", view_func=Home.as_view(name="index"))  # 在蓝图中定义路由
app.register_blueprint(userBlue)  # 把蓝图注册到app


# 注意:当一个蓝图被声明后,如果要使用url_for进行自定寻址需要加上蓝图名称
# 即:url_for(blue_name.endpoint)
# 例如,上面的"/index"路由,as_view后的第一个参数(name)就是endpoint
url_for("userBlue.index")


if __name__ == '__main__':
    app.run()
复制代码

 

16、蓝图的templates和项目的templates

1、首先看一下我定义的蓝图结构(使用的是CBV模式)

说明

蓝图会根据蓝图的template_folder参数去找模板,但是需要注意的是:

  1. 首先都是在项目级别的模板中查找,如果项目级别的templates没有我们视图指定的html文件,那么再去找蓝图级别的templates
  2. 如果有两个蓝图或多个蓝图,它们的模板目录结构一样,如果在它们的templates下也有着同名html文件,
    那么后面的蓝图加载的同名html文件会是第一个蓝图中的html。

  3. 蓝图的templates要跟实例化蓝图的py文件在同一级,否则找html文件的时候会找不到,因为蓝图查找html文件是从实例化蓝图的地方开始,向下查找
  4. 即我上面的结构中,蓝图的实例化在home_url.py,那么蓝图的templates就要建立在与home_url.py同一级的地方

 

 2、示例代码

 项目的__init__.py

复制代码
from flask import Flask
from flask_session import Session
from flask_demo.home.home_url import homeBlue


def create_app():
    app = Flask(__name__)
    app.config.from_object('setting.DevConfig')
    Session(app)
    app.register_blueprint(homeBlue)
    return app
__init__.py
复制代码
复制代码
from flask_demo import create_app

app = create_app()

if __name__ == '__main__':
    app.run()
manager.py
复制代码
复制代码
from flask import views, session, render_template


class Home(views.MethodView):
    methods = ["GET"]

    def get(self):
        session['login'] = 'login'
        return render_template('home.html')


class GetSession(views.MethodView):
    def get(self):
        resp = session.get('login')
        return resp
flask_demo/home/views/home.py
复制代码
复制代码
from flask import Blueprint
from flask_demo.home.views.home import Home, GetSession

homeBlue = Blueprint('homeBlue', __name__, template_folder='templates')

homeBlue.add_url_rule('/home', view_func=Home.as_view('home'))
homeBlue.add_url_rule('/get', view_func=GetSession.as_view('get'))
flask_demo/home/home_url.py
复制代码

如果项目的templates没有home.html,那么就会去蓝图的templates中查找,如果项目的templates有home.html那么就使用项目templates下的home.html

 

posted @   我用python写Bug  阅读(561)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示