【问题标题】:How can I set a table constraint "deferrable initially deferred" in django model?如何在 django 模型中设置表约束“最初可延迟”?
【发布时间】:2017-04-14 23:10:35
【问题描述】:

我正在尝试使用 postgresql 数据库在 django 中为表模型设置约束。

我可以通过 postgresql 用这句话做到这一点:

ALTER TABLE public.mytable ADD CONSTRAINT "myconstraint" UNIQUE(field1, field2) DEFERRABLE INITIALLY DEFERRED;

但我想通过 django 模型来实现。 阅读 django 官方文档我没有发现任何相关内容。

我需要这样的东西:

class Meta:
  unique_together = (('field1', 'field2',), DEFERRABLE INITIALLY DEFERRED)

有可能做这样的事情吗?

【问题讨论】:

标签: django postgresql django-models constraints


【解决方案1】:

Django 不支持。

您可以使用自定义 SQL 来完成。在您的 models.py 中,添加以下内容:

from django.db import connection
from django.db.models.signals import post_migrate

def after_migrate(sender, **kwargs):
    cursor = connection.cursor()
    cursor.execute('ALTER TABLE public.mytable ALTER CONSTRAINT '
                   'myconstraint DEFERRABLE INITIALLY DEFERRED')

post_migrate.connect(after_migrate)

虽然我过去做过这样的事情,但我发现多年来我更喜欢让我的工作更简单,并且独立于任何特定的 RDBMS。例如,您真的想支持 SQLite,因为它使开发变得非常容易。只需稍加改动设计,您通常就可以摆脱这些东西。

更新:我认为@fpghost 的回答更好。我不知道我在想什么:-)

【讨论】:

  • postgresql.org/docs/current/static/sql-altertable.html "ALTER CONSTRAINT 这种形式改变了之前创建的约束的属性。目前只有外键约束可以改变。"
  • 为什么要在每次使用post_migrate 迁移后运行它?在添加约束的迁移之后使用RunSQL 创建一个迁移不是更好吗?还鉴于 Django 任意命名约束,您如何以编程方式找到名称“myconstraint”?
  • @fpghost 想想当我做出这个回复时我在想什么。我认为当我看到这个问题时,我想“等等,我想我以前做过这个......”,我找到了我的代码并复制了它,但该代码可能是在迁移之前编写的(也可能是在 @987654326 @)。我更新了我的回复以指向您的回复。
【解决方案2】:

我会通过一次迁移来做到这一点。首先以编程方式获取唯一约束名称,然后删除并重新添加(因为更改它似乎仅适用于 FK 约束,而不适用于唯一约束)。添加反向迁移也可以撤消此操作。

from django.db import migrations, connection



def _make_deferrable(apps, schema_editor):
    """
    Change the unique constraint to be deferrable
    """
    # Get the db name of the constraint
    MyModel = apps.get_model('myapp', 'MyModel')
    CONSTRAINT_NAME = schema_editor._constraint_names(MYModel,
                                                                   ['col1', 'col2'],
                                                                   unique=True)[0]
    TABLE_NAME = MyModel._meta.db_table


    # Drop then re-add with deferrable as ALTER doesnt seem to work for unique constraints in psql
    with schema_editor.connection.create_cursor() as curs:
        curs.execute(
            f'ALTER TABLE {TABLE_NAME} DROP CONSTRAINT "{CONSTRAINT_NAME}";'
        )
        curs.execute(
            f'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT'
            f' {CONSTRAINT_NAME}'
            f' UNIQUE (col1, col2) DEFERRABLE INITIALLY DEFERRED;'
        )


def _unmake_deferrable(apps, schema_editor):
    """
    Reverse the unique constraint to be not deferrable
    """
    # Get the db name of unique constraint
    MyModel = apps.get_model('myapp', 'MyModel')
    CONSTRAINT_NAME = schema_editor._constraint_names(MyModel,
                                                                   ['col1', 'col2'],
                                                                   unique=True)[0]
    TABLE_NAME = MyModel._meta.db_table

    with schema_editor.connection.create_cursor() as curs:
        curs.execute(
            f'ALTER TABLE {TABLE_NAME} DROP CONSTRAINT "{CONSTRAINT_NAME}";'
        ) 
        curs.execute(
            f'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT'
            f' {CONSTRAINT_NAME}'
            f' UNIQUE (col1, col2) NOT DEFERRABLE;'
        )

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '<previous_mig>'),
    ]

    operations = [
        migrations.RunPython(code=_make_deferrable,  reverse_code=_unmake_deferrable)
    ]

【讨论】:

    【解决方案3】:

    最近,Django 增加了对这个特性的支持(参见ticket)。从 Django 3.1 开始,您可以编写:

    class UniqueConstraintDeferrable(models.Model):
        name = models.CharField(max_length=255)
        shelf = models.CharField(max_length=31)
    
        class Meta:
            required_db_features = {
                'supports_deferrable_unique_constraints',
            }
            constraints = [
                models.UniqueConstraint(
                    fields=['name'],
                    name='name_init_deferred_uniq',
                    deferrable=models.Deferrable.DEFERRED,
                ),
                models.UniqueConstraint(
                    fields=['shelf'],
                    name='sheld_init_immediate_uniq',
                    deferrable=models.Deferrable.IMMEDIATE,
                ),
            ]
    

    【讨论】:

      猜你喜欢
      • 2012-05-20
      • 1970-01-01
      • 2020-04-19
      • 2012-04-28
      • 1970-01-01
      • 2010-11-03
      • 2012-07-15
      • 2013-04-25
      • 2010-11-03
      相关资源
      最近更新 更多