既然是应用程序,那么数据库就是必不可少的一部分。数据库按照一定规则保存程序数据,程序再发起查询取回所需的数据。Web 程序最常用基于关系模型的数据库,这种数据库也称为 SQL 数据库,因为它们使用结构化查询语言。不过最近几年文档数据库和键值对数据库成了流行的替代选择,这两种数据库合称 NoSQL数据库,比如 redis 等等。
Flask 中的数据库框架
每一种语言,都有对应的比较完善的数据库框架,这些框架可以帮助我们更加方便的进行数据库操作,从而屏蔽掉相关的具体 SQL 语句,也可以防止 SQL 注入等安全隐患。Python 当然不例外,可以通过 ORM 来把底层 SQL 转换成 Python 对象,这样一来,我们甚至不需要了解 SQL,只通过 Python 代码就可以完成数据库操作。
而在 Flask 当中,就有这么一个插件,可以非常方便的操作数据库:Flask-SQLAlchemy
Flask-SQLAlchemy
Flask-SQLAlchemy 是一个 Flask 扩展,简化了在 Flask 程序中使用 SQLAlchemy 的操作。
SQLAlchemy 是一个很强大的关系型数据库框架,支持多种数据库后台。SQLAlchemy 提
供了高层 ORM,也提供了使用数据库原生 SQL 的低层功能。和其他大多数扩展一样,Flask-SQLAlchemy 也使用 pip 安装:
pip install flask-sqlalchemy
在 Flask-SQLAlchemy 中,数据库使用 URL 指定。最流行的数据库引擎采用的数据库 URL
格式如下所示
数据库引擎 | 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 时,每次请求结束后都会自动提交数据库中的变动。
下面就是配置数据库的代码
代码语言:javascript复制from flask_sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
db 对象是 SQLAlchemy 类的实例,表示程序使用的数据库,同时还获得了 Flask-SQLAlchemy 提供的所有功能。
定义模型
模型这个术语表示程序使用的持久化实体。在 ORM 中,模型一般是一个 Python 类,类中的属性对应数据库表中的列。
Flask-SQLAlchemy 创建的数据库实例为模型提供了一个基类以及一系列辅助类和辅助函数,可用于定义模型的结构。
下面我们定义一个 User 模型
代码语言:javascript复制class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
在这里,类变量 __tablename__
用于定义表名,表中列的属性由 db.Column 来定义
下面是一些常用的列类型
下面是一些常用的列选项
表关系
在我们当前的数据模型下,角色与用户是一对多的关系,一个角色可以属于多个用户,而一个用户只可以是一个角色。下面我们修改下上面定义的两个模型
代码语言:javascript复制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')
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'))
db.relationship() 中的 backref 参数向 User 模型中添加一个 role 属性,从而定义反向关 系。这一属性可替代 role_id 访问 Role 模型,此时获取的是模型对象,而不是外键的值。
添加到 User 模型中的 role_id 列被定义为外键,就是这个外键建立起了关系。传递 db.ForeignKey() 的参数 'roles.id' 表明,这列的值是 roles 表中行的 id 值。
数据库操作
下面我们看下如何进行数据库的相关操作,我们在 Python shell 中实际操作下
创建数据库
要注意,我们这里是使用的是最新的 flask 版本(1.1.2),所以是自带了 shell 命令的,直接执行 flask shell 命令
代码语言:javascript复制Zhouluobo:HelloFlask edisonvera$ flask shell
/usr/local/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:834: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning.
'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
Python 3.6.4 (default, Jan 6 2018, 11:49:38)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
App: app [production]
Instance: /Users/edisonvera/Develop_zw/FlaskCode/FlaskCode/HelloFlask/instance
>>>
可以看到我们已经进入了一个 shell 的操作界面,在该界面,我们执行数据库创建命令 db.create_all()
代码语言:javascript复制>>> from app import db
>>> db.create_all()
>>>
这样,我们在当前的目录下就生成了一个 data.sqlite 的数据库文件,并且有两个数据表 如果我们要删除当前的数据库,可以使用 db.drop_all()
在视图函数中操作数据库
下面我们就开始在视图函数中进行数据库的操作,这才是最为重要的。
我们先把在登录页面的用户保存到数据库表当中
代码语言:javascript复制@app.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
username = form.username.data
session['username'] = username
user = User(username=username)
db.session.add(user)
db.session.commit()
flash("Login Successful!")
return redirect(url_for('index'))
return render_template('login.html', form=form)
这样,我们在登录页面输入用户名和密码之后,我们的用户就会被保存到数据库当中了。
下面我们就可以修改 index 函数,查看 session 中的用户名,如果不存在则打印当前为陌生人
代码语言:javascript复制@app.route('/')
def index():
user = session.get('username')
isUser = User.query.filter_by(username=user).first()
if isUser is None:
session['known'] = False
else:
session['known'] = True
return render_template('index.html', user=user, known=session.get('known', False))
当然还需要修改模板
代码语言:javascript复制{% extends "base.html" %}
{% block title %}My Web - Index{% endblock %}
{% block page_content %}
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
<div class="page-header">
<h1>Hello, {% if user %}{{ user }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>你是个陌生人哦!</p>
{% else %}
<p>很高兴再次遇见你!</p>
{% endif %}
</div>
{% endblock %}
可以看到如下效果
常用过滤器与执行函数
从上面的视图函数中我们看到,使用了 filter_by,那么我们再看下其他的过滤器
filter_by() 等过滤器在 query 对象上调用,返回一个更精确的 query 对象。多个过滤器可以一起调用,直到获得所需结果。
下面我们再来看下执行函数
在查询上应用指定的过滤器后,通过调用 all() 执行查询,以列表的形式返回结果。除了all() 之外,还有其他方法能触发查询执行。
数据库迁移
在开发程序的过程中,我们会发现有时需要修改数据库模型,而且修改之后还需要更新数据库。
仅当数据库表不存在时,Flask-SQLAlchemy 才会根据模型进行创建。因此,更新表的唯一方式就是先删除旧表,不过这样做会丢失数据库中的所有数据。
更新表的更好方法是使用数据库迁移框架,源码版本控制工具可以跟踪源码文件的变化,
类似地,数据库迁移框架能跟踪数据库模式的变化,然后增量式的把变化应用到数据库中。
SQLAlchemy 的主力开发人员编写了一个迁移框架,称为 Alembic(https://alembic.readthedocs.org/en/latest/index.html)。除了直接使用 Alembic 之外,Flask 程序还可使用 Flask-Migrate(http://flask-migrate.readthedocs.org/en/latest/)扩展。
首先我们先安装 Flask-Migrate
代码语言:javascript复制pip install flask-migrate
接下来配置 migrate
代码语言:javascript复制from flask_migrate import Migrate
migrate = Migrate(app, db)
使用 init 来创建迁移仓库
代码语言:javascript复制flask db init
Flask-Migrate 提供了一个命令集,使用 db 作为命名集名称,它提供的命令都以 flask db 开头。我们可以在命令行中输入 flask--help 查看所有可 用的命令和说明。
迁移环境只需要创建一次,这会在我们的项目根目录下创建一个 migrations 文件夹,其中包含了自动生成的配置文件和迁移版本文件夹。
如下图
下面我们就可以生成迁移脚本和更新数据库了
生成迁移脚本
代码语言:javascript复制flask db migrate -m "need update"
更新数据库
代码语言:javascript复制flask db upgrade
当然了,还有更多的数据库高级进阶操作,我们就留到后面的内容中慢慢学习吧 这部分完整代码,可以检出5a
总结
本节我们学习了数据库相关的内容,从 SQLAlchemy 到 flask_SQLAlchemy,以及如何在视图函数中使用,还有更加方便的迁移数据库等等知识。
最后的最后,如果觉得文章给了你一些启发或者帮助,还请帮忙点个赞,给辛苦码字的我一点小小鼓励,谢谢!!