#34647 closed Bug (duplicate)

Foreign Key index names are not renamed when a model is renamed causing duplicate key if a model with the origin name and column is added

Reported by: Steven Mapes Owned by: nobody
Component: Migrations Version: 4.2
Severity: Normal Keywords: migrations, sql, foreign_key, mysql
Cc: Bhuvnesh, Simon Charette, Mariusz Felisiak, David, Wobrock Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Steven Mapes)

I encountered an issue yesterday where Django created a foreign key that already exists.

The way I encountered this was to take a model called CriticalPathTemplate with a property called archived_by which is a foreign key to the user table, then renamed the model to CriticalPathTemplateOld and ran migrations. This renamed the table but left the indexes as they were.

I then added a new model called CriticalPathTemplate, a business requirement, and ran makemigrations and migrate. The migrations were made but makemigrations failed due to a foreign key name clash. I ended up renaming the column but then hit further issues for the next FK in the table, there are a few.

Looking at the output of sqlmigrate for the new migration and comparing the name of the foreign keys that Django generated I can see several that are going to clash as the indexes on the old table were not renamed when the table was renamed by the migration.

Here's how to create the problem on a fresh project

from django.db import models
from django.contrib.auth.models import User


class ThisIsAModelWithALongName(models.Model):
    name = models.CharField(max_length=128, blank=True)
    archived_by = models.ForeignKey(User, null=True, blank=True, default=None, related_name="model_with_log_names", on_delete=models.SET_NULL)


class AnotherClassHere(models.Model):
    long_name_property = models.ForeignKey(ThisIsAModelWithALongName, null=True, blank=True, default=None, related_name="another_class_here", on_delete=models.SET_NULL)

python manage.py makemigrations && python manage.py migrate

Step 2 - Rename the top class and the FK in the 2nd class but leave the related name in the first class

from django.db import models
from django.contrib.auth.models import User


class ThisIsAModelWithALongNameOld(models.Model):
    name = models.CharField(max_length=128, blank=True)
    archived_by = models.ForeignKey(User, null=True, blank=True, default=None, related_name="model_with_log_names", on_delete=models.SET_NULL)


class AnotherClassHere(models.Model):
    long_name_property_old = models.ForeignKey(ThisIsAModelWithALongNameOld, null=True, blank=True, default=None, related_name="another_class_here", on_delete=models.SET_NULL)


Run migrations

Step 3 - Add the new model back, add an FK into the AnotherClassHere table and rename the related_name on the first (old) model

from django.db import models
from django.contrib.auth.models import User


class ThisIsAModelWithALongNameOld(models.Model):
    name = models.CharField(max_length=128, blank=True)
    archived_by = models.ForeignKey(User, null=True, blank=True, default=None, related_name="model_with_log_names_old", on_delete=models.SET_NULL)


class ThisIsAModelWithALongName(models.Model):
    name = models.CharField(max_length=128, blank=True)
    archived_by = models.ForeignKey(User, null=True, blank=True, default=None, related_name="model_with_log_names", on_delete=models.SET_NULL)

    
class AnotherClassHere(models.Model):
    long_name_property_old = models.ForeignKey(ThisIsAModelWithALongNameOld, null=True, blank=True, default=None, related_name="another_class_here", on_delete=models.SET_NULL)
    long_name_property = models.ForeignKey(ThisIsAModelWithALongName, null=True, blank=True, default=None, related_name="another_class_here", on_delete=models.SET_NULL)

run migrations, the error will occur and the DB will be left in a partially migrated state that can't be rolled back.

It's important to do this as a three step process and not to just jump from Step 1 to Step 2 as that will result in the table being dropped and recreated which will bypass the issue but you'd loose any data that was in the table.

It's the first time I've every had to rename a model then create a new class with the same name so I'm sure it's a rare occurrence. My work around at the moment will be to create the migration, run a sql migrate to capture the raw SQL, then edit the migration to stop it running and create a new empty migration that can run the raw SQL but after I manually edit all the FKs that are clashing

Change History (4)

comment:1 by Steven Mapes, 17 months ago

Description: modified (diff)

comment:2 by Steven Mapes, 17 months ago

Description: modified (diff)
Summary: Foreign Key index names are not renamed when a model is renamedForeign Key index names are not renamed when a model is renamed causing duplicate key if a model with the origin name and column is added

comment:3 by Natalia Bidart, 17 months ago

Cc: Bhuvnesh Simon Charette Mariusz Felisiak David Wobrock added

I'm inclined to accept this ticket because:

  1. I was able to reproduce the issue following the instructions (using sqlite), the error I got was:

django.db.utils.ProgrammingError: relation "ticket_34647_thisisamodelwithalongname_archived_by_id_63c0d1af" already exists

  1. I'm not sure how to propose a working alternative to the issue described that would leave the user with the re-renamed models and working migrations, other than manually renaming the indexes when renaming the models.

Adding as CC a few folks who I think would have a better understanding of the migrations logic in order to better triage this ticket.

comment:4 by Mariusz Felisiak, 17 months ago

Resolution: duplicate
Status: newclosed

Duplicate of #23577.

Note: See TracTickets for help on using tickets.
Back to Top