数据库关系
关系数据库擅长存储数据项之间的关系。 考虑用户发表动态的情况, 用户将在 user
表中有一个记录,并且这条用户动态将在 post
表中有一个记录。 标记谁写了一个给定的动态的最有效的方法是链接两个相关的记录。
一旦建立了用户和动态之间的关系,数据库就可以在查询中展示它。最小的例子就是当你看一条用户动态的时候需要知道是谁写的。一个更复杂的查询是, 如果你好奇一个用户时,你可能想知道这个用户写的所有动态。 Flask-SQLAlchemy 有助于实现这两种查询。
让我们扩展数据库来存储用户动态,以查看实际中的关系。 这是一个新表 post
的设计(译者注:实际表名分别为 user 和 post):
post
表将具有必须的 id
、用户动态的 body
和 timestamp
字段。 除了这些预期的字段之外,我还添加了一个 user_id
字段,将该用户动态链接到其作者。 你已经看到所有用户都有一个唯一的 id
主键, 将用户动态链接到其作者的方法是添加对用户 id
的引用,这正是 user_id
字段所在的位置。 这个 user_id
字段被称为 外键 。 上面的数据库图显示了外键作为该字段和它引用的表的 id
字段之间的链接。 这种关系被称为 一对多 ,因为“一个”用户写了“多”条动态。
修改后的 app/models.py 如下:
from datetime import datetime
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', backref='author', lazy='dynamic')
def __repr__(self):
return '<User {}>'.format(self.username)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Post {}>'.format(self.body)
新的“Post”类表示用户发表的动态。 timestamp
字段将被编入索引,如果你想按时间顺序检索用户动态,这将非常有用。 我还为其添加了一个 default
参数,并传入了 datetime.utcnow
函数。 当你将一个函数作为默认值传入后,SQLAlchemy 会将该字段设置为调用该函数的值(请注意,在 utcnow
之后我没有包含 ()
,所以我传递函数本身,而不是调用它的结果)。 通常,在服务应用中使用 UTC 日期和时间是推荐做法。 这可以确保你使用统一的时间戳,无论用户位于何处,这些时间戳会在显示时转换为用户的当地时间。
user_id
字段被初始化为 user.id
的外键,这意味着它引用了来自用户表的 id
值。本处的 user
是数据库表的名称,Flask-SQLAlchemy 自动设置类名为小写来作为对应表的名称。 User
类有一个新的 posts
字段,用 db.relationship
初始化。这不是实际的数据库字段,而是用户和其动态之间关系的高级视图,因此它不在数据库图表中。对于一对多关系, db.relationship
字段通常在“一”的这边定义,并用作访问“多”的便捷方式。因此,如果我有一个用户实例 u
,表达式 u.posts
将运行一个数据库查询,返回该用户发表过的所有动态。 db.relationship
的第一个参数表示代表关系“多”的类。 backref
参数定义了代表“多”的类的实例反向调用“一”的时候的属性名称。这将会为用户动态添加一个属性 post.author
,调用它将返回给该用户动态的用户实例。 lazy
参数定义了这种关系调用的数据库查询是如何执行的,这个我会在后面讨论。不要觉得这些细节没什么意思,本章的结尾将会给出对应的例子。
一旦我变更了应用模型,就需要生成一个新的数据库迁移:
(venv) $ flask db migrate -m "posts table"
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'post'
INFO [alembic.autogenerate.compare] Detected added index 'ix_post_timestamp' on '['timestamp']'
Generating /home/miguel/microblog/migrations/versions/780739b227a7_posts_table.py ... done
并将这个迁移应用到数据库:
(venv) $ flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade e517276bb1c2 -> 780739b227a7, posts table
如果你对项目使用了版本控制,记得将新的迁移脚本添加进去并提交。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论