How can I set a table constraint “deferrable initially deferred” in django model?

前端 未结 3 1249
南旧
南旧 2021-01-05 20:10

I am trying to set a constraint to a table model in django with a postgresql database.

I can do it via postgresql with this sentence:

ALTER TABLE pub         


        
相关标签:
3条回答
  • 2021-01-05 20:52

    Django doesn't support that.

    You can do it with custom SQL. In your models.py, add this:

    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)
    

    Although I've done such things in the past, I've found that over the years I prefer to keep my work simpler and independent from any specific RDBMS. For example, you really want to support SQLite, because it makes development so much easier. With a little change in design you can often get rid of such stuff.

    Update: I think @fpghost's answer is better. I don't know what I was thinking :-)

    0 讨论(0)
  • 2021-01-05 20:58

    Very recently, Django has added support for this feature (See ticket). Starting from Django 3.1 you can write:

    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,
                ),
            ]
    
    0 讨论(0)
  • 2021-01-05 21:03

    I would do this via a single migration. First programatically get the unique constraint name, then drop and re-add (since altering it seems to only work for FK constraints, not unique constraints). Add reverse migration that undoes this too.

    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)
        ]
    
    0 讨论(0)
提交回复
热议问题