15

I have a Django model that has a foreign key to another model:

class Example(models.Model)
   something = models.ForeignKey(SomeModel, db_index=True)

I want to keep the underlying DB column as a field, but to get rid of the foreign key constraint in the database.

So the model will change to:

class Example(models.Model):
   something_id = models.IntegerField() 

And, to be clear, something_id is the column that Django had created for the foreign key field.

I do not want to drop the column and re-create it (this is what Django does when I auto-generate migrations after changing the model as above).

I want to keep the field but I want to remove the foreign key constraint in the database with a migration. It's not clear to me how to do this with a Django migration - is there some built in support for it or do I have to run some raw SQL and, if so, how do I programatically get the name of the constraint?

J. Doe
  • 555
  • 1
  • 6
  • 15

3 Answers3

22

This is how I managed to do it, it's based on nimasmi's answer above:

class Migration(migrations.Migration):
    dependencies = [
        ('my_app', '0001_initial'),
    ]

    # These *WILL* impact the database!
    database_operations = [
        migrations.AlterField(
            model_name='Example',
            name='something',
            field=models.ForeignKey('Something', db_constraint=False, db_index=True, null=False)
        ),
    ]

    # These *WON'T* impact the database, they update Django state *ONLY*!
    state_operations = [
        migrations.AlterField(
            model_name='Example',
            name='something',
            field=models.IntegerField(db_index=True, null=False)
        ),
        migrations.RenameField(
            model_name='Example',
            old_name='something',
            new_name='something_id'
        ),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations
        )
    ]
J. Doe
  • 555
  • 1
  • 6
  • 15
  • I've tried that too and worked. The problem is when I need to do a new migration after this, it will not recognize the state change and only work based on database schema. Have you cross that too? – Jonatas CD Mar 10 '17 at 10:32
8

See SeparateDatabaseAndState. It allows you to specify a Django (state) part of the migration separately from the database part of the migration.

  1. Amend the field in your models file.
  2. Create the migration, as normal. You will end up with something like:

    class Migration(migrations.Migration):
    
        dependencies = [
            ('my_app', '0001_whatever.py'),
        ]
    
        operations = [
            migrations.AlterField(
                model_name='example',
                name='something',
                field=models.CharField(max_length=255, null=True)),
            ),
        ]
    
  3. Now manually amend this to:

    class Migration(migrations.Migration):
    
        dependencies = [
            ('my_app', '0001_whatever.py'),
        ]
    
        state_operations = [
            migrations.AlterField(
                model_name='example',
                name='something',
                field=models.CharField(max_length=255, null=True)),
            ),
        ]
        operations = [
            migrations.SeparateDatabaseAndState(state_operations=state_operations)
        ]
    

Note that you are not specifying any database_operations argument, so the Django relationships are amended, but the database data is unchanged.

Needless to say: take a backup before you try this.

nimasmi
  • 3,978
  • 1
  • 25
  • 41
  • Thank you for your answer but this isn't quite what I want; I want to remove the database foreign key constraint (i.e. I want a migration that does something like `ALTER TABLE examples DROP CONSTRAINT x`) – J. Doe Jul 12 '16 at 11:02
  • You want Django to think it's a foreign key still, but the database to think it's a plain text field? – nimasmi Jul 12 '16 at 11:04
  • Sorry, I missed this part: I want to keep the underlying DB column (something_id), the model will update to have `something_id` as an integer field, but I want to drop the database foreign key constraint. I'll update the question – J. Doe Jul 12 '16 at 11:08
  • Okay, I see anyway that what I suggested wouldn't have dropped the constraint. Apologies for that. How about using SeparateDatabaseAndState, and adding a second operation which calls `migrations.RunSQL` (https://docs.djangoproject.com/en/1.9/ref/migration-operations/#runsql)? I don't speak SQL, I'm afraid, so this is only the framework. I realise I've not fully answered your question. – nimasmi Jul 12 '16 at 11:38
5

As of Django 2.0, changing your field to models.ForeignKey(db_constraint=False, db_index=False, ...) will generate a migration that does ALTER TABLE DROP CONSTRAINT and DROP INDEX IF EXISTS, which appears to be exactly what you want.

btown
  • 2,273
  • 3
  • 27
  • 38
  • 1
    It didn't do that for me, though it should've.... See https://code.djangoproject.com/ticket/30741#ticket – Scott Stafford Aug 29 '19 at 16:31
  • Weird. Did you look at what's generated by `sqlmigrate`? A workaround is to wrap the migration with a RunSQL and put the existing ops in the `state_operations` kwarg, so you can control exactly how the db_constraint=False is interpreted. – btown Aug 29 '19 at 22:54
  • `sqlmigrate` shows a noop, which I wrote up in that linked Django bug ticket. – Scott Stafford Aug 30 '19 at 17:28