一、数据库的设置
Web应用中普遍使用的是关系模型的数据库,关系型数据库把所有的数据都存储在表中,表用来给应用的实体建模,表的列数是固定的,行数是可变的。它使用结构化的查询语言。关系型数据库的列定义了表中表示的实体的数据属性。比如:商品表里有name、price、number等。 Flask本身不限定数据库的选择,你可以选择SQL或NOSQL的任何一种。也可以选择更方便的SQLALchemy,类似于Django的ORM。SQLALchemy实际上是对数据库的抽象,让开发者不用直接和SQL语句打交道,而是通过Python对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升。
SQLAlchemy是一个关系型数据库框架,它提供了高层的ORM和底层的原生数据库的操作。flask-sqlalchemy是一个简化了SQLAlchemy操作的flask扩展。
1.1数据库安装
安装服务端
sudo apt-get install mysql-server
安装客户端
sudo apt-get install mysql-client
sudo apt-get install libmysqlclient-dev
1.2 数据库的基本命令
登录数据库
mysql -u root -p
创建数据库,并设定编码
create database <数据库名> charset=utf8;
显示所有数据库
show databases;
1.3 在Flask中使用mysql数据库,需要安装一个flask-sqlalchemy的扩展。
pip install flask-sqlalchemy
要连接mysql数据库,仍需要安装flask-mysqldb
pip install flask-mysqldb
1.4 使用Flask-SQLAlchemy管理数据库
使用Flask-SQLAlchemy扩展操作数据库,首先需要建立数据库连接。数据库连接通过URL指定,而且程序使用的数据库必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI键中。
对比下Django和Flask中的数据库设置:
Django的数据库设置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydb',
'HOST':'192.168.18.128',
'PORT':3306,
'USER':'root',
'PASSWORD':'123456',
}
}
Flask的数据库设置:
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysq+pymysqll://root:mysql@127.0.0.1:3306/mydb'
常用的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中无法自行决定时,指定多对多关系中的二级联结条件 |
代码演示
db_demo.py
# coding:utf-8
#db_demo.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
class Config(object):
"""配置参数"""
# sqlalchemy的配置参数
SQLALCHEMY_DATABASE_URI = "mysql://root:mysql@127.0.0.1:3306/db_python04"
# 设置sqlalchemy自动更跟踪数据库
SQLALCHEMY_TRACK_MODIFICATIONS = True
app.config.from_object(Config)
# 创建数据库sqlalchemy工具对象
db = SQLAlchemy(app)
class Role(db.Model):
"""用户角色/身份表"""
__tablename__ = "tbl_roles"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), unique=True)
users = db.relationship("User", backref="role")
def __repr__(self):
"""定义之后,可以让显示对象的时候更直观"""
return "Role object: name=%s" % self.name
# 表名的常见规范
# ihome -> ih_user 数据库名缩写_表名
# tbl_user tbl_表名
# 创建数据库模型类
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), unique=True)
password = db.Column(db.String(128))
role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))
def __repr__(self):
return "User object: name=%s" % self.name
if __name__ == '__main__':
# 清除数据库里的所有数据
db.drop_all()
# 创建所有的表
db.create_all()
# 创建对象
role1 = Role(name="admin")
# session记录对象任务
db.session.add(role1)
# 提交任务到数据库中
db.session.commit()
role2 = Role(name="stuff")
db.session.add(role2)
db.session.commit()
us1 = User(name='wang', email='wang@163.com', password='123456', role_id=role1.id)
us2 = User(name='zhang', email='zhang@189.com', password='201512', role_id=role2.id)
us3 = User(name='chen', email='chen@126.com', password='987654', role_id=role2.id)
us4 = User(name='zhou', email='zhou@163.com', password='456789', role_id=role1.id)
# 一次保存多条数据
db.session.add_all([us1, us2, us3, us4])
db.session.commit()
terminal
Role.query.all()
Out[2]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]
In [3]: li = Role.query.all()
In [4]: li
Out[4]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]
In [5]: r = li[0]
In [6]: type(r)
Out[6]: db_demo.Role
In [7]: r.name
Out[7]: u'admin'
In [8]: Role.query.first()
Out[8]: <db_demo.Role at 0x10388d190>
In [9]: r = Role.query.first()
In [10]: r.name
Out[10]: u'admin'
# 根据主键id获取对象
In [11]: r = Role.query.get(2)
In [12]: r
Out[12]: <db_demo.Role at 0x10388d310>
In [13]: r.name
Out[13]: u'stuff'
In [14]:
# 另一种查询方式
In [15]: db.session.query(Role).all()
Out[15]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]
In [16]: db.session.query(Role).get(2)
Out[16]: <db_demo.Role at 0x10388d310>
In [17]: db.session.query(Role).first()
Out[17]: <db_demo.Role at 0x10388d190>
In [18]:
In [18]: User.query.filter_by(name="wang")
Out[18]: <flask_sqlalchemy.BaseQuery at 0x1038c90d0>
In [19]: User.query.filter_by(name="wang").all()
Out[19]: [<db_demo.User at 0x1038c87d0>]
In [20]: User.query.filter_by(name="wang").first()
Out[20]: <db_demo.User at 0x1038c87d0>
In [21]: user = User.query.filter_by(name="wang").first()
In [22]: user.name
Out[22]: u'wang'
In [23]: user.email
Out[23]: u'wang@163.com'
In [24]: User.query.filter_by(name="wang", role_id=1).first()
Out[24]: <db_demo.User at 0x1038c87d0>
In [25]: User.query.filter_by(name="wang", role_id=2).first()
In [26]: user = User.query.filter_by(name="wang", role_id=2).first()
In [27]: type(user)
Out[27]: NoneType
In [28]:
In [28]: user = User.query.filter(User.name=="wang", User.role_id==1).first
...: ()
In [29]: user
Out[29]: <db_demo.User at 0x1038c87d0>
In [30]: user.name
Out[30]: u'wang'
In [31]: from sqlalchemy import or_
In [32]: User.query.filter(or_(User.name=="wang", User.email.endswith("163.com")
...: )).all()
Out[32]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>]
In [33]: li = User.query.filter(or_(User.name=="wang", User.email.endswith("163.
...: com"))).all()
In [34]: li[0].name
Out[34]: u'wang'
In [35]: li[1].name
Out[35]: u'zhou'
In [36]:
# offset偏移 跳过几条
In [36]: User.query.offset(2).all()
Out[36]: [<db_demo.User at 0x1038c0950>, <db_demo.User at 0x1038ef310>]
In [37]: li = User.query.offset(2).all()
In [38]: li[0].name
Out[38]: u'chen'
In [39]: li[1].name
Out[39]: u'zhou'
In [40]:
In [42]: li = User.query.offset(1).limit(2).all()
In [43]: li
Out[43]: [<db_demo.User at 0x1038fd990>, <db_demo.User at 0x1038c0950>]
In [44]: li[0].name
Out[44]: u'zhang'
In [45]: li[1].name
Out[45]: u'chen'
In [46]:
In [50]: User.query.order_by("-id").all()
Out[50]:
[<db_demo.User at 0x1038ef310>,
<db_demo.User at 0x1038c0950>,
<db_demo.User at 0x1038fd990>,
<db_demo.User at 0x1038c87d0>]
In [51]:
In [51]: li = User.query.order_by(User.id.desc()).all()
In [52]: li
Out[52]:
[<db_demo.User at 0x1038ef310>,
<db_demo.User at 0x1038c0950>,
<db_demo.User at 0x1038fd990>,
<db_demo.User at 0x1038c87d0>]
In [53]: li[0].name
Out[53]: u'zhou'
In [54]: li[3].name
Out[54]: u'wang'
In [55]:
In [55]: from sqlalchemy import func
In [56]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
...: d)
Out[56]: <flask_sqlalchemy.BaseQuery at 0x103a38050>
In [57]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
...: d).all()
Out[57]: [(1L, 2L), (2L, 2L)]
In [58]:
In [61]: ro = Role.query.get(1)
In [62]: type(ro)
Out[62]: db_demo.Role
In [63]: ro.users
Out[63]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>]
In [64]: ro.users[0].name
Out[64]: u'wang'
In [65]: ro.users[1].name
Out[65]: u'zhou'
In [66]:
In [67]: user
Out[67]: <db_demo.User at 0x1038c87d0>
In [68]: user.role_id
Out[68]: 1L
In [69]: Role.query.get(user.role_id)
Out[69]: <db_demo.Role at 0x10388d190>
In [70]: user.role
Out[70]: <db_demo.Role at 0x10388d190>
In [71]: user.role.name
Out[71]: u'admin'
In [72]:
# 更新
In [14]: User.query.filter_by(name="zhou").update({"name": "python", "emai
...: l": "python@itast.cn"})
Out[14]: 1L
In [15]: db.session.commit()
In [16]:
# 删除
In [16]: user = User.query.get(3)
In [17]: db.session.delete(user)
In [18]: db.session.commit()
In [19]:
图书管理
(删除函数:注释代码部分为post请求写法)
后端部分
# coding:utf-8
from flask import Flask, render_template, request, redirect, url_for, jsonify
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的配置参数
SQLALCHEMY_DATABASE_URI = "mysql://root:mysql@127.0.0.1:3306/author_book_py04"
# 设置sqlalchemy自动更跟踪数据库
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = "doiso7fd89fyd9^(fsd"
app.config.from_object(Config)
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=u"作者", validators=[DataRequired(u"作者必填")])
book_name = StringField(label=u"书籍", validators=[DataRequired(u"书籍必填")])
submit = SubmitField(label=u"保存")
@app.route("/", methods=["GET", "POST"])
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()
# 查询数据库
author_li = Author.query.all()
return render_template("author_book.html", authors=author_li, form=form)
# post /delete_book json
# {"book_id":x}
# @app.route("/delete_book", methods=["POST"])
# def delete_book():
# """删除数据"""
# # 提取参数
# # 如果前端发送的请求体数据是json格式,get_json会解析成字典
# # get_json 要求前端传送的数据的Content-Type: application/json
# req_dict = request.get_json()
# book_id = req_dict.get("book_id")
#
# # 删除数据
# book = Book.query.get(book_id)
# db.session.delete(book)
# db.session.commit()
#
# # "Content-Type": "application/json"
# return jsonify(code=0, message="OK")
# /delete_book?book_id=xx
@app.route("/delete_book", methods=["GET"])
def delete_book():
"""删除数据"""
# 提取参数
book_id = request.args.get("book_id")
# 删除数据
book = Book.query.get(book_id)
db.session.delete(book)
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="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
{{ form.csrf_token }}
{{ form.author_name.label }}
<p>{{form.author_name}}</p>
{% for msg in form.author_name.errors %}
<p>{{msg}}</p>
{% endfor %}
{{ form.book_name.label }}
<p>{{form.book_name}}</p>
{% for msg in form.book_name.errors %}
<p>{{msg}}</p>
{% endfor %}
{{ form.submit }}
</form>
<hr/>
<ul>
{% for author in authors %}
<li>作者:{{ author.name }}</li>
<ul>
{% for book in author.books%}
<li>书籍:{{ book.name }}</li>
<!--<a href="javascript:;" book-id="{{book.id}}">删除</a>-->
<a href="/delete_book?book_id={{book.id}}">GET删除</a>
{% endfor %}
</ul>
{% endfor %}
</ul>
<script type="text/javascript" src="/static/js/jquery.min.js"></script>
<script>
// $("a").click(
// function () {
// var data = {
// book_id: $(this).attr("book-id")
// };
// // 将js中的对象转换为 json字符串
// var req_json = JSON.stringify(data);
// // $.post("/delete_book", req_json, function (resp) {
// // if (resp.code == 0) {
// // location.href = "/";
// // }
// // })
// $.ajax({
// url: "/delete_book", // 请求的后端url
// type: "post", // 请求方式
// data: req_json, // 向后端发送的请求体数据
// contentType: "application/json", // 指明向后端发送的数据格式
// dataType: "json", // 指明后端返回的数据格式
// success: function (resp) {
// if (resp.code == 0) {
// alert("oK");
// location.href = "/";
// }
// }
// })
// }
// )
</script>
</body>
</html>
1.6数据库迁移
在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。
首先要在虚拟环境中安装Flask-Migrate。
pip install flask-migrate
文件:database.py
#coding=utf-8 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) app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/Flask_test' app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True db = SQLAlchemy(app) #第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例 migrate = Migrate(app,db) #manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令 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 database.py db init
创建迁移脚本
自动创建迁移脚本有两个函数,upgrade()函数把迁移中的改动应用到数据库中。downgrade()函数则将改动删除。自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。对比不一定完全正确,有可能会遗漏一些细节,需要进行检查
#创建自动迁移脚本 python database.py db migrate -m 'initial migration'
更新数据库
python database.py db upgrade
回退数据库
回退数据库时,需要指定回退版本号,由于版本号是随机字符串,为避免出错,建议先使用python database.py db history命令查看历史版本的具体版本号,然后复制具体版本号执行回退。
python database.py db downgrade 版本号