python 全栈开发,Day120(路由系统, 实例化Flask的参数, 蓝图(BluePrint), before_request after_request)

昨日内容回顾

1.Flask:
    from flask import Flask
    app = Flask(__name__)
    # 从源码中可以看出,Flask集成的run方法是由werkzeug中的run_simple方法提供的。
    app.run() # run_simple(host,port,obj_or_func())
    obj() 对象加括号相当于调用 __call__() 方法

2.简单路由
    @app.route("/index",methods=["GET","POST"])
    def index(){
        return
    }
    默认GET请求,如果改写了methods,GET一定要加上

3.flask 中的返回值:
    左边是django,右边是flask

    HttpResponse  == return "OK!"
    render == render_template
    redirect == redirect

    return send_file() # 将一个文件打开,然后返回文件内容


4.request:
    request
    form:POST中的FormData数据存放在form(字典) 通过字典的方式去取值
    args:URL参数存放在args(字典) 通过字典的方式去取值
    values: url + FormData .to_dict()同名key url覆盖Formdata
    data: 当 content-type:None 也就是无法处理时,将数据存放在data中 并且以b"{k:v}"形式存储,是bytes类型
    json: 当 content-type:application/json 数据以Json放存放

5.Flask Jinja2
    {{}} 非逻辑
    {%%} 包含逻辑

    {% for i in a %}
    {% endfor %}

    {% if True %}
    {% endif %}

    前端:| safe :安全标签字符串儿
    后端: Markup(tag) :安全标签字符串儿

    @app.template_global() # 定义模板全局函数
    def func():
        return 1+1

    前端:
    {{ func() }}

    @app.template_filter() # 定义模板全局函数,类似偏函数
    def func(a):
        return a

    前端:
    {{ 1 | func() }}

    extends 模板继承
    include 加载模板

    宏定义:很少用
    {% macro func(ty , na) %}
        <input type="{{ ty }}" name="{{ na }}" >
    {% endmacro %}
    调用:
    func("text","username")


6.flask 中的 session
    app.secret_key = "alex DSB"
    session 序列化一个加密字符串,存在前端的cookie。它的键名就是session
    当发起请求的时候,将字符串发送到后端反序列化,拿出存放在服务器端的session
    session["key"] = "value" # 就是字典

    注意:cookie存储在浏览器中,session存储在服务器中。
    cookie相当于钥匙,session相当于银行保险柜!
View Code

昨日作业讲解

昨天的作业就是,有3个视图函数,分别是/login,/student_list,/student_detail。写一个装饰器,除了/login以外,其他视图函数都要登录才行!

使用session判断!

原始代码

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

app = Flask(__name__)

USER = {'username': 'xiao', 'password': "123"}


@app.route("/login",methods=["POST","GET"])
def login():
    if request.method == "GET":
        # 前端模板中使用了msg,这里就算是传递空,也要出现msg
        return render_template("login.html", msg="")

    if request.form["username"] == USER["username"] and request.form["password"] == USER["password"]:
        return redirect("/student_list")

    return render_template("login.html", msg="用户名密码错误")


@app.route("/student_list")
def student_list():
    return "学生列表"

@app.route("/student_detail")
def student_detail():
    return "学生详情"


if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

 

使用装饰器

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

app = Flask(__name__)
# 使用session,必须设置secret_key
app.secret_key = "123asdzxc"

USER = {'username': 'xiao', 'password': "123"}

def auth(func):
    def inner(*args,**kwargs):
        if session.get("user"):
            return func(*args,**kwargs)
        else:
            return redirect("/login")

    return inner

@app.route("/login",methods=["POST","GET"])
def login():
    if request.method == "GET":
        # 前端模板中使用了msg,这里就算是传递空,也要出现msg
        return render_template("login.html", msg="")

    username = request.form["username"]  # 获取POST请求时,FormData中的参数
    # password = request.form.get("password")
    if request.form["username"] == USER["username"] and request.form["password"] == USER["password"]:
        # 设置session
        session["user"] = username
        return redirect("/student_list")

    return render_template("login.html", msg="用户名密码错误")


@app.route("/student_list",endpoint="student_list")
@auth
def student_list():
    return "学生列表"

@app.route("/student_detail",endpoint="student_detail")
@auth
def student_detail():
    return "学生详情"


if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

 

重启flask,直接访问student_list

http://127.0.0.1:5000/student_list

打开浏览器工具,查看网络。它会跳转至登录页面!

输入正确的用户名和密码

提交之后,跳转页面

 

 注意:使用装饰器的视图函数,必须要定义endpoint参数。

因为使用装饰器之后,视图函数都是inner,所以flask无法区分,路由到底指向哪一个视图函数。

启动flask之后,会直接报错。endpoint参数,是给url起别名,唯一标识。可以做url反向解析!

 

还有一种方法,使用@functools.wraps装饰器,保留原函数信息,比如函数名。

但是不推荐使用。因为定义视图函数,本身就应该定义endpoint参数

 

一、路由系统

Flask中的路由系统其实我们并不陌生了,从一开始到现在都一直在应用

@app.route("/",methods=["GET","POST"])

为什么要这么用?其中的工作原理我们知道多少?

@app.route() 装饰器中的参数

1. @app.route() 装饰器中的参数

如果不明白装饰器 点击这里

methods

methods : 当前 url 地址,允许访问的请求方式

from flask import Flask,request

app = Flask(__name__)

@app.route("/info", methods=["GET", "POST"])
def student_info():
    stu_id = int(request.args["id"])
    return f"hello kitty {stu_id}"  # Python3.6的新特性 f"{变量名}"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

访问url,注意:要带上参数id,否则报错

http://127.0.0.1:5000/info?id=1

效果如下:

endpoint

endpoint : 反向url地址,默认为视图函数名 (url_for)

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info", methods=["GET", "POST"],endpoint="r_info")
def student_info():
    print(url_for("r_info"))  # /info
    stu_id = int(request.args["id"])
    return f"hello kitty {stu_id}"  # Python3.6的新特性 f"{变量名}"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

刷新页面,效果同上!查看Pycharm控制台输出:

/info

注意:这并不是完整的url。如果要给前端妹子,返回一个完整的URL怎么办呢?

url_for

url_for:用于反向生成url,也可以附带一些参数,比如想要完整的URL,可以设置_external为Ture:

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info", methods=["GET", "POST"],endpoint="r_info")
def student_info():
    stu_id = int(request.args["id"])
    print(url_for("r_info", _external=True))
    return f"hello kitty {stu_id}"  # Python3.6的新特性 f"{变量名}"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

刷新页面,效果同上!查看Pycharm控制台输出:

http://127.0.0.1:5000/info

 

但是还不够,参数没有啊?怎么办?再加上url参数。

注意:由于id是动态参数,所以后台获取时,要和实际的参数匹配。这里是id

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info", methods=["GET", "POST"],endpoint="r_info")
def student_info():
    stu_id = int(request.args["id"])
    print(url_for("r_info", _external=True,id=stu_id))
    return f"hello kitty {stu_id}"  # Python3.6的新特性 f"{变量名}"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

刷新页面,效果同上!查看Pycharm控制台输出:

http://127.0.0.1:5000/info?id=1

这下,就非常完美了!

defaults

defaults : 视图函数的参数默认值{"nid":100}

注意:视图函数必须要设置形参nid,否则报错!

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info", methods=["GET", "POST"],endpoint="r_info",defaults={"nid": 100})
def student_info(nid):
    # stu_id = int(request.args["nid"])
    print(url_for("r_info", _external=True))
    print(nid)
    return f"hello kitty {nid}"  # Python3.6的新特性 f"{变量名}"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

 

访问url:  http://127.0.0.1:5000/info

效果如下:

strict_slashes

strict_slashes : url地址结尾符"/"的控制 False : 无论结尾 "/" 是否存在均可以访问 , True : 结尾必须不能是 "/"

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info", strict_slashes=True)
def student_info():
    return "hello kitty info"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

 

末尾不带 "/"

 

 

末尾带 "/"

也就是说:strict_slashes = True ,表示开启路由严格匹配模式!即使末尾多了一个"/",也不允许访问!

 

将参数改为False

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info", strict_slashes=False)
def student_info():
    return "hello kitty info"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

 

刷新页面,又可以访问了

如果多加一个s,会报404

总结:strict_slashes 用来控制末尾是否有"/",为Ture,带 "/"不允许!

为False,不管你带不带"/",都可以访问!

redirect_to

redirect_to : url地址重定向

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info", redirect_to="/infos")
def student_info():
    return "hello kitty info"

@app.route("/infos")
def student_infos():
    return "Hello Tom infos"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000, debug=True)
View Code

访问url:  http://127.0.0.1:5000/info,会发生重定向

查看浏览器工具,查看网络。它经历了2次请求!

它有什么应用场景呢?

比如你的网站域名是xx.com,后来你的网页改版了,换了新的域名qqxx.com。但是老客户还不知道你的新域名啊!怎么办呢?用重定向就可以了!

subdomain

subdomain : 子域名前缀 subdomian="qq" 这样写可以得到 qq.xx.com 前提是app.config["SERVER_NAME"] = "xx.com:5000"

from flask import Flask,request,url_for

app = Flask(__name__)

# 一定一定一定要写端口!!!!!!
app.config["SERVER_NAME"] = "xx.com:5000"

@app.route("/",endpoint="r_info",subdomain="qq")
def student_info():
    print(url_for("r_info", _external=True,))
    return "hello kitty info"

if __name__ == '__main__':
    # 监听端口为5000,注意:要和SERVER_NAME匹配!
    app.run("0.0.0.0", 5000, debug=True)
View Code

注意:app.config["SERVER_NAME"] = "xx.com:5000"。

一定要加端口!一定要加端口!一定要加端口!重要的事情说三遍!!!

就是因为没有加端口,快要放弃了!google了一把,终于找到原因了!

 

这里是window 10访问。必须要修改本机的Hosts文件,至于怎么修改,为啥没有权限,请自行百度!

修改Hosts文件,添加一条记录

127.0.0.1    qq.xx.com

打开cmd窗口,ping qq.xx.com,请确保返回地址是127.0.0.1

 

打开浏览器访问url,注意:只能是qq.xx.com访问。不能是xx.com!访问时,一定要带上端口!

http://qq.xx.com:5000/

效果如下:

 

关于路由目前就说这么多,之后的课程中会有关于Flask路由系统的源码剖析,再详细说明Flask路由系统的工作原理

 

动态参数路由

2.动态参数路由:

<int:参数名> 参数转换为整形

<int:nid> 就是在url后定义一个参数接收

但是这种动态参数路由,在url_for的时候,一定要将动态参数名+参数值添加进去,否则会抛出参数错误的异常

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info/<int:nid>", endpoint="r_info")
def student_info(nid):
    print(url_for("r_info", _external=True,nid=nid))
    return f"hello kitty {nid}"  # Python3.6的新特性 f"{变量名}"

if __name__ == '__main__':
    # 监听端口为5000
    app.run("0.0.0.0", 5000, debug=True)
View Code

访问url,一定要带上参数,而且参数必须是数字!

查看Pycharm控制台输出:

12 <class 'int'>
http://127.0.0.1:5000/info/12

 

如果是字符串,会报错

 

<string:参数名> 参数转换为字符串

from flask import Flask,request,url_for

app = Flask(__name__)

@app.route("/info/<string:nid>", endpoint="r_info")
def student_info(nid):
    print(nid,type(nid))
    print(url_for("r_info", _external=True,nid=nid))
    return f"hello kitty {nid}"  # Python3.6的新特性 f"{变量名}"

if __name__ == '__main__':
    # 监听端口为5000
    app.run("0.0.0.0", 5000, debug=True)
View Code

刷新页面,就可以访问了

查看Pycharm控制台输出:

ask <class 'str'>
http://127.0.0.1:5000/info/ask

 

参数是数字也是正常的

路由正则

3.路由正则:

一般不用,如果有特殊需求,不怕麻烦的话,这个东西还是挺好用的,前提是你的正则玩儿的很6

from flask import Flask,request,url_for
# 导入转换器基类
from werkzeug.routing import BaseConverter

app = Flask(__name__)

# 自定义正则转换器
class RegexConverter(BaseConverter):
    def __init__(self, url_map, *args):
        super(RegexConverter, self).__init__(url_map)
        # 将接受的第1个参数当作匹配规则进行保存
        self.regex = args[0]


# 将自定义转换器添加到转换器字典中,并指定转换器使用时名字为: re
app.url_map.converters['re'] = RegexConverter

# 使用转换器去实现自定义匹配规则
# 当前此处定义的规则是:3位数字
@app.route('/info/<re("[0-9]{3}"):nid>', endpoint="r_info")
def student_info(nid):
    print(url_for("r_info", _external=True,nid=nid))
    return f"nid 为 {nid}"  # Python3.6的新特性 f"{变量名}"

if __name__ == '__main__':
    # 监听端口为5000
    app.run("0.0.0.0", 5000, debug=True)
View Code

访问url,注意:参数必须是3位数字

 

本文参考:

https://www.cnblogs.com/DragonFire/p/9260295.html

二、实例化Flask的参数

Flask 是一个非常灵活且短小精干的web框架 , 那么灵活性从什么地方体现呢?

有一个神奇的东西叫 Flask配置 , 这个东西怎么用呢? 它能给我们带来怎么样的方便呢?

首先展示一下:

from flask import Flask

app = Flask(__name__)  # type:Flask
app.config["DEBUG"] = True

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

这句 app.config["DEBUG"] = True 可以实现的功能可刺激了

代码只要发生改动,自动重启Flask程序(app.run)

在控制台打印的信息非常全面

以上两个功能就是传说中的 DEBUG 模式(调试模式)

 

Flask的配置就是在 app.config 中添加一个键值对,但是你存进去的键必须是config中应该存在的,如果不再存在的话,它会默认无用,就这么放着

config中有多少有用的key 呢?

{
    '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 异常。
    # 因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。
    # 如果这个值被设置为 True ,你只会得到常规的回溯。
    'EXPLAIN_TEMPLATE_LOADING': False,
    'PREFERRED_URL_SCHEME': 'http',  # 生成URL的时候如果没有可用的 URL 模式话将使用这个值
    'JSON_AS_ASCII': True,
    # 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False ,
    # Flask不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。
    # 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。
    'JSON_SORT_KEYS': True,
    #默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。
    # 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。
    # 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。
    'JSONIFY_PRETTYPRINT_REGULAR': True,
    'JSONIFY_MIMETYPE': 'application/json',
    'TEMPLATES_AUTO_RELOAD': None,
}
View Code

以上这些Key,都可以被改写,当然他们也都是有默认值存在的,如果没有特殊情况,不要改写它的默认值

修改配置的方式

修改配置的方式大约是两种

1.直接对app.config进行修改

app.config["DEBUG"] = True

2.使用类的方式导入

首先要有一个settings.py的文件

class FlaskSetting(object):
    DEBUG = True

 

然后我们在Flask的启动文件中就可以这么写

from flask import Flask
# 导入自定义配置文件的配置类
from settings import FlaskSetting

app = Flask(__name__)
# 应用配置类
app.config.from_object(FlaskSetting)

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

这叫做类导入配置

实例化配置

这是针对一个已经实例化的app进行的配置

那么在Flask实例化的时候,传递的参数是什么鬼呢?

其实可以理解为对Flask实例进行的初始配置,这里面的参数是非常好理解,注意关键字是非常非常非常好理解

static_folder = 'static',  # 静态文件目录的路径 默认当前项目中的static目录
static_host = None,  # 远程静态文件所用的Host地址,默认为空
static_url_path = None,  # 静态文件目录的url路径 默认不写是与static_folder同名,远程静态文件时复用
# host_matching是否开启host主机位匹配,是要与static_host一起使用,如果配置了static_host, 则必须赋值为True
# 这里要说明一下,@app.route("/",host="localhost:5000") 就必须要这样写
# host="localhost:5000" 如果主机头不是 localhost:5000 则无法通过当前的路由
host_matching = False,  # 如果不是特别需要的话,慎用,否则所有的route 都需要host=""的参数
subdomain_matching = False,  # 理论上来说是用来限制SERVER_NAME子域名的,但是目前还没有感觉出来区别在哪里
template_folder = 'templates'  # template模板目录, 默认当前项目中的 templates 目录
instance_path = None,  # 指向另一个Flask实例的路径
instance_relative_config = False  # 是否加载另一个实例的配置
root_path = None  # 主模块所在的目录的绝对路径,默认项目目录
View Code

这里面,我们常用的参数有

static_folder = 'static',  # 静态文件目录的路径 默认当前项目中的static目录
static_url_path = None,  # 静态文件目录的url路径 默认不写是与static_folder同名,远程静态文件时复用
template_folder = 'templates'  # template模板目录, 默认当前项目中的 templates 目录

记住这些就好了,一般的项目中,只修改这些参数

 

template_folder 

昨天已经讲到了template_folder,它是用来指定模板目录的,默认是templates

注意:如果设置template_folder = 'templates',这里面的templates它是相对路径!

假设py文件和templates不在同一目录下,比如这样:

./
├── bin
│   └── start.py
└── templates
    └── home.html

那么start.py使用模板时,应该这么设置  template_folder = '../templates'

完整代码如下:

from flask import Flask,render_template

app = Flask(__name__,template_folder="../templates")

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

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

 

如果找不到模板文件,会提示

 

static_folder

静态文件目录的路径 默认当前项目中的static目录

目录结构如下:

./
├── bin
│   └── start.py
├── static
│   └── meizi.jpg
└── templates
    └── home.html

 

start.py

from flask import Flask,render_template

app = Flask(__name__,template_folder="../templates",static_folder="../static")

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

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

home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>古装美女</h3>
<img src="/static/meizi.jpg" alt="">
</body>
</html>
View Code

meizi.jpg 这是一个妹子图片,自己百度搜索一下

 

重启flask程序,效果如下:

查看网络,图片的实际路径是:

http://127.0.0.1:5000/static/meizi.jpg

 

static_url_path

静态文件目录的url路径 默认不写是与static_folder同名,远程静态文件时复用

怎么知道static_url_path和static_folder默认同名呢?

修改start.py,打印变量

from flask import Flask,render_template

app = Flask(__name__,template_folder="../templates",static_folder="../static")

@app.route("/")
def index():
    print(app.static_folder)
    print(app.static_url_path)
    return render_template("home.html")

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

刷新页面,查看Pycharm控制台输出:

/static
/static

这2个确实是一样的!

 

static_url_path变量是可以修改的,但是和前端保持一致

修改start.py

from flask import Flask,render_template

app = Flask(__name__,template_folder="../templates",static_folder="../static",static_url_path="/app")

@app.route("/")
def index():
    print(app.static_folder)
    print(app.static_url_path)
    return render_template("home.html")

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

修改home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>古装美女</h3>
<img src="/app/meizi.jpg" alt="">
</body>
</html>
View Code

重启flask,刷新页面,效果同上!

 

注意:如果使用蓝图,并做了代码拆分。静态目录名不能是static,模板目录不能是templates。必须改名!!!

必须指定3个参数

template_folder="../templates",static_folder="../statics",static_url_path="/statics" 

否则会出现,模板无法渲染。css访问出现404的问题!

视图渲染时,使用

return render_template("index.html")

模板引用css时,使用

<link rel="stylesheet" href="/statics/bootstrap.min.css">

 

本文参考:

https://www.cnblogs.com/DragonFire/p/9260299.html

三、蓝图(BluePrint)

什么是蓝图

Flask中提供了蓝图,专门用作Flask的模块化。对于蓝图,可以看官方介绍,这里翻译过来的:

Flask使用蓝图的概念来制作应用程序组件和支持应用程序内部或跨应用程序的通用模式。蓝图可以大大简化大型应用程序的工作方式,并为Flask扩展提供了在应用程序上注册操作的中心手段。Blueprint对象的工作方式与Flask应用程序对象类似,但实际上它不是一个应用程序。相反,它是如何构造或扩展应用程序的蓝图。

总之,蓝图可以使我们的程序更加模块化,不同功能的路由可以放在不同的模块下,最后集中到启动类中

 

蓝图,听起来就是一个很宏伟的东西

在Flask中的蓝图 blueprint 也是非常宏伟的

它的作用就是将 功能 与 主服务 分开怎么理解呢?

比如说,你有一个客户管理系统,最开始的时候,只有一个查看客户列表的功能,后来你又加入了一个添加客户的功能(add_user)模块, 然后又加入了一个删除客户的功能(del_user)模块,然后又加入了一个修改客户的功能(up_user)模块,在这个系统中,就可以将

查看客户,修改客户,添加客户,删除客户的四个功能做成蓝图加入到客户管理系统中,本篇最后会做一个这样的例子,但是首先我们要搞清楚什么是蓝图 blueprint

初识Flask蓝图

1.初识Flask蓝图(blueprint)

创建一个项目然后将目录结构做成:

./
├── manager.py
└── student_view
    └── s_view.py

注意:要手动创建目录student_view,并在此目录下创建s_view.py

 

s_view.py

from flask import Blueprint  # 导入 Flask 中的蓝图 Blueprint 模块

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


@sv.route("/svlist")  # 这里添加路由和视图函数的时候与在Flask对象中添加是一样的
def view_list():
    return "svlist_view_list"
View Code

 

manager.py

from flask import Flask

# 导入此前写好的蓝图模块
from student_view import s_view

app = Flask(__name__)  # type:Flask

# 在Flask对象中注册蓝图模块中的蓝图对象 s_view 中的 sv
app.register_blueprint(s_view.sv)

if __name__ == '__main__':
    app.run("0.0.0.0",5000)
    # 现在Flask对象中并没有写任何的路由和视图函数
View Code

开启服务,然后访问 http://127.0.0.1:5000/svlist 查看结果

 

很明显,我们没有在Flask对象中添加路由,但是我们注册了有路由和视图函数的sv蓝图对象

理解蓝图

2.如何理解蓝图呢?

其实我们可以理解成一个没有run方法的Flask对象,这个理论虽然有很多的漏洞,但是对于刚接触蓝图的你来说,就这么样理解,没有错

下面来看一下,在实例化蓝图的时候可以传递的参数都有什么,你就能完全理解了

目录结构:

./
├── manager.py
├── student_view
│   └── s_view.py
├── sv_static
│   └── meizi.jpg
└── sv_template
    └── svlist.html

 

s_view.py

from flask import Blueprint  # 导入 Flask 中的蓝图 Blueprint 模块
from flask import render_template

sv = Blueprint("sv",
               __name__,
               # 这里是相对路径,要加../
               template_folder="../sv_template",  # 每个蓝图都可以为自己独立出一套template模板文件夹,如果不写则共享项目目录中的templates
               static_folder="../sv_static"  # 静态文件目录也是可以独立出来的
               )  # 实例化一个蓝图(Blueprint)对象


@sv.route("/svlist")
def view_list():
    return render_template("svlist.html")
View Code

 

svlist.html 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>Hello ! I am sv_template</p>
    <img src="/sv_static/meizi.jpg">
</body>
</html>
View Code

manager.py的代码,不需要改动。

重启flask,刷新页面:

妹子,还是那个妹子

 

从这个例子中我们总结出:

Blueprint 其实可以理解为一个了没有run方法的 Flask 对象

只要Blueprint被 Flask 注册了,就一定会生效

坑来了!坑来了!

蓝图内部的视图函数及route不要出现重复,否则~你们自己试试吧

 

增删改查用户

3.使用蓝图,做一个增删改查用户

要有一个文件存放我们的原始数据

student_data.py 文件中的内容:

STUDENT = [
    {'id':1,'name': '韩雪', 'age': 24, 'gender': ''},
    {'id':2,'name': '舒畅', 'age': 23, 'gender': ''},
    {'id':3,'name': '唐嫣', 'age': 25, 'gender': ''}
]

然后我们根据以上内容进行增删改查

web应用搭建

3.1 使用蓝图进行web应用搭建:

目录结构如下:

./
├── manager.py
├── student
│   └── __init__.py
└── student_data.py

__init__.py 文件中的内容:

from flask import Flask


def create_app():
    app = Flask(__name__)

    return app
View Code

这个文件我们会修改函数 create_app中的代码

 

manager.py 文件中的内容

from student import create_app

flask_app = create_app()

if __name__ == '__main__':
    flask_app.run("0.0.0.0",5000,debug=True)
View Code

通过这种方式启动 Flask 程序

查看学生信息

3.2 使用Flask蓝图,查看学生信息

项目结构如下:

./
├── html
│   └── s_list.html
├── manager.py
├── student
│   └── __init__.py
├── student_data.py
└── student_select
    └── stu_select.py

s_list.html 文件中的内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生列表</title>
</head>
<body>
<table border="3xp">
    <thead>
    <tr>
        <td>ID</td>
        <td>name</td>
        <td>age</td>
        <td>gender</td>
        <td>options</td>
    </tr>
    </thead>
    <tbody>
    {% for foo in student %}
        <tr>
            <td>{{ foo.id }}</td>
            <td>{{ foo["name"] }}</td>
            <td>{{ foo.get("age") }}</td>
            <td>{{ foo.gender }}</td>
            <td><a href="/s_update/{{ foo.id }}">修改</a> | <a href="/s_del?id={{ foo.id }}">删除</a></td>
        </tr>
    {% endfor %}
    </tbody>
</table>
<a href="/s_add"> 添加学生 </a>
</body>
</html>
View Code

 

stu_select.py 文件中的内容:

from flask import Blueprint
from flask import render_template
from student_data import STUDENT

ss_blueprint = Blueprint("ss_b", __name__, template_folder="../html")


@ss_blueprint.route("/s_list")
def s_list():
    return render_template("s_list.html", student=STUDENT)
View Code

 

student/__init__.py 文件中的内容:

from flask import Flask
from student_select import stu_select


def create_app():
    app = Flask(__name__)

    app.register_blueprint(stu_select.ss_blueprint)

    return app
View Code

 

赶紧运行一下manager.py 来访问一下,我们的成果

 

什么链接都不要点,因为点啥都不好使,之后咱们一个一个的做

添加一个学生

3.3. 使用Flask蓝图,添加一个学生

增加一个目录,结构如下:

./
├── html
│   ├── s_add.html
│   └── s_list.html
├── manager.py
├── student
│   └── __init__.py
├── student_add
│   └── stu_add.py
├── student_data.py
└── student_select
    └── stu_select.py

 

s_add.html 文件中的内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生列表</title>
</head>
<body>
<form method="post">
    ID:<input type="text" name="id"> <br>
    姓名:<input type="text" name="name"><br>
    年龄:<input type="text" name="age"><br>
    性别:<input type="text" name="gender"><br>
    <input type="submit" value="添加学生">
</form>

</body>
</html>
View Code

 

stu_add.py 文件中的内容

from flask import Blueprint
from flask import redirect
from flask import request
from flask import render_template
from student_data import STUDENT

s_add = Blueprint("s_add", __name__, template_folder="html", static_folder="static")  # type:Blueprint


@s_add.route("/s_add", methods=["GET", "POST"])
def s_add_view():
    if request.method == "POST":
        stu_dic = {
            "id": request.form["id"],
            "name": request.form["name"],
            "age": request.form["age"],
            "gender": request.form["gender"]
        }

        STUDENT.append(stu_dic)

        return redirect("/s_list")

    return render_template("s_add.html")
View Code

 

这里面我们让他添加完一个学生,就返回到s_list查看学生列表

student/__init__.py 文件中的内容

from flask import Flask
from student_select import stu_select
from student_add import stu_add


def create_app():
    app = Flask(__name__)

    app.register_blueprint(stu_select.ss_blueprint)
    app.register_blueprint(stu_add.s_add)

    return app
View Code

 

重启flask服务,点击添加学生

 

如果你要是重新启动服务了,那么你刚刚添加的学生信息就没有了

添加完成之后

 

添加学生的Blueprint已经做完了

修改学生信息

3.4. 使用Flask蓝图,修改学生信息

增加一个目录,结构如下:

./
├── html
│   ├── s_add.html
│   ├── s_list.html
│   └── s_update.html
├── manager.py
├── student
│   └── __init__.py
├── student_add
│   └── stu_add.py
├── student_data.py
├── student_select
│   └── stu_select.py
└── student_update
    └── stu_update.py

 

s_update.html 文件中的内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生列表</title>
</head>
<body>
<form method="post">
    <input type="text" name="id" hidden value="{{ student.id }}"><br>
    姓名:<input type="text" name="name" value="{{ student.name }}"><br>
    年龄:<input type="text" name="age" value="{{ student.age }}"><br>
    性别:<input type="text" name="gender" value="{{ student.gender }}"><br>
    <input type="submit" value="修改信息">
</form>

</body>
</html>
View Code

 

stu_update.py 文件中的内容:

from flask import Blueprint
from flask import render_template
from flask import redirect
from flask import request
from student_data import STUDENT

s_update = Blueprint("s_update", __name__, template_folder="html", static_folder="static")


@s_update.route("/s_update/<int:nid>",methods=["GET","POST"])
def s_update_view(nid):
    if request.method == "POST":
        stu_id = int(request.form["id"])
        stu_dic = {
            "id": stu_id,
            "name": request.form["name"],
            "age": request.form["age"],
            "gender": request.form["gender"]
        }

        for index,stu in enumerate(STUDENT):
            if stu["id"] == stu_id:
                STUDENT[index] = stu_dic

        return redirect("/s_list")

    for stu in STUDENT:
        if stu["id"] == nid :
            return render_template("s_update.html", student=stu)

    return render_template("s_update.html", student="")
View Code

 

student/__init__.py 文件中的内容:

from flask import Flask
from student_select import stu_select
from student_add import stu_add
from student_update import stu_update


def create_app():
    app = Flask(__name__)  # type:Flask

    app.register_blueprint(stu_select.ss_blueprint)
    app.register_blueprint(stu_add.s_add)
    app.register_blueprint(stu_update.s_update)

    return app
View Code

 

重启flask,刷新网页。点击一条记录,并修改

 

修改年龄

 点击修改信息,效果如下:

 

 

修改的功能也已经做完了,删除功能也是一样的。

 

删除学生

3.4. 使用Flask蓝图,删除学生信息

增加一个目录,结构如下:

./
├── html
│   ├── s_add.html
│   ├── s_list.html
│   └── s_update.html
├── manager.py
├── student
│   └── __init__.py
├── student_add
│   └── stu_add.py
├── student_data.py
├── student_delete
│   └── stu_delete.py
├── student_select
│   └── stu_select.py
└── student_update
    └── stu_update.py

注意:删除不需要html文件

 

修改s_list.html,这里的删除链接有问题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生列表</title>
</head>
<body>
<table border="3xp">
    <thead>
    <tr>
        <td>ID</td>
        <td>name</td>
        <td>age</td>
        <td>gender</td>
        <td>options</td>
    </tr>
    </thead>
    <tbody>
    {% for foo in student %}
        <tr>
            <td>{{ foo.id }}</td>
            <td>{{ foo["name"] }}</td>
            <td>{{ foo.get("age") }}</td>
            <td>{{ foo.gender }}</td>
            <td><a href="/s_update/{{ foo.id }}">修改</a> | <a href="/s_delete/{{ foo.id }}">删除</a></td>
        </tr>
    {% endfor %}
    </tbody>
</table>
<a href="/s_add"> 添加学生 </a>
</body>
</html>
View Code

 

stu_delete.py 文件中的内容:

from flask import Blueprint
from flask import render_template
from flask import redirect
from flask import request
from student_data import STUDENT

s_delete = Blueprint("s_delete", __name__, template_folder="html", static_folder="static")


@s_delete.route("/s_delete/<int:nid>",methods=["GET","POST"])
def s_delete_view(nid):
    for stu in STUDENT:
        if stu["id"] == nid :
            STUDENT.remove(stu)  # 列表移除key
            return redirect("/s_list")

    return redirect("/s_list")
View Code

 

student/__init__.py 文件中的内容,注册蓝图

from flask import Flask
from student_select import stu_select
from student_add import stu_add
from student_update import stu_update
from student_delete import stu_delete


def create_app():
    app = Flask(__name__)

    app.register_blueprint(stu_select.ss_blueprint)
    app.register_blueprint(stu_add.s_add)
    app.register_blueprint(stu_update.s_update)
    app.register_blueprint(stu_delete.s_delete)

    return app
View Code

 

重启flask,测试效果:

 

增删改查,功能全部完结了!各位看官,有时间的话,可以使用pymysql实现!

建表以及插入数据

# 创建数据库
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL COMMENT '用户名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `gender` enum('','') DEFAULT '' COMMENT '性别',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 插入3条数据
INSERT INTO `student` (`id`, `name`, `age`, `gender`) VALUES ('1', '韩雪', '24', '');
INSERT INTO `student` (`id`, `name`, `age`, `gender`) VALUES ('2', '舒畅', '23', '');
INSERT INTO `student` (`id`, `name`, `age`, `gender`) VALUES ('3', '唐嫣', '25', '');
View Code

 

完整代码,请参考:

链接:https://pan.baidu.com/s/1wOOM2xk6YDZRo5HljgszpQ 密码:viel

 

本文参考:

https://www.cnblogs.com/DragonFire/p/9264381.html

四、before_request after_request

Flask我们已经学习很多基础知识了,现在有一个问题

我们现在有一个 Flask 程序其中有3个路由和视图函数,如下:

from flask import Flask

app = Flask(__name__)


@app.route("/login")
def login():
    return "Login"

@app.route("/index")
def index():
    return "Index"

@app.route("/home")
def home():
    return "Home"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000)
View Code

启动flask,访问home页面

 

现在要求是,如果登陆了,就可以访问 index 和 home 页面,如果没登录就跳转到 login 登录

要怎么解决呢, session 对, 用 session 除了 Login 函数之外的所有函数里面全校验 session 是否登录了

太麻烦了,现在咱们只有3个函数,如果成百上千个怎么整啊

 

装饰器,对没错,装饰器是一个很好的方案,但是啊,我现在还是成败上千个函数,我要在每一个函数定义的时候加上@装饰器,还是很麻烦

那么就引出了我们要学习的第一个知识点:

@app.before_request

from flask import Flask,request,redirect,session

app = Flask(__name__)
app.secret_key = "DragonFire"


@app.before_request
def is_login():  # 判断是否登录
    # 白名单设置,判断为登录页面时
    if request.path == "/login":
        # 跳过处理
        return None
    # 判断session是不存在时
    if not session.get("user"):
        # 重定向到登录页面
        return redirect("/login")

@app.route("/login")
def login():
    return "Login"

@app.route("/index")
def index():
    return "Index"

@app.route("/home")
def home():
    return "Home"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000)
View Code

@app.before_request 也是一个装饰器,他所装饰的函数,都会在请求进入视图函数之前执行

request.path 是来读取当前的url地址如果是 /login 就允许直接通过 return None 你可以理解成通过放行

校验session中是否有user 如果没有的话,证明没有登录,所以毫不留情的 redirect("/login") 跳转登录页面

还有一个要提的 @app.before_first_request 它与 @app.before_request 极为相似或者说是一模一样,只不过它只会被执行一次

 

@app.before_request修饰器在开发中用处非常大,比如判断某个ip是否有恶意访问行为,从而进行拦截等操作

 

重启flask,再次访问home页面,效果如下:

打开浏览器工具,查看网络

 

@app.after_request 

 2. @app.after_request 在响应(response)之前做出响应

from flask import Flask,request,redirect,session

app = Flask(__name__)
app.secret_key = "DragonFire"


@app.before_request
def is_login():  # 判断是否登录
    # 白名单设置,判断为登录页面时
    if request.path == "/login":
        # 跳过处理
        return None
    # 判断session是不存在时
    if not session.get("user"):
        # 重定向到登录页面
        return redirect("/login")

@app.after_request
def foot_log(environ):  # 记录访问日志
    print(environ)  # 响应信息
    # 判断请求路径不是登录页面
    if request.path != "/login":
        # 打印访问路径
        print("有客人访问了",request.path)

    return environ

@app.route("/login",methods=["POST","GET"])
def login():
    if request.method == "GET":
        return "Login"

    user = request.form["username"]  # form表单获取
    pwd = request.form["password"]  # form表单获取
    # 判断form表示数据和 后台数据库匹配
    # models.UserInfo.objects.filter(username=user,password=pwd).first()
    if user == 'xiao' and pwd == '123':
        # 设置session
        session["user"] = user
        # 跳转首页
        return redirect("/index")


@app.route("/index")
def index():
    return "Index"

@app.route("/home")
def home():
    return "Home"

if __name__ == '__main__':
    app.run("0.0.0.0", 5000)
View Code

 

@app.after_request修饰器是在用户请求得到函数响应后被执行,不过需要注意的是这个执行是在函数返回数据前被调用,即请求已经被app.route修饰的函数响应过了,已经形成了response,但还未返回给用户的时候,调用的。

它可以做访问统计,很少应用,但是要了解有这么个东西

 

重启flask,访问home页面,效果同上!它还是会跳转到登录页面

查看Pycharm控制台输出:

<Response 219 bytes [302 FOUND]>
有客人访问了 /home

 使用postman发送post请求

点击发送,查看返回结果

从上图可以看出,验证通过了!跳转到首页

 

可能有人会有疑问,咦?@app.before_request不是装饰器吗?为什么没有在home视图函数中应用,却生效了呢?
应该这样才对啊

@app.route("/home")
@app.before_request
def home():
    return "Home"

NoNoNo!@app.before_request是一个全局装饰器,它是针对所有视图函数的。只要定义了@app.before_request,那么每一个视图函数都会应用。同理,@app.after_request也是全局装饰器!

你可以把@app.before_request理解为django的中间件,请求到达视图函数之前,先走中间件!

 

如果还是不明白,看下面的流程图

流程图

解释:

is_login()函数被@app.before_request修饰以后,每一次请求到来后,都会先进入函数is_login()中,如上代码,获取请求的路径以及session。如果是登录页面,不处理。如果session中user不存在,跳转到登录页面。当session中user存在时,请求才会正常进入到app.route修饰的函数中响应,如果有多个函数被@app.before_request修饰了,那么这些函数会被依次执行。


比如这样:

@app.before_request
    def malicious_ip(): # 判断恶意IP
    pass

@app.before_request
    def xss(): # 判断xss攻击
    pass

@app.before_request
    def is_login(): # 判断登录
    pass

@app.before_request修饰器在开发中用处非常大,比如判断某个ip是否有恶意访问行为,从而进行拦截等操作。


此外同理,app.after_request修饰器是在用户请求得到函数响应后被执行,不过需要注意的是这个执行是在函数返回数据前被调用,即请求已经被app.route修饰的函数响应过了,已经形成了response,但还未返回给用户的时候,调用的。

 

本文参考:

https://www.cnblogs.com/DragonFire/p/9269303.html

 

今日总结:

1.flask路由
    endpoint 反向url标签 
    url_for 通过endpoint反向生成url
    methods=[] 允许进入视图函数的请求方式,默认GET
    strict_slashes 是否严格要求URL地址 /login/ /login
    redirect_to 跳转地址,重定向地址
    动态URL参数 /index/1 /index/<int:id>  def index(id)
    
2.Flask实例化配置
    template_folder 指定模板路径
    static_folder 指定静态文件路径
    static_url_path 静态文件路径访问url

3.app对象配置:
    app.config.from_object(Class)
    DEBUG # 开启代码调试模式(开发)
    SECRET_KEY # 用到session时必须添加
    
4.蓝图:
    Blueprint 相当于是一个不能够被run的flask对象
    Blueprint("blue",__name__,template_folder,static_folder,static_url_path)
    可以为蓝图增加独立的模板和静态目录
    为蓝图增加路由
    让flask实例 注册蓝图 register_blueprint(蓝图)
    功能和主程序分离,注册
    
5. send_file jsonify
    send_file() # 打开文件并返回,并加入返回头
    jsonify # 将字典json后,加入返回头applction/json


6.特殊装饰器:
    before_request: 请求进入视图函数之前执行
    after_request: 响应返回前端之前执行
    errorhandler(404): 错误响应
    before_first_request: 第一次访问时,请求进入视图函数之前执行
    
    be1 - be2 - af2 - af1
    be1 - af2 - af1


7.闪现 flash:
    
    flash("内容","标签")
    get_flashed_messages()
    get_flashed_messages(category_filter=["标签"])
View Code

 

posted @ 2018-09-06 15:28  肖祥  阅读(2408)  评论(0编辑  收藏  举报