Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#25040 closed Bug (fixed)

get_related_models_recursive() cannot handle a model with a GenericFK

Reported by: Kai Richard König Owned by: Kai Richard König
Component: Migrations Version: 1.8
Severity: Normal Keywords: migrations generic fk
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 (last modified by Kai Richard König)

Example models.py:

from django.db import models
from django.contrib.contenttypes import generic
from django.db.migrations.state import get_related_models_recursive

class BaseModel(models.Model):
    current_state = generic.GenericForeignKey()

get_related_models_recursive(BaseModel) # Exception!

Change History (12)

comment:1 Changed 5 years ago by Kai Richard König

Description: modified (diff)
Needs tests: set

comment:2 Changed 5 years ago by Kai Richard König

Has patch: set
Owner: changed from nobody to Kai Richard König
Status: newassigned

comment:3 Changed 5 years ago by Marten Kenbeek

get_related_models_recursive() is a private API that is only used in a context where there shouldn't be any generic foreign keys. I'm a bit reluctant to change this, as it can potentially hide other bugs which are more difficult to debug.

Could you give some more context for the exception you're seeing? If you're seeing this exception in the migrations framework, and not because you're using this function on an actual model class as above, there must be some root cause that is a bug.

For further reference, it is extremely useful if you include the full traceback.

comment:4 Changed 5 years ago by Kai Richard König

The Exception is the same as when i run the above example.
Up until now i have been unable to reproduce the root cause, so I will explain what I have "seen" so far. (Fairly large project)
While walking the related_models in _related_models there is a relation that returns a real model
for related_model instead of the __fake__ one. Also I am seeing the meta containing the django.app registry instead of the state.apps any idea how to might end up there?

comment:5 Changed 5 years ago by Kai Richard König

Stacktrace for completeness

  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
    utility.execute()
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 330, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 30, in run_from_argv
    super(Command, self).run_from_argv(argv)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/base.py", line 390, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 74, in execute
    super(Command, self).execute(*args, **options)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/base.py", line 441, in execute
    output = self.handle(*args, **options)
  File "/var/www/app/releases/1/app/django-sites/paessler/core/management/commands/bsxtest.py", line 19, in handle
    TestCommand.handle(self, *test_labels, **options)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 90, in handle
    failures = test_runner.run_tests(test_labels)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/test/runner.py", line 210, in run_tests
    old_config = self.setup_databases()
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/test/runner.py", line 166, in setup_databases
    **kwargs
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/test/runner.py", line 370, in setup_databases
    serialize=connection.settings_dict.get("TEST", {}).get("SERIALIZE", True),
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/db/backends/base/creation.py", line 368, in create_test_db
    test_flush=not keepdb,
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 120, in call_command
    return command.execute(*args, **defaults)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/base.py", line 441, in execute
    output = self.handle(*args, **options)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/core/management/commands/migrate.py", line 221, in handle
    executor.migrate(targets, plan, fake=fake, fake_initial=fake_initial)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/db/migrations/executor.py", line 89, in migrate
    for migration, _ in full_plan:
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/db/migrations/migration.py", line 83, in mutate_state
    operation.state_forwards(self.app_label, new_state)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/db/migrations/operations/models.py", line 53, in state_forwards
    list(self.managers),
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/db/migrations/state.py", line 84, in add_model
    self.reload_model(app_label, model_name)
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/db/migrations/state.py", line 125, in reload_model
    related_models.update(get_related_models_recursive(rel_model))
  File "/var/www/app/releases/1/venv-latest/local/lib/python2.7/site-packages/django/db/migrations/state.py", line 60, in get_related_models_recursive
    rel_app_label, rel_model_name = rel_mod._meta.app_label, rel_mod._meta.model_name
AttributeError: 'NoneType' object has no attribute '_meta'

comment:6 Changed 5 years ago by Tom Wardill

I have seen this too.

In my case, I managed to pin it to an interaction between django-filer, django-cms and the migrations framework.

If I had a FilerImageField on a model that inherited CMSPlugin and attempted to migrate it, it would pull in a model in my code that had a GenericForeignKey, leading to the same interaction as described in this ticket.

I can validate that the sample code given here reproduces the issue in the smallest case.

comment:7 Changed 5 years ago by Tim Graham

Needs tests: unset
Triage Stage: UnreviewedReady for checkin
Type: UncategorizedBug

comment:8 Changed 5 years ago by Tim Graham

Summary: #get_related_models_recursive cannot digest a Model with a GenericFKget_related_models_recursive() cannot handle a model with a GenericFK

comment:9 Changed 5 years ago by Tim Graham <timograham@…>

Resolution: fixed
Status: assignedclosed

In 60f795c0:

Fixed #25040 -- Fixed migrations state crash with GenericForeignKey

comment:10 Changed 5 years ago by Tim Graham

Resolution: fixed
Status: closednew

Reopening to backport to 1.8 as it's a regression.

comment:11 Changed 5 years ago by Tim Graham <timograham@…>

Resolution: fixed
Status: newclosed

In 1815287:

[1.8.x] Fixed #25040 -- Fixed migrations state crash with GenericForeignKey

Backport of 60f795c0608119cdb02d61d3eb59f34ebdb55754 from master

comment:12 Changed 5 years ago by Tim Graham <timograham@…>

In 26dcf739:

Forwardported release note for refs #25040.

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