安装

安装 pip 包管理器和 Python 虚拟环境

pip install virtualenv

pip 安装包速度过慢可以使用豆瓣镜像源

安装 Flask 两种方法

pip install -r requeirements.txt #在文件中写入要安装的扩展,pip 读取这个文件的内容。
pip install flask #直接安装

路由

路由即路径,这里给一段URL http://baidu.com/user 其中 user 就是路由。

flask_router1.py

"""
路由即路径
"""
from flask import Flask

app = Flask(__name__)


# 最简易版的路由,当访问根(/)就返回Hello ,I love Flask.。
# 参考文档:https://dormousehole.readthedocs.io/en/latest/quickstart.html#id7
@app.route('/')
def hell():
    return 'Hello ,I love Flask.'


# 这是稍微进阶一点的路由,可以接收参数。
# 通过接收路径/my后面的参数,以关键字参数的方式将它传入my()。
@app.route('/my/<username>')
def my(username):
    return f'my page:{username}'


if __name__ == '__main__':
    # host参数默认为127.0.0.1,port参数默认为5000。
    # 开启了debug模式修改代码后保存会自动重启服务。
    app.run(port=80, debug=True)

路由不常用一种定义方式

add_url_rule@app.route作用一致,@app.route底层还是调用add_url_rule方法。

from flask import Flask

app = Flask(__name__)


# @app.route('/')
def hell():
    return 'Hello ,I love Flask.'


# @app.route('/my/<username>')
def my(username):  # 会
    return f'my page:{username}'


app.add_url_rule(rule='/', view_func= hell)
app.add_url_rule(rule='/my/<username>', view_func= my)

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

蓝图

其中有一种路由是在项目中使用的最多的,叫蓝图,它的一个好处在于模块化并且减少定义路由时的重复代码量。

比如你编写一个模块在定义路由时/user/是所有关于用户的操作,此时有两个功能,更改密码&查看用户信息,那在定义路由时就得写/user/info表示查看用户信息,/user/forget_password来表示更改密码,使用蓝图可以将/user提取出来作为前缀,在注册路由时就可省略前缀减少代码。

模块化是将一个个功能抽离出来,作为模块存在,使用一个入口文件调用就行,你可能会想路由不也可以抽离出来作为模块嘛,是可以抽离出来,但你在入口文件还得一个个导入方法,蓝图就直接一行代码就能全部导入,比路由方便。

使用过程很简单,定义蓝图 --> 使用蓝图 --> 注册蓝图,下面是一个最基本的示例。

flask_router_blueprint.py

"""
Blueprint用来定义蓝图
"""
from flask import Flask, Blueprint

app = Flask(__name__)

# 定义蓝图,第一个参数为蓝图名字,第二个参数为表示蓝图属于那个包下的模块。
index_page = Blueprint('index_page', __name__)


# 使用蓝图
@index_page.route('/')
def index_page_index():
    return 'use blueprint'


# 注册蓝图(必须所有内容写完了才能注册,内容敲定了才能板上钉钉嘛)
app.register_blueprint(index_page, url_prefix='/i')


# 普通路由规则
@app.route('/')
def hell():
    return 'Hello Flask router'


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

在实际使用过程中一般分为入口文件和若干个功能模块,下面为示例。

flask_router_control.py

from flask import Flask, Blueprint

app = Flask(__name__)

# 定义首页蓝图
index_page = Blueprint('index_page', __name__)

# 定义用户功能蓝图
users_ops = Blueprint('users', __name__)


# 使用蓝图定义首页
@index_page.route('/')
def index_page_index():
    return 'this is index, use blueprint'


@index_page.route('/opt')
def options():
    return '使用蓝图自动带上路径前缀'


# 蓝图定义用户功能
@users_ops.route('')  # 默认就是根,这样就不用写/uesr,方便啊hhh
def user_index():
    return '用户首页'


@users_ops.route('forgetPwd')
def forget_password():
    return '修改密码页面'


@users_ops.route('getName')
def get_name():
    return '获取用户名'

flask_router_index.py

from flask import Flask
from flask_router_control import index_page, users_ops

app = Flask(__name__)

# 注册蓝图
app.register_blueprint(index_page, url_prefix='/')  # 所有内容依照这个前缀来访问
app.register_blueprint(users_ops, url_prefix='/user')

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

加载配置文件

配置文件路径config/base_setting.py,内容是DEBUG = True,下面通过几种不同的方式加载它。

flask_config_file.py

from flask import Flask

app = Flask(__name__)

# 通过Python文件的方式加载配置文件(常用!)
app.config.from_pyfile('config/base_setting.py')


@app.route('/')
def hello():
    return 'hello web application word'


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

flask_config_modle.py

from flask import Flask

app = Flask(__name__)

# 通过模块的方式加载配置文件(常用)
app.config.from_object('config.base_setting')


@app.route('/')
def hello():
    return 'hello web application word'


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

flask_config_sysenv.py

from flask import Flask

app = Flask(__name__)

# 通过系统环境变量的方式加载配置文件
# Linux使用export ops_config=./base_setting.py
# Windows使用set
app.config.from_envvar('ops_config')


@app.route('/')
def hello():
    return 'hello web application word'


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

flask_config_var.py

from flask import Flask

app = Flask(__name__)

# 通过变量的方式加载配置文件(用得少)
app.config['DEBUG'] = True


@app.route('/')
def hello():
    return 'hello web application word'


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

Request对象

flask_request.py

"""
request对象可以用来获取请求
"""
from flask import Flask, Blueprint, request

app = Flask(__name__)

index_page = Blueprint('index_page', __name__)


@index_page.route('/')
def index_page_index():
    return 'index'


# 默认接收GET请求
@index_page.route('/get')
def get():
    # 获取参数a的值,当参数a不存在时可以设置默认值。
    # 当定义为GET请求时就不能以POST请求传输消息否则报错(405)。
    var_a = request.args.get('a', 'i love imooc')

    return f'<h2>ok, method:{request.method} params:{request.args} var_a:{var_a}</<h2>>'


# 启用POST需单独设定
@index_page.route('/post', methods=['post'])
def post():
    # 获取参数a的值,POST不支持默认值,当你不传递a这个值就会报错,所以给它做个判断。
    # curl -d 'a=1&b=2' 127.0.0.1:5000/post
    # 除了curl还可使用postman这个API调试工具
    var_a = request.form['a'] if 'a' in request.form else ''
    return f'method:{request.method},params:{request.form},var_a:{var_a}'


@index_page.route('/mix_get')
def mix_get():
    # request.values可以接收GET和POST请求,无需单独记request.args.get()和request.form。
    var_a = request.values['a'] if 'a' in request.values else 'i love imooc'
    return f'method:{request.method}, params:{request.args}, var_a:{var_a}'


@index_page.route('/mix_post', methods=['POST'])
def mix_post():
    var_a = request.values['a'] if 'a' in request.values else ''
    return f'method:{request.method}, params:{request.values}, var_a:{var_a}'


# 获取上传的文件具体信息
@index_page.route('/upload', methods=['POST'])
def upload():
    # 获取具体文件对象 request.files['file']
    # curl -F file=@./log.xml 127.0.0.1:5000/upload
    f = request.files['file'] if 'file' in request.files else ''  # 判断一下用户到底传了文件没有,没传则会报错。
    return f'method:{request.method}, params:{request.files}, file_obj:{f}'


# 作业:现有一个POST请求,在URL中请求a=bget,并且POST请求参数也有a=bpost,此时用request.values对a的取值是?
# 盲答:已经使用POST就不能再以GET方式传输参数,否则返回405,此时values取值一定是bpost。
# 实际测验:
# curl -d a=bpost 127.0.0.1:5000/home_work?a=bget
# method:POST, params:CombinedMultiDict([ImmutableMultiDict([('a', 'bget')]), ImmutableMultiDict([('a', 'bpost')])]), var_a:bget
# 结论:POST请求传同名GET参数和POST参数,会先获取GET参数。
@index_page.route('/home_work', methods=['POST'])
def home_work():
    var_a = request.values['a'] if 'a' in request.values else ''
    return f'method:{request.method}, params:{request.values}, var_a:{var_a}'


app.register_blueprint(index_page, url_prefix='/')

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

Response对象

flask_response.py

"""
make_resonse 返回相应对象,可以设置响应头信息。
jsonify 在Flask中以更简单操作将对象转换为JSON并设置相关响应头信息。
render_template 返回模板文件。
"""
from flask import Flask, Blueprint, make_response, jsonify, render_template

app = Flask(__name__)

index_page = Blueprint('index_page', __name__)


@index_page.route('/response_html')
def index_page_html():
    # 默认返回状态码200,也可返回其他状态。
    return '默认是返回HTML文档的', 200


@index_page.route('response_same_html')
def index_page_same_html():
    # 第二个值依然可以设置状态码。
    response = make_response('使用make_response返回HTML文档', 200)
    return response


@index_page.route('json')
def resp_json():
    # 使用json.dumps将字典序列化为JSON格式,再设置内容类型的值为JSON。
    # JSON Viewer插件可以美化JSON格式
    import json
    data = {"blog_address": "https://www.raingray.com"}
    response = make_response(json.dumps(data))
    response.headers['Content-Type'] = 'application/json'
    return response


@index_page.route('json_same')
def resp_json_same():
    # jsonify会调用json.dumps来序列化对象,最后帮我们加上JSON对应的headres。
    data = {"blog_address": "https://www.raingray.com"}
    response = make_response(jsonify(data))
    return response


@index_page.route('template')
def resp_template():
    # 模板文件会到当前目录下的templates目录内寻找。
    # 参考文档:https://dormousehole.readthedocs.io/en/latest/quickstart.html#id10
    return render_template('index.html')


app.register_blueprint(index_page, url_prefix='/i')


# 普通路由规则
@app.route('/')
def hell():
    return 'Hello Flask Router'


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

Jinja2模板语法

文件结构

jina2_template
    templates
        common
            layout.html
        index.html
        template.html
    control.py
    index.py

定义视图返回模板,还可以将一些表达式传入模板,在模板将值打印出来。

control.py

from flask import Flask, Blueprint, render_template, make_response

index_page = Blueprint('index', __name__)


@index_page.route('/')
def template():
    name = 'gbb'
    age = 18
    dict_data = {'name': 'gbb', 'phone': [13412341234, 13422223333]}
    # 将变量的值输出在模板中,
    # render_template第二个值为可变参数,可接收解包后的字典或者是关键字参数,并将这个值传到模板文件中。
    return render_template('index.html', **dict_data, age=age)


@index_page.route('/template')
def sub_template():
    return render_template('template.html')

Jinja2 模板语法和 Python 类似,很简单。

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模板</title>
</head>
<body>
    <h1>模板加载成功</h1>
    <h2>输出表达式:</h2>
    语法:<code>{{'{{ Expressions }}'}}</code><br />
    疑问:怎么能够同时输出两个表达式?<code>{{'{{ Expressions, Expressions }}'}}</code>这样写会报错
    <p>{{ name }}</p>
    <h2>循环</h2>
    用法和Python循环一致<br />
    语法:
    <pre>
        {{'{{% for elements in obj %}}'}}
        {{'{{ elements }}'}}
        {{'{{% endfor %}}'}}
    </pre>
    {% for str in name %}
    {{ str }}
    {% endfor %}
    <h2>条件判断</h2>
    用法和Python条件语句一致<br />
    语法:
    <pre>
        {{'{{% if obj %}}'}}
        {{'{{% endif %}}'}}
    </pre>
    {% if phone %}
    {{ phone }}<br />
    输出方法1:{{ phone.1 }}<br />
    输出方法2:{{ phone[1] }}<br />
    参考文档:<a href="http://docs.jinkan.org/docs/jinja2/templates.html#variables">变量</a>
    {% endif %}

</body>
</html>

作为入口文件注册蓝图并启动 Flask。

index.py

from flask import Flask
from control import index_page

app = Flask(__name__)

app.register_blueprint(index_page)

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

模板继承

模板继承就是一个不变框架,往里面儿填充内容,下面使用的语法是{% block content %}{% endblock %},那你在子模板中也必须使用相同的语法来填充内容。

templates/common/layout.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>公共布局</title>
</head>
<body>
<h1>公共布局文件</h1>
{% block content %}{% endblock %}

<p>使用<code>{{ '{{ self.content() }}' }}</code>重复引用相同内容:{{ self.content() }}</p>
</body>
</html>

template.html

{# 必须使用extends先继承模板 #}
{% extends "common/layout.html" %}

{# 下面的内容是子模板可以填充的值,这个block语法要和继承的模板保持一致 #}
{% block content %}这是子模板填充的内容{% endblock %}

连接MySQL

安装扩展

pip install flask_sqlalchemy mysqlclient

设置好数据库配置信息,这个 application.py 就专门来初始化应用。

application.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# 定义数据库配置信息
# scheme://username:pasword@host/database_name
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:@localhost/mysql'

# 初始化对象
db = SQLAlchemy(app)

所有路由相关的定义就放在 control.py 中。

control.py

from flask import Flask, Blueprint, render_template, make_response
from sqlalchemy import text
from application import db

index_page = Blueprint('index', __name__)


@index_page.route('/')
def template():
    name = 'gbb'
    age = 18
    dict_data = {'name': 'gbb', 'phone': [13412341234, 13422223333]}

    # 查询mysql数据库内user表所有字段内容,这样可能造成语句拼接导致SQL注入。
    sql = text('select * from `user`')

    result = db.engine.execute(sql)
    return render_template('index.html', **dict_data, age=age, sql_result=result)

数据库查询内容将在模板文件数据

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模板</title>
</head>
<body>
    <h2>SQL查询结果</h2>
    <p>原始数据:{{ sql_result }}<p />
    {% for result in sql_result %}
    {% for r in result %}
    {{ r }}
    {% endfor %}
    {% endfor %}
</body>
</html>

www.py 用来注册蓝图的。

www.py

from control import index_page
from application import app

app.register_blueprint(index_page)

启动文件

from application import app
from www import *

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

使用 model 查询内容

使用 ORM 可以不用输入 SQL 语句就能查询。

在前面的基础上创建与 templates 同级文件common/model/user.py

这个文件就是model。

from application import db


# 会把类名字当做表名并把类变量当做字段查询,验证方法是把类名改为一个不存在的表,在模板文件输出结果就可以看到报错结果。
# SQL: SELECT user.`Host` AS `user_Host`, user.`User` AS `user_User`, user.`Password` AS `user_Password` FROM user
class User(db.Model):
    # 我发现必须要设置主键不然报错,而且只有设置主键的内容才会显示出来...
    Host = db.Column(db.String(60), primary_key=True)
    User = db.Column(db.String(80), primary_key=True)
    Password = db.Column(db.String(41), primary_key=True)

在 control.py 使用query.all()查询数据

from flask import Flask, Blueprint, render_template, make_response
from sqlalchemy import text
from application import db
from common.model.user import User

index_page = Blueprint('index', __name__)


@index_page.route('/')
def template():
    name = 'gbb'
    age = 18
    dict_data = {'name': 'gbb', 'phone': [13412341234, 13422223333]}

    # 查询mysql数据库内user表所有字段内容,这样可能造成语句拼接导致SQL注入。
    # sql = text('select * from user')
    # result = db.engine.execute(sql)

    # 用Model查询
    result = User.query.all()

    # 将变量的值输出在模板中,
    # render_template第二个值为可变参数,可接收解包后的字典或者是关键字参数,并将这个值传到模板文件中。
    return render_template('index.html', **dict_data, age=age, sql_result=result)

由于 query.all() 返回是列表,但模板中我们使用了嵌套循环,这样会报错,修改后内容如下。

    {% for result in sql_result %}
    {{ result }}
    {% endfor %}

使用sqlacodgen生成model

数据库结构设计完成后,在开发时不需要手写,可直接用工具生成。

所有操作依靠此插件进行。

pip install flask-sqlacodegen

运行次命令将会在 common/model 目录下产生名为 user.py 的 model 文件。

flask-sqlacodegen "mysql://root:@localhost/mysql" --tables user --outfile "common/model/user.py" --flask

运行没报错,删除 user.py 中自动导入的内容(不知道为什么不删除在运行时会报错,也许是用不到这些功能吧,具体课程里没讲)

from sqlalchemy import Column, Integer, LargeBinary, Numeric, String, Text
from sqlalchemy.schema import FetchedValue
from sqlalchemy.dialects.mysql.enumerated import ENUM

接着将代码内的 db 对象改为本项目中与 Flask 关键的 db,而不使用自动生成 db,因为它没有指向任何一个 Flask。

通过model建立表

千万不要忘了指定你要在那个数据库下创建表。

# 定义数据库配置信息
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123@localhost/tmp_test'

在导入于 Flask 关联的 db 对象后使用此方法创建表结构。

db.create_all()

MVC

参考链接

标签: none

讨论讨论讨论!