Peewee迁移更改外键约束

时间:2018-06-28 06:35:40

标签: python-3.x postgresql sqlite flask peewee

我遇到一种情况,我需要更改外键约束以实现级联删除,并为更改编写迁移步骤。我将Flask和peewee用作ORM工具,将peewee_migrate用作迁移工具。

从peewee_migrate路由器中提供的迁移模板创建迁移时,会向该迁移中添加一组可用方法。

"""Peewee migrations -- 007_Added_cascade_delete.py.

Some examples (model - class or model name)::

    > Model = migrator.orm['model_name']            # Return model in current state by name

    > migrator.sql(sql)                             # Run custom SQL
    > migrator.python(func, *args, **kwargs)        # Run python code
    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
    > migrator.remove_model(model, cascade=True)    # Remove a model
    > migrator.add_fields(model, **fields)          # Add fields to a model
    > migrator.change_fields(model, **fields)       # Change fields
    > migrator.remove_fields(model, *field_names, cascade=True)
    > migrator.rename_field(model, old_field_name, new_field_name)
    > migrator.rename_table(model, new_table_name)
    > migrator.add_index(model, *col_names, unique=False)
    > migrator.drop_index(model, *col_names)
    > migrator.add_not_null(model, *field_names)
    > migrator.drop_not_null(model, *field_names)
    > migrator.add_default(model, field_name, default)

"""

我尝试使用remove_fields方法删除该列,然后使用add_fields方法通过层叠删除添加一个新的外键列,但未成功,因为以某种方式未删除该列但未显示任何错误,这导致了错误具有该名称的外键已经存在。

使用change_fields方法时,我收到一个错误,指出在Peewee的剧场迁移中未实现drop_foreign_key_constraint,因为change_fields方法尝试在更改字段之前删除外键,并且如果更改的字段也是外键,则尝试使用add_foreign_key_constraint方法添加外键约束(也未实现)

@operation
def drop_foreign_key_constraint(self, table, column_name):
    raise NotImplementedError

@operation
def add_foreign_key_constraint(self, table, column_name, field):
    raise NotImplementedError

我想出的唯一解决方案是使用允许自定义sql执行的sql方法,但是问题是,由于postgresql,mysql和sqlite之间的语法不同,因此只能在特定的数据库上运行。用于开发/生产的数据库是postgres,而使用sqlite内存数据库进行测试,这会导致所有测试失败,但是在开发/生产上迁移成功,因此这是一个“解决方法”。

if current_app.config["TESTING"] != True:
    def migrate(migrator, database, fake=False, **kwargs):
        """Write your migrations here."""

        migrator.sql('alter table {} drop constraint {};'.format(TABLE_NAME, CONSTRAINT_NAME))

        migrator.sql('alter table {} add constraint {} foreign key (device_id) references device(device_id) on delete cascade;'.format(TABLE_NAME, CONSTRAINT_NAME))


    def rollback(migrator, database, fake=False, **kwargs):
        """Write your rollback migrations here."""

        migrator.sql('alter table {} drop constraint {};'.format(TABLE_NAME, CONSTRAINT_NAME))

        migrator.sql('alter table {} add constraint {} foreign key (device_id) references device(device_id);'.format(TABLE_NAME, CONSTRAINT_NAME))
else:
    def migrate(migrator, database, fake=False, **kwargs):
        """Write your migrations here."""

    def rollback(migrator, database, fake=False, **kwargs):
        """Write your rollback migrations here."""

由于sqlite不支持alter table drop constraint,因此我了解到您需要创建表的副本,将原始表中的所有数据插入副本中,然后删除原始表并重命名副本桌子,但是我认为这太过分了。

您能建议我做一个更好的方法吗?可以使用peewee_migrate以某种方式执行此操作而不使用自定义sql,以便peewee可以与数据库无关吗?

1 个答案:

答案 0 :(得分:1)

  

由于sqlite不支持alter table drop约束,因此我了解您需要创建表的副本,将原始表中的所有数据插入副本中,然后删除原始表并重命名副本表,但我认为这太过分了。

我对“ peewee_migrate”一无所知,但是peewee本身带有迁移扩展。 Peewee公开了与数据库无关的API,用于执行常规操作(添加/删除/重命名列,约束等),但是由于sqlite支持的操作类型有限,因此sqlite迁移实现必须采取一些变通办法。

例如sqlite可以添加列,但不能删除它们。因此,当删除列时peewee所做的就是重命名表,重新创建不包含该列的表,复制数据,并删除旧副本。

不幸的是,sqlite不支持在创建表后添加约束,因此,对于您而言,最好的选择是删除该列然后重新添加。

我已经写了一个测试用例来说明它可能如何工作,并验证它是否可以正常工作:

https://github.com/coleifer/peewee/blob/7e61d86bf6c3f256d09b2a3e1897693dfd68b48d/tests/migrations.py#L665-L715