4.操作数据库

在Shell中操作

创建数据库

方法是用db.create_all()函数:

(venv) $ python hello.py shell
>>> from hello import db
>>> db.creat_all()

会发现在程序根目录多了一个名为data.sqlite的文件,如果数据库表已经存在,那么db.create_all()不会重新创建或者更新这个表,如果修改模型后要把改动应用到现有的数据库中,这一特性会带来不便,更新现有数据库的简单粗暴方法是先删除旧表再创建新表

>>> db.drop_all()
>>> db.create_all()

这个方法会把数据库中原有的数据全都销毁,不是很提倡

插入行

创建一些用户和角色:

>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> 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)

现在还不能取到值,因为会话没有提交:

>>>print(admin_role.id)
None 
>>>print(mod_role.id)
None
>>>print(user_role.id)
None

会话由db.session表示,准备把对象写入数据库之前,先要将其添加到会话中:

>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)

也简写成:

>>> db.session.add_all([admin_role, mod_role, user_role, 
...     user_john, user_susan, user_david])

调用commit()方法提交会话:


>>> db.session.commit()

再次查看id属性,它们已经赋值了

数据库会话db.session和之前介绍的Flask session对象没有关系,数据库会话也称为事务

数据库会话也可以回滚,调用db.session.rollback()后,添加到数据库会话中的所有对象都会还原到它们在数据库时的状态

修改行

在数据库会话上调用add()方法也能更新模型,把Admin角色重命名为Administrator:

>>> admin_role.name= 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()

删除与插入和更新一样,提交会话后才会执行

查询行

Flask-SQLAlchemy为每个模型类都提供了query对象,最基本的模型查询是取回对应表中的所有记录:

>>> Role.query.all()
[<Role u'Administrator'>, <Role u'Moderator'>, <Role u'User'>, <Role u'john'>, <Role u'susan'>, <Role u'david'>]
>>> User.query.all()
[<User u'john'>, <User u'susan'>, <User u'david'>]

使用过滤器(filter_by())可以配置query对象进行更精确的数据库查询,下例为查找角色为’User’的所有用户:

>>> User.query.filter_by(role=user_role).all()
[<User u'susan'>,<User u'david'>]

若要查看SQLAlchemy为查询生成的原生SQL查询语句,只需把query对象转换成字符串:

>>> str(User.query.filter_by(role=user_role))

退出Shell后的查询:下例发起了一个查询,加载名为User的用户角色:

>>> user_role = Role.query.filter_by(name='User').first()

filter_by()等过滤器在query对象上调用,返回一个更精确的query对象,多个过滤器可以一起调用,知道获得所需结果

下表是可在query对象上调用的常用过滤器

过滤器说明
filter()把过滤器添加到原查询上,返回一个新查询
filter_by()把等值过滤器添加到原查询上,返回一个新查询
limit()使用指定的值限制原查询返回的结果数量,返回一个新查询
offset()偏移原查询返回的结果,返回一个新查询
order_by()根据指定条件对原查询结果进行排序,返回一个新查询
group_by()根据指定条件对原查询结果进行分组,返回一个新查询

在查询上应用指定的过滤器后,通过调用all()执行查询,以列表的形式返回结果,除了all()以外,还有其他方法能触发查询执行,下表是方法:

方法说明
all()以列表形式返回查询的所有结果
first()返回查询的第一个结果,如果没有结果则返回None
first_or_404()返回查询的第一个结果,如果没有结果,则中止请求,返回404错误响应
get()返回指定主键对应的行,如果没有对应的行,则返回None
get_or_404()返回指定主键对应的行,如果没找到指定的主键,则中止请求,返回404错误响应
count()返回查询结果的数量
paginate返回一个paginate对象,它包含指定范围内的结果

关系和查询的处理方式类似,下例分别从关系的两端查询用户和角色之间的一对多关系:

>>> users = user_role.users
>>> users
[<User u'susan>,<User u'david'>]
>>> users[0].role
<Role 'User'>

这个例子中的user_role.users查询有个小问题,执行user_role.users表达式时,隐含的查询会调用all()返回一个用户列表,query对象是隐藏的,因此无法指定更精确的查询过滤器,就这个特定示例而言,返回一个按照字母顺序排序的用户列表可能更好
下例中,我们修改了关系的设置,加入了lazy=’dynamic’参数,从而禁止自动执行查询

class Role(db.Model):
    #....
    users = db.relationship('User', backref='role', lazy='dynamic')
    #...

这样配置关系之后,user_role.users会返回一个尚未执行的查询,因此可以添加过滤器:

>>> user_role.users.order_by(User.username).all()
[<User u'david'>,<User u'susan'>]
>>> user_role.users.count()
2

在视图函数中操作数据库

数据库操作可以直接在视图函数中进行,示例如下:

@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        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
        form.name.data = ''
        return redirect(url_for('index')),
         form=form, name=session.get('name'),
         known=session.get('known', False))

在这个修改后的版本中,提交表单后,程序会使用filter_by()查询过滤器在数据库中查找提交的名字,变量known被写入用户会话中,因此重定向之后,可以把数据传给模板,用来显示自定义的欢迎消息,要想让程序正常运行,必须按照之前的方法,在Python shell中创建数据库表

下面是对应的模板新版本,使用known参数在欢迎消息中加入了第二行,从而对已知用户和新用户显示不同的内容

# index.html

{% extends 'base.html' %}
{% import "bootstrap/wtf.html" as wtf %}

{% block page_content %}
<div class="page-header">
   <h1>Hello, {% if name %} {{ name }}{% else %}Stringer{% endif %}!!</h1>
   {% if not known %}
   <p>Pleased to meet you!</p>
   {% else %}
   <p>Happy to see you again!</p>
   {% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}

集成Python shell

每次启动shell会话都要导入库实例和模型,这很繁琐,我们可以做些配置,让Flask-Script的shell命令自动导入特定的对象

若想把对象添加到导入列表中,我们要为shell命令注册一个make_context回调函数,如下:

# 为shell命令添加一个上下文
from flask_script import Shell
def make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role)
manager.add_command("shell", Shell(make_context=make_shell_context))

make_shell_context()函数注册了程序、数据库实例以及模型,所以这些对象能直接导入shell

>>> app
<Flask 'hello'>
>>> db
<SQLAlchemy engine='sqlite:///D:\\flasky\\env\\data.sqlite'>
>>> User
<class '__main__.User'>
>>>

使用Flask-Migrate实现数据库迁移

在开发程序的过程中,会发现有时需要修改数据库模型,而且修改之后还需要更新数据库

仅当数据库表不存在时,Flask-SQLAlchemy才会根据模型进行创建,因此更新表的唯一方式就是先删除旧表,不过这么做会丢失数据库中的所有数据

更好的方法是使用数据库迁移框架, 源码版本控制工具可以跟踪源码文件的变化,类似的,数据库迁移框架能跟踪数据库模式的变化,然后增量式的把变化应用到数据库中

SQLAlchemy的主力开发人员编写了一个迁移框架Alembic,除了直接使用Alembic之外,Flask程序还可以使用Flask-Migrate拓展,这个拓展对Alembic做了轻量级包装,并集成到Flask-Script中

创建迁移仓库

首先,我们要在虚拟环境中安装Flask-Migrate

(venv) % pip install flask-Migrate

然后配置

from flask_migrate import Migrate, MigrateCommand

# ...
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
# ...

为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可附加到Flask-Script的manager对象上,在这个例子中,MigrateCommand类使用db命令附加

在维护数据库迁移之前,要使用init子命令创建迁移仓库

(env) PS D:\flasky\env> python hello.py db init
Creating directory D:\flasky\env\migrations ... done
Creating directory D:\flasky\env\migrations\versions ... done
Generating D:\flasky\env\migrations\alembic.ini ... done
Generating D:\flasky\env\migrations\env.py ... done
Generating D:\flasky\env\migrations\env.pyc ... done
Generating D:\flasky\env\migrations\README ... done
Generating D:\flasky\env\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'D:\\flasky\\env\\migrations\\alembic.ini' before proceeding.

这个命令会创建migrations文件夹,所有迁移脚本都存放在其中

数据库迁移仓库中的文件要和程序的其他文件一起纳入版本控制

创建迁移脚本

在Alembic中,数据库迁移用迁移脚本表示,脚本中有两个函数,分别是upgrade()downgrade()upgrade()函数把迁移中的改动应用到数据库中,downgrade()函数则将改动删除,Alembic具有增加和删除改动的能力,因此数据库可重设到修改历史的任意一点

我们可以使用revision命令手动创建Alembic迁移,也可使用migrate命令自动创建,手动创建的迁移只是个骨架,upgrade()downgrade()都是空的,开发者要使用Alembic提供的Operations对象指令实现具体操作,自动创建的迁移会根据模型定义和数据库当前状态之间的差异生成upgrade()downgrade()函数的内容

自动创建的迁移不一定总是正确的,有可能会漏掉一些细节,自动生成迁移脚本后一定要进行检查

migrate子命令用来自动创建迁移脚本

(env) PS D:\flasky\env> python hello.py db migrate -m 'initial migration'

更新数据库

检查并修正好迁移脚本后,我们可以使用db upgrade命令把迁移应用到数据库中

(env) PS D:\flasky\env> python hello.py db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.

对第一个迁移来说,其作用和调用db.create_all()方法一样,但在后续的迁移中,upgrade命令能把改动应用到数据库中,且不影响其中保存的数据

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值