#34807 closed Bug (fixed)

importing `django.forms` causes circular import error

Reported by: Collin Anderson Owned by: Natalia Bidart
Component: Forms Version: dev
Severity: Release blocker Keywords:
Cc: Nick Pope 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 Tim Graham)

circular import between forms and models and choices.

before [500e01073adda32d514962] you could import forms just fine.

git checkout 500e01073adda32d514962^
python3 -c"from django.db import models"  # works fine
python3 -c"from django import forms"  # works fine

but since [500e01073adda32d514962] this hasn't worked due to circular import.

git checkout 500e01073adda32d514962
python3 -c"from django.db import models"  # works fine
python3 -c"from django import forms"  # fails:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "django/forms/__init__.py", line 6, in <module>
    from django.forms.boundfield import *  # NOQA
  File "django/forms/boundfield.py", line 5, in <module>
    from django.forms.widgets import MultiWidget, Textarea, TextInput
  File "django/forms/widgets.py", line 15, in <module>
    from django.utils.choices import normalize_choices
  File "django/utils/choices.py", line 3, in <module>
    from django.db.models.enums import ChoicesMeta
  File "django/db/models/__init__.py", line 3, in <module>
    from django.db.models.aggregates import *  # NOQA
  File "django/db/models/aggregates.py", line 5, in <module>
    from django.db.models.expressions import Case, Func, Star, Value, When
  File "django/db/models/expressions.py", line 12, in <module>
    from django.db.models import fields
  File "django/db/models/fields/__init__.py", line 18, in <module>
    from django.utils.choices import CallableChoiceIterator, normalize_choices
ImportError: cannot import name 'CallableChoiceIterator' from partially initialized module 'django.utils.choices' (most likely due to a circular import) (django/utils/choices.py)

python3 -c"from django.utils import choices" # also fails:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "django/utils/choices.py", line 3, in <module>
    from django.db.models.enums import ChoicesMeta
  File "django/db/models/__init__.py", line 3, in <module>
    from django.db.models.aggregates import *  # NOQA
  File "django/db/models/aggregates.py", line 5, in <module>
    from django.db.models.expressions import Case, Func, Star, Value, When
  File "django/db/models/expressions.py", line 12, in <module>
    from django.db.models import fields
  File "django/db/models/fields/__init__.py", line 10, in <module>
    from django import forms
  File "django/forms/__init__.py", line 6, in <module>
    from django.forms.boundfield import *  # NOQA
  File "django/forms/boundfield.py", line 5, in <module>
    from django.forms.widgets import MultiWidget, Textarea, TextInput
  File "django/forms/widgets.py", line 15, in <module>
    from django.utils.choices import normalize_choices
ImportError: cannot import name 'normalize_choices' from partially initialized module 'django.utils.choices' (most likely due to a circular import) (django/utils/choices.py)

Models has always had a dependency on Forms, but now Models depends on Forms depends on utils.choices which depend on models.enums.ChoicesMeta

Summary of the circle: django.forms -> forms.widgets -> django.utils.choices -> django.db.models.enums -> models.__init__ -> models.aggregates -> models.fields -> django.forms.

If django.db.models is imported before forms then it happens to import fine because the order of imports happens to lines up perfectally, but it's very very fragile. models.__init__ -> models.aggregates -> models.fields -> django.forms -> forms.widgets -> django.utils.choices -> django.db.models.enums.

Change History (8)

comment:1 by Tim Graham, 15 months ago

Description: modified (diff)
Severity: NormalRelease blocker
Triage Stage: UnreviewedAccepted

comment:2 by Natalia Bidart, 15 months ago

Owner: changed from nobody to Natalia Bidart
Status: newassigned

comment:3 by Natalia Bidart, 15 months ago

Cc: Nick Pope added

comment:4 by Natalia Bidart, 15 months ago

One thing that I don't understand is that when using a Django shell, the import works just fine. Tests also perform multiple from django import forms and they do not fail. OTOH, I can reproduce with the provided:

python3 -c"from django import forms"

Does anyone understand why the difference? (my main question is why this wasn't picked up by the tests or my IRL testing within a Django test project)

Version 0, edited 15 months ago by Natalia Bidart (next)

comment:5 by Natalia Bidart, 15 months ago

Has patch: set

comment:6 by Collin Anderson, 15 months ago

./manage.py shell imports all models as part of django.setup().

comment:7 by Natalia Bidart, 15 months ago

Triage Stage: AcceptedReady for checkin

comment:8 by GitHub <noreply@…>, 15 months ago

Resolution: fixed
Status: assignedclosed

In 9c68792:

Fixed #34807 -- Avoided circular import between forms, models, and utils' choices.

Thanks Collin Anderson for the report.

Regression in 500e01073adda32d5149624ee9a5cb7aa3d3583f.

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