0

I have two Django (foreign key) fields - FieldA and FieldB - referring to the same class but being different objects. Then I wanted to rename FieldB to FieldC. I renamed this in one migration (automatic detection).

Then I realised I actually need a new field with the same name as the old FieldB (also foreign key to the same class). Therefore I created a second migration to add a new field: FieldB. Since I just renamed the other one I assumed that this would not give any issues in the DB.

Locally I develop on an SQLite DB and this works fine. When I pushed it to our Postgres DB, this returned an error.

Model class

class ModelClass(Model):
    field_a: ForeignClassA = models.ForeignKey(ForeignClassA, on_delete=models.SET_NULL, blank=True, null=True, related_name='FieldA')
    # this one should be renamed to field_c after which I create another field with the same name and properties.
    field_b: ForeignClassA = models.ForeignKey(ForeignClassA, on_delete=models.SET_NULL, blank=True, null=True, related_name='FieldB')

Migration one: rename

operations = [
        migrations.RenameField(
            model_name='modelname',
            old_name='FieldB',
            new_name='FieldC',
        ),]

Migration two: add field

operations = [
        migrations.AddField(
            model_name='modelname',
            name='FieldB',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='FieldB', to='app.othermodel'),
        ),]

When I run this migration I get the following error

  Applying app.xxx1_previous... OK
  Applying app.xxx2_rename_field... OK
  Applying app.xxx3_add_field...Traceback (most recent call last):
  File "/app/.heroku/python/lib/python3.10/site-packages/django/db/backends/utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
psycopg2.errors.DuplicateTable: relation "app_modelname_fieldB_id_8c448c6a" already exists

Normally this column should have been deleted and replaced with a new column with the new name. I found this issue describing a similar issue in Postgress and now I wonder if this is a bug in Django. Could it be that the cleanup from the rename is not done correctly?

EDIT 1

After a closer inspection of the Postgres DB I can see that after the rename I still have two columns in my table: FieldA_id and FieldB_id, while I would expect to have FieldA_id and FieldC_id. Obviously this creates an issue if I subsequently try to add FieldB again.

Could it be that Postgres (or the Django controller) does not rename this column for some reason?

EDIT 2

I inspected the SQL query to the Postgres DB. The following SQL is produced:

BEGIN;
--
-- Rename field field_b on modelclass to field_c
--
SET CONSTRAINTS "app_modelclass_field_b_id_8c448c6a_fk_otherapp_otherclass_id" IMMEDIATE; ALTER TABLE "app_modelclass" DROP CONSTRAINT "app_modelclass_field_b_id_8c448c6a_fk_otherapp_otherclass_id";
ALTER TABLE "app_modelclass" RENAME COLUMN "field_b_id" TO "field_c_id";
ALTER TABLE "app_modelclass" ADD CONSTRAINT "app_modelclass_field_c_id_9f82ac2c_fk_otherapp_otherclass_id" FOREIGN KEY ("field_c_id") REFERENCES "otherapp_otherclass" ("id") DEFERRABLE INITIALLY DEFERRED;
COMMIT;

This rename however is seems to be only executed partially since the next step reports an issue, but the migration succeeds.

However, when I manually run this SQL query, the command succeeds and the upgrade is partly successful. In summary:

  1. The SQL query works
  2. While running the SQL, the column name is updated
  3. While running the SQL, the constraint is updated
  4. Still the next step complains about a reference to this rename. Still clueless at the moment what this might be.

1 Answer 1

7

After a long search down the SQL rabbit hole, I found out that the rename migration for PostgresQL does not drop the old index.

When I wanted to create a new field, it tried to create a new index with the same name as the old index (which wasn't removed).

A simple way to avoid it is to 'sandwich' the rename with two alter operations. One to unset the index and one to set it back afterwards.

operations = [
        migrations.AlterField(
            model_name='modelclass',
            name='field_b',
            field=models.ForeignKey(blank=True, db_index=False, null=True, on_delete=django.db.models.deletion.SET_NULL,
                                    to='otherapp.otherclass'),
        ),
        migrations.RenameField(
            model_name='modelclass',
            old_name='field_b',
            new_name='field_c',
        ),
        migrations.AlterField(
            model_name='modelclass',
            name='field_c',
            field=models.ForeignKey(blank=True, db_index=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
                                    to='otherapp.otherclass'),
        ),
    ]

I reported this issue also on the Django bug tracker after which they closed it since there exists a duplicate from 7 years ago.

Sign up to request clarification or add additional context in comments.

2 Comments

Did you also find a way to solve this issue for ManyToMany tables?
Ah, I found the solution. For ManyToMany you have to explicitly add the "through" model (using SeparateDatabaseAndState) set db_index=False on the foreignKeys in this model. Then it will work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.