Opened 5 years ago

Closed 5 years ago

Last modified 4 years ago

#22932 closed Bug (fixed)

"makemigrations" generates circular dependencies

Reported by: Manuel Kaufmann Owned by: nobody
Component: Migrations Version: master
Severity: Normal Keywords: circular dependency, migrations, makemigrations
Cc: Manuel Kaufmann, berto Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I'm having an issue with the new migrations system included in Django. The steps that I'm following to reproduce is, once the example project is downloaded just run:

  1. manage.py makemigrations
  2. manage.py migrate
Traceback (most recent call last):
  File "/home/humitos/Source/migration_test/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 330, in execute_from_command_line
    utility.execute()
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 322, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/core/management/base.py", line 363, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/core/management/base.py", line 412, in execute
    output = self.handle(*args, **options)
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/core/management/commands/makemigrations.py", line 78, in handle
    loader.project_state(),
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/db/migrations/loader.py", line 268, in project_state
    return self.graph.make_state(nodes=nodes, at_end=at_end, real_apps=list(self.unmigrated_apps))
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/db/migrations/graph.py", line 140, in make_state
    for migration in self.forwards_plan(node):
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/db/migrations/graph.py", line 55, in forwards_plan
    return self.dfs(node, lambda x: self.dependencies.get(x, set()))
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/db/migrations/graph.py", line 121, in dfs
    return _dfs(start, get_children, [])
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/db/migrations/graph.py", line 113, in _dfs
    results = _dfs(n, get_children, path) + results
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/db/migrations/graph.py", line 113, in _dfs
    results = _dfs(n, get_children, path) + results
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/db/migrations/graph.py", line 113, in _dfs
    results = _dfs(n, get_children, path) + results
  File "/home/humitos/.virtualenvs/tmp-e997f5cc453b28eb/local/lib/python2.7/site-packages/django/db/migrations/graph.py", line 105, in _dfs
    raise CircularDependencyError(path[path.index(start):] + [start])
django.db.migrations.graph.CircularDependencyError: [('challenge', u'0001_initial'), (u'team', u'0001_initial'), ('challenge', u'0001_initial')]

If I remove the myapp.team.TeamCaptain model and the migration files already created and I run those commands again, it creates the proper migration files and it works.

I've been taking a look at #22325 but it doesn't seem to be the same situation.

I tried this in Django 1.7b4, 1.7c1 and master and I always got the same error.

What other kind of information can I provide?

Attachments (3)

migration_test.tar.bz2 (2.2 KB) - added by Manuel Kaufmann 5 years ago.
Project example that makes Django to create Circular Migration Dependencies
team.0001_initial.py.diff (969 bytes) - added by Manuel Kaufmann 5 years ago.
Migration edited by hand
team.0002_auto_20140704_1453.py (769 bytes) - added by Manuel Kaufmann 5 years ago.
New migration added with --empty and then edited

Download all attachments as: .zip

Change History (9)

Changed 5 years ago by Manuel Kaufmann

Attachment: migration_test.tar.bz2 added

Project example that makes Django to create Circular Migration Dependencies

comment:1 Changed 5 years ago by Manuel Kaufmann

Cc: Manuel Kaufmann added

comment:2 Changed 5 years ago by Andrew Godwin <andrew@…>

Resolution: fixed
Status: newclosed

In e9249bc20b49c7b6721a8a58bc4bb9dd4827855a:

Fixed #22932: Documented circular dependency issues with swappable user

comment:3 Changed 5 years ago by Andrew Godwin

This is unavoidable, as Django can't detect swappable dependencies to resolve the loop at makemigrations time (thanks, swappable models!), but I've added some documentation to the custom user model docs to highlight that this might occur and how to fix it.

The fix, incidentally, is to manually uncircularise the dependency loop. In this example, the dependency graph is:

    team.TeamCaptain -> challenge.MyUser -> team.Team
                   \_________________________^

As you can see, this isn't actually circular, it's just that the start and end are in the same app and Django can't work out what app the middle one is in (as it's dynamic and can change based on project settings). The solution is to move the TeamCaptain model into a second migration in the team app, and make sure the migration dependencies are modified appropriately (i.e. that the AUTH_USER_MODEL dependency is also moved to that second migration). This will solve this case.

The other possible case is when there actually is a dependency loop (e.g. two models have foreign keys pointing at each other). You can resolve this by splitting one ForeignKey off into a separate migration; just construct this situation without swappable models and run makemigrations and you'll get an example of how it's done.

comment:4 Changed 5 years ago by Manuel Kaufmann

Well, I followed the solution suggested by andrewgodwin and it worked. I will explain here what are the specific steps that I followed (actually, I followed these steps in the production site and it also worked):

  1. Download the .tar.gz and uncompress it in /tmp, then cd into it
  2. Run dj makemigrations
  3. Remove the swappable_dependency and the migrations.CreateModel(name='TeamCaptain', ... from team.0001_initial.py
  4. Run dj makemigrations --empty team
  5. Edit this new empty migration created by Django by adding the swappable_dependency and the migrations.CreateModel(name='TeamCaptain', ... removed from team.0001_initial.py
  6. Run dj migrate

I'm attaching the .diff and the new migration

Changed 5 years ago by Manuel Kaufmann

Attachment: team.0001_initial.py.diff added

Migration edited by hand

Changed 5 years ago by Manuel Kaufmann

New migration added with --empty and then edited

comment:5 Changed 4 years ago by berto

I am running into this error when squashing migrations. The swappable dep is from Django REST Framework's authkey app. After reading the fix above I'm not exactly sure how to go about fixing this within a squashed migration.

When running the squashed migration it tells me that the authkey tables do not exist. When adding ('authkey', '0001_initial') to the dependencies list, I get the circular dependency error.

I'm seeing this when running tests, and I'm using Django 1.7.10.

Any clues? Thank you!

comment:6 Changed 4 years ago by berto

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