0

I want to add a constraint to my django model. I want this migration to do nothing if the constraint has already been added (manually in sql, not by django). I know how to check if constraint is already applied thanks to this question.

However, how could I skip the "add constraint" operation in my migration file based on this condition? Do I need to create a RunPython operation or can I rely on django migration objects? I am using postgres.

EDIT I added the constraint previously directly in sql to avoid doing it during deployment, for performance reason. Also, this constraint isn't applied in all environments so I cannot assume it's already there.

Martin Faucheux
  • 884
  • 9
  • 26
  • You can do `RemoveConstraint` and then `AddConstraint`? – DanielM Feb 14 '22 at 15:37
  • I added some clarification: I don't want to remove then apply for performance reason. Also I cannot assume the constraint is there because the sql has been run only in some environments. – Martin Faucheux Feb 14 '22 at 15:51

2 Answers2

1

I managed to implement a child class of the AddConstraint migration operation. It first check if the constraint already exists.

import warnings

from django.db import migrations


class SkipIfExistAddConstraint(migrations.AddConstraint):
    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        db_table = to_state.apps.get_model(app_label, self.model_name)._meta.db_table
        if self.constraint_exists(schema_editor, db_table):
            warnings.warn(
                f"Constraint {self.constraint.name} of table {db_table} already exists. "
                "Skipping..."
            )
            return
        return super().database_forwards(app_label, schema_editor, from_state, to_state)

    def constraint_exists(self, schema_editor, db_table):
        with schema_editor.connection.cursor() as cursor:
            cursor.execute(
                (
                    "select 1 "
                    "from information_schema.constraint_column_usage "
                    f"where table_name = '{db_table}' "
                    f"and constraint_name = '{self.constraint.name}'"
                )
            )
            return cursor.fetchone() is not None
 
Martin Faucheux
  • 884
  • 9
  • 26
0

Take a look at "fake" migrations. Allow django to create the migration as it would, however you can run the below to tell Django "I've run the migration" when you actually havent and it won't suggest that migration again:

python manage.py migrate --fake my_module

https://docs.djangoproject.com/en/4.0/ref/django-admin/#cmdoption-migrate-fake

For the other environments that don't have the constraint applied, just skip the fake option

Artisan
  • 1,974
  • 1
  • 18
  • 23
  • That would mean I have to intervene manually during the deployment process, which I want to avoid. I'd like that this logic is handled by the migration itself. – Martin Faucheux Feb 15 '22 at 09:14