Old description:
> 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}}} (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 E.G {{{django.db.utils.OperationalError: (1826, "Duplicate foreign
> key constraint name
> 'critical_path_templa_origin_agent_shortco_f9715e87_fk_address_s'")
> }}}
>
> 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
New description:
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
--
--
Ticket URL: <https://code.djangoproject.com/ticket/34647#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.