Flask数据库
一 数据库的设置
web应用中普遍使用的是关系模型的数据库,关系型数据库把所有的数据都存储在表中,表用来给应用的实体建模,表的列数是固定的,行数是可变的。它使用结构化的查询语言。关系型数据库的列定义了表中表示的实体的数据属性。比如:商品表里有name、price、number等。 flask本身不限定数据库的选择,你可以选择sql或nosql的任何一种。也可以选择更方便的sqlalchemy,类似于django的orm。sqlalchemy实际上是对数据库的抽象,让开发者不用直接和sql语句打交道,而是通过python对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升。
sqlalchemy是一个关系型数据库框架,它提供了高层的orm和底层的原生数据库的操作。flask-sqlalchemy是一个简化了sqlalchemy操作的flask扩展。
在flask中使用mysql数据库,需要安装一个flask-sqlalchemy的扩展。
pip3 install flask-sqlalchemy
要连接mysql数据库,仍需要安装flask-mysqldb
pip3 install flask-mysqldb
使用flask-sqlalchemy管理数据库
使用flask-sqlalchemy扩展操作数据库,首先需要建立数据库连接。数据库连接通过url指定,而且程序使用的数据库必须保存到flask配置对象的sqlalchemy_database_uri键中。
flask的数据库设置:
app.config['sqlalchemy_database_uri'] = 'mysql://root:mysql@127.0.0.1:3306/test'
常用的sqlalchemy字段类型
类型名 | python中类型 | 说明 |
---|---|---|
integer | int | 普通整数,一般是32位 |
smallinteger | int | 取值范围小的整数,一般是16位 |
biginteger | int或long | 不限制精度的整数 |
float | float | 浮点数 |
numeric | decimal.decimal | 普通整数,一般是32位 |
string | str | 变长字符串 |
text | str | 变长字符串,对较长或不限长度的字符串做了优化 |
unicode | unicode | 变长unicode字符串 |
unicodetext | unicode | 变长unicode字符串,对较长或不限长度的字符串做了优化 |
boolean | bool | 布尔值 |
date | datetime.date | 时间 |
time | datetime.datetime | 日期和时间 |
largebinary | str | 二进制文件 |
常用的sqlalchemy列选项
选项名 | 说明 |
---|---|
primary_key | 如果为true,代表表的主键 |
unique | 如果为true,代表这列不允许出现重复的值 |
index | 如果为true,为这列创建索引,提高查询效率 |
nullable | 如果为true,允许有空值,如果为false,不允许有空值 |
default | 为这列定义默认值 |
常用的sqlalchemy关系选项
选项名 | 说明 |
---|---|
backref | 在关系的另一模型中添加反向引用 |
primary join | 明确指定两个模型之间使用的联结条件 |
uselist | 如果为false,不使用列表,而使用标量值 |
order_by | 指定关系中记录的排序方式 |
secondary | 指定多对多中记录的排序方式 |
secondary join | 在sqlalchemy中无法自行决定时,指定多对多关系中的二级联结条件 |
二 自定义模型类
模型表示程序使用的数据实体,在flask-sqlalchemy中,模型一般是python类,继承自db.model,db是sqlalchemy类的实例,代表程序使用的数据库。
类中的属性对应数据库表中的列。id为主键,是由flask-sqlalchemy管理。db.column类构造函数的第一个参数是数据库列和模型属性类型。
如下示例:定义了两个模型类,用户和角色。
from flask import flask from flask_sqlalchemy import sqlalchemy app = flask(__name__) class config(object): """配置参数""" # sqlalchemy的配置参数 sqlalchemy_database_uri = "mysql://root:123456@127.0.0.1:3306/db_flask" # 设置成 true,sqlalchemy 将会追踪对象的修改并且发送信号。这需要额外的内存, 如果不必要的可以禁用它。 sqlalchemy_track_modifications = true # 实例化sqlalchemy对象 app.config.from_object(config) # 创建数据库sqlalchemy工具对象 db = sqlalchemy(app) # 表名常见规范 # ihome --> ih_user 数据库缩写_表名 # tbl_user --> tbl_表名 # 创建数据库模型类 class role(db.model): """用户表""" __tablename__ = "tbl_roles" id = db.column(db.integer, primary_key=true) name = db.column(db.string(64), unique=true) users = db.relationship("user", backref="role") class user(db.model): """用户表""" __tablename__ = "tbl_users" # 指明数据库的表名 id = db.column(db.integer, primary_key=true) # 整型的主键, 会默认设置为自增主键 name = db.column(db.string(64), unique=true) email = db.column(db.string(128)) password = db.column(db.string(128)) role_id = db.column(db.integer, db.foreignkey("tbl_roles.id"))
三 数据库基本操作
在flask-sqlalchemy中,插入、修改、删除操作,均由数据库会话管理。会话用db.session表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用commit()方法提交会话。
数据库会话是为了保证数据的一致性,避免因部分更新导致数据不一致。提交操作把会话对象全部写入数据库,如果写入过程发生错误,整个会话都会失效。
数据库会话也可以回滚,通过db.session.rollback()方法,实现会话提交数据前的状态。
在flask-sqlalchemy中,查询操作是通过query对象操作数据。最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
创建表:
db.create_all()
删除表:
db.drop_all()
插入一条数据:
# 创建对象 ro1 = role(name='admin') # session记录对象任务 db.session.add(ro1) # 提交任务到数据库 db.session.commit() ro2 = role(name='user') db.session.add(ro2) db.session.commit()
一次插入多条数据
us1 = user(name='wang', email='wang@163.com', password='123456', role_id=ro1.id) us2 = user(name='zhang', email='zhang@163.com', password='201512', role_id=ro2.id) us3 = user(name='chen', email='chen@126.com', password='987654', role_id=ro2.id) us4 = user(name='zhou', email='zhou@163.com', password='456789', role_id=ro1.id) # 一次保存多条数据 db.session.add_all([us1, us2, us3, us4]) db.session.commit()
3.1 查询:
常用的sqlalchemy查询过滤器
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit | 使用指定的值限定原查询返回的结果 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
常用的sqlalchemy查询执行器
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果未查到,返回none |
first_or_404() | 返回查询的第一个结果,如果未查到,返回404 |
get() | 返回指定主键对应的行,如不存在,返回none |
get_or_404() | 返回指定主键对应的行,如不存在,返回404 |
count() | 返回查询结果的数量 |
paginate() | 返回一个paginate对象,它包含指定范围内的结果 |
filter_by精确查询:
>>> user.query.filter_by(name="wang").all() [<user 1>] >>> user = user.query.filter_by(name="wang").all() >>> user[0].name 'wang'
first()返回查询到的第一个对象
>>> user = user.query.first() >>> user <user 1> >>> user.name 'wang'
all()返回查询到的所有对象
>>> user.query.all() [<user 1>, <user 2>, <user 3>, <user 4>]
filter模糊查询,返回名字结尾字符为g的所有数据。
>>> users = user.query.filter(user.name.endswith("g")).all() >>> users [<user 1>, <user 2>] >>> users[0].name 'wang'
get(),参数为主键,如果主键不存在没有返回内容
>>> user.query.get(1) <user 1> >>> user = user.query.get(1) >>> user.name 'wang'
逻辑非,返回名字不等于wang的所有数据。
>>> user.query.filter(user.name!="wang").all() [<user 2>, <user 3>, <user 4>]
逻辑与,需要导入and,返回and()条件满足的所有数据。
>>> from sqlalchemy import and_ >>> user = user.query.filter(and_(user.name!="wang",user.email.endswith("163.com"))).all() >>> user [<user 2>, <user 4>]
逻辑或,需要导入or_
>>> from sqlalchemy import or_ >>> user.query.filter(or_(user.name!="wang", user.email.endswith("163.com"))).all() [<user 1>, <user 2>, <user 3>, <user 4>]
not_ 相当于取反
>>> from sqlalchemy import not_ >>> user.query.filter(not_(user.name=="chen")).all() [<user 1>, <user 2>, <user 4>]
使用db.session查询
>>> db.session.query(role).all() [<role 1>, <role 2>] >>> db.session.query(role).get(2) <role 2> >>> db.session.query(role).first() <role 1>
取不到数据返回none
>>> user= user.query.get(5) >>> user >>> type(user) <class 'nonetype'>
offset偏移
>>> users = user.query.offset(2).all() >>> users[0].name 'chen'
limit
>>> users = user.query.offset(1).limit(2).all() >>> users[0].name 'zhang' >>> users[1].name 'chen'
order_by
>>> users = user.query.order_by(user.id.desc()).all() >>> users [<user 4>, <user 3>, <user 2>, <user 1>] >>> users[0].name 'zhou'
group_by
>>> from sqlalchemy import func >>> db.session.query(user.role_id, func.count(user.role_id)).group_by(user.role_id) <flask_sqlalchemy.basequery object at 0x0000025dba5b0cc0> >>> db.session.query(user.role_id, func.count(user.role_id)).group_by(user.role_id).all() [(1, 2), (2, 2)]
在每个模型类中添加__repr__:
class role(db.model): """用户表""" __tablename__ = "tbl_roles" id = db.column(db.integer, primary_key=true) name = db.column(db.string(64), unique=true) users = db.relationship("user", backref="role") def __repr__(self): return "role object:%s" % self.name class user(db.model): """用户表""" __tablename__ = "tbl_users" # 指明数据库的表名 id = db.column(db.integer, primary_key=true) # 整型的主键, 会默认设置为自增主键 name = db.column(db.string(64), unique=true) email = db.column(db.string(128)) password = db.column(db.string(128)) role_id = db.column(db.integer, db.foreignkey("tbl_roles.id")) def __repr__(self): return "user object:%s" % self.name
>>> user.query.get(1) user object:wang
关联查询
>>> ro = role.query.get(1) >>> type(ro) <class 'db_demo.role'> >>> ro.users [user object:wang, user object:zhou] >>> user = user.query.get(1) >>> user user object:wang >>> user.role_id 1 >>> role.query.get(user.role_id) role object:admin >>> user.role role object:admin
3.2 更新数据
第一种方法:
>>> user = user.query.get(4) >>> user user object:zhou >>> user.name = "test" >>> db.session.add(user) >>> db.session.commit() >>> user.query.get(4) user object:test
第二种方法:
>>> user = user.query.filter_by(id=1).update({"name":"test1"}) >>> db.session.commit() >>> user.query.get(1) user object:test1
3.3 删除
>>> user =user.query.get(1) >>> db.session.delete(user) >>> db.session.commit()
四 图书案例
视图
from flask import flask, render_template, request, redirect, url_for from flask_sqlalchemy import sqlalchemy from flask_wtf import flaskform from wtforms import stringfield, submitfield from wtforms.validators import datarequired app = flask(__name__) class config(object): sqlalchemy_database_uri = "mysql://root:123456@127.0.0.1:3306/db_flask" sqlalchemy_track_modifications = true secret_key = "sahq28y1qhihsd0-121ewq" # 实例化sqlalchemy对象 app.config.from_object(config) # 创建数据库sqlalchemy工具对象 db = sqlalchemy(app) # 定义数据库的模型 class author(db.model): """作者""" __tablename__ = "tbl_authors" id = db.column(db.integer, primary_key=true) name = db.column(db.string(32), unique=true) books = db.relationship("book", backref="author") class book(db.model): """书籍""" __tablename__ = "tbl_books" id = db.column(db.integer, primary_key=true) name = db.column(db.string(64), unique=true) author_id = db.column(db.integer, db.foreignkey("tbl_authors.id")) # 创建表单模型类 class authorbookform(flaskform): """作者数据表单模型类""" author_name = stringfield(label="作者", validators=[datarequired("作者必填")]) book_name = stringfield(label="书籍", validators=[datarequired("书籍必填")]) submit = submitfield(label="保存") @app.route("/index", methods=["post", "get"]) def index(): form = authorbookform() if form.validate_on_submit(): author_name = form.author_name.data book_name = form.book_name.data author = author(name=author_name) db.session.add(author) db.session.commit() book = book(name=book_name, author_id=author.id) # book = book(name=book_name, author=author) db.session.add(book) db.session.commit() authors = author.query.all() return render_template("author_book.html", form=form, authors=authors) @app.route("/delete_book") def delete_book(): book_id = request.args.get("book_id") book = book.query.get(book_id) author_id = book.id author = author.query.get(author_id) db.session.delete(book) db.session.commit() db.session.delete(author) db.session.commit() return redirect(url_for("index")) if __name__ == '__main__': # db.drop_all() # db.create_all() # au_xi = author(name='我吃西红柿') # au_qian = author(name='萧潜') # au_san = author(name='唐家三少') # db.session.add_all([au_xi, au_qian, au_san]) # db.session.commit() # # bk_xi = book(name='吞噬星空', author_id=au_xi.id) # bk_xi2 = book(name='寸芒', author_id=au_qian.id) # bk_qian = book(name='飘渺之旅', author_id=au_qian.id) # bk_san = book(name='冰火魔厨', author_id=au_san.id) # db.session.add_all([bk_xi, bk_xi2, bk_qian, bk_san]) db.session.commit() app.run(debug=true)
模板
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <title>title</title> <meta http-equiv='content-type' content='text/htm'> </head> <body> <form method="post"> <p>添加作者和书籍</p> {{ form.csrf_token }} {{ form.author_name.label }} {{ form.author_name }} {{ form.author_name.errors.0 }} <br/> {{ form.book_name.label }} {{ form.book_name }} {{ form.book_name.errors.0 }} <br/> {{ form.submit }} <br/> </form> <hr/> {% for author in authors %} {{ author.name }} <ul> {% for book in author.books %} <a href="/delete_book?book_id={{ book.id }}">删除</a> <li>{{ book.name }}</li> {% endfor %} </ul> {% endfor %} </body> </html>
五 数据库迁移
在flask中可以使用flask-migrate扩展,来实现数据迁移。并且集成到flask-script中,所有操作通过命令就能完成。
为了导出数据库迁移命令,flask-migrate提供了一个migratecommand类,可以附加到flask-script的manager对象上。
pip3 install flask-migrate
文件: _migration.py
from flask import flask from flask_sqlalchemy import sqlalchemy from flask_script import manager from flask_migrate import migrate, migratecommand app = flask(__name__) class config(object): sqlalchemy_database_uri = "mysql://root:123456@127.0.0.1:3306/db_flask" sqlalchemy_track_modifications = true app.config.from_object(config) # 创建sqlalchemy的数据库连接对象 db = sqlalchemy(app) # 创建flask脚本管理工具对象 manager = manager(app) # 创建数据库迁移工具对象 migrate(app, db) # 向manager对象中添加数据库的操作命令 manager.add_command("db", migratecommand) # 定义模型role class role(db.model): # 定义表名 __tablename__ = 'roles' # 定义列对象 id = db.column(db.integer, primary_key=true) name = db.column(db.string(64), unique=true) def __repr__(self): return 'role:'.format(self.name) # 定义用户 class user(db.model): __tablename__ = 'users' id = db.column(db.integer, primary_key=true) username = db.column(db.string(64), unique=true, index=true) def __repr__(self): return 'user:'.format(self.username) if __name__ == '__main__': manager.run()
创建迁移仓库
#这个命令会创建migrations文件夹,所有迁移文件都放在里面。 python _migrate.py db init
创建迁移脚本
自动创建迁移脚本有两个函数,upgrade()函数把迁移中的改动应用到数据库中。downgrade()函数则将改动删除。自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。对比不一定完全正确,有可能会遗漏一些细节,需要进行检查
#创建自动迁移脚本 python _migrate.py db migrate -m 'initial migration' # -m 表示备注
更新数据库
python _migrate.py db upgrade
此时数据库里已经存在数据表了,如果需要回到之前的迁移版本,使用回退命令
回退数据库
回退数据库时,需要指定回退版本号,由于版本号是随机字符串,为避免出错,建议先使用python _migrate.py db history命令查看历史版本的具体版本号,然后复制具体版本号执行回退。
python _migrate.py db downgrade 版本号
六 发送邮件
在开发过程中,很多应用程序都需要通过邮件提醒用户,flask的扩展包flask-mail通过包装了python内置的smtplib包,可以用在flask程序中发送邮件。
flask-mail连接到简单邮件协议(simple mail transfer protocol,smtp)服务器,并把邮件交给服务器发送。
如下示例,通过开启qq邮箱smtp服务设置,发送邮件。
from flask import flask from flask_mail import mail, message app = flask(__name__) #配置邮件:服务器/端口/传输层安全协议/邮箱名/密码 app.config.update( debug = true, mail_server='smtp.qq.com', mail_prot=465, mail_use_tls = true, mail_username = 'xxxxxxxx@qq.com', mail_password = 'xxxxxx', ) mail = mail(app) @app.route('/') def index(): # sender 发送方,recipients 接收方列表 msg = message("this is a test ",sender='11111@qq.com', recipients=['aaaaaa@163.com','11111@qq.com']) #邮件内容 msg.body = "flask test mail" #发送邮件 mail.send(msg) print "mail sent" return "sent succeed" if __name__ == "__main__": app.run()