大部分程序都须要保存数据,因此不可避免要使用数据库。用来操做数据库的数据库管理系统(DBMS)有不少选择,对于不一样类型的程序,不一样的使用场景,都会有不一样的选择。在这个教程中,咱们选择了属于关系型数据库管理系统(RDBMS)的 SQLite,它基于文件,不须要单独启动数据库服务器,适合在开发时使用,或是在数据库操做简单、访问量低的程序中使用。html
为了简化数据库操做,咱们将使用 SQLAlchemy——一个 Python 数据库工具(ORM,即对象关系映射)。借助 SQLAlchemy,你能够经过定义 Python 类来表示数据库里的一张表(类属性表示表中的字段 / 列),经过对这个类进行各类操做来代替写 SQL 语句。这个类咱们称之为模型类,类中的属性咱们将称之为字段。python
Flask 有大量的第三方扩展,这些扩展能够简化和第三方库的集成工做。咱们下面将使用一个叫作 Flask-SQLAlchemy 的官方扩展来集成 SQLAlchemy。git
首先使用 Pipenv 安装它:github
$ pipenv install flask-sqlalchemy复制代码
大部分扩展都须要执行一个“初始化”操做。你须要导入扩展类,实例化并传入 Flask 程序实例:sql
from flask_sqlalchemy import SQLAlchemy # 导入扩展类
app = Flask(__name__)
db = SQLAlchemy(app) # 初始化扩展,传入程序实例 app复制代码
为了设置 Flask、扩展或是咱们程序自己的一些行为,咱们须要设置和定义一些配置变量。Flask 提供了一个统一的接口来写入和获取这些配置变量:Flask.config
字典。配置变量的名称必须使用大写,写入配置的语句通常会放到扩展类实例化语句以前。shell
下面写入了一个 SQLALCHEMY_DATABASE_URI
变量来告诉 SQLAlchemy 数据库链接地址:数据库
import os
# ...
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////' + os.path.join(app.root_path, 'data.db')复制代码
注意 这个配置变量的最后一个单词是 URI,而不是 URL。flask
对于这个变量值,不一样的 DBMS 有不一样的格式,对于 SQLite 来讲,这个值的格式以下:服务器
sqlite:////数据库文件的绝对地址复制代码
数据库文件通常放到项目根目录便可,app.root_path
返回程序实例所在模块的路径(目前来讲,即项目根目录),咱们使用它来构建文件路径。数据库文件的名称和后缀你能够自由定义,通常会使用 .db、.sqlite 和 .sqlite3 做为后缀。session
另外,若是你使用 Windows 系统,上面的 URI 前缀部分须要写入三个斜线(即 sqlite:///
)。在本书的示例程序代码里,作了一些兼容性处理,另外还新设置了一个配置变量,实际的代码以下:
import os
import sys
from flask import Flask
WIN = sys.platform.startswith('win')
if WIN: # 若是是 Windows 系统,使用三个斜线
prefix = 'sqlite:///'
else: # 不然使用四个斜线
prefix = 'sqlite:////'
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(app.root_path, 'data.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 关闭对模型修改的监控复制代码
若是你固定在某一个操做系统上进行开发,部署时也使用相同的操做系统,那么能够不用这么作,直接根据你的须要写出前缀便可。
提示 你能够访问 Flask 文档的配置页面查看 Flask 内置的配置变量;一样的,在 Flask-SQLAlchemy 文档的配置页面能够看到 Flask-SQLAlchemy 提供的配置变量。
在 Watchlist 程序里,目前咱们有两类数据要保存:用户信息和电影条目信息。下面分别建立了两个模型类来表示这两张表:
app.py:建立数据库模型
class User(db.Model): # 表名将会是 user(自动生成,小写处理)
id = db.Column(db.Integer, primary_key=True) # 主键
name = db.Column(db.String(20)) # 名字
class Movie(db.Model): # 表名将会是 movie
id = db.Column(db.Integer, primary_key=True) # 主键
title = db.Column(db.String(60)) # 电影标题
year = db.Column(db.String(4)) # 电影年份复制代码
模型类的编写有一些限制:
db.Model
。db.Column
,传入的参数为字段的类型,下面的表格列出了经常使用的字段类。db.Column()
中添加额外的选项(参数)能够对字段进行设置。好比,primary_key
设置当前字段是否为主键。除此以外,经常使用的选项还有 nullable
(布尔值,是否容许为空值)、index
(布尔值,是否设置索引)、unique
(布尔值,是否容许重复值)、default
(设置默认值)等。经常使用的字段类型以下表所示:
db.Integer
整型db.String (size)
字符串,size 为最大长度,好比db.String(20)
db.Text
长文本db.DateTime
时间日期,Pythondatetime
对象db.Float
浮点数db.Boolean
布尔值模型类建立后,还不能对数据库进行操做,由于咱们尚未建立表和数据库文件。下面在 Python Shell 中建立了它们:
$ flask shell
>>> from app import db
>>> db.create_all()复制代码
打开文件管理器,你会发现项目根目录下出现了新建立的数据库文件 data.db。这个文件不须要提交到 Git 仓库,咱们在 .gitignore 文件最后添加一行新规则:
*.db复制代码
若是你改动了模型类,想从新生成表模式,那么须要先使用 db.drop_all()
删除表,而后从新建立:
>>> db.drop_all()
>>> db.create_all()复制代码
注意这会一并删除全部数据,若是你想在不破坏数据库内的数据的前提下变动表的结构,须要使用数据库迁移工具,好比集成了 Alembic 的 Flask-Migrate 扩展。
提示 上面打开 Python Shell 使用的是 flask shell
命令,而不是 python
。使用这个命令启动的 Python Shell 激活了“程序上下文”,它包含一些特殊变量,这对于某些操做是必须的(好比上面的 db.create_all()
调用)。请记住,后续的 Python Shell 都会使用这个命令打开。
和 flask shell
相似,咱们能够编写一个自定义命令来自动执行建立数据库表操做:
import click
@app.cli.command() # 注册为命令
@click.option('--drop', is_flag=True, help='Create after drop.') # 设置选项
def initdb(drop):
"""Initialize the database."""
if drop: # 判断是否输入了选项
db.drop_all()
db.create_all()
click.echo('Initialized database.') # 输出提示信息复制代码
默认状况下,函数名称就是命令的名字,如今执行 flask initdb
命令就能够建立数据库表:
$ flask initdb复制代码
使用 --drop
选项能够删除表后从新建立:
$ flask initdb --drop复制代码
在前面打开的 Python Shell 里,咱们来测试一下常见的数据库操做。你能够跟着示例代码来操做,也能够自由练习。
下面的操做演示了如何向数据库中添加记录:
>>> from app import User, Movie # 导入模型类
>>> user = User(name='Grey Li') # 建立一个 User 记录
>>> m1 = Movie(title='Leon', year='1994') # 建立一个 Movie 记录
>>> m2 = Movie(title='Mahjong', year='1996') # 再建立一个 Movie 记录
>>> db.session.add(user) # 把新建立的记录添加到数据库会话
>>> db.session.add(m1)
>>> db.session.add(m2)
>>> db.session.commit() # 提交数据库会话,只须要在最后调用一次便可复制代码
提示 在实例化模型类的时候,咱们并无传入 id
字段(主键),由于 SQLAlchemy 会自动处理这个字段。
最后一行 db.session.commit()
很重要,只有调用了这一行才会真正把记录提交进数据库,前面的 db.session.add()
调用是将改动添加进数据库会话(一个临时区域)中。
经过对模型类的 query
属性调用可选的过滤方法和查询方法,咱们就能够获取到对应的单个或多个记录(记录以模型类实例的形式表示)。查询语句的格式以下:
<模型类>.query.<过滤方法(可选)>.<查询方法>复制代码
下面是一些经常使用的过滤方法:
filter()
使用指定的规则过滤记录,返回新产生的查询对象filter_by()
使用指定规则过滤记录(以关键字表达式的形式),返回新产生的查询对象order_by()
根据指定条件对记录进行排序,返回新产生的查询对象group_by()
根据指定条件对记录进行分组,返回新产生的查询对象下面是一些经常使用的查询方法:
all()
返回包含全部查询记录的列表first()
返回查询的第一条记录,若是未找到,则返回Noneget(id)
传入主键值做为参数,返回指定主键值的记录,若是未找到,则返回Nonecount()
返回查询结果的数量first_or_404()
返回查询的第一条记录,若是未找到,则返回404错误响应get_or_404(id)
传入主键值做为参数,返回指定主键值的记录,若是未找到,则返回404错误响应paginate()
返回一个Pagination对象,能够对记录进行分页处理下面的操做演示了如何从数据库中读取记录,并进行简单的查询:
>>> from app import Movie # 导入模型类
>>> movie = Movie.query.first() # 获取 Movie 模型的第一个记录(返回模型类实例)
>>> movie.title # 对返回的模型类实例调用属性便可获取记录的各字段数据
'Leon'
>>> movie.year
'1994'
>>> Movie.query.all() # 获取 Movie 模型的全部记录,返回包含多个模型类实例的列表
[<Movie 1>, <Movie 2>]
>>> Movie.query.count() # 获取 Movie 模型全部记录的数量
2
>>> Movie.query.get(1) # 获取主键值为 1 的记录
<Movie 1>
>>> Movie.query.filter_by(title='Mahjong').first() # 获取 title 字段值为 Mahjong 的记录
<Movie 2>
>>> Movie.query.filter(Movie.title=='Mahjong').first() # 等同于上面的查询,但使用不一样的过滤方法
<Movie 2>复制代码
提示 咱们在说 Movie 模型的时候,实际指的是数据库中的 movie 表。表的实际名称是模型类的小写形式(自动生成),若是你想本身指定表名,能够定义 __tablename__
属性。
对于最基础的 filter()
过滤方法,SQLAlchemy 支持丰富的查询操做符,具体能够访问文档相关页面查看。除此以外,还有更多的查询方法、过滤方法和数据库函数可使用,具体能够访问文档的 Query API 部分查看。
下面的操做更新了 Movie
模型中主键为 2
的记录:
>>> movie = Movie.query.get(2)
>>> movie.title = 'WALL-E' # 直接对实例属性赋予新的值便可
>>> movie.year = '2008'
>>> db.session.commit() # 注意仍然须要调用这一行来提交改动复制代码
下面的操做删除了 Movie
模型中主键为 1
的记录:
>>> movie = Movie.query.get(1)
>>> db.session.delete(movie) # 使用 db.session.delete() 方法删除记录,传入模型实例
>>> db.session.commit() # 提交改动复制代码
通过上面的一番练习,咱们能够在 Watchlist 里进行实际的数据库操做了。
由于设置了数据库,负责显示主页的 index
能够从数据库里读取真实的数据:
@app.route('/')
def index():
user = User.query.first() # 读取用户记录
movies = Movie.query.all() # 读取全部电影记录
return render_template('index.html', user=user, movies=movies)复制代码
在 index
视图中,原来传入模板的 name
变量被 user
实例取代,模板 index.html 中的两处 name
变量也要相应的更新为 user.name
属性:
{{ user.name }}'s Watchlist复制代码
由于有了数据库,咱们能够编写一个命令函数把虚拟数据添加到数据库里。下面是用来生成虚拟数据的命令函数:
import click
@app.cli.command()
def forge():
"""Generate fake data."""
db.create_all()
# 全局的两个变量移动到这个函数内
name = 'Grey Li'
movies = [
{'title': 'My Neighbor Totoro', 'year': '1988'},
{'title': 'Dead Poets Society', 'year': '1989'},
{'title': 'A Perfect World', 'year': '1993'},
{'title': 'Leon', 'year': '1994'},
{'title': 'Mahjong', 'year': '1996'},
{'title': 'Swallowtail Butterfly', 'year': '1996'},
{'title': 'King of Comedy', 'year': '1999'},
{'title': 'Devils on the Doorstep', 'year': '1999'},
{'title': 'WALL-E', 'year': '2008'},
{'title': 'The Pork of Music', 'year': '2012'},
]
user = User(name=name)
db.session.add(user)
for m in movies:
movie = Movie(title=m['title'], year=m['year'])
db.session.add(movie)
db.session.commit()
click.echo('Done.')复制代码
如今执行 flask forge
命令就会把全部虚拟数据添加到数据库里:
$ flask forge复制代码
本章咱们学习了使用 SQLAlchemy 操做数据库,后面你会慢慢熟悉相关的操做。结束前,让咱们提交代码:
$ git add .
$ git commit -m "Add database support with Flask-SQLAlchemy"
$ git push复制代码
提示 你能够在 GitHub 上查看本书示例程序的对应 commit:7f167d2。