【问题标题】:Flask SQL Alchemy listens_for decoratorFlask SQLAlchemy listeners_for 装饰器
【发布时间】:2018-12-30 16:28:00
【问题描述】:

我正在尝试在 Flask 中创建一个待办事项列表应用程序。我对 Python 有很多经验,但对 Flask 框架还是很陌生。

我有 3 个模型 - 用户、任务和项目,我希望有一个名为 Inbox 的默认项目,每个用户甚至在自己创建任何项目之前都拥有该项目。

用户和项目通过多对多关系关联。

我对此的尝试是在一个函数上使用以下装饰器,该函数创建一个名为 Inbox 的新项目。

@event.listens_for(Project.__table__, 'after_create')
def create_inbox(*args, **kwargs):
    inbox = Project(name='Inbox')
    db.session.add(inbox)
    db.session.commit()

然后,当我创建一个新用户时,我在 __init__ 函数中将“收件箱”项目添加到他们的项目中。

这个类只是用来建立用户和项目之间的多对多关系:

class UserProject(UserMixin, db.Model):
    __tablename__ = 'userprojects'
    project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)

这是监听表创建的项目模型和装饰器:

class Project(UserMixin, db.Model):
    __tablename__ = 'projects'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32))
    tasks = db.relationship('Task', backref='project', lazy='dynamic') # one project has many tasks
    users = db.relationship('User', secondary='userprojects', lazy='dynamic', backref=db.backref('user', lazy='dynamic'))

@event.listens_for(Project.__table__, 'after_create')
def create_inbox(*args, **kwargs):
    inbox = Project(name='Inbox')
    db.session.add(inbox)
    db.session.commit()

这是用户模型:

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=True )
    email = db.Column(db.String(120), unique=True, nullable=True)
    password_hash = db.Column(db.String(128))
    tasks = db.relationship('Task', backref='author', lazy='dynamic') # one user has many tasks
    projects = db.relationship('Project', secondary='userprojects', lazy='dynamic', backref=db.backref('project', lazy='dynamic'))

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        p = Project.query.first()
        if p not in self.projects.all():
            self.projects.append(p)

还有一个任务模型,但我没有将其包含在此处,因为它与问题无关。

出现的问题是当我尝试使用迁移和升级时。我执行flask db init,迁移和升级并得到以下输出,所以看起来一切正常,迁移文件看起来很好。

flask db init

Creating directory /Users/Jasmine/projects/flask/tasky/migrations ... done
Creating directory /Users/Jasmine/projects/flask/tasky/migrations/versions ... done
Generating /Users/Jasmine/projects/flask/tasky/migrations/script.py.mako ... done
Generating /Users/Jasmine/projects/flask/tasky/migrations/env.py ... done
Generating /Users/Jasmine/projects/flask/tasky/migrations/README ... done
Generating /Users/Jasmine/projects/flask/tasky/migrations/alembic.ini ... done
Please edit configuration/connection/logging settings in '/Users/Jasmine/projects/flask/tasky/migrations/alembic.ini' before proceeding.

flask db migrate

INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'projects'
INFO  [alembic.autogenerate.compare] Detected added table 'users'
INFO  [alembic.autogenerate.compare] Detected added table 'tasks'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_tasks_due' on '['due']'
INFO  [alembic.autogenerate.compare] Detected added table 'userprojects'
Generating /Users/Jasmine/projects/flask/tasky/migrations/versions/64311763e16f_creating_the_tables.py ... done

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  -> 64311763e16f, creating the tables

但是,当我进入 flask shell 并输入 Project.query.all() 时,我会得到一个空列表,我希望在其中得到一个包含一个项目的列表 - 一个名为“收件箱”的项目。

我不确定错误出在哪里,因此我们将不胜感激任何有关此问题的建议。

【问题讨论】:

  • 也许我误解了,但你最好使用after_insert 并为收件箱创建一个Project 实例并在插入时将其添加到新创建的User 对象中吗?
  • 感谢@SuperShoot 的评论。我以前没有遇到过after_insert,但会看看它是否有效。为什么会比使用after_create 更好?
  • 因为它是一个绑定到创建新实例的事件,而after_create 绑定到发布创建表语句。每个用户都会有自己独特的收件箱实例吗?还是他们都共享同一个收件箱? IE。如果一个用户更改了他们的,所有其他用户都会看到更改吗?还是只有做出更改的用户?
  • @SuperShoot 好的,我明白了。我确实希望每个用户都有自己独特的收件箱实例,因此使用after_insert 而不是after_create 确实有意义。非常感谢。

标签: python-3.x flask sqlalchemy flask-sqlalchemy flask-migrate


【解决方案1】:

我经常看到有两种不同的方法来处理在数据库中创建初始数据,我猜你想出了第三种。

一种选择是创建一个将所有初始值添加到数据库的脚本(或flask 命令的自定义扩展)。例如,在您执行flask db upgrade 之后,您可以执行flask db initdb 来添加该收件箱项目。您可以编写这个initdb 函数,使其可以多次运行,它只会添加缺少的内容,而不会创建重复项。

另一个选项是编辑您的迁移脚本以包括添加此数据。 Alembic 提供了可用于此目的的 execute()bulk_insert() 操作。因此,在您运行flask db migrate 之后,您可以在编辑器中打开迁移脚本并手动编辑它以包含收件箱条目的创建。只有在您做出这些更改后,您才能运行flask db upgrade

我认为您的解决方案有点矫枉过正。您正在为在应用程序安装的整个生命周期中将发生一次的事情设置一个事件观察器。考虑到此事件处理程序将在应用程序运行期间处于活动状态,即使在运行时根本不需要它。我建议您改用上述两种更直接的方法之一。

【讨论】:

  • 太棒了,我现在可以正常工作了。非常感谢@Miguel 的建议。
猜你喜欢
  • 2020-07-15
  • 2019-06-23
  • 2014-02-16
  • 2013-09-21
  • 1970-01-01
  • 1970-01-01
  • 2012-12-05
  • 2018-06-05
  • 2018-02-27
相关资源
最近更新 更多