VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • 带你认识 flask 中的数据库(2)

flask db migrate),你可能会审查它以确保自动生成的正确性,然后将更改应用到你的开发数据库(flask db upgrade)。测试无误后,将迁移脚本添加到源代码管理并提交。

当准备将新版本的应用发布到生产服务器时,你只需要获取包含新增迁移脚本的更新版本的应用,然后运行flask db upgrade即可。Alembic将检测到生产数据库未更新到最新版本,并运行在上一版本之后创建的所有新增迁移脚本。

正如我前面提到的,flask db downgrade命令可以回滚上次的迁移。虽然在生产系统上不太可能需要此选项,但在开发过程中可能会发现它非常有用。你可能已经生成了一个迁移脚本并将其应用,只是发现所做的更改并不完全是你所需要的。在这种情况下,可以降级数据库,删除迁移脚本,然后生成一个新的来替换它。

数据库关系

关系数据库擅长存储数据项之间的关系。考虑用户发表动态的情况, 用户将在user表中有一个记录,并且这条用户动态将在post表中有一个记录。标记谁写了一个给定的动态的最有效的方法是链接两个相关的记录。

一旦建立了用户和动态之间的关系,数据库就可以在查询中展示它。最小的例子就是当你看一条用户动态的时候需要知道是谁写的。一个更复杂的查询是, 如果你好奇一个用户时,你可能想知道这个用户写的所有动态。Flask-SQLAlchemy有助于实现这两种查询。

让我们扩展数据库来存储用户动态,以查看实际中的关系。这是一个新表post的设计(译者注:实际表名分别为user和post):

 

post表将具有必须的id、用户动态的bodytimestamp字段。除了这些预期的字段之外,我还添加了一个user_id字段,将该用户动态链接到其作者。你已经看到所有用户都有一个唯一的id主键, 将用户动态链接到其作者的方法是添加对用户id的引用,这正是user_id字段所在的位置。这个user_id字段被称为外键。上面的数据库图显示了外键作为该字段和它引用的表的id字段之间的链接。这种关系被称为一对多,因为“一个”用户写了“多”条动态。

修改后的app/models.py如下:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
from datetime import datetimefrom 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 upgradeINFO  [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

如果你对项目使用了版本控制,记得将新的迁移脚本添加进去并提交。

表演时刻

经历了一个漫长的过程来定义数据库,我却还没向你展示它们如何使用。由于应用还没有任何数据库逻辑,所以让我们在Python解释器中来使用以便熟悉它。立即运行python命令来启动Python(在启动解释器之前,确保您的虚拟环境已被激活)。

进入Python交互式环境后,导入数据库实例和模型:

  •  
  •  
>>> from app import db>>> from app.models import User, Post

 

开始阶段,创建一个新用户:

  •  
  •  
  •  
>>> u = User(username='john', email='john@example.com')>>> db.session.add(u)>>> db.session.commit()

对数据库的更改是在会话的上下文中完成的,你可以通过db.session进行访问验证。允许在会话中累积多个更改,一旦所有更改都被注册,你可以发出一个指令db.session.commit()来以原子方式写入所有更改。如果在会话执行的任何时候出现错误,调用db.session.rollback()会中止会话并删除存储在其中的所有更改。要记住的重要一点是,只有在调用db.session.commit()时才会将更改写入数据库。会话可以保证数据库永远不会处于不一致的状态。

添加另一个用户:

  •  
  •  
  •  
>>> u = User(username='susan', email='susan@example.com')>>> db.session.add(u)>>> db.session.commit()

数据库执行返回所有用户的查询:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
>>> users = User.query.all()>>> users[<User john>, <User susan>]>>> for u in users:...     print(u.id, u.username)...1 john2 susan

所有模型都有一个query属性,它是运行数据库查询的入口。最基本的查询就是返回该类的所有元素,它被适当地命名为all()。请注意,添加这些用户时,它们的id字段依次自动设置为1和2。

另外一种查询方式是,如果你知道用户的id,可以用以下方式直接获取用户实例:

  •  
  •  
  •  
>>> u = User.query.get(1)>>> u<User john>

现在添加一条用户动态:

  •  
  •  
  •  
  •  
>>> u = User.query.get(1)>>> p = Post(body='my first post!', author=u)>>> db.session.add(p)>>> db.session.commit()

我不需要为timestamp字段设置一个值,因为这个字段有一个默认值,你可以在模型定义中看到。那么user_id字段呢?回想一下,我在User类中创建的db.relationship为用户添加了posts属性,并为用户动态添加了author属性。我使用author虚拟字段来调用其作者,而不必通过用户ID来处理。SQLAlchemy在这方面非常出色,因为它提供了对关系和外键的高级抽象。

为了完成演示,让我们看看另外的数据库查询案例:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

相关教程