<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Flask-SQLAlchemy &#8211; 李辉 / Grey Li</title>
	<atom:link href="https://greyli.com/tag/flask-sqlalchemy/feed/" rel="self" type="application/rss+xml" />
	<link>https://greyli.com</link>
	<description>一个编程和写作爱好者的在线记事本</description>
	<lastBuildDate>Thu, 06 Nov 2025 11:36:11 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.9.26</generator>

<image>
	<url>https://greyli.com/wp-content/uploads/2025/03/avatar-500-compressed-144x144.jpg</url>
	<title>Flask-SQLAlchemy &#8211; 李辉 / Grey Li</title>
	<link>https://greyli.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>为已存在的数据库生成 SQLAlchemy / Flask-SQLAlchemy 模型类</title>
		<link>https://greyli.com/generate-flask-sqlalchemy-model-class-for-exist-database/</link>
		<comments>https://greyli.com/generate-flask-sqlalchemy-model-class-for-exist-database/#respond</comments>
		<pubDate>Wed, 03 Apr 2019 03:46:15 +0000</pubDate>
		<dc:creator><![CDATA[李辉]]></dc:creator>
				<category><![CDATA[计算机与编程]]></category>
		<category><![CDATA[Flask]]></category>
		<category><![CDATA[Flask-SQLAlchemy]]></category>
		<category><![CDATA[SQLAcodegen]]></category>
		<category><![CDATA[SQLAlchemy]]></category>

		<guid isPermaLink="false">http://greyli.com/?p=2425</guid>
		<description><![CDATA[SQLAlchemy 基于模型类对数据库表进行操作，所以，如果你想对已存在的数据库表进行操作，就要先为它编写对 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>
	SQLAlchemy 基于模型类对数据库表进行操作，所以，如果你想对已存在的数据库表进行操作，就要先为它编写对应的模型类。
</p>
<p>
	对于简单的数据库，比如只有几张表，没有复杂的关系，表字段也很少，你可以直接对照表模式手写模型类。
</p>
<p>
	其他情况下，使用自动化工具 <a data-editable="true" data-offset-key="b32v9-3-0" href="https://pypi.python.org/pypi/sqlacodegen" target="_blank">SQLAcodegen</a>&nbsp;/&nbsp;<a data-editable="true" data-offset-key="b32v9-1-0" href="https://github.com/ksindi/flask-sqlacodegen" target="_blank">Flask-SQLAcodegen</a>&nbsp;自动生成模型类定义会更加方便，根据单独使用 SQALchemy 还是使用扩展 Flask-SQLAlchemy，你可以选择阅读对应的章节。
</p>
<h2>
	单独使用 SQLAlchemy<br />
</h2>
<p>
	首先使用 pip 安装：
</p>
<pre>
$ pip install sqlacodegen</pre>
<p>
	执行下面的命令将模型类输出到 models.py 文件里（将覆盖目标文件原内容）：
</p>
<pre>
$ sqlacodegen --outfile models.py sqlite:///database.db</pre>
<p>
	这个命令的格式如下：
</p>
<pre>
$ sqlacodegen --outfile &lt;输出的文件名&gt; &lt;数据库连接 URI&gt;</pre>
<p>
	&#8211;outfile / -o 选项设置输出的目标文件，不给出这个选项将直接在命令行输出生成的模型类定义，比如：
</p>
<pre>
$ sqlacodegen sqlite:///data.db
# coding: utf-8
from sqlalchemy import Column, DateTime, Integer, String
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()
metadata = Base.metadata


class Message(Base):
&nbsp; &nbsp; __tablename__ = &#39;message&#39;

&nbsp; &nbsp; id = Column(Integer, primary_key=True)
&nbsp; &nbsp; name = Column(String(20))
&nbsp; &nbsp; body = Column(String(200))
&nbsp; &nbsp; timestamp = Column(DateTime, index=True)</pre>
<p>
	<em>提示 如上所示，生成的模型类定义会包含 Base 和 metadata 对象定义和相关导入语句，你或许需要进行细微的调整。</em>
</p>
<p>
	你可以使用下面的命令查看更多可用的设置选项：
</p>
<pre>
$ sqlacodegen --help</pre>
<p>
	<em>提示 除了使用 SQLAcodegen，你也可以直接使用内置的 <a data-editable="true" data-offset-key="eprpr-1-0" href="https://docs.sqlalchemy.org/en/latest/orm/extensions/automap.html" target="_blank">Automap</a> 扩展生成模型类。</em>
</p>
<h2>
	使用 Flask-SQLAlchemy<br />
</h2>
<p>
	首先使用 pip 安装 Flask-SQLAcodegen：
</p>
<pre>
$ pip install flask-sqlacodegen</pre>
<p>
	执行下面的命令将模型类输出到 models.py 文件里（将覆盖目标文件原内容）：
</p>
<pre>
$ flask-sqlacodegen --flask --outfile models.py sqlite:///database.db</pre>
<p>
	这个命令的格式如下：
</p>
<pre>
$ flask-sqlacodegen --flask --outfile &lt;输出的文件名&gt; &lt;数据库连接 URI&gt;</pre>
<p>
	&#8211;flask 选项设置输出 Flask-SQLAlchemy 模型类，不给出这个选项将直接输出 SQLAlchemy 原生模型类定义；&#8211;outfile&nbsp;选项设置输出的目标文件，不给出这个选项将直接在命令行输出生成的模型类定义，比如：
</p>
<pre>
$ flask-sqlacodegen --flask sqlite:///data.db
# coding: utf-8
from sqlalchemy import Column, DateTime, Integer, String
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Message(db.Model):
    __tablename__ = &#39;message&#39;

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20))
    body = db.Column(db.String(200))
    timestamp = db.Column(db.DateTime, index=True)</pre>
<p>
	<em>提示 如上所示，生成的模型类定义会包含一个 db 对象定义和相关导入语句，你或许需要进行细微的调整。</em>
</p>
<p>
	你可以使用下面的命令查看更多可用的设置选项：
</p>
<pre>
$ flask-sqlacodegen --help
</pre>
]]></content:encoded>
			<wfw:commentRss>https://greyli.com/generate-flask-sqlalchemy-model-class-for-exist-database/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《Flask 入门教程》第 5 章：数据库</title>
		<link>https://greyli.com/flask-tutorial-chapter-5-database/</link>
		<comments>https://greyli.com/flask-tutorial-chapter-5-database/#comments</comments>
		<pubDate>Mon, 24 Dec 2018 03:10:03 +0000</pubDate>
		<dc:creator><![CDATA[李辉]]></dc:creator>
				<category><![CDATA[计算机与编程]]></category>
		<category><![CDATA[Flask]]></category>
		<category><![CDATA[Flask 入门教程]]></category>
		<category><![CDATA[Flask-SQLAlchemy]]></category>
		<category><![CDATA[SQLAlchemy]]></category>
		<category><![CDATA[数据库]]></category>

		<guid isPermaLink="false">http://greyli.com/?p=2073</guid>
		<description><![CDATA[大部分程序都需要保存数据，所以不可避免要使用数据库。用来操作数据库的数据库管理系统（DBMS）有很多选择，对于 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>
	大部分程序都需要保存数据，所以不可避免要使用数据库。用来操作数据库的数据库管理系统（DBMS）有很多选择，对于不同类型的程序，不同的使用场景，都会有不同的选择。在这个教程中，我们选择了属于关系型数据库管理系统（RDBMS）的&nbsp;<a href="https://www.sqlite.org/" rel="nofollow">SQLite</a>，它基于文件，不需要单独启动数据库服务器，适合在开发时使用，或是在数据库操作简单、访问量低的程序中使用。
</p>
<h2>
	使用 SQLAlchemy 操作数据库<br />
</h2>
<p>
	为了简化数据库操作，我们将使用&nbsp;<a href="https://www.sqlalchemy.org/" rel="nofollow">SQLAlchemy</a>&mdash;&mdash;一个 Python 数据库工具（ORM，即对象关系映射）。借助 SQLAlchemy，你可以通过定义 Python 类来表示数据库里的一张表（类属性表示表中的字段 / 列），通过对这个类进行各种操作来代替写 SQL 语句。这个类我们称之为<strong>模型类</strong>，类中的属性我们将称之为<strong>字段</strong>。
</p>
<p>
	Flask 有大量的第三方扩展，这些扩展可以简化和第三方库的集成工作。我们下面将使用一个叫做&nbsp;<a href="http://flask-sqlalchemy.pocoo.org/2.3/" rel="nofollow">Flask-SQLAlchemy</a>&nbsp;的官方扩展来集成 SQLAlchemy。
</p>
<p>
	首先使用 Pipenv 安装它：
</p>
<pre>
$ pipenv install flask-sqlalchemy</pre>
<p>
	大部分扩展都需要执行一个&ldquo;初始化&rdquo;操作。你需要导入扩展类，实例化并传入 Flask 程序实例：
</p>
<pre>
from flask_sqlalchemy import SQLAlchemy  # 导入扩展类

app = Flask(__name__)

db = SQLAlchemy(app)  # 初始化扩展，传入程序实例 app</pre>
<h2>
	设置数据库 URI<br />
</h2>
<p>
	为了设置 Flask、扩展或是我们程序本身的一些行为，我们需要设置和定义一些配置变量。Flask 提供了一个统一的接口来写入和获取这些配置变量：<code>Flask.config</code>&nbsp;字典。配置变量的名称必须使用大写，写入配置的语句一般会放到扩展类实例化语句之前。
</p>
<p>
	下面写入了一个&nbsp;<code>SQLALCHEMY_DATABASE_URI</code>&nbsp;变量来告诉 SQLAlchemy 数据库连接地址：
</p>
<pre>
import os

# ...

app.config['SQLALCHEMY_DATABASE_URI'] = &#39;sqlite:////&#39; + os.path.join(app.root_path, &#39;data.db&#39;)</pre>
<p>
	<strong>注意</strong>&nbsp;这个配置变量的最后一个单词是 URI，而不是 URL。
</p>
<p>
	对于这个变量值，不同的 DBMS 有不同的格式，对于 SQLite 来说，这个值的格式如下：
</p>
<pre>
sqlite:////数据库文件的绝对地址</pre>
<p>
	数据库文件一般放到项目根目录即可，<code>app.root_path</code>&nbsp;返回程序实例所在模块的路径（目前来说，即项目根目录），我们使用它来构建文件路径。数据库文件的名称和后缀你可以自由定义，一般会使用 .db、.sqlite 和 .sqlite3 作为后缀。
</p>
<p>
	另外，如果你使用 Windows 系统，上面的 URI 前缀部分需要写入三个斜线（即&nbsp;<code>sqlite:///</code>）。在本书的示例程序代码里，做了一些兼容性处理，另外还新设置了一个配置变量，实际的代码如下：
</p>
<pre>
import os
import sys

from flask import Flask

WIN = sys.platform.startswith(&#39;win&#39;)
if WIN:  # 如果是 Windows 系统，使用三个斜线
    prefix = &#39;sqlite:///&#39;
else:  # 否则使用四个斜线
    prefix = &#39;sqlite:////&#39;

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(app.root_path, &#39;data.db&#39;)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 关闭对模型修改的监控</pre>
<p>
	如果你固定在某一个操作系统上进行开发，部署时也使用相同的操作系统，那么可以不用这么做，直接根据你的需要写出前缀即可。
</p>
<p>
	<strong>提示</strong>&nbsp;你可以访问&nbsp;<a href="http://flask.pocoo.org/docs/1.0/config/" rel="nofollow">Flask 文档的配置页面</a>查看 Flask 内置的配置变量；同样的，在&nbsp;<a href="http://flask-sqlalchemy.pocoo.org/2.1/config/" rel="nofollow">Flask-SQLAlchemy 文档的配置页面</a>可以看到 Flask-SQLAlchemy 提供的配置变量。
</p>
<h2>
	创建数据库模型<br />
</h2>
<p>
	在 Watchlist 程序里，目前我们有两类数据要保存：用户信息和电影条目信息。下面分别创建了两个模型类来表示这两张表：
</p>
<p>
	<em>app.py：创建数据库模型</em>
</p>
<pre>
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))  # 电影年份</pre>
<p>
	模型类的编写有一些限制：
</p>
<ul>
<li>
		模型类要声明继承&nbsp;<code>db.Model</code>。
	</li>
<li>
		每一个类属性（字段）要实例化&nbsp;<code>db.Column</code>，传入的参数为字段的类型，下面的表格列出了常用的字段类。
	</li>
<li>
		在&nbsp;<code>db.Column()</code>&nbsp;中添加额外的选项（参数）可以对字段进行设置。比如，<code>primary_key</code>&nbsp;设置当前字段是否为主键。除此之外，常用的选项还有&nbsp;<code>nullable</code>（布尔值，是否允许为空值）、<code>index</code>（布尔值，是否设置索引）、<code>unique</code>（布尔值，是否允许重复值）、<code>default</code>（设置默认值）等。
	</li>
</ul>
<p>
	常用的字段类型如下表所示：
</p>
<table>
<thead>
<tr>
<th>
				字段类
			</th>
<th>
				说明
			</th>
</tr>
</thead>
<tbody>
<tr>
<td>
				db.Integer
			</td>
<td>
				整型
			</td>
</tr>
<tr>
<td>
				db.String (size)
			</td>
<td>
				字符串，size 为最大长度，比如&nbsp;<code>db.String(20)</code>
			</td>
</tr>
<tr>
<td>
				db.Text
			</td>
<td>
				长文本
			</td>
</tr>
<tr>
<td>
				db.DateTime
			</td>
<td>
				时间日期，Python&nbsp;<code>datetime</code>&nbsp;对象
			</td>
</tr>
<tr>
<td>
				db.Float
			</td>
<td>
				浮点数
			</td>
</tr>
<tr>
<td>
				db.Boolean
			</td>
<td>
				布尔值
			</td>
</tr>
</tbody>
</table>
<h2>
	创建数据库表<br />
</h2>
<p>
	模型类创建后，还不能对数据库进行操作，因为我们还没有创建表和数据库文件。下面在 Python Shell 中创建了它们：
</p>
<pre>
$ flask shell
&gt;&gt;&gt; from app import db
&gt;&gt;&gt; db.create_all()</pre>
<p>
	打开文件管理器，你会发现项目根目录下出现了新创建的数据库文件 data.db。这个文件不需要提交到 Git 仓库，我们在 .gitignore 文件最后添加一行新规则：
</p>
<pre>
<code>*.db
</code></pre>
<p>
	如果你改动了模型类，想重新生成表模式，那么需要先使用&nbsp;<code>db.drop_all()</code>&nbsp;删除表，然后重新创建：
</p>
<pre>
&gt;&gt;&gt; db.drop_all()
&gt;&gt;&gt; db.create_all()</pre>
<p>
	注意这会一并删除所有数据，如果你想在不破坏数据库内的数据的前提下变更表的结构，需要使用数据库迁移工具，比如集成了&nbsp;<a href="https://alembic.sqlalchemy.org/en/latest/" rel="nofollow">Alembic</a>&nbsp;的&nbsp;<a href="https://github.com/miguelgrinberg/Flask-Migrate">Flask-Migrate</a>&nbsp;扩展。
</p>
<p>
	<strong>提示</strong>&nbsp;上面打开 Python Shell 使用的是&nbsp;<code>flask shell</code>命令，而不是&nbsp;<code>python</code>。使用这个命令启动的 Python Shell 激活了&ldquo;程序上下文&rdquo;，它包含一些特殊变量，这对于某些操作是必须的（比如上面的&nbsp;<code>db.create_all()</code>调用）。请记住，后续的 Python Shell 都会使用这个命令打开。
</p>
<p>
	和&nbsp;<code>flask shell</code>类似，我们可以编写一个自定义命令来自动执行创建数据库表操作：
</p>
<pre>
import click

@app.cli.command()  # 注册为命令
@click.option(&#39;--drop&#39;, is_flag=True, help=&#39;Create after drop.&#39;)  # 设置选项
def initdb(drop):
    &quot;&quot;&quot;Initialize the database.&quot;&quot;&quot;
    if drop:  # 判断是否输入了选项
        db.drop_all()
    db.create_all()
    click.echo(&#39;Initialized database.&#39;)  # 输出提示信息</pre>
<p>
	默认情况下，函数名称就是命令的名字，现在执行&nbsp;<code>flask initdb</code>&nbsp;命令就可以创建数据库表：
</p>
<pre>
$ flask initdb</pre>
<p>
	使用&nbsp;<code>--drop</code>&nbsp;选项可以删除表后重新创建：
</p>
<pre>
$ flask initdb --drop</pre>
<h2>
	创建、读取、更新、删除<br />
</h2>
<p>
	在前面打开的 Python Shell 里，我们来测试一下常见的数据库操作。你可以跟着示例代码来操作，也可以自由练习。
</p>
<h3>
	创建<br />
</h3>
<p>
	下面的操作演示了如何向数据库中添加记录：
</p>
<pre>
&gt;&gt;&gt; from app import User, Movie  # 导入模型类
&gt;&gt;&gt; user = User(name=&#39;Grey Li&#39;)  # 创建一个 User 记录
&gt;&gt;&gt; m1 = Movie(title=&#39;Leon&#39;, year=&#39;1994&#39;)  # 创建一个 Movie 记录
&gt;&gt;&gt; m2 = Movie(title=&#39;Mahjong&#39;, year=&#39;1996&#39;)  # 再创建一个 Movie 记录
&gt;&gt;&gt; db.session.add(user)  # 把新创建的记录添加到数据库会话
&gt;&gt;&gt; db.session.add(m1)
&gt;&gt;&gt; db.session.add(m2)
&gt;&gt;&gt; db.session.commit()  # 提交数据库会话，只需要在最后调用一次即可</pre>
<p>
	<strong>提示</strong>&nbsp;在实例化模型类的时候，我们并没有传入&nbsp;<code>id</code>&nbsp;字段（主键），因为 SQLAlchemy 会自动处理这个字段。
</p>
<p>
	最后一行&nbsp;<code>db.session.commit()</code>&nbsp;很重要，只有调用了这一行才会真正把记录提交进数据库，前面的&nbsp;<code>db.session.add()</code>&nbsp;调用是将改动添加进数据库会话（一个临时区域）中。
</p>
<h3>
	读取<br />
</h3>
<p>
	通过对模型类的&nbsp;<code>query</code>&nbsp;属性调用可选的过滤方法和查询方法，我们就可以获取到对应的单个或多个记录（记录以模型类实例的形式表示）。查询语句的格式如下：
</p>
<pre>
&lt;模型类&gt;.query.&lt;过滤方法（可选）&gt;.&lt;查询方法&gt;</pre>
<p>
	下面是一些常用的过滤方法：
</p>
<table>
<thead>
<tr>
<th>
				过滤方法
			</th>
<th>
				说明
			</th>
</tr>
</thead>
<tbody>
<tr>
<td>
				filter()
			</td>
<td>
				使用指定的规则过滤记录，返回新产生的查询对象
			</td>
</tr>
<tr>
<td>
				filter_by()
			</td>
<td>
				使用指定规则过滤记录（以关键字表达式的形式），返回新产生的查询对象
			</td>
</tr>
<tr>
<td>
				order_by()
			</td>
<td>
				根据指定条件对记录进行排序，返回新产生的查询对象
			</td>
</tr>
<tr>
<td>
				group_by()
			</td>
<td>
				根据指定条件对记录进行分组，返回新产生的查询对象
			</td>
</tr>
</tbody>
</table>
<p>
	下面是一些常用的查询方法：
</p>
<table>
<thead>
<tr>
<th>
				查询方法
			</th>
<th>
				说明
			</th>
</tr>
</thead>
<tbody>
<tr>
<td>
				all()
			</td>
<td>
				返回包含所有查询记录的列表
			</td>
</tr>
<tr>
<td>
				first()
			</td>
<td>
				返回查询的第一条记录，如果未找到，则返回None
			</td>
</tr>
<tr>
<td>
				get(id)
			</td>
<td>
				传入主键值作为参数，返回指定主键值的记录，如果未找到，则返回None
			</td>
</tr>
<tr>
<td>
				count()
			</td>
<td>
				返回查询结果的数量
			</td>
</tr>
<tr>
<td>
				first_or_404()
			</td>
<td>
				返回查询的第一条记录，如果未找到，则返回404错误响应
			</td>
</tr>
<tr>
<td>
				get_or_404(id)
			</td>
<td>
				传入主键值作为参数，返回指定主键值的记录，如果未找到，则返回404错误响应
			</td>
</tr>
<tr>
<td>
				paginate()
			</td>
<td>
				返回一个Pagination对象，可以对记录进行分页处理
			</td>
</tr>
</tbody>
</table>
<p>
	下面的操作演示了如何从数据库中读取记录，并进行简单的查询：
</p>
<pre>
&gt;&gt;&gt; from app import Movie  # 导入模型类
&gt;&gt;&gt; movie = Movie.query.first()  # 获取 Movie 模型的第一个记录（返回模型类实例）
&gt;&gt;&gt; movie.title  # 对返回的模型类实例调用属性即可获取记录的各字段数据
&#39;Leon&#39;
&gt;&gt;&gt; movie.year
&#39;1994&#39;
&gt;&gt;&gt; Movie.query.all()  # 获取 Movie 模型的所有记录，返回包含多个模型类实例的列表
[<movie 1="">, <movie 2="">]
&gt;&gt;&gt; Movie.query.count()  # 获取 Movie 模型所有记录的数量
2
&gt;&gt;&gt; Movie.query.get(1)  # 获取主键值为 1 的记录
&lt;Movie 1&gt;
&gt;&gt;&gt; Movie.query.filter_by(title=&#39;Mahjong&#39;).first()  # 获取 title 字段值为 Mahjong 的记录
&lt;Movie 2&gt;
&gt;&gt;&gt; Movie.query.filter(Movie.title==&#39;Mahjong&#39;).first()  # 等同于上面的查询，但使用不同的过滤方法
&lt;Movie 2&gt;</movie></movie></pre>
<p>
	<strong>提示</strong>&nbsp;我们在说 Movie 模型的时候，实际指的是数据库中的 movie 表。表的实际名称是模型类的小写形式（自动生成），如果你想自己指定表名，可以定义&nbsp;<code>__tablename__</code>&nbsp;属性。
</p>
<p>
	对于最基础的&nbsp;<code>filter()</code>&nbsp;过滤方法，SQLAlchemy 支持丰富的查询操作符，具体可以访问<a href="http://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.operators.ColumnOperators" rel="nofollow">文档相关页面</a>查看。除此之外，还有更多的查询方法、过滤方法和数据库函数可以使用，具体可以访问文档的&nbsp;<a href="https://docs.sqlalchemy.org/en/latest/orm/query.html" rel="nofollow">Query API</a>&nbsp;部分查看。
</p>
<h3>
	更新<br />
</h3>
<p>
	下面的操作更新了&nbsp;<code>Movie</code>&nbsp;模型中主键为&nbsp;<code>2</code>&nbsp;的记录：
</p>
<pre>
&gt;&gt;&gt; movie = Movie.query.get(2)
&gt;&gt;&gt; movie.title = &#39;WALL-E&#39;  # 直接对实例属性赋予新的值即可
&gt;&gt;&gt; movie.year = &#39;2008&#39;
&gt;&gt;&gt; db.session.commit()  # 注意仍然需要调用这一行来提交改动</pre>
<h3>
	删除<br />
</h3>
<p>
	下面的操作删除了&nbsp;<code>Movie</code>&nbsp;模型中主键为&nbsp;<code>1</code>&nbsp;的记录：
</p>
<pre>
&gt;&gt;&gt; movie = Movie.query.get(1)
&gt;&gt;&gt; db.session.delete(movie)  # 使用 db.session.delete() 方法删除记录，传入模型实例
&gt;&gt;&gt; db.session.commit()  # 提交改动</pre>
<h2>
	在程序里操作数据库<br />
</h2>
<p>
	经过上面的一番练习，我们可以在 Watchlist 里进行实际的数据库操作了。
</p>
<h3>
	在主页视图读取数据库记录<br />
</h3>
<p>
	因为设置了数据库，负责显示主页的&nbsp;<code>index</code>&nbsp;可以从数据库里读取真实的数据：
</p>
<pre>
@app.route(&#39;/&#39;)
def index():
    user = User.query.first()  # 读取用户记录
    movies = Movie.query.all()  # 读取所有电影记录
    return render_template(&#39;index.html&#39;, user=user, movies=movies)</pre>
<p>
	在&nbsp;<code>index</code>&nbsp;视图中，原来传入模板的&nbsp;<code>name</code>&nbsp;变量被&nbsp;<code>user</code>&nbsp;实例取代，模板 index.html 中的两处&nbsp;<code>name</code>&nbsp;变量也要相应的更新为&nbsp;<code>user.name</code>&nbsp;属性：
</p>
<pre>
{{ user.name }}&#39;s Watchlist</pre>
<h3>
	生成虚拟数据<br />
</h3>
<p>
	因为有了数据库，我们可以编写一个命令函数把虚拟数据添加到数据库里。下面是用来生成虚拟数据的命令函数：
</p>
<pre>
import click

@app.cli.command()
def forge():
    &quot;&quot;&quot;Generate fake data.&quot;&quot;&quot;
    db.create_all()
    
    # 全局的两个变量移动到这个函数内
    name = &#39;Grey Li&#39;
    movies = [
        {&#39;title&#39;: &#39;My Neighbor Totoro&#39;, &#39;year&#39;: &#39;1988&#39;},
        {&#39;title&#39;: &#39;Dead Poets Society&#39;, &#39;year&#39;: &#39;1989&#39;},
        {&#39;title&#39;: &#39;A Perfect World&#39;, &#39;year&#39;: &#39;1993&#39;},
        {&#39;title&#39;: &#39;Leon&#39;, &#39;year&#39;: &#39;1994&#39;},
        {&#39;title&#39;: &#39;Mahjong&#39;, &#39;year&#39;: &#39;1996&#39;},
        {&#39;title&#39;: &#39;Swallowtail Butterfly&#39;, &#39;year&#39;: &#39;1996&#39;},
        {&#39;title&#39;: &#39;King of Comedy&#39;, &#39;year&#39;: &#39;1999&#39;},
        {&#39;title&#39;: &#39;Devils on the Doorstep&#39;, &#39;year&#39;: &#39;1999&#39;},
        {&#39;title&#39;: &#39;WALL-E&#39;, &#39;year&#39;: &#39;2008&#39;},
        {&#39;title&#39;: &#39;The Pork of Music&#39;, &#39;year&#39;: &#39;2012&#39;},
    ]
    
    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(&#39;Done.&#39;)</pre>
<p>
	现在执行&nbsp;<code>flask forge</code>&nbsp;命令就会把所有虚拟数据添加到数据库里：
</p>
<pre>
$ flask forge</pre>
<h2>
	本章小结<br />
</h2>
<p>
	本章我们学习了使用 SQLAlchemy 操作数据库，后面你会慢慢熟悉相关的操作。结束前，让我们提交代码：
</p>
<pre>
$ git add .
$ git commit -m &quot;Add database support with Flask-SQLAlchemy&quot;
$ git push</pre>
<p>
	<strong>提示</strong> 你可以在 GitHub 上查看本书示例程序的对应 commit：<a href="https://github.com/greyli/watchlist/commit/4d2442a41e55fb454e092864206af08e4e3eeddf" spellcheck="false">4d2442a</a>。
</p>
<h2>
	进阶提示<br />
</h2>
<ul>
<li>
		在生产环境，你可以更换更合适的 DBMS，因为 SQLAlchemy 支持多种 SQL 数据库引擎，通常只需要改动非常少的代码。
	</li>
<li>
		我们的程序只有一个用户，所以没有将 User 表和 Movie 表建立关联。访问 Flask-SQLAlchemy 文档的&rdquo;<a href="http://flask-sqlalchemy.pocoo.org/2.3/models/#one-to-many-relationships" rel="nofollow">声明模型</a>&ldquo;章节可以看到相关内容
	</li>
<li>
		<a href="http://helloflask.com/book/" rel="nofollow">《Flask Web 开发实战》</a>第 5 章详细介绍了 SQLAlchemy 和 Flask-Migrate 的使用，第 8 章和第 9 章引入了更复杂的模型关系和查询方法。
	</li>
<li>
		阅读&nbsp;<a href="https://docs.sqlalchemy.org/en/latest/" rel="nofollow">SQLAlchemy 官方文档和教程</a>详细了解它的用法。注意我们在这里使用 Flask-SQLAlchemy 来集成它，所以用法和单独使用 SQLAlchemy 有一些不同。作为参考，你可以同时阅读&nbsp;<a href="http://flask-sqlalchemy.pocoo.org/2.3/" rel="nofollow">Flask-SQLAlchemy 官方文档</a>&nbsp;。
	</li>
<li>
		本书主页 &amp; 相关资源索引：<a data-editable="true" data-offset-key="eh3t9-1-0" href="http://helloflask.com/tutorial" target="_blank">http://helloflask.com/tutorial</a>。
	</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://greyli.com/flask-tutorial-chapter-5-database/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>使用Flask-SQLAlchemy调用create_all()创建数据库表前是否需要导入模型类？</title>
		<link>https://greyli.com/import-models-before-call-create_all-in-sqlalchemy/</link>
		<comments>https://greyli.com/import-models-before-call-create_all-in-sqlalchemy/#respond</comments>
		<pubDate>Tue, 17 Jul 2018 11:12:52 +0000</pubDate>
		<dc:creator><![CDATA[李辉]]></dc:creator>
				<category><![CDATA[计算机与编程]]></category>
		<category><![CDATA[Flask]]></category>
		<category><![CDATA[Flask-SQLAlchemy]]></category>
		<category><![CDATA[SQLAlchemy]]></category>

		<guid isPermaLink="false">http://greyli.com/?p=1763</guid>
		<description><![CDATA[当继承db.Model基类的子类被声明创建时，根据db.Model基类继承的元类中设置的行为，类声明后会将表信 [&#8230;]]]></description>
				<content:encoded><![CDATA[<div class="RichContent-inner">
<p>当继承<code>db.Model</code>基类的子类被声明创建时，根据<code>db.Model</code>基类继承的元类中设置的行为，类声明后会将表信息注册到<code>db.Model.metadata.tables</code>属性中。</p>
<p><code>create_all()</code>方法被调用时正是通过这个属性来获取表信息。因此，当我们调用<code>create_all()</code>前，需要确保模型类被声明创建。<b>如果模型类存储在单独的模块中，不导入该模块就不会执行其中的代码，模型类便不会被创建，进而便无法注册表信息到</b><code>db.Model.metadata.tables</code><b>中，所以这时需要导入相应的模块。</b></p>
<p><span data-offset-key="638qe-0-0">因为我们的目的是让模型类被创建，所以不论是导入整个模块还是导入其中某个模型类都可以，并不需要导入全部模型类。</span></p>
<p><i>一般情况下，我们不用关心这个问题。在单脚本的Flask程序中自不必说，在使用包组织的Flask程序中，创建程序实例时必然需要导入视图函数所在的模块，或是蓝本所在的模块，而这些模块会导入模型类。</i></p>
<p>下面我们会通过解析源码简单了解模型类对应的表信息是如何注册到db.Model.metadata.tables中的。</p>
<p>不论是单独使用SQLAlchemy时创建的<code>Base</code>基类，还是使用Flask-SQLAlchemy时创建db对象后的<code>db.Model</code>基类，都是通过<code>declarative_base()</code>函数创建，具体源码如下：</p>
<div>
<pre class="">def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
                     name='Base', constructor=_declarative_constructor,
                     class_registry=None,
                     metaclass=DeclarativeMeta):

    lcl_metadata = metadata or MetaData()
    if bind:
        lcl_metadata.bind = bind

    if class_registry is None:
        class_registry = weakref.WeakValueDictionary()

    bases = not isinstance(cls, tuple) and (cls,) or cls
    class_dict = dict(_decl_class_registry=class_registry,
                      metadata=lcl_metadata)

    if isinstance(cls, type):
        class_dict['__doc__'] = cls.__doc__

    if constructor:
        class_dict['__init__'] = constructor
    if mapper:
        class_dict['__mapper_cls__'] = mapper

    return metaclass(name, bases, class_dict)
</pre>
</div>
<blockquote><p>源码位置：<a class=" external" href="https://link.zhihu.com/?target=https%3A//github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/ext/declarative/api.py%23L286" target="_blank" rel="nofollow noopener noreferrer" data-za-detail-view-id="1043"><span class="invisible">https://</span><span class="visible">github.com/zzzeek/sqlal</span><span class="invisible">chemy/blob/master/lib/sqlalchemy/ext/declarative/api.py#L286</span></a></p></blockquote>
<p>这个函数返回一个元类实例，对应的元类为<code>DeclarativeMeta</code>，这个元类定义了一些特殊行为：</p>
<div>
<pre class="">class DeclarativeMeta(type):
    def __init__(cls, classname, bases, dict_):
        if '_decl_class_registry' not in cls.__dict__:
            _as_declarative(cls, classname, cls.__dict__)
        type.__init__(cls, classname, bases, dict_)

    def __setattr__(cls, key, value):
        _add_attribute(cls, key, value)

    def __delattr__(cls, key):
        _del_attribute(cls, key)
</pre>
</div>
<blockquote><p>源码位置：<a class=" external" href="https://link.zhihu.com/?target=https%3A//github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/ext/declarative/api.py%23L62" target="_blank" rel="nofollow noopener noreferrer" data-za-detail-view-id="1043"><span class="invisible">https://</span><span class="visible">github.com/zzzeek/sqlal</span><span class="invisible">chemy/blob/master/lib/sqlalchemy/ext/declarative/api.py#L62</span></a></p></blockquote>
<p>在<code>_as_declarative()</code>函数以及附加的其他多层调用为这个类进行了更多设置，比如添加<code>__table__</code>属性为存储表信息的<code>Table</code>对象，设置<code>metadata</code>属性等等。</p>
<p>其中魔法方法<code>__setattr__()</code>中调用了<code>_add_attribute()</code>函数，这个函数执行了一系列模型类属性的注册操作，其中的一个操作便是向基类<code> __table__</code>属性指向的<code>Table</code>对象调用<code>append_column()</code>方法添加表字段信息：</p>
<div>
<pre class="">def _add_attribute(cls, key, value):
    if '__mapper__' in cls.__dict__:
        if isinstance(value, Column):
            _undefer_column_name(key, value)
            cls.__table__.append_column(value)
            cls.__mapper__.add_property(key, value)
        ...
</pre>
</div>
<blockquote><p>源码位置：<a class=" external" href="https://link.zhihu.com/?target=https%3A//github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/ext/declarative/base.py%23L646" target="_blank" rel="nofollow noopener noreferrer" data-za-detail-view-id="1043"><span class="invisible">https://</span><span class="visible">github.com/zzzeek/sqlal</span><span class="invisible">chemy/blob/master/lib/sqlalchemy/ext/declarative/base.py#L646</span></a></p></blockquote>
<p>经过这一系列注册操作，表信息就被添加到<code>db.Model.metadata.tables</code>属性中，这个属性返回包含所有表信息的字典（表名称与<code>Table</code>实例的映射），下面是一个示例：</p>
<div>
<pre class="">immutabledict({'comment': Table('comment', MetaData(bind=None), Column('id'
, Integer(), table=&lt;comment&gt;, primary_key=True, nullable=False), Column('bo
dy', Text(), table=&lt;comment&gt;), Column('timestamp', DateTime(), table=&lt;comme
nt&gt;, default=ColumnDefault(&lt;function utcnow at 0x03D2E3F0&gt;)), Column('flag'
, Integer(), table=&lt;comment&gt;, default=ColumnDefault(0)), Column('author_id
', Integer(), ForeignKey('user.id'), table=&lt;comment&gt;), Column('photo_id', I
nteger(), ForeignKey('photo.id'), table=&lt;comment&gt;), schema=None)})             
</pre>
</div>
<p><code>db.Model.metadata</code>存储<a class=" wrap external" href="https://link.zhihu.com/?target=https%3A//github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/sql/schema.py%23L3521" target="_blank" rel="nofollow noopener noreferrer" data-za-detail-view-id="1043">MetaData</a>类实例，<code>MetaData</code>类实例存储<a class=" wrap external" href="https://link.zhihu.com/?target=https%3A//github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/sql/schema.py%23L133" target="_blank" rel="nofollow noopener noreferrer" data-za-detail-view-id="1043">Table</a>类实例的集合，而<code>Table</code>类实例存储模型类对应的数据库表字段信息，可以通过模型类的<code>__table__</code>属性获取。<code>create_all()</code>方法实际调用的是<code><a class=" wrap external" href="https://link.zhihu.com/?target=https%3A//github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/sql/schema.py%23L3980" target="_blank" rel="nofollow noopener noreferrer" data-za-detail-view-id="1043">db.Model.metadata.create_all</a></code> 方法，这个方法会将<code>Table</code>类实例存储的信息转换为数据库模式（通过<a class=" wrap external" href="https://link.zhihu.com/?target=https%3A//github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/sql/ddl.py%23L693" target="_blank" rel="nofollow noopener noreferrer" data-za-detail-view-id="1043">sqlclchemy.sql.ddl.SchemaGenerator</a>）。</p>
<p>顺便说一句，因为表信息存储在特定的基类中，所以为了正确创建数据库表，你需要对模型类继承的基类调用<code>create_all()</code>方法，即<code>db.create_all()</code>，或是<code>Base.metadata.create_all(engine)</code>。如果你在测试时新创建一个<code>db</code>对象或是<code>Base</code>基类，那么它是不会包含表信息的。</p>
<p>这篇文章来自我在知乎写的<a href="https://www.zhihu.com/question/284904297/answer/444926077">这个回答</a>，作为备份。</p>
</div>
]]></content:encoded>
			<wfw:commentRss>https://greyli.com/import-models-before-call-create_all-in-sqlalchemy/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
