flask框架

中文文档(http://docs.jinkan.org/docs/flask/)
英文文档(http://flask.pocoo.org/docs/0.11/)
postman
回顾HTTP通信
在这里插入图片描述


历史

Flask诞生于2010年,是Armin ronacher(人名)用Python语言基于Werkzeug工具箱编写的轻量级Web开发框架。它主要面向需求简单的小应用。

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

可以说Flask框架的核心就是Werkzeug和Jinja2

Python最出名的框架要数Django,此外还有Flask、Tornado等框架。虽然Flask不是最出名的框架,但是Flask应该算是最灵活的框架之一,这也是Flask受到广大开发者喜爱的原因。


01-flask安装配置

虚拟环境是一个互相隔离的目录

mkvirtualenv flask_py2
pip install flask==0.10.1

pip freeze>requirements.txt
pip install -r requirements.txt

flask第一个 Hello World

# coding:utf-8

# 导入 Flask 类
from flask import Flask

# 创建一个flask的应用对象
# __name__ 表示当前的模块名字
#            模块名,flask以这个模块所在的目录为总目录,默认这个目录中的static为静态目录,templates为模版目录
app = Flask(__name__)
# app = Flask("__main_") # 直接当成启动文件的模式

@app.route("/") #装饰器
def index():
    """定义视图函数"""
    return "Hello Flask"

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

02-app对象的初始化和配置

目录

|-flask_project
|—static
|—templates
|—app.py
|—demo.py

flask应用对象初始化参数说明

"""
import name; #导入路径(寻找静态目录与模版目录位置的参数)
static_url_path # 访问静态资源的url前缀,默认static
static_folder # 静态文件的目录,默认 static
template_folder # 模版文件目录,默认是templates
"""

Flask(__name__, # __name__ 就是当前模块的名字
    static_url_path="/static", # 默认值是static
    static_folder="static",
    template_folder="templates"
)

静态文件目录与路由说明

/static/ index.html
将 static 目录摘取出来,默认访问静态资源了就;
约定成熟的!

url.py

flask 设置 - 配置参数

  • 3 直接操作 config app.config['DEBUG'] = TRUE
    flask中的配置参数不是在一个单独的文件中设置的,而是在app.config中保存的
    app.config的操作与对字典的操作相同,可以通过[]运算符直接设置其中的内容,还可以通过get取出其中的内容

    之前在启动程序的时候,如果设置debug=True的参数,就可以打开调试模式,如果不在启动的时候给出debug参数,也可以在app.config中设置DEBUG,同样可以打开调试模式。本次就以这个参数为例,演示一下flask的配置参数的方法

    from flask import Flask
    
    app = Flask(__name__)
    
    # 通过直接操作app.config字典,来完成配置参数
    # 这并不是一个好的配置参数的方法,仅供参考
    app.config["DEBUG"] = True
    
    @app.route("/")
    def index():
        return "<h1>hello world</h1>"
    
    
    if __name__ == "__main__":
        app.run()
    
  • 1 使用配置文件读取配置文件app.config.from_pyfile()
    通过app.config.from_pyfile(),指定配置文件,可以完成参数配置
    首先,创建一个settings.py文件,settings.py中的代码如下
    DEBUG = True

    from flask import Flask
    app = Flask(__name__) 
    app.config.from_pyfile("./settings.py")
    
  • 2 通过对象配置参数(项目中的方式) app.config.from_object()
    定义一个配置参数的类对象,然后从中读取配置信息
    从对象中读取文件的方法:app.config.from_object()
    具体的使用代码如下

    from flask import Flask
    
    app = Flask(__name__)
    
    class FlaskConfig:
        DEBUG = True
    
    app.config.from_object(FlaskConfig)
    
  • 4 使用环境变量读取配置文件
    同上一个方法一样,仍然是通过配置文件完成配置信息,但是不同的地方在于配置文件的名字没有被直接写在代码中,而是通过系统的环境变量读取的
    环境变量名设置为:任意,例如FLASK_SETTING
    读取环境变量使用:app.config.from_envvar(环境变量名的字符串)
    例如,在linux的系统中,通过如下的方式设置环境变量名

    # 环境变量的设置,用于linux系统,windows系统可通过相似的方法完成设置
    export FLASK_SETTING="./settings.py"
    

    然后在代码中,按照如下方式使用

    from flask import Flask
    
    app = Flask(__name__)
    app.config.from_envvar("FLASK_SETTING")
    

    补充:从环境变量中读取配置文件的分析
    在python中,通过内置的os模块,可以读取到环境变量,因此从环境变量中读取配置文件,和直接读取配置文件其实是一样的,以上代码相当于

    from flask import Flask
    import os
    
    app = Flask(__name__)
    
    # 通过os模块,从os.environ中读取到FLASK_SETTING的配置文件路径
    flask_setting = os.environ.get("FLASK_SETTING")
    
    # 通过刚刚读取到的配置文件的路径,通过from_pyfile配置文件
    app.config.from_pyfile(flask_setting)
    

读取 - 配置参数

视图中使用配置参数?

  • 1.直接从全局对象app的config字典中取值
    app.config.get()
  • 2.current_app
    from flask import Flask,current_app
    current_app.config.get()

app的run使用说明

Flask app.run的执行流程
在使用app.run执行的时候我们不是再本地跑或者说我们不想使用127.0.0.1的方式来执行。那我们应该怎么执行呢?
‘’’
1.host:要监听的主机名。 默认为127.0.0.1(localhost)。设置为“0.0.0.0”以使服务器在外部可用
2.port:默认值为5000
3.debug:默认为false。 如果设置为true,则提供调试信息
4.options:要转发到底层的Werkzeug服务器
‘’’
app.run(host=‘0.0.0.0’,port=5000,debug=True)

03-视图函数的路由

使用route() 装饰器来告诉 Flask 触发函数的 URL
路由转换器

视图函数的路由规则设置说明

  • 查看全部路由信息

    app.url_map 查看所有路由

  • 给路由指定访问方式

    通过 methods 限定访问方式, 接受参数形式为列表

    # 通过method限定访问方式
    @app.route('/post_only', methods=['POST', 'GET'])
    def post_only():
        return 'post'
    
  • 反解析 url_for()和redirect()

    使用 url_for() 的函数,可以通过 视图函数的名字 找到视图对应的 url 路径
    而 redirect() 函数则是 url 重定向,会再次发送一个新的请求。
    从 flask 库中导入这两个函数,一般这两个函数会一起使用。
    切记: url_for() 接收的是 视图函数的名称,返回的是对应的 url 路径,切勿把 url 路径传进去。


案例:

from flask import Flask,current_app,redirect,url_for

# 创建flask应用对象
# __name__表示当前的模块名字
# 模块名,flask以整个模块所在的目录为总目录,默认整个目录中的static为静态目录
# templates为模板目录
app = Flask(__name__)

@app.route("/")
def index():
    """
    定义的视图函数
    :return:
    """
    print(app.url_map)
    return "hello flask"

@app.route("/post_only",methods=["POST"])
def post_only():
    return "post only page"

@app.route("/hello",methods=["POST"])
def hello():
    return "hello 1"

@app.route("/hello",methods=["GET"])
def hello2():
    return "hello 2"

# 多个路由绑定同一视图函数
@app.route("/hi1")
@app.route("/hi2")
def hi():
    return "hi page"


@app.route("/login")
def login():
    # 使用url_for函数,通过视图函数的名字,找到视图对应的url路径
    url = url_for("index")
    return redirect(url)

@app.route("/register")
def register():
    url = "/"
    return redirect(url)

if __name__ == '__main__':
    # 通过url_map可以查看整个flask中路由信息
    print(app.url_map)
    app.run(debug=True)

路由提取参数与自定义路由转换器

路由转换器: 有时我们需要将同一类URL映射到同一个视图函数处理
比如:使用同一个视图函数 来显示不同用户的个人信息。

注意 转换器的使用格式为:<转换器类型:参数名称>

Flask内置转换器

转换器描述
int接受整数
float同int,但是接受浮点数
path和默认的相似,但也接受斜线
  • [ X ] 字符串转换器
    注意:视图函数里接受的参数必须和route捕获尖括号<>里的参数一致
# str转换器 不加转换器类型, 默认是普通字符串规则(除了/的字符)
@app.route('/user/<username>')
def user_str(username):
    response = 'hello {}'.format(username)
    return response
  • [ X ] 整型转换器
# int转换器
@app.route('/user/<int:user_id>')
def user_int(user_id):
    response = 'hello {}'.format(user_id)
    return response
  • [ X ] 路径转换器
# path转换器
@app.route('/user/<path:user>')
def user_path(user):
    response = 'hello {}'.format(user)
    return response

自定义转换器

自定义一个匹配正则 url 的转换器
```py
# -- coding: utf-8 --

from flask import Flask, redirect, url_for

# 1. 定义自己的转换器
# 首先导入 werkzeug.routing 中 BaseConverter
from werkzeug.routing import BaseConverter

app = Flask(__name__)


# 定义自己的转换器 MoblieConverter
class MoblieConverter(BaseConverter):
    def __init__(self, url_map):
        # 调用父类的构造方法
        super() # python3 中直接调用
        super(MoblieConverter,self).__init__(url_map) # python2 中的调用
        # 将正则表达式参数保存到对象属性中,flask会使用这个属性来进行路由的正则匹配
        self.regex = r'1[34578]\d{9}'


# 然后自定义类继承 BaseConverter
class RegexConverter(BaseConverter):
    """"""
    def __init__(self, url_map, regex):
        # 调用父类的构造方法
        super() # python3 中直接调用
        super(RegexConverter,self).__init__(url_map) # python2 中的调用
        # 将正则表达式参数保存到对象属性中,flask会使用这个属性来进行路由的正则匹配
        self.regex = regex

# 2. 将自定义的转换器添加到flask的应用当中
app.url_map.converters["re"] = RegexConverter
app.url_map.converters["mobile"] = MoblieConverter

# 3. 使用
# 127.0.0.1:5000/send/15912456789
@app.route("/send/<re(r'1[34578]\d{9}'):mobile_num>")
@app.route("/send/<mobile:mobile_num>")
def send_sms(mobile_num):
    return "send sms to %s" % mobile_num

if __name__ == '__main__':

    # 启动flask程序
    app.run(debug=True)
```

路由转换器进阶使用 to_python()、to_url()

重写父类 BaseConverter to_python()、to_url()方法
to_python() 用于处理转换器提取出来的参数,可以进行修改。(转换器的核心)
to_url() 使用 url_for() 时调用,其结果将作为 url_for的返回值;每次进行url_for提交的参数都会先经过to_url,经过处理后才会返回回去。这里我就不过多演示。

```py
from flask import Flask, redirect, url_for

# 然后自定义类继承 BaseConverter
class RegexConverter(BaseConverter):
    """"""
    def __init__(self, url_map, regex):
        # 调用父类的构造方法
        super() # python3 中直接调用
        super(RegexConverter,self).__init__(url_map) # python2 中的调用
        # 将正则表达式参数保存到对象属性中,flask会使用这个属性来进行路由的正则匹配
        self.regex = regex

    def to_python(self, value):
        return value+'RegexConverter'

    def to_url(self, value):
        return value+'RegexConverter'

@app.route('/')
def index():
    url = url_for("send_sms", mobile_num="15912354697")
    return redirect(url)
```

04-request对象

request中包含了前端发送过来的所有请求数据,在使用前要进行导入request库
from flask import request

request 常用的属性如下表

属性说明类型
data记录请求的数据,并转换为字符串*
form记录请求中的表单数据MultiDict
args记录请求中的查询 参数MultiDict
cookies记录请求中的 cookie 信息Dict
headers记录请求中的报文头EnvironHeaders
methd记录请求使用的Http方法GET/POST
url记录请求的url地址string
files记录请求上传的文件*

request的form_data_args

表单格式数据: k=v&kk=vv&kkk=vvv

  1. form和data是用来提取请求体数据,通过request.form可以直接提取请求体中的表单格式的数据,是一个类字典的对象,例如:
from flask import Flask,request

app = Flask(__name__)

@app.route("/index",methods=["GET","POST"])
def index():
    # request中包含了前端发送过来的所有请求数据
    # form和data是用来提取请求体数据
    # 通过request.form可以直接提取请求体中的表单格式的数据,是一个类字典的对象
    name = request.form.get("name")
    age = request.form.get("age")
    return "hello world name=%s ,age=%s"%(name, age)

if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8000,debug=True)

注意:通过get方法只能拿到多个同名参数的第一个,getlist方法可以拿到多个同名参数的列表,例如:

from flask import Flask,request

app = Flask(__name__)

@app.route("/index",methods=["GET","POST"])
def index():
    # request中包含了前端发送过来的所有请求数据
    # form和data是用来提取请求体数据
    # 通过request.form可以直接提取请求体中的表单格式的数据,是一个类字典的对象
    # 通过get方法只能拿到多个同名参数的第一个
    name = request.form.get("name")
    age = request.form.get("age")
    print(request.data)
    # getlist方法可以拿到多个同名参数的列表
    name_li = request.form.getlist("name")
    return "hello world name=%s ,age=%s, name_li=%s"%(name, age, name_li)

if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8000,debug=True)

  1. 若数据不是表单格式的数据,可用requset.data来进行提取
from flask import Flask,request

app = Flask(__name__)

@app.route("/index",methods=["GET","POST"])
def index():
    result= request.data
    return result

if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8000,debug=True)
  1. args是用来提取url中的参数,例如http://127.0.0.1:8000/index?city=BeiJing 查询字符串,可以用args方式进行提取city参数,范例如下:
from flask import Flask,request

app = Flask(__name__)


# http://127.0.0.1:8000/index?city=huizhou 查询字符串 可以用args方式进行提取
@app.route("/index",methods=["GET","POST"])
def index():
    # args是用来提取url中的参数
    city = request.args.get("city")
    day = request.args.get("day")
    return "hello world city=%s, day=%s"%( city, day)

if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8000,debug=True)

上传文件

只能是POST方式上传文件!!!
上传文件主要用了request当中最后一个属性files
填写headers:
Key:Content-Type
Value:multipart/form-data

# -*- coding: utf-8 -*-
from flask import Flask,request

app = Flask(__name__)


@app.route("/upload", methods=["GET","POST"])
def upload():
    f = request.files.get("pic")
    if f is None:
        # 没有发送文件
        return "未上传文件"
    # 将文件保存在本地
    # 1.创建一个文件
    f1 = open("./demo.png", "wb")
    # 2.向文件写内容
    data = f.read()
    f1.write(data)
    f1.close()
    return "上传成功"

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

更简单的写法


# -*- coding: utf-8 -*-
from flask import Flask,request

app = Flask(__name__)


@app.route("/upload", methods=["GET","POST"])
def upload():
    f = request.files.get("pic")
    if f is None:
        # 没有发送文件
        return "未上传文件"
    # 直接使用上传的文件对象保存
    f.save("./demo1.jpg")
    return "上传成功"

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

with使用

有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。其中一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。

基本思想是:with所求值的对象必须有一个enter()方法,一个exit()方法。
enter 进入with调用
exit 离开with的时候调用

python的with用法

# coding:utf-8

f = open('./test.txt','wb')
try:
    f.write('hello')
except Exception:
    pass
finally:
    f.close()

## 等价于
with open('./test.txt','wb') as f:
    f.write('hello')

### with 完成finally完成的操作!!!

05 - abort函数、自定义错误、 视图函数的返回值

abort函数 from flask import abort

立即终止视图函数的运行,并且返回给前端特定的信息
1. 传递状态码信息 `abort(400)`;[必须是标准的状态码]
2. 传递响应体信息 `abort(Response("faild"))`
```py
# coding:utf-8

from flask import Flask, abort, Response

app = Flask(__name__)


@app.route("/login", methods=["GET"])
def login():
    name = ""
    pwd = ""
    if name != "zhangsan" or pwd != "admin":
        # abort函数可以立即终止视图函数的执行
        # 并返回给前端特定的信息
        # 1 传递状态码信息, 必须是标准的http状态码
        abort(404)

        # # 2. 传递响应体信息
        # resp = Response("login failed")
        # abort(resp)  # 返回Response响应对象

    return "login success"


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)
```

自定义错误

```py
@app.errorhandler(404)
def error(err):
    return '您请求的页面不存在了,请确认后再次访问! %s' % err
```

设置响应信息的方法

- [x]  字符串响应形式
  字符串:直接使用return关键字返回一个字符串: return 'hello flask'
- [x]  模版响应形式
  模版的返回,建立 templates 文件夹 --> 在templates中建立要返回的HTML页面 --> 在视图中使用`render_template(HTML文件名)` --> 返回模版
- [x]  重定向响应形式
  重定向:重定向是跳转至一个新的路由,使用redirect
  重定向的形式:
    重定向一个外链
    重定向一个内部链接
- [x] 响应json形式与元组
  json: 返回json数据需要借助flask提供的jsonify来实现
  元组形式: 可以返回一个元组。这样的元组必须是(response, status, headers)的形式,且至少包含2个元素。status 值会覆盖状态代码, headers可以是一个列表或者字典,作为额外的消息表头值
  > import json
  >  json.dumps('字典')
  >  json.loads('字符串')
  ```python
    # 实例如下:
    from flask import Flask
    from flask import jsonify
    
    app = Flask(__name__)
    
    # --- json ---
    @app.route('/index', endpoint='idx')
    def index():
        # 字典形式数据
        data = {
                'name': 'jeremy',
                'age': 18
            }
        # 返回json数据;并设置header头
        return jsonify(data)
    # 看一下源码
    
    # --- 元组 ---
    @app.route('/index', endpoint='idx')
    def index():
        # return ('要返回的字符串', 6969是状态码, {'author': 'jeremy'}字典是设置在响应头中的键值对信息)
        return ('要返回的字符串', 6969, {'author': 'jeremy'})

    if __name__ == '__main__':
        app.run(debug=True)
  ```
- [x] make_response()自定义响应
  make_response()形式:flask可以返回make_response自定义的响应,make_response()内部需要传递返回的字符串,起创建的对象还有headers属性用于设置响应头,status属性用于设置状态码
  ```python
    # 示例如下:
    from flask import Flask
    from flask import make_response
    
    app = Flask(__name__)
    
    @app.route('/index', endpoint='idx')
    def index():
        # 响应字符串
        resp = make_response('自定义的响应字符串')
        # 响应头键值对
        resp.headers['author'] = 'jeremy'
        # 状态码与状态码提示信息
        resp.status = '6969 a post'
        # 注意: 响应头键值对与状态码提示信息均为英文, 否则抛异常
        return resp
    
    if __name__ == '__main__':
        app.run(debug=True)
  ```

06-cookie和session

Flask 中的session 和 cookie

cookie 的使用

注意:默认有效期是临时有效
res = make_response(‘success’)
res.set_cookie(‘key’,‘value’,max_age=3600)

request.cookies.get(‘key’)

res = make_response(‘success’)
res.del_cookie(‘key’)

session 机制说明

注意:flask的session需要用到的密钥字段;防串改的处理
app.config[‘SECRET_KEY’] = “dddfdfdfdfdfddfddfdfd”

session[“name”] = “python”

cookie set_cookie 中保存 session_id

服务器生成[session_id] --> 客户端 --> 客户端保存session_id到cookie中
客户端携带cookie[session_id] --> 服务器

特殊:flask默认把session保存在cookie中

session 保存位置:数据库、redis、mongodb、文件、
程序运行内存中?问题:1.占用服务器内存!
2.session 跨服务器:多台机器的话,nginx负载均衡将用户分配到不同的机器?

问:如果禁用cookie可以保存session_id吗?
答:当然可以,将session_id放到请求的路径当中!!! http://www.url.cn?session_id=***

问:url中和cookie中,session的有效期?
cookie中可以设置cookie中的有效期,url中只能是不关闭网站的情况下session有效,再次重新登陆需要重新获取和设置!!!

07-flask上下文和请求钩子

Flask的请求钩子与上下文简记

上下文

上下文
什么是上下文?上下文相当于一个容器,它保存了程序运行过程中的一些信息,它是当前环境的一个快照(snapshot)。
Flask 项目中有两个上下文,一个是应用上下文(app),另外一个是请求上下(request)。请求上下文 request 和 应用上下文 current_app 都是一个全局变量,所有请求都共享的。
程序上下文中包含了程序运行所必须的信息;请求上下文里包含了请求的各种信息,比如请求的URL、HTTP方法等

from flask import Flask,request # 引入的是全局变量

隔离:flask分装好的线程局部变量的概念!!!
全局变量,在不同线程内看到的是不一样的,使用起来就像使用线程变量一样

线程编号作为键 --> {form:“xxx”,args:[]}

-[x]请求上下文 request
-[x]应用上下文 app
current_app
g变量:在本次请求之内,一次请求之内的多个函数之间传递参数!!!

请求钩子

钩子说明
before_first_request注册一个函数,在处理请求前运行
before_request注册一个函数,在处理每个请求前运行
after_request注册一个函数,如果有未处理的一场抛出。会在每个请求结束后运行
teardown_request工作在非调试模式的时候,注册一个函数,即使有未处理的异常抛出,会在每个请求介绍后执行。如果发生异常,会传入异常对象作为参数注册到函数中;debug=False
after_this_request在视图函数内注册一个函数,在这个请求结束后运行
假如我们创建了三个视图函数A、B、C,其中视图C使用了after_this_request钩子,那么当请求A进入后,整个请求处理周期的请求处理函数调用流程如图:

flask_script脚本扩展的使用

flask_script
-[x]第一步:安装扩展脚本库
pip install flask-Script
-[x]第二步:创建Manager管理对象类,将app托管给他

# coding:utf-8

# 文件名:01_flask_script.py

from flask import Flask
from flask_script import Manager  # 启动命令的管理类

app = Flask(__name__)

# 创建Manger管理类的对象
manager = Manager(app)

@app.route('/index')
def index():
    print("index 被执行")
    return "index page"


if __name__ == "__main__":
    # 通过管理对象来启动flask应用
    manager.run()

-[x]第三步:通过–help查看该管理文件当前支持的命令
-[x]第四步:启动flask应用runserver
python 01_flask_script.py runserver
-[x]第五步:shell,通过shell可操作当前问价内的所有对象
python 01_flask_script.py shell

app.url_map
-[x]第六步:查看runserver支持的命令
python 01_flask_script.py runserver --help

08-模板

模板与自定义过滤器

支持链式使用过滤器
<p>{{"jello world" |trim |upper }}</p>
first:获取第一个元素 {{[1,2,3,4,5,6]|first}}
last:最后一个元素 {{[1,2,3,4,5,6]|last}}
length:获取列长度 {{[1,2,3,4,5,6]|length}}

  • 字符串过滤器:
    safe:禁止转义
    capitalize:把变量值的首字母转成大写,其余字母转小写
    lower:把值转成小写
    upper:把值转成大写
    title:把值中的每个单词的首字母都转成大写
    trim:把值的首尾空格去掉
    reverse:字符串反转
    format:格式化输出
    striptags:渲染之前把值中所有的HTML标签都删掉

  • 自定义过滤器
    第一种方法,编写相关函数添加进过滤器模块中:

"""第一种方法"""
# 1.编写过滤器函数
def list_step_2(li):
    return li[::2]

# 注册过滤器
"""app.template_filter第一个参数为过滤器函数,过滤器函数名字"""
app.add_template_filter(list_step_2,"li2")

第二种方法,使用装饰器的方法给过滤器模块添加新功能:

"""第二种方法"""
@app.template_filter("li3")
def list_step_3(li):
    return li[::3]

引入表单扩展

pip install Flask-WTF
Flask web开发实战之基础篇 Flask-表单
Flask-WTF 默认为每个表单启用CSRF保护,他会为我们自动生成和验证CSRF令牌。默认情况下,Flask-WTF 使用程序密钥来对CSRF令牌进行签名,所以我们需要为程序设置密钥:
app.sercret_key='sercret string'

创建表单模型类与模版使用

```python
import os
from pickle import TRUE

from flask import Flask, redirect, render_template, request, session, url_for
from flask_wtf import CSRFProtect, FlaskForm
from wtforms import PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired, EqualTo, Length

SECRET_KEY = os.urandom(24)


app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = SECRET_KEY
```
```html
    <form action="" methods="POST">
        {{ form.csrf_token() }}
        <p>{{ form.user_name }}</p>
        {% for msg in form.user_name.errors %} {{msg}} {% endfor %}
    </form>
```

WTForms 支持的HTML标准字段

字段类型说 明
StringField文本字段
TextAreaField多行文本字段
PasswordField密码文本字段
HiddenField隐藏文本字段
DateField文本字段,值为 datetime.date 格式
DateTimeField文本字段,值为 datetime.datetime 格式
IntegerField文本字段,值为整数
DecimalField文本字段,值为 decimal.Decimal
FloatField文本字段,值为浮点数
BooleanField复选框,值为 True 和 False
RadioField一组单选框
SelectField下拉列表
SelectMultipleField下拉列表,可选择多个值
FileField文件上传字段
SubmitField表单提交按钮
FormField把表单作为字段嵌入另一个表单
FieldList一组指定类型的字段

常见的验证函数

验证函数说 明
Email验证电子邮件地址
EqualTo比较两个字段的值;常用于要求输入两次密码进行确认的情况
IPAddress验证 IPv4 网络地址
Length验证输入字符串的长度
NumberRange验证输入的值在数字范围内
Optional无输入值时跳过其他验证函数
Required确保字段中有数据
Regexp使用正则表达式验证输入值
URL验证 URL
AnyOf确保输入值在可选值列表中
NoneOf确保输入值不在可选值列表中

模版宏的使用

```html
<!-- 模版中 -->
定义:
{% macro input() %}
 <input type="text" value="" size="30"/>
{% endmacro %}
使用:
{{ input() }}

带参数的定义
{% macro input(value,typr,size) %}
 <input type={{}} value={{}} size={{}}/>
{% endmacro %}
使用:
{{ input() }}
```

宏定义在外部使用

提取出来创建单独的一个文件
macro_input.html
{% macro input4() %}

{% endmacro %}

导入宏文件
{% import “macro_input.html” as m_input %}
{{ m_input.input4() }}

模版的闪现使用

  • 模版的继承
# 父模版
{% block top %}
顶部菜单
{% endblock top %}
{% block content %}
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
----
# 子模版
{% extends 'base.html'%}
{% block content %}
需要填充的内容
{% endblock content %}
  • extends 扩展
  • include 包含

flask 特殊的变量和方法

在模版中可以直接使用
config 对象
request 对象
url_for 方法
get_flashed_message方法,返回通过 flash()传入的信息,只能看一次[闪现]

09-数据库扩展包flask-sqlalchemy

使用sqlalchemy的配置

sqlalchemy

sqlalchemy 是一个关系型数据库框架,它提供了高层的ORM和底层的原生数据库操作。flask-sqlalchemy 是一个简化了SQLAlchemy操作的flash扩展。
pip install flask-sqlalchemy
仅仅转换为模型类 --> 转换换成模型类的对象

数据库驱动:要连接mysql数据库,仍需要安装flask-mysqldb、pymysql(python3)

pymysql.install_as_mysqldb()
pip install MySQL-Python :python2默认安装官方的
pip install flask-mysqldb

方法1:pip install PyMySQL
#然后在需要的项目中,把 init.py中添加两行:
import pymysql
pymysql.install_as_MySQLdb()
方法2: pip3 install mysqlclient

config

# coding:utf8

import pymysql
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm

pymysql.install_as_MySQLdb()

app = Flask(__name__)


class Config():
    """配置参数"""
    # sqlalchemy
    SQLALCHEMY_DATABASE_URI = 'mysql://root:123456@127.0.0.1:3306/flask'
    # 设置 sqlalchemy 自动关跟踪数据库
    SQLALCHEMY_TRACK_MODIFICATIONS = True


app.config.from_object(Config)

# 方案二
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test3'
# #每次请求结束后会自动提交数据库中的改动;官方不建议了已经
# app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']=True
# app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=True

# # 查询时会显示原始的sql语句
# app.config['SQLALCHEMY_ECHO']=True

# 使用sqlalchemy的配置
db = SQLAlchemy(app)

########## ------------------------------ ########
"""
创建模型类

表名常见命名规范: 
- 数据库名缩写_表名 db_table
- tbl_表名
"""
class Role(db.Model):
    """角色表"""
    __tablename__ = "tbl_roles" # 指名数据库的表名

    id = db.Column(db.Integer,primary_key=True,unique=True) #整型的主键默认设置为自增
    name = db.Column(db.Srting(32),unique=True)
    users = db.relationship("User",backref="role") # 模型类角度考虑;反推,给User模型添加一个role属性
    
    def __repr__(self):
        """定义以后让对象显示更加直观"""
        return "Role object:name%s" % self.name

class User(db.Model):
    """用户表"""
    __tablename__ = "tbl_users" # 指名数据库的表名

    id = db.Column(db.Integer,primary_key=True,unique=True) #整型的主键默认设置为自增
    name = db.Column(db.Srting(64),unique=True)
    password = db.Column(db.Srting(128),unique=True)
    role_id = db.Column(db.Integer,db.ForeignKey("tbl_roles.id")) #底层考虑
    

@app.route('/')
def index():
    pass


if __name__ == '__main__':
    # 删除所有表
    db.drop_all()
    # 创建所有表
    db.create_all()

    role1 = Role(name="admin")
    # session 记录对象任务
    db.session.add(role1)
    # 提交到任务数据库中
    db.session.commit()

    us1=User(name="zhang",password="12334",role_id=role1)
    us2=User(name="zhang2",password="12334",role_id=role1)
    # 一次保存多条
    db.session.add_all(us1,us2)
    db.session.commit()

    # 查询操作
    Role.query.all()   # 多条,查询的列表对象
    Role.query.first() # 单条,返回第一条记录
    Role.query.get(id) # 根据主键 ID获取对象

    db.session.query(Role).all()
    db.session.query(Role).get()

    app.run(debug=TRUE)

SQLALCHEMY 配置说明

名称说明
SQLALCHEMY_BINDS一个映射绑定 (bind) 键到 SQLAlchemy 连接 URIs 的字典。 更多的信息请参阅 绑定多个数据库。
SQLALCHEMY_ECHO如果设置成 True,SQLAlchemy 将会记录所有发到标准输出(stderr)的语句,这对调试很有帮助。
SQLALCHEMY_RECORD_QUERIES可以用于显示地禁用或者启用查询记录。查询记录在调试或者测试模式下自动启用。更多信息请参阅 get_debug_queries()。
SQLALCHEMY_NATIVE_UNICODE可以用于显示地禁用支持原生的 unicode。这是 某些数据库适配器必须的(像在 Ubuntu 某些版本上的
SQLALCHEMY_POOL_SIZE数据库连接池的大小。默认是数据库引擎的默认值 (通常是 5)。
SQLALCHEMY_POOL_TIMEOUT指定数据库连接池的超时时间。默认是 10。
SQLALCHEMY_POOL_RECYCLE自动回收连接的秒数。这对 MySQL 是必须的,默认 情况下 MySQL 会自动移除闲置 8 小时或者以上的连接。 需要注意地是如果使用 MySQL 的话, Flask-SQLAlchemy 会自动地设置这个值为 2 小时。
SQLALCHEMY_MAX_OVERFLOW控制在连接池达到最大值后可以创建的连接数。当这些额外的 连接回收到连接池后将会被断开和抛弃。
SQLALCHEMY_TRACK_MODIFICATIONS如果设置成 True (默认情况),Flask-SQLAlchemy 将会追踪对象的修改并且发送信号。这需要额外的内存,

列选项

选项名说明
primary_key如果为True,代表主键
unique如果为True,代表这列不允许出现重复值
index创建索引
nullabletrue允许空值,false不允许空
default为这列定义默认值

常用的 SQLAlchemy 查询过滤器

过滤器说明
filter( == )把过滤器添加到原查询上,返回一个新查询;需要指定类名
filter_by()把等值过滤器添加到原查询上,返回一个新查询;只能是等于
limit使用指定的值限定原查询返回的结果
offset()偏移查询返回的结果,返回一个新查询
order_by()根据指定条件对原查询结果进行排序,返回一个新的查询
group_by()根据指定条件对原查询结果进行分组,返回一个新的查询

查询失败 Django 返回异常,flask 返回 null

常用的 SQLAlchemy 查询执行器

关联查询与自定义显示信息

user = User.query.get(1)
user.role.name

def repr():
“”“定义以后让对象显示更加直观”“”
pass

数据的修改与删除

  • 先查询再更新
    user = User.query.get(1)
    user.name

user.name=“python”
db.session.add(user)
db.session.commit()

  • 查询时候同时更新
    User.query.fiter_by(name=“zhou”).update(“password”:“111111”)
    db.session.commit()

  • 删除
    user = User.query.get(3)
    db.session.delete(user)
    db.session.commit()

11-数据库迁移扩展包Flask-Migrate、 邮件扩展包Flask-Mail

Flask-Migrate

pip install flask-migrate
pip install flask-script

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate,MigrateCommand
from flask_script import shell,Manager

app = Flask(__name__)
manager = Manager(app)

db=SQLAlchemy(app)

migrate = Migrate(app,db)
manager.add_command('db',MigrateCommand)

class Role(db.Model):
    pass

class User(db.Model):
    pass

if __name__ =='__main__':
    manager.run()
  • 初始化操作,生成文件夹
    python author_book.py db init
  • 生成迁移文件;等价 django->migrations (-m 操作的备注信息)
    python author_book.py db migrate -m ‘add mobile’
  • 更新数据库,升级
    python author_book.py db upgrade
  • 查看历史
    python author_book.py db history
  • 回退降级操作
    python author_book.py db downgrade 版本号

Flask-Mail

流程:链接到邮箱服务,开启 SMTP 服务!!!
第三方生成授权码,将该码添加到 Flask 的配置变量

from flask import flask
from flask_mail import Mail,Message

app = Flask(__name__)
# 一次性添加多个键值对
app.config.update([
    DEBUD = True,
    MAIL_SERVER = 'smtp.qq.com',
    MAIL_PORT=465,
    MAIL_USE_TLS = True,
    MAIL_USERNAME = 'xxx',
    MAIL_PASSWORD='xxxxxx'
])
mail = Mail(app)

msg = Message('This is a test',sender='xxxx@qq.com',recipients=['',''])
msg.body = "Flask test mail"
mail.send(msg)

12-不使用蓝图解决模块划分问题

app:相当于project工程
蓝图:对应的小模块的划分

循环引用

推迟一方的导入,让另一方先完成。

使用装饰器解决模块分割问题

app.route(“/get_goods”)(被装饰的函数)
注意:
@ 只是一个语法行
装饰器的实质是一个闭包函数!!!
带参数的装饰器:三层闭包

13-使用蓝图划分模块

蓝图的概念:在Flask中, 蓝图(blueprint)是一种用来扩展已有Flask应用结构的方式, 蓝图提供了一种把功能类似的视图函数组合在一起的方式, 通过这种方式, 开发者能够把他们的应用拆分成不同的组件. 在我们的架构中, 蓝图的作用类似于控制器(controller).
蓝图flask自带的核心的内容!!!

蓝图的使用步骤分为三步:

  1. 创建一个蓝图对象
  2. 在这个蓝图对象上进行操作,注册路由,指定静态文件,注册模版过滤器
  3. 在应用对象上注册蓝图对象

蓝图的基本定义与使用

# 蓝图使用示例:
# 1创建蓝图 & 2添加蓝图路由
from flask import Blueprint

# 创建蓝图对象
users_bp = Blueprint('users', __name__)

# 添加蓝图路由
@users_bp.route('/index')
def index():
    return 'users bp response'

---
# 3.注册蓝图
from flask import Flask
# 导入蓝图
from apps.users_bp import users_bp

# 创建Flask实例
app = Flask(__name__)
# 注册蓝图
app.register_blueprint(users_bp)
# 带前缀的蓝图
app.register_blueprint(users_bp,url_prefix="/xxx")

if __name__ == '__main__':
    # 查看路由映射
    print(app.url_map)
    app.run()

以目录形式定义蓝图

在包的初始化文件中定义
目录
|-main.py
|—cart
|------static
|------templates
|------init.py
|------view.py
init.py

from flask import Blueprint
# 创建一个蓝图
app_cart = Blueprint('app_cart', __name__,template_folder="templates")
# 在 __init__.py文件被执行的时候把视图加载进来,让蓝图与应用程序知道蓝图的存在
from .views import get_cart

view.py

from flask import render_template
from . import app_cart

@app_cart.route("/get_cart")
def get_cart():
    return "get cart"

main.py

from flask import Flask
from cart import app_cart

app = Flask(__name__)
app.register_blueprint(app_cart,url_prefix="/cart")

蓝图里模板目录的处理

模块里面找不到模版可以向工程里面去找模版~~~

14-单元测试

Dev - test - demo - product
pip install pytest-flask

单元测试的引入

单元测试(unit testing)是开发者自己编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。

assert断言使用

assert 断言后面是一个表达式,如果表达式返回成功,则断言成功,程序能够继续往下执行,
如果表达式返回假,断言失败,assert会抛出异常AssertionError,终止程序继续往下执行!!!

实现简单的单元测试

单元测试:setUp与flask的测试模式
*** setUp:在进行测试之前先被执行
*** tearDown: 所有测试执行后执行[通常用来清理操作]
*** 测试模式: app.config[‘TESTING’] = True / app.testing = True
login.py

# coding:utf-8
from flask import Flask, jsonify, request

app = Flask(__name__)


@app.route("/login")
def login():
    user_name = request.form.get('user_name')
    password = request.form.get('password')
    if not all([user_name, password]):
        resp = {
            "code": 1,
            "message": "invalid params"
        }
        return jsonify(resp)

    if user_name == 'admin' and password == '123456':
        resp = {
            "code": 2,
            "message": "Login Success"
        }
        return jsonify(resp)

    else:
        resp = {
            "code": 3,
            "message": "wrong user_name or passwd !!"
        }

        return jsonify(resp)


if __name__ == "__main__":
    app.run(debug=True)

test.py

# coding:utf-8
import json
import unittest

from login import app


class LoginTest(unittest.TestCase):
    def setUp(self):
        """在进行测试之前先被执行"""
        # app.config['TESTING'] = True
        app.testing = True
        # 创建进行web请求的客户端,使用flask提供的
        self.client = app.test_client()

    def test_empty_user_name_pasword(self):
        # 利用client客户端模拟发送web请求
        ret = self.client.post("/login", data={})
        # ret 是视图返回的响应对象,data属性是响应体的数据
        resp = ret.data
        resp = json.loads(resp)

        # 拿到返回值后进行断言测试
        self.assertIn("code", resp)
        self.assertEqual(resp["code"], 1)

        # 测试只是传递用户名或者密码
        ret = self.client.post("/login", data={"user_name": "admin"})
        resp = ret.data
        resp = json.loads(resp)
        self.assertIn("code", resp)
        self.assertEqual(resp["code"], 1)

    # 测试用户名或者密码错误
    def test_empty_user_name_pasword(self):
        pass


if __name__ == "__main__":
    unittest.main()

15-flask部署

Gunicorn(绿色独角兽) + Flask

业务服务器:统一入口 nginx nginx负载均衡,提供静态文件

# 安装
pip install gunicron
# 帮助
gunicorn -h
# 4个进程运行 / -D 后台守护进程运行
gunicorn -w 4 -b 127.0.0.1:5000 -D --access-logfile ./logs/log main:app

在这里插入图片描述

nginx 配置

nginx 轮转发根据 upstream 配置

# nginx.conf

http{
    upstream flask{
        server IP:port;
        server IP:port;
        server IP:port;
    }

    server{
        listen 80;
        server_name loclahost;
    
        # 转发
        location /{
            # proxy_pass http://10.211.55.2.5000; # 单台服务器
            proxy_pass http://flask  # 组服务器 和 upstream 后面的值一样
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

sqlalchemy操作mysql提示警告的问题

mysql5.7以上的事物隔离级别出错问题
在这里插入图片描述


总结

常用的扩展包

Flask-SQLalchemy:操作数据库;
Flask-script:插入脚本;
Flask-migrate:管理迁移数据库;
Flask-Session:Session存储方式指定;
Flask-WTF:表单;
Flask-Mail:邮件;
Flask-Bable:提供国际化和本地化支持,翻译;
Flask-Login:认证用户状态;
Flask-OpenID:认证;
Flask-RESTful:开发REST API的工具;
Flask-Bootstrap:集成前端Twitter Bootstrap框架;
Flask-Moment:本地化日期和时间;
Flask-Admin:简单而可扩展的管理接口的框架

虚拟环境

  1. 创建虚拟环境,virtualenv虚拟开发环境
    pip install virtualenv
    pip install virtualenvwrapper-win
    
  2. 利用安装好的模块创建一个虚拟环境 mkvirtualenv first_env
    创建的路径C:\*\Local\pypa\virtualenv
  3. 虚拟环境其他相关命令:
    切换到指定的虚拟环境:workon workon first_env
    退出虚拟环境deactivate
    删除指定的虚拟环境rmvirtaulenv first_env
    列出所有虚拟环境lsvirtualenv
    进入到虚拟环境所在的目录cdvirtualenv
    在虚拟环境中安装Flask模块 pip install flask
  4. 打开pycharm选择已经存在的虚拟环境 C:\*\Envs\first_env\Scripts\python.exe

** 或直接新建flask项目,选择Virtualenv,配置python解释器后新建,在teminal终端上安装pip install flask。**

项目目录详解

static文件夹 用于存放各种静态文件 css、js、图片等
templates文件夹 用于存放html模板文件
app.py 为主文件 启动项目需要启动该文件
app.py 文件的名字可以自由命名

初始app.py

The flask object implements a WSGI application and acts as the central object.

from flask import Flask
app = Flask(__name__) # __name__参数:Python会根据所处的模块来赋予__name__变量相应的值,此时值为app
@app.route('/') # 用来匹配url,以装饰器实现,装饰器引用的是上面实例化核心类出来的对象。
def hello_world(): # 路由下面跟的函数是视图函数,如果匹配到了路由就会触发视图函数执行,并且return回具体的数据。
	return 'Hello World!'
if __name__ == '__main__':
	app.run(debug=True) # app.run()实现了flask程序在开发环境下运行起来,并且默认ip和端口是127.0.0.1:5000 host,port参数可以设定

导入Flask的核心类实例化对象app,然后app作为装饰器使用匹配url分发给下面的视图函数,然后执行该页面会触发app调用run()方法运行起来整个项目。此时为b/s型。

DEBUG模式

app参数设置 debug=True开发阶段对代码进行修改时使用,flask默认部署后再修改代码,刷新页面不起作用。flask代码中如果出现了异常,浏览器中不会提示具体的错误信息,开启debug模式后会把具体的错误信息发送到浏览器上。flask代码如果被修改了,必须要重启项目修改的代码才会有效,开启debug模式后修改代码后只要ctrl+s项目就会自动重新加载,不需要手动加载整个网站。

对这些配置操作手动解耦,创建settings文件

ENV = 'development'
DEBUG = True

此时app文件改为

from flask import Flask, Response
import settings
app = Flask(__name__)
app.config.from_object(settings) # 配置文件解耦
# app.config.from_pyfile('settings') # 或这个
@app.route('/')
def hello_world():
	return 'Hello World!' # 底层将其送入Response进行封装

@app.route('/han/')
def hello_world():
    return Response('你好啊')
if __name__ == '__main__':
	app.run()

运行python app.py
用http://localhost:5000/或http://127.0.0.1:5000/访问,url是统一资源定位符
默认只能是本机访问,如果需要通过本机ip地址访问,则将host改为‘0.0.0.0’
查看本机ip:cmd–ipconfig 查找ipv4地址

针对sever端写东西:(扩展)

b/s:浏览器/服务器 c/s:客户机/服务器
CS = Client - Server = 客户端- 服务器。
例子: QQ,迅雷,快播,暴风影音,各种网络游戏等等。
BS = Browser - Server = 浏览器 - 服务器。
例子:所有的网站都是BS结构。( 知乎 / 果壳 / 微博 / 等等等等等等 )

区别:
1、开发维护成本
cs开发维护成本高于bs。因为采用cs结构时,对于不同的客户端要开发不同的程序,而且软件安装调试和升级都需要在所有客户机上进行。
bs只需要将服务器上的软件版本升级,然后重新登录就可以了。
2、客户端负载
cs客户端负载大。cs客户端不仅负责和用户的交互,收集用户信息,而且还需要通过网络向服务器发出请求。
bs把事务处理逻辑部分交给了服务器,客户端只是负责显示。
3、安全性
cs安全性高。cs适用于专人使用的系统,可以通过严格的管理派发软件。
bs使用人数多,不固定,安全性低。
4、作用范围
Client/Server是建立在局域网的基础上的。Browser/Server是建立在广域网的基础上的。

Werkzeug工具包简介

WSGI:Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。Werkzeug是一个WSGI工具包,可以作为一个Web框架的底层库,封装了很多 Web 框架的东西,例如 Request,Response 等 。但它不是一个web服务器,也不是一个web框架。

注意

在 python2中有一个编码问题: 在接收的数据转换一下
str “utf-8” “gbk”

a=“中国” # str
a=u"中国" #unicode

表名常见命名规范:

  • 数据库名缩写_表名 db_table
  • tbl_表名

版权声明:本文为m0_38144883原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。