《Flask 入门教程》第 5 章:数据库

大部分程序都须要保存数据,因此不可避免要使用数据库。用来操做数据库的数据库管理系统(DBMS)有不少选择,对于不一样类型的程序,不一样的使用场景,都会有不一样的选择。在这个教程中,咱们选择了属于关系型数据库管理系统(RDBMS)的 SQLite,它基于文件,不须要单独启动数据库服务器,适合在开发时使用,或是在数据库操做简单、访问量低的程序中使用。html

使用 SQLAlchemy 操做数据库

为了简化数据库操做,咱们将使用 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复制代码

设置数据库 URI

为了设置 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()复制代码

注意这会一并删除全部数据,若是你想在不破坏数据库内的数据的前提下变动表的结构,须要使用数据库迁移工具,好比集成了 AlembicFlask-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() 返回查询的第一条记录,若是未找到,则返回None
  • get(id) 传入主键值做为参数,返回指定主键值的记录,若是未找到,则返回None
  • count() 返回查询结果的数量
  • 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

进阶提示

  • 在生产环境,你能够更换更合适的 DBMS,由于 SQLAlchemy 支持多种 SQL 数据库引擎,一般只须要改动很是少的代码。
  • 咱们的程序只有一个用户,因此没有将 User 表和 Movie 表创建关联。访问 Flask-SQLAlchemy 文档的”声明模型“章节能够看到相关内容
  • 《Flask Web 开发实战》第 5 章详细介绍了 SQLAlchemy 和 Flask-Migrate 的使用,第 8 章和第 9 章引入了更复杂的模型关系和查询方法。
  • 阅读 SQLAlchemy 官方文档和教程详细了解它的用法。注意咱们在这里使用 Flask-SQLAlchemy 来集成它,因此用法和单独使用 SQLAlchemy 有一些不一样。做为参考,你能够同时阅读 Flask-SQLAlchemy 官方文档
相关文章
相关标签/搜索