Django 1.9 drop foreign key in migration
Asked Answered
B

3

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?

Blair answered 11/7, 2016 at 16:7 Comment(0)
B
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
        )
    ]
Blair answered 18/7, 2016 at 11:34 Comment(1)
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?Hatband
B
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.

Butyrin answered 11/7, 2016 at 19:40 Comment(4)
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)Blair
You want Django to think it's a foreign key still, but the database to think it's a plain text field?Butyrin
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 questionBlair
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 (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.Butyrin
C
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.

Cormier answered 9/8, 2019 at 23:49 Comment(3)
It didn't do that for me, though it should've.... See code.djangoproject.com/ticket/30741#ticketEnvy
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.Cormier
sqlmigrate shows a noop, which I wrote up in that linked Django bug ticket.Envy

© 2022 - 2024 — McMap. All rights reserved.