Flask 数据库
新手用的上的,转发自 https://blog.csdn.net/hanyuyang19940104/article/details/80519003
1. 使用Flask-SQLAlchemy管理数据库
Flask使用Flask-SQLAlchemy
管理数据库,安装方式:
$ pip install flask-sqlalchemy
- 1
在 Flask-SQLAlchemy 中,数据库使用 URL 指定:
MySQL mysql://username:password@hostname/database
Postgres postgresql://username:password@hostname/database
SQLite( Unix) sqlite:////absolute/path/to/database
SQLite( Windows) sqlite:///c:/absolute/path/to/database
程序使用的数据库 URL
必须保存到 Flask
配置对象的 SQLALCHEMY_DATABASE_URI
键中。
配置对象中还有一个很有用的选项, 即 SQLALCHEMY_COMMIT_ON_TEARDOWN
键,将其设为 True
时,每次请求结束后都会自动提交数据库中的变动。
初始化以及配置一个简单的SQLite
数据库:
from os
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
2. 定义模型
在 ORM 中,模型一般是一个 Python 类,类中的属性对应数据库表中的列。
下面定义两个表对应的Python类。
class Role(db.Model):
__tablename__ = '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 '' % 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)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return '' % self.username
类变量 __tablename__
定义在数据库中使用的表名。如果没有定义 __tablename__
, Flask-SQLAlchemy
会使用一个默认名字,但默认的表名没有遵守使用复数形式进行命名的约定,所以最好由我们自己来指定表名。其余的类变量都是该模型的属性,被定义为 db.Column
类的实例。
下图显示了最常用的SQLAlchemy列的类型:
下图显示了最常用的SQLAlchemy列选项:
Flask-SQLAlchemy 要求每个模型都要定义主键,这一列经常命名为 id。
3. 定义模型的关系
上面的User
类和Role
类中,通过下面的代码定义了两个类的关系:
class Role(db.Model):
# ...
users = db.relationship('User', backref='role', lazy='dynamic')
class User(db.Model):
# ...
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
之前定义了两个表的名字:
users
和roles
。
关系使用 users
表中的外键连接了两行。添加到 User
模型中的 role_id
列被定义为外键, 就是这个外键建立起了关系。传给 db.ForeignKey()
的参数 'roles.id'
表明,这列的值是 roles
表中行的 id
值。
添加到 Role
模型中的 users
属性代表这个关系的面向对象视角。对于一个 Role
类的实例,其 users
属性将返回与角色相关联的用户组成的列表。db.relationship()
的第一个参数表明这个关系的另一端是哪个模型。
db.relationship()
中的 backref
参数向 User
模型中添加一个 role
属性,从而定义反向关系。这一属性可替代 role_id
访问 Role
模型,此时获取的是模型对象,而不是外键的值。
其中参数lazy
的作用是:禁止自动查询,当在一个Role
对象中调用users
属性的时候,会自动执行query
,返回所有的属于该Role
对象的User
对象,因此无法在此结果上附加更精确的查询过滤器。例如原来这样直接获取User
对象users = user_role.users
,增加lazy
参数后这样获取:user_role.users.order_by(User.username).all()
,增加了更精确的过滤器。
4. 数据配置
在该部分实现数据库的创建、初始化等工作,所以把这部分代码放在manage.py
文件中:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask_script import Manager, Server, Shell
from somepackage import app, db
from somepackage import User, Role
manager = Manager(app)
@manager.command
def init_db():
"""
Init the DB and Insert test data
"""
# this db is a SQLAlchemy instance, must be imported.
# the create_all method is belonged to SQLAlchemy instance.
db.create_all()
admin_role = Role(id=1, name="Admin")
user_role = Role(id=2, name="User")
user_jhon = User(id=1, username="Jhon", role=admin_role)
user_david = User(id=2, username="david", role=user_role)
db.session.add_all([admin_role, user_role, user_jhon, user_david])
try:
db.session.commit()
except Exception, e:
db.session.rollback()
print e
if __name__ == "__main__":
manager.run()
注意:其中的
User
和Role
,以及db
都要从合适的包或者模块中引入进来。
这样就能够创建并初始化数据库。下面看SQLAlchemy提供的过滤器和查询函数:
此为第二份文章
# -*- coding:utf-8 -*-
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# url的格式为:数据库的协议://用户名:密码@ip地址:端口号(默认可以不写)/数据库名
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:mysql@localhost/first_flask"
# 动态追踪数据库的修改. 性能不好. 且未来版本中会移除. 目前只是为了解决控制台的提示才写的
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# 创建数据库的操作对象
db = SQLAlchemy(app)
class Role(db.Model):
__tablename__ = "roles"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(16),unique=True)
# 给Role类创建一个uses属性,关联users表。
# backref是反向的给User类创建一个role属性,关联roles表。这是flask特殊的属性。
users = db.relationship('User',backref="role")
# 相当于__str__方法。
def __repr__(self):
return "Role: %s %s" % (self.id,self.name)
class User(db.Model):
# 给表重新定义一个名称,默认名称是类名的小写,比如该类默认的表名是user。
__tablename__ = "users"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(16),unique=True)
email = db.Column(db.String(32),unique=True)
password = db.Column(db.String(16))
# 创建一个外键,和django不一样。flask需要指定具体的字段创建外键,不能根据类名创建外键
role_id = db.Column(db.Integer,db.ForeignKey("roles.id"))
def __repr__(self):
return "User: %s %s %s %s" % (self.id,self.name,self.password,self.role_id)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
# 删除所有的表
db.drop_all()
# 创建表
db.create_all()
ro1 = Role(name = "admin")
# 先将ro1对象添加到会话中,可以回滚。
db.session.add(ro1)
ro2 = Role()
ro2.name = 'user'
db.session.add(ro2)
# 最后插入完数据一定要提交
db.session.commit()
us1 = User(name='wang', email='[email protected]', password='123456', role_id=ro1.id)
us2 = User(name='zhang', email='[email protected]', password='201512', role_id=ro2.id)
us3 = User(name='chen', email='[email protected]', password='987654', role_id=ro2.id)
us4 = User(name='zhou', email='[email protected]', password='456789', role_id=ro1.id)
us5 = User(name='tang', email='[email protected]', password='158104', role_id=ro2.id)
us6 = User(name='wu', email='[email protected]', password='5623514', role_id=ro2.id)
us7 = User(name='qian', email='[email protected]', password='1543567', role_id=ro1.id)
us8 = User(name='liu', email='[email protected]', password='867322', role_id=ro1.id)
us9 = User(name='li', email='[email protected]', password='4526342', role_id=ro2.id)
us10 = User(name='sun', email='[email protected]', password='235523', role_id=ro2.id)
db.session.add_all([us1, us2, us3, us4, us5, us6, us7, us8, us9, us10])
db.session.commit()
app.run(debug=True)
下面插播一条bug:
当把表格创建完成,注释这两句话:
# 删除所有的表
db.drop_all()
# 创建表
db.create_all()
然后向表格里面插入数据,此时会出现这样的错误:
sqlalchemy.exc.IntegrityError: (_mysql_exceptions.IntegrityError) (1062, "Duplicate entry 'admin' for key 'name'") [SQL: u'INSERT INTO roles (name) VALUES (%s)'] [parameters: ('admin',)]
查了网上的好多资料说把字段的约束unique=True去掉就好了,但是根本原因不在这。
原因就是因为app.run(debug=True)。开启debug模式之后,当我们修改代码的时候,比如将删除表和创建表这两句话注释,然后打开插入数据的注释。这个过程debug模式默认就已经把程序运行一遍了。此时数据库就已经有了数据,当我们再次手动执行的时候,又往数据库中插入了一条数据,这时候就会报错。因为字段的约束是唯一性的unique,所以解决的办法有两种:
第一种:就是不要将删除表和创建表这两句话注释,每次执行都要带着这两个句话。无论是debug模式自动执行还是我们手动执行程序,都会先删除表然后再创建表,所以执行多少次都不怕。
第二种:关闭debug模式。就是这样app.run()
2.数据库的增删改查:
1.以下的方法都是返回一个新的查询,需要配合执行器使用。
filter(): 过滤,功能比较强大。
filter_by():过滤,用在一些比较简单的过滤场景。
order_by():排序。默认是升序,降序需要导包:from sqlalchemy import * 。然后引入desc方法。比如order_by(desc("email")).按照邮箱字母的降序排序。
group_by():分组。
2.以下都是一些常用的执行器:配合上面的过滤器使用。
get():获得id等于几的函数。比如:查询id=1的对象。get(1)。切记:括号里没有“id=”,直接传入id的数值就ok。因为该函数的功能就是查询主键等于几的对象。
all():查询所有的数据。
first():查询第一个数据。
count():返回查询结果的数量。
paginate():分页查询,返回一个分页对象。paginate(参数1,参数2,参数3)
参数1:当前是第几页,参数2:每页显示几条记录,参数3:是否要返回错误。
返回的分页对象有三个属性:items:获得查询的结果,pages:获得一共有多少页,page:获得当前页。
3.常用的逻辑符:
需要倒入包才能用的有:from sqlalchemy import *
not_ and_ or_ 还有上面说的排序desc。
常用的内置的有:in_ 表示某个字段在什么范围之中。
4.其他关系的一些数据库查询:
endswith():以什么结尾。
startswith():以什么开头。
contains():包含
5.下面体会一下上面的这些用法:
1. 查询所有用户数据
User.query.all()
2. 查询有多少个用户
User.query.count()
3. 查询第1个用户
User.query.first()
4. 查询id为4的用户[3种方式]
User.query.get(4)
User.query.filter_by(id=4).first()
User.query.filter(User.id==4).first()
filter:(类名.属性名==)
filter_by:(属性名=)
filter_by: 用于查询简单的列名,不支持比较运算符
filter比filter_by的功能更强大,支持比较运算符,支持or_、in_等语法。
5. 查询名字结尾字符为g的所有数据[开始/包含]
User.query.filter(User.name.endswith('g')).all()
User.query.filter(User.name.contains('g')).all()
6. 查询名字不等于wang的所有数据[2种方式]
from sqlalchemy import not_
注意了啊:逻辑查询的格式:逻辑符_(类属性其他的一些判断)
User.query.filter(not_(User.name=='wang')).all()
User.query.filter(User.name!='wang').all()
7. 查询名字和邮箱都以 li 开头的所有数据[2种方式]
from sqlalchemy import and_
User.query.filter(and_(User.name.startswith('li'), User.email.startswith('li'))).all()
User.query.filter(User.name.startswith('li'), User.email.startswith('li')).all()
8. 查询password是 `123456` 或者 `email` 以 `itheima.com` 结尾的所有数据
from sqlalchemy import or_
User.query.filter(or_(User.password=='123456', User.email.endswith('itheima.com'))).all()
9. 查询id为 [1, 3, 5, 7, 9] 的用户列表
User.query.filter(User.id.in_([1, 3, 5, 7, 9])).all()
10. 查询name为liu的角色数据
关系引用
User.query.filter_by(name='liu').first().role.name
11. 查询所有用户数据,并以邮箱排序
排序
User.query.order_by('email').all() 默认升序
User.query.order_by(desc('email')).all() 降序
12. 查询第2页的数据, 每页只显示3条数据
help(User.query.paginate)
三个参数: 1. 当前要查询的页数 2. 每页的数量 3. 是否要返回错误
pages = User.query.paginate(2, 3, False)
pages.items # 获取查询的结果
pages.pages # 总页数
pages.page # 当前页数
3.使用第三方扩展框架迁移数据库文件。
使用框架需要配置的代码如下:
# -*- coding:utf-8 -*-
from flask import Flask
from flask_sqlalchemy import SQLAlchemy # 操作数据库的扩展包
from flask_script import Manager # 用命令操作的扩展包
from flask_migrate import Migrate,MigrateCommand # 操作数据库迁移文件的扩展包
app = Flask(__name__)
app.debug = True
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:mysql@localhost/second_flask"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
manager = Manager(app)
# 创建迁移对象
migrate = Migrate(app,db)
# 将迁移文件的命令添加到‘db’中
manager.add_command('db',MigrateCommand)
class Role(db.Model):
__tablename__ = "table_roles"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(16),unique=True)
info = db.Column(db.String(100))
Users = db.relationship("User",backref='role')
class User(db.Model):
__tablename__ = "table_users"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(16),unique=True)
info = db.Column(db.String(200))
role_id = db.Column(db.Integer,db.ForeignKey("table_roles.id"))
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
manager.run()
使用迁移命令如下:
比如上面的代码所在的文件名称为database.py。
1.python database.py db init 生成管理迁移文件的migrations目录
2.python database.py db migrate -m "注释" 在migrations/versions中生成一个文件,该文件记录数据表的创建和更新的不同版本的代码。
3.python database.py db upgrade 在数据库中生成对应的表格。
4.当需要改表格的时候,改完先执行第二步,然后再执行第三步。
5.需要修改数据表的版本号的时候需要做的操作如下:
python database.py db upgrade 版本号 向上修改版本号
python database.py db downgrade 版本号 向下修改版本号
可能用到的其他的语句:
python database.py db history 查看历史版本号
python database.py db current 查看当前版本号