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.
Note:
See TracTickets
for help on using tickets.
Please refer to the documentation about
Model.__init__and the note about overriding itYou 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.