[Django]-Django Migration RunSQL Conditional on Database Type

27đź‘Ť

Here is how I solved the problem since I couldn’t get RunSQL to work inside RunPython. Thankfully, the schema_editor object has an execute() method.

def forwards(apps, schema_editor):
    if not schema_editor.connection.vendor.startswith('postgres'):
        logger.info('Database vendor: {}'.format(schema_editor.connection.vendor))
        logger.info('Skipping migration without attempting to ADD CONSTRAINT')
        return

    schema_editor.execute('ALTER TABLE my_table ADD CONSTRAINT my_constraint (my_field != \'NaN\';)')


def backwards(apps, schema_editor):
    if not schema_editor.connection.vendor.startswith('postgres'):
        logger.info('Database vendor: {}'.format(schema_editor.connection.vendor))
        logger.info('Skipping migration without attempting to DROP CONSTRAINT')
        return

    schema_editor.execute('ALTER TABLE my_table DROP CONSTRAINT my_constraint;')


class Migration(migrations.Migration):

    dependencies = [
        ...
    ]

    operations = [
        migrations.RunPython(forwards, backwards, atomic=True)
    ]
👤PaulMest

8đź‘Ť

I just had the same need. I had to edit a migration that set the initial value of a sequence, which works on postgres but not sqlite. Here’s how I wrapped the RunSQL inside a RunPython, following the documentation that Daniel linked to.

from django.db import migrations


def forwards(apps, schema_editor):
    if not schema_editor.connection.vendor == 'postgres':
        return
    migrations.RunSQL(
        "alter sequence api_consumer_id_seq restart with 1000500;")


class Migration(migrations.Migration):
    dependencies = [
        ('api', '0043_auto_20160416_2313'),
    ]

    operations = [
        migrations.RunPython(forwards),
    ]
👤Matthew

6đź‘Ť

I solved a similar problem today — needing to perform a migration to create a new model, but only for a postgres DB — and I found this question. However, Matthew’s answer did not help me. In fact, I’m not sure it works at all. That is because the line with migrations.RunSQL(...) does not actually run SQL; it creates a new object of type RunSQL which is a Command, and then immediately discards it.

Here’s how I ended up solving the problem, in case anyone tries to search for “django conditional migration” in the future:

from __future__ import unicode_literals

import django.contrib.postgres.fields
from django.db import migrations, models


class PostgresOnlyCreateModel(migrations.CreateModel):
    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        if schema_editor.connection.vendor.startswith("postgres"):
            super(PostgresOnlyCreateModel, self).database_forwards(app_label, schema_editor, from_state, to_state)

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        if schema_editor.connection.vendor.startswith("postgres"):
            super(PostgresOnlyCreateModel, self).database_backwards(app_label, schema_editor, from_state, to_state)


class Migration(migrations.Migration):

    dependencies = [
        ...whatever...
    ]

    operations = [
        PostgresOnlyCreateModel(
            name='...whatever...',
            fields=[...whatever...],
        ),
    ]
👤Shaggy Frog

0đź‘Ť

That information is not provided in the Migration class, it is provided in the schema_editor attribute passed to a RunPython operation. See the documentation for some examples on using this.

0đź‘Ť

Another option is to have the actual sql depend on db.connection.vendor:

from django.db import connection

CONCURRENTLY = "CONCURRENTLY" if connection.vendor == "postgres" else ""
SQL = f"CREATE INDEX {CONCURRENTLY}..."

At that point you can just use migrations.RunSQL, which is handy, particularly if you use the state_operations argument.

👤Doug Bradshaw

0đź‘Ť

If you want to apply migrations depending on app or model, I think the best solution is using django database router.

First define a database router class:

from django.db import connections

class PgOnlyMigrateRouter:

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'pgonly_app' or model_name == 'pgonly_model':
            return connections[db].vendor.startswith("postgres")
        return None

Then in your setting.py file, add this line:

DATABASE_ROUTERS = ['path.to.PgOnlyMigrateRouter']

As you see, this works for all migrations in specified model or app, not just a single migration operation.

👤jayvynl

Leave a comment