Flask前言

Django是个大而全的框架,Flask是一个轻量级的框架

Dijango内部为我们提供了非常多的组件:orm / session / cookie / admin / form / modelform / 路由 / 视图 / 模板 / 中间件 / 分页 / auth / contenttype / 缓存 / 信号 / 多数据库连接

Flask框架本身没有太多的功能:路由 / 视图 / 模板(jinia2) / session / 中间件,第三方组件非常齐全

注意:Django的请求处理是逐一封装和传递;Flask的请求是利用上下文管理来实现的

依赖wsgi Werkzeug

wsgi:web服务网关接口

# 基于werkzeug
from werkzeug.serving import run_simple


def func(environ, start_response):
print('request coming')
pass


if __name__ == '__main__':
run_simple('127.0.0.1', 5000, func)
from werkzeug.serving import run_simple


class Flask(object):
def __call__(self, environ, start_response):
return 'xx'


app = Flask()

if __name__ == '__main__':
run_simple('127.0.0.1', 5000, app)
from werkzeug.serving import run_simple


class Flask(object):
def __call__(self, environ, start_response):
return 'xx'

def run(self):
run_simple('127.0.0.1', 5000, self)


app = Flask()

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

快速使用Flask

# 从Flask包中导入flask类
from flask import Flask, request

# 使用Flask类创建一个app对象
# __name__:代表当前app.py模块
# 1.以后出现bug,可以帮助我们快速定位
# 2.对于寻找模块文件有一个相对路径
app = Flask(__name__)


# 创建一个路由和视图函数的映射
@app.route('/')
def hello_world(): # put application's code here
return 'Hello World!'


if __name__ == '__main__':
app.run()
  • Flask是基于werkzeug的wsgi实现的,Flask本身没有wsgi

  • 用户请求一旦到来,就会使用app.__call__方法

  • Flask有标准流程:

    • 创建Flask对象
    • 编写路由和视图
    • 使用Flask对象调用app.run()

Flask路由

现代web应用都使用有意义的URL,这帮助用户记忆,网页会更得到用户青睐

@app.route('/index') # 路由
def hello_world(): # 视图函数 mtv: view视图 函数
pass

路由参数

@app.route('/index', methods=['GET', 'POST'], endpoint='hw')
# 第一个参数为路由地址(url)
# 第二个参数为使用方法(接收,发送)
# 第三个参数为别名,不可以重名
def hello_world(): # put application's code here
pass

动态路由

@app.route('/index')
def hello_world(): # put application's code here
pass

@app.route('/index/<name>')
def hello_world(name): # put application's code here
pass

@app.route('/index/<int:nid>')
def hello_world(nid): # put application's code here
pass

获取提交数据

from flask import request

@app.route('/index')
def login():
# GET形式传递的参数
request.args
# POST形式提交的参数
request.form

返回数据

@app.route('/index')
def login():
return render_template('模板文件')
return jsonify()
return redirect('/index') # return redirect(url_for('别名'))
return '...'

Flask请求与响应

请求:request

http协议:

请求行: http://127.0.0.1:5000/index(请求方法为get和post)

请求头:key:value

请求体:

响应:response

响应行:状态码200 ok、404 not found、500 Internal Server Error、302 Status Code

响应头:key:value

响应体:响应标签html

GET与POST

  • 传参不同,get将参数直接拼到url上,post用的是requestBody方式传参
  • 编码不同,url用的是ASCII编码,所以get也是此编码,post没有编码限制
  • url长度有限制,所以get的请求路径和参数不宜过长,post无限制
  • get发送请求时的参数会被浏览器完整保留在历史记录里,post不会
  • get请求会被浏览器缓存,post不会
  • get的历史记录回退不会访问服务器,post是重新对服务器发送请求

项目配置

Debug模式

是否开启调试模式

DeBug模式开启

Debug优势:

  • 开启Debug模式后,只要修改代码保存(Ctrl+s),就会自动重新加载,不需要手动重启项目
  • 在开发状态下,出现bug。开启Debug模式后,在浏览器上就可以看到出错信息

Hosh配置

项目运行使用的Host(访问项目的域名)

设置不同的ip地址号以达到不同效果,例如:0.0.0.0局域网访问,127.0.0.1本机

局域网访问

Port配置

项目运行监听的端口号

更换默认端口

当某个端口(5000)被其他程序占用,可以通过修改port来监听其他端口以达到效果

URL

一般url分为http和https两种协议,http协议使用80端口,https协议使用443端口,所以一般不需要在域名后面输入端口

一般格式为:http[80]/https[443]://www.xxx.com/path

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

# 等价形式
def hello_world():
pass
app.add_url_rule('/index', view_func=hello_world)

定义无参数的URL

from flask import Flask

app = Flask(__name__)


@app.route('/blog')
def blog():
return 'Blog Center!'


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

定义有参数的URL

from flask import Flask

app = Flask(__name__)


@app.route('/blog/<blog_id>')
def blog_detail(blog_id):
return '%s Blog Center!' % blog_id


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

可以使用/<int:blog_id>定义输入的必须为整型的id值

获取不同约束下的内容

from flask import Flask, request

app = Flask(__name__)


@app.route('/book/list')
def book_page():
page = request.args.get('page', default=1, type=int)
return 'Get page %d content!' % page


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

当我们需要获取某一页图书的内容时/book/list?page=页码

当我们不输入需要获取的页码时,将返回默认值中的内容/book/list

url_for

在实际开发过程中,我们通常会在路由后面加入endpoint=路径名(匿名)

url_for可以直接解析路径名到实际路径

视图

response回应:

  • 字符串,自动生成response对象
  • dict,json
  • response对象
  • make_response()
  • redirect()重定向
  • render_template模板渲染

Jinja2

渲染模板

Jinja2是一款模板渲染插件,在安装flask同时会自动添加jinja2

在模板中获取view中传递的变量值

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/blog/<blog_id>')
def blog_detail(blog_id):
return render_template('blog_detail.html', blog_id=blog_id, username='xxx')


if __name__ == '__main__':
app.run()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>博客ID:{{ blog_id }}</p>
<h1>用户名:{{ username }}</h1>
</body>
</html>

模板访问不同对象

访问的对象是一个类对象时,可以在html文件中使用:对象名.对象中的参数名

class User:
def __init__(self, username, email):
self.username = username
self.email = email

@app.route('/')
def index():
user = User(username = 'Bob', email = 'xxx@gamil.com')
return render_template('index.html', user=user)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ user.username } / { user.email }}
</body>
</html>

访问对象是一个字典对象时,可以在html文件中使用:对象名.对象中的参数名

@app.route('/')
def index():
person = {
'username':'Bob',
'email':'xxx@gamil.com'
}
return render_template('index.html', person=person)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>{{ person['username'] }}</div>
<div>{{ person.username }}</div>
<div>{{ person.get(username) }}</div>
</body>
</html>

过滤器

变量可以通过过滤器修改。过滤器与变量用管道符号( | )分割,并且也 可以用圆括号传递可选参数。多个过滤器可以链式调用,前一个过滤器的输出会被作为 后一个过滤器的输入

class User:
def __init__(self, username, email):
self.username = username
self.email = email


@app.route('/filter')
def filter():
user = User(username = 'Bob', email = 'xxx@gamil.com')
return render_template('filter.html', user=user)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ user.uesename }}-{{ user.username|length }}
</body>
</html>

自定义过滤器

过滤器本质是python函数,他会把被过滤的第一个参数产给这个函数,函数经过一些逻辑处理后,再返回新的值。过滤器写好之后可以使用@app.template_filter装饰器或app.add_template_filter函数把自定义函数注册为jinja2过滤器

def datetime_formate(value, formate=%Y-%m-%d %H:%M):
return value.strftime(formate)
app.add_template_filter(datetime_fotmate, 'dformate')
# 方式二
def 函数名(变量名):
...
return 变量名
app.add_template_filter(函数名, '自定义过滤器名')

# 方式二
@app.template_filter('自定义过滤器名')
def 函数名(变量名):
return 变量名

控制语句

if

@app.route('/control')
def control():
age = 20
return render_template('control.html', age=age)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% if age>18 %}
<div>你已经满18岁了</div>
{% elif age==18 %}
<div>刚满18岁</div>
{% else %}
<div>不满18岁</div>
{% endif %}
</body>
</html>

for

@app.route('/control')
def control():
books = [{
'name': '三国演义',
'author': '罗贯中'
}, {
'name': '水浒传',
'author': '施耐庵'
}]
return render_template('control.html', books=books)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% for book in books %}
<div>图书名称:{{ book.name }}, 作者{{ book.author }}</div>
{% endfor %}
</body>
</html>

jinja2中的for循环不存在break语句

模板继承

一个网页中大部分网页的模板都是重复的,例如顶部导航条,底部的注册信息。如果在每个页面都重复的去写这些代码,会让代码变得臃肿,提高后期维护成本。模板继承就可以把一些重复性的代码写在父模板当中,子模板继承父模板后再实现自身代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title%}{% endblock %}</title>
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
{% extends '父模板文件名' %}

{% block title %}
子模板内容
{% endblock %}

{% block body %}
子模板内容
{% endblock %}

加载静态文件

一个网页中,除了HTML代码以外,还需要CSS、JavaScript和图片文件才能更加美观和实用。静态文件默认存放在static文件夹中,如果想要修改静态文件存放路径,可以在创建Flask对象时设置static_folder

在html文件中通过link导入外部静态样式"{{ url_for('static', filename='static下的文件名') }}"

html文件中的语法

变量: 
{{ 变量 }}
{{ url_for('static', filename='') }}

块:
{% if 条件 %}...{% endif %}
{% for 条件 %}...{% endfor %}
{% block 条件 %}...{% endblock %}
{% macro 条件 %}...{% endmacro %}

{% include '' %}包含
{% import '' %}导入宏
{% extends '' %}继承

蓝图

Blueprint是一种组织一组相关视图及其他代码的方式。与把视图及其他代码直接注册到应用的方式不同,蓝图方式是把它们注册到蓝图,然后再工厂函数中吧蓝图注册到应用


Flask操作数据库

Flask连接MySQL数据库

在Flask中我们很少会使用功能pymysql直接写原生的SQL语句去操作数据库,更多的是通过SQLAlchemy提供的ORM技术,类似于操作普通Python对象一样实现数据库的增删改查操作

#连接mysql方式一
# MySQL所在的主机名
HOSTNAME = '127.0.0.1'
# MySQL监听的端口号,默认3306
PORT = 3306
# 连接MySQL的用户名
USERNAME = 'root'
# 连接MySQL的密码
PASSWORD = 'root'
# MySQL上创建的数据库名称
DATABASE = 'database_learn'

app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4'

# 在app.config中设置好连接数据库的信息
# 使用SQLAlchemy(app)创建一个db对象
# SQLAlchemy会自动读取app.config中连接数据库的信息
db = SQLAlchemy(app)

# 测试数据库是否连接成功
#####
#####
#####
# 报错未解决
# 其他设备未报错
# sqlalchemy.exc.ObjectNotExecutableError: Not an executable object: 'select 1'
with app.app_conntext():
with db.engine.connect() as conn:
rs = conn.execute('select 1')
print(rs.fetchone()) # (1,)
# 连接mysql方式二
class Config(object):
# MySQL所在的主机名
HOSTNAME = 'localhost'
# MySQL监听的端口号,默认3306
PORT = 3306
# 连接MySQL的用户名
USERNAME = 'root'
# 连接MySQL的密码
PASSWORD = 'root'
# MySQL上创建的数据库名称
DATABASE = 'database_learn'

app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4'

# 设置sqlalchemy自动跟踪数据库
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
# 禁止自动提交数据处理
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = False


# 读取配置文件
app.config.from_object(Config)

# 在app.config中设置好连接数据库的信息
# 使用SQLAlchemy(app)创建一个db对象
# SQLAlchemy会自动读取app.config中连接数据库的信息

db = SQLAlchemy(app)

ORM模型

对象关系映射(Object Relationship Mapping),是一种可以用Python面向对象的方式来操作关系型数据库的技术,具有可以映射到数据库表能力的Python类称为ORM模型

优点:

  • 开发效率高:几乎不需要写原生的SQL语句,使用纯Python的方式来操作数据库
  • 安全性高:ORM模型的底层代码对一些常见的安全问题做了防护
  • 灵活性强:Flask-SQLAlchemy底层支持SQLite、MySQL、Oracle、PostgreSQL等关系型数据库,但是ORM模型代码几乎一样

数据库的CRUD操作

class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(100), nullable=False)
password = db.Column(db.String(100), nullable=False)

create:

@app.route('/user/add')
def add_user():
# 创建orm对象
user = User(username='zhangsan', password='111111')
# 将orm对象添加到session
db.session.add(user)
# 将db.session对象同步到数据库
db.session.commit()
return '用户创建成功'

read:

@app.route('/user/query')
def query_user():
# get查找,通过主键查找
user = User.query.get(1)
print("user.id:%d\nuser.username:%s\nuser.password:%s" % (user.id, user.username, user.password))
# filter_by查找
# Quert对象:类数组对象
users = User.query.filter_by(username='zhangsan')
for user in users:
print(user.username)
return '数据查找成功'

update:

@app.route('/user/update')
def update_user():
user = User.query.filter_by(username='zhangsan').first()
user.password = '222222'
db.session.commit()
return '数据修改成功'

delete:

@app.route('/user/delete')
def delete_user():
# 查询删除对象
user = User.query.filter_by(username='zhangsan').first()
# 从db.session中删除
db.session.delete(user)
# 将db.session中修改,同步到数据库
db.session.commit()
return '数据删除成功'

表关系

关系型数据库一个强大的功能,就是多个表之间可以建立关系。比如文章表中,通常需要保存作者数据,但是我们不需要直接把作者数据放到文章表中,而是通过外键引用用户表。这种强大的表关系,可以存储非常复杂的数据,并且可以让查询非常迅速。在 Flask.SQLAlchemy 中,同样也支持表关系的建立。表关系建立的前提,是通过数据库层面的外键实现的。表关系总体来讲可以分为三种,分别是:一对多(多对一)、一对一、多对多

db.relationship()

flask_migrate迁移ORM模型

from flask_migrate import Migrate
...
migrate = Migrate(app, db)

ORM模型映射三部:终端输入

  • flask db init:这步只需要一次,生成migrations的文件夹
  • flask db migrate:识别ORM模型的改变,生成迁移脚本
  • flask db upgrade:运行迁移脚本,同步到数据库中