Opened 4 years ago

Closed 4 years ago

#31954 closed Bug (fixed)

MigrationOptimizer mangles operation order if app name contains uppercase letters.

Reported by: Koen De Wit Owned by: Koen De Wit
Component: Migrations Version: 3.1
Severity: Normal Keywords:
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I am aware that app names are preferably all-lowercase according to PEP 8, but uppercase letters are nevertheless valid.

Steps to reproduce:

  • Create a new project and an app with uppercase letters in the app name :
    django-admin startproject mysite
    cd mysite
    python manage.py startapp MyApp
    
  • Add 'MyApp' to the INSTALLED_APPS in mysite/settings.py
  • Edit MyApp/models.py :
    from django.db import models
    
    class RefModel(models.Model):
        pass
    
    class BaseModel(models.Model):
        r = models.ForeignKey(RefModel, on_delete=models.PROTECT)
    
    class SubModel(BaseModel):
        pass
    
  • Run python ./manage.py makemigrations . In the resulting migration file, the create operation for SubModel comes before the create operation for BaseModel, which is wrong.
  • Run python ./manage.py migrate , which will fail with this error:
    Operations to perform:
      Apply all migrations: MyApp, admin, auth, contenttypes, sessions
    Running migrations:
      Applying MyApp.0001_initial...Traceback (most recent call last):
      File "./manage.py", line 22, in <module>
        main()
      File "./manage.py", line 18, in main
        execute_from_command_line(sys.argv)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
        utility.execute()
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
        self.fetch_command(subcommand).run_from_argv(self.argv)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/core/management/base.py", line 330, in run_from_argv
        self.execute(*args, **cmd_options)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/core/management/base.py", line 371, in execute
        output = self.handle(*args, **options)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/core/management/base.py", line 85, in wrapped
        res = handle_func(*args, **kwargs)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/core/management/commands/migrate.py", line 243, in handle
        post_migrate_state = executor.migrate(
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/executor.py", line 117, in migrate
        state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/executor.py", line 147, in _migrate_all_forwards
        state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/executor.py", line 227, in apply_migration
        state = migration.apply(state, schema_editor)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/migration.py", line 114, in apply
        operation.state_forwards(self.app_label, project_state)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/operations/models.py", line 80, in state_forwards
        state.add_model(ModelState(
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/state.py", line 95, in add_model
        self.reload_model(app_label, model_name)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/state.py", line 156, in reload_model
        self._reload(related_models)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/state.py", line 189, in _reload
        self.apps.render_multiple(states_to_be_rendered)
      File "/home/koen/.virtualenvs/dtest/lib/python3.8/site-packages/django/db/migrations/state.py", line 310, in render_multiple
        raise InvalidBasesError(
    django.db.migrations.exceptions.InvalidBasesError: Cannot resolve bases for [<ModelState: 'MyApp.SubModel'>]
    This can happen if you are inheriting models from an app with migrations (e.g. contrib.auth)
     in an app with no migrations; see https://docs.djangoproject.com/en/3.1/topics/migrations/#dependencies for more
    

This bug does not occur if the app name is all-lowercase.

Digging into the code, I found that the MigrationOptimizer will combine two operations (Create model BaseModel and add ForeignKey-field r to BaseModel) without taking into account that SubModel depends on BaseModel.

Change History (5)

comment:1 by Koen De Wit, 4 years ago

Has patch: set
Owner: Koen De Wit removed
Status: assignednew

comment:2 by Mariusz Felisiak, 4 years ago

Needs tests: set
Owner: set to Koen De Wit
Status: newassigned
Summary: MigrationOptimizer mangles operation order if app name contains uppercase lettersMigrationOptimizer mangles operation order if app name contains uppercase letters.
Triage Stage: UnreviewedAccepted

comment:3 by Koen De Wit, 4 years ago

Needs tests: unset

comment:4 by Mariusz Felisiak, 4 years ago

Triage Stage: AcceptedReady for checkin

comment:5 by Mariusz Felisiak <felisiak.mariusz@…>, 4 years ago

Resolution: fixed
Status: assignedclosed

In 4c0b472:

Fixed #31954 -- Fixed migration optimization for MTI model creation with parent model with mixed case app label.

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