5.1 SQL数据库
SQL数据库:基于关系模型的数据库
主键:值为表中各行的唯一标识符
外键:引用同一个表或不同的表中某行的主键
行之间的这种联系称为关系,这是关系型数据库模型的基础
5.2 NoSQL数据库
NoSQL:所有不遵循关系模型的数据库
5.4 Python数据库框架
Python的数据库抽象层包:SQLAlchemy,MomgoEngine
ORM:对象关系型映射
5.5 使用Flask-SQLAlchemy管理数据库
在Flask-SQLAlchemy中,数据库试用URL指定;
数据库引擎 | URL |
---|---|
MySQL | mysql://username:password@hostname/database |
Postgres | postgresql://username:password@hostname/database |
SQLite(windows) | sqlite:///c:/absolute/path/to/database |
SQLite(Unix) | sqlite:///absolute/path/to/database |
sqlite不需要使用服务器,因此不用指定hostname、username和password。database是硬盘上的文件名;
程序使用的数据库URL必须保存在配置对象的SQLALCHEMY_DATABASE_URI键中;
配置对象中还有一个有用的选项,SQLALCHEMY-COMMIT-ON-TEARDOWN键,设置为True后,请求成功后会自动提交数据库中的变动;
# 配置一个简单的SQLite数据库
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
# 创建SQLAlchemy类的实例
db = SQLAlchemy(app)
5.6 定义模型
在ORM(对象关系型映射)中,模型一般是一个Python类,类中的属性对应数据库表中的列;
# 利用Flask-SQLAlchemy创建的数据库实例
class Role(db.Model):
__tablename__ = 'roles' # 定义在数据库中使用的表名
id = db.Column(db.Interger, primary_key = True)
name = db.Column(db.String(64), unique = True)
user = db.relationship('User', backref = 'role')
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__tabelname__ = 'user'
id = db.Column(db.Interger, primary_key = True)
username = db.Column(db.String(64), unique = True, index = True)
def __repr__(self):
return '<User %r>' % self.username
以上代码中,tablename定义数据库中使用的表名。
其余的类变量都是该模型的属性,被定义为db.column类的实例;db.column类构造函数的第一个参数是数据库列和模型属性的类型(P69),其余参数指定其他属性的配置选项;
Flask-SQLAlchemy要求每个模型都要定义主键,这一列经常命名为id。
选项名 | 说明 |
---|---|
primary_key | True,即为主键 |
unique | True,此列不允许出现重复的值 |
index | True,为这列创建索引,提升查询效率 |
nullable | True,允许使用空值 |
default | 为这列定义默认值 |
5.7 关系
# role和user的一对多关系的表示方法
class Role(db.model):
# ...
users = db.relationship('User', backref = 'role')
class User(db.model):
# ...
role_id = db.column(db.Interger, db.ForeignKey('roles.id'))
以上代码中,创建了一个从role到user的一对多关系;
添加到User模型中的role_id列被定义为外键。外键中的db.ForeignKey('roles.id')表明这列的值是roles表中的id值;
添加到Role模型中的users属性代表这个关系的面向对象视角;针对Role类的实例,user属性返回与角色相关联的用户组成的列表。db.relationship第一个参数是这个关系的另一端是什么模型。其他关系选项如下:
选项名 | 说明 |
---|---|
backref | 在关系的另一个模型中添加反向引用 |
primaryjoin | 明确指定两个模型之间的联结条件 |
lazy | 指定如何加载相关记录,select,immediate,joined,subquery,noload,dynamic |
userlist | 设置为False,不使用列表,而是用标量值 |
常见的关系主要有一对多关系,多侧模型增加外键;
多对一关系对调两个表即可,也可以把外键和db.relationship都放在多这一侧;
5.8数据库操作
创建表
(venv) $ python hello.py shell
>>> from hello import db
>>> db.create_all()
>>> db.drop_all()
插入行
首先创建一些角色和用户
>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderate')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=user_role)
# 以上新建对象的id属性没有明确设定,因为主键由Flask-SQLAlchemy管理,
# 这些对象只存在在Python中,未写入数据库
# db.session.add()将对象先写入会话中
>>> db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
# db.session.commit() 提交会话
# 提交后,每个对象的id会被赋值
>>> db.session.commit()
数据库会话也可以使用db.session.rollback()
回滚
修改行
在数据库会话上调用add()方法也能更新模型
>>> admin_role.name = 'administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()
删除行
>>> db.session.delete(mod_role)
>>> db.session.commit()
查询行
>>> User.query.all()
>>> User.query.filter_by(role=user_role).all()
如果退出了shell对话,前面例子中创建的对象会作为各自数据表中的行。如果打开了新的shell对话,就要从数据库中读取行,再重新创建Python对象。
常用的SQLAlchemy查询过滤器
过滤器 | 说明 |
---|---|
filter() | 将过滤器添加到原查询上 |
filter_by() | 将等值过滤器添加到原查询上 |
limit() |
常用的SQLAlchemy查询执行函数
方法 | 说明 |
---|---|
all() | 以列表形式返回所有结果 |
first() | 返回查询的第一个结果,没有结果返回None |
first_or_404() | 返回查询的第一个结果,没有则返回404 |
get() | 返回指定主键对应的行,没有结果返回None |
get_or_404() | 返回指定主键对应的行,没有结果返回404 |
count() | 返回查询结果的数量 |
paginate() | 返回一个paginate对象 |
5.9 在视图函数中操作数据库
下例为把用户输入的名字写入数据库的代码
import os
from flask import Flask, render_template, session, redirect, url_for
from flask_script import Manager
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from flask_wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
from flask_sqlalchemy import SQLAlchemy
# get abspath of this file
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
manager = Manager(app)
bootstrap = Bootstrap(app)
moment = Moment(app)
db = SQLAlchemy(app)
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 '<Role %r>' % self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return '<User %r>' % self.username
# define a form class
class NameForm(Form):
name = StringField('Input your name', validators=[Required()])
submit = SubmitField('SUBMIT')
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm() # create a form instance
if form.validate_on_submit():
# .query.filter_by().first(): get the correct User, if not, return None
user = User.query.filter_by(username=form.name.data).first()
if user is None:
user = User(username=form.name.data)
db.session.add(user)
session['known'] = False
else:
session['known'] = True
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html',
form=form,
name=session.get('name'),
known=session.get('known', False))
if __name__ == '__main__':
manager.run()
小结:
-
from flask import session
: session作为用户会话,是一种私有存储,存在每个连接到服务器的客户端中; -
return render_template()
中使用session.get('name')
是为了防止没有键的情况下异常返回,get返回None - 视图函数逻辑:表单后台验证成功后,检索数据库中username等于输入的值的行;如果不存在,就将通过实例化User创建这一行,并添加到session中供传入数据库;
5.10 集成Python Shell
避免每次启动shell都要导入数据库实例和模型
# python shell
# do not need to `from addToSQL import Role, User, app, db`
# make_shell_context register app, db ...
def make_shell_context():
return dict(app=app, db=db, Role=Role, User=User)
manager.add_command("shell", Shell(make_context=make_shell_context))
5.11 使用Flask-Migrate实现数据库迁移
更新表的唯一方式是先删除旧表,不过这样会丢失所有数据
创建迁移仓库
from flask_migrate import Migrate, MigrateCommand
migrate=Migrate(app, db)
manager.add_command('db', MigrateCommand)
创建迁移脚本
数据库迁移用迁移脚本表示。脚本中有upgrade()和downgrade()两个函数。
使用migrate子命令自动创建迁移脚本
python hello.py db migrate -m "migrate 1st"
更新数据库
将迁移应用到数据库
python hello.py db upgrade