Opened 32 hours ago

Closed 28 hours ago

#36713 closed Uncategorized (invalid)

RecursionError with only() and fields usage in __init__

Reported by: Michal Čihař Owned by:
Component: Uncategorized Version: 5.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Django ends up in infinite recursion when combining only() and accessing class attributes in __init__:

  File "/tmp/django-recursion-test/app/models.py", line 14, in __init__
    print(self.descriptions)
          ^^^^^^^^^^^^^^^^^
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query_utils.py", line 267, in __get__
    instance.refresh_from_db(fields=[field_name])
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/base.py", line 758, in refresh_from_db
    db_instance = db_instance_qs.get()
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query.py", line 633, in get
    num = len(clone)
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query.py", line 370, in __len__
    self._fetch_all()
    ~~~~~~~~~~~~~~~^^
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query.py", line 1995, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query.py", line 126, in __iter__
    obj = model_cls.from_db(
        db, init_list, row[model_fields_start:model_fields_end]
    )
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/base.py", line 605, in from_db
    new = cls(*values)
  File "/tmp/django-recursion-test/app/models.py", line 15, in __init__
    print(self.notes)
          ^^^^^^^^^^
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query_utils.py", line 267, in __get__
    instance.refresh_from_db(fields=[field_name])
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/base.py", line 758, in refresh_from_db
    db_instance = db_instance_qs.get()
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query.py", line 633, in get
    num = len(clone)
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query.py", line 370, in __len__
    self._fetch_all()
    ~~~~~~~~~~~~~~~^^
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query.py", line 1995, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/query.py", line 126, in __iter__
    obj = model_cls.from_db(
        db, init_list, row[model_fields_start:model_fields_end]
    )
  File "/tmp/django-test/lib/python3.13/site-packages/django/db/models/base.py", line 605, in from_db
    new = cls(*values)
  File "/tmp/django-recursion-test/app/models.py", line 14, in __init__
    print(self.descriptions)
          ^^^^^^^^^^^^^^^^^

Preconditions for the error:

  • Model with multiple fields
  • Using at least two model fields in __init__ (it does work fine with accessing just one)
  • Using only() with fields used in __init__ but missing a field that will be used later

To trigger this error, following model was used:

class Project(models.Model):
    name = models.CharField(max_length=100)
    slug = models.CharField(max_length=100)
    descriptions = models.TextField()
    notes = models.TextField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print(self.descriptions)
        print(self.notes)

And this is the code to trigger it:

        Project.objects.create(name="Test", slug="test")
        projects = Project.objects.only("descriptions", "notes")
        # object is constructed and the field included only can be accessed:
        self.assertEqual(projects[0].notes, "")
        # this fails with RecursionError
        self.assertEqual(projects[0].slug, "test")

Reproduced with both Django 5.2 and 6.0b1.

Reproducing tests are available at https://github.com/nijel/django-recursion-test.

Change History (1)

comment:1 by Simon Charette, 28 hours ago

Resolution: invalid
Status: newclosed

Please refer to the documentation about Model.__init__ and the note about overriding it

You may be tempted to customize the model by overriding the __init__ method. If you do so, however, take care not to change the calling signature as any change may prevent the model instance from being saved. Additionally, referring to model fields within __init__ may potentially result in infinite recursion errors in some circumstances.

You can refer to #31435 for some discussion on the subject but the TL;DR is that your __init__ override must account for some fields being potentially deferred.

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