3 | | I think the the ORM should either have `RelatedManager.only` include the reverse field implicitly (which we've kind of rules out against in #33835) or we should find a way to more safely assign known related objects in the face of deferred fields. |
| 3 | I think the the ORM should either have `RelatedManager.only` include the reverse field implicitly (which we've kind of rules out against in #33835) or we should find a way to more safely assign known related objects in the face of deferred fields (e.g. make sure required `attrs` are in `obj.__dict__`). |
| 4 | |
| 5 | Potentially naive patch |
| 6 | |
| 7 | {{{#!diff |
| 8 | diff --git a/django/db/models/query.py b/django/db/models/query.py |
| 9 | index cb5c63c0d1..883a7a71e4 100644 |
| 10 | --- a/django/db/models/query.py |
| 11 | +++ b/django/db/models/query.py |
| 12 | @@ -107,18 +107,18 @@ def __iter__(self): |
| 13 | ( |
| 14 | field, |
| 15 | related_objs, |
| 16 | - operator.attrgetter( |
| 17 | - *[ |
| 18 | - ( |
| 19 | - field.attname |
| 20 | - if from_field == "self" |
| 21 | - else queryset.model._meta.get_field(from_field).attname |
| 22 | - ) |
| 23 | - for from_field in field.from_fields |
| 24 | - ] |
| 25 | - ), |
| 26 | + attnames := [ |
| 27 | + ( |
| 28 | + field.attname |
| 29 | + if from_field == "self" |
| 30 | + else queryset.model._meta.get_field(from_field).attname |
| 31 | + ) |
| 32 | + for from_field in field.from_fields |
| 33 | + ], |
| 34 | + operator.attrgetter(*attnames), |
| 35 | ) |
| 36 | for field, related_objs in queryset._known_related_objects.items() |
| 37 | + # if not is_deferred(field) |
| 38 | ] |
| 39 | for row in compiler.results_iter(results): |
| 40 | obj = model_cls.from_db( |
| 41 | @@ -131,10 +131,14 @@ def __iter__(self): |
| 42 | setattr(obj, attr_name, row[col_pos]) |
| 43 | |
| 44 | # Add the known related objects to the model. |
| 45 | - for field, rel_objs, rel_getter in known_related_objects: |
| 46 | + for field, rel_objs, attnames, rel_getter in known_related_objects: |
| 47 | # Avoid overwriting objects loaded by, e.g., select_related(). |
| 48 | if field.is_cached(obj): |
| 49 | continue |
| 50 | + # Avoid fetching potentially deferred attributes that would |
| 51 | + # result in unexpected queries. |
| 52 | + if any(attname not in obj.__dict__ for attname in attnames): |
| 53 | + continue |
| 54 | rel_obj_id = rel_getter(obj) |
| 55 | try: |
| 56 | rel_obj = rel_objs[rel_obj_id] |
| 57 | }}} |