Search code examples
pythondjangodatabasepostgresqlmigration

Django doesn't recognise column added using RunSQL Migration


I am trying to add a SERIAL non-primary column to an existing table. Django does not seem to have support for this (AutoField must be primary, and I cannot make this field primary).

However postgres does support SERIAL columns, so in the generated migration file I can add the following operation:

class Migration(migrations.Migration):

    dependencies = [
        ...
    ]

    operations = [
        migrations.RunSQL(
            sql="ALTER TABLE portal_referral ADD referral_id SERIAL;",
            reverse_sql="ALTER TABLE portal_referral DROP referral_id;"
        )
    ]

however, it does not appear that Django 'registers' the effects of this migration. That is to say, when running manage.py makemigrations again, the tool will attempt to add referral_id again in a new migration.

The model is defined as

class Referral(utils.UUIDPrimaryKeyBase, utils.TimeStampedModel):
    data = models.JSONField(encoder=DjangoJSONEncoder)
    supplier = models.ForeignKey(Supplier, blank=True, null=True, on_delete=models.PROTECT, related_name="referrals")
    session_id = models.UUIDField(editable=False, blank=True, null=True, unique=True)
    referral_id = models.IntegerField()

    def __str__(self):
        return f"<referral id={self.id} supplier={self.supplier}>"

I did find a way to work around this, where after letting the migration generate its code I can insert some overriding SQL statements afterwards.

class Migration(migrations.Migration):

    dependencies = [
        ...
    ]

    operations = [
        migrations.AddField(
            model_name='referral',
            name='referral_id',
            field=models.IntegerField(default=0),
            preserve_default=False,
        ),
        migrations.RunSQL(
            sql="ALTER TABLE portal_referral DROP referral_id;",
            reverse_sql="ALTER TABLE portal_referral ADD referral_id INT DEFAULT 0;"
        ),
        migrations.RunSQL(
            sql="ALTER TABLE portal_referral ADD referral_id SERIAL;",
            reverse_sql="ALTER TABLE portal_referral DROP referral_id;"
        )
    ]

This works, but doesn't seem particularly effective. Is there a way to flag to Django that I have added this field myself & it does not need to add the column itself?


Solution

  • You can utilize RunSQL state_operations

    migrations.RunSQL(
        sql="ALTER TABLE portal_referral ADD referral_id SERIAL;",
        reverse_sql="ALTER TABLE portal_referral DROP referral_id;",
        state_operations=[
            migrations.AddField(
                "referral",
                "referral_id",
                field=models.IntegerField(default=0),
                preserve_default=False,
            ),
        ],
    )