Darien Marks
Darien Marks

Reputation: 535

Django won't migrate because one model used to be a subclass

I'm using Django 3.0.4 with MySQL 5.7 on Ubuntu 18.04.

I have a model Apples. I created a second model BadApples as a subclass of it:

# models.py

class Apples(models.Model):
    # fields
    
class BadApples(Apples):
    pass

I ran the migration, which completed successfully.

Then, I decided that the 'subclass' approach wasn't working and that BadApples should be its own model. I rewrote the models like this:

# models.py

class Apples(models.Model):
    # fields
    
class BadApples(models.Model):
    # fields

When I tried to run the migration, I ran into the following error:

MySQLdb._exceptions.OperationalError: (1090, "You can't delete all columns with ALTER TABLE; use DROP TABLE instead")

As best I can tell, migrating BadApples from one form to the other involves changing all of its columns. Instead of dropping the table and recreating it, Django uses ALTER TABLE commands only, which throws the MySQL error when it attempts to remove the last column of the original BadApples. This seems related to this bug, purportedly fixed over two years ago, but evidently not fully.

To work around this bug, my idea was to remove BadApples from models.py and all references to BadApples from the rest of the code (in this case, views.py and admin.py). Then I'd run a migration, which would drop the BadApples table entirely because it no longer exists, and then I could recreate it as a separate migration.

Instead, after confirming that there is no trace of the BadApples model anywhere in my code, running makemigrations throws this error:

django.core.exceptions.FieldError: Local field 'id' in class 'BadApples' clashes with field of the same name from base class 'Apples'.

When I originally created BadApples as a child of Apples, its table had no id field because its data was stored in the Apples table. After I changed it to be an independent model, its table now gets assigned an id field. Django retains some memory of BadApples being a child of Apples, however, and it sees this as a conflict.

To try to fix this, I reverted to the migration immediately before I ever created BadApples in the first place. This migration completed successfully:

$ python3 manage.py migrate AppName 0012_auto_some_number
Operations to perform:
  Target specific migration: 0012_auto_some_number, from AppName
Running migrations:
  Rendering model states... DONE
  Unapplying AppName.0013_badapples... OK

At this point, there is NO reference to BadApples anywhere in my code, and the models are at a point where BadApples has never even existed. Yet, when I try to run a migration (with no further changes to models.py), I still get the error

django.core.exceptions.FieldError: Local field 'id' in class 'BadApples' clashes with field of the same name from base class 'Apples'.

Django is fixated on the idea that BadApples used to exist and be a subclass of Apples. How do I un-fixate it?

Upvotes: 0

Views: 729

Answers (1)

Soviut
Soviut

Reputation: 91555

This type of operation you're trying to perform is sufficiently complicated that Django Migrate can't infer what you're trying to do. In situations like these you should write a custom migration to handle how it can migrate the database forward. In this migration you can tell it to properly drop the table and create a new one.

In the Migration Files topic there is an example that shows the migrations.DeleteModel() method.

from django.db import migrations, models

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

    operations = [
        migrations.DeleteModel('Tribble'),
        migrations.AddField('Author', 'rating', models.IntegerField(default=0)),
    ]

Upvotes: 1

Related Questions