diff options
| author | Simon Charette <charette.s@gmail.com> | 2024-04-05 23:20:41 -0400 |
|---|---|---|
| committer | nessita <124304+nessita@users.noreply.github.com> | 2024-04-23 13:17:17 -0300 |
| commit | 195d885ca01b14e3ce9a1881c3b8f7074f953736 (patch) | |
| tree | 559f8ababd0bb79626a0fa20c00a4d1bf6827a48 /django/db/models/sql/query.py | |
| parent | 83f5478225588f31e7cbbfed63a4a2b936abc03f (diff) | |
Refs #35356 -- Clarified select related with masked field logic.
By always including related objects in the select mask via adjusting the
defer logic (_get_defer_select_mask()), it becomes possible for
select_related_descend() to treat forward and reverse relationships
indistinctively.
This work also simplifies and adds comments to
select_related_descend() to make it easier to understand.
Diffstat (limited to 'django/db/models/sql/query.py')
| -rw-r--r-- | django/db/models/sql/query.py | 46 |
1 files changed, 23 insertions, 23 deletions
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index c5c58b1788..a7bc0610c8 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -791,44 +791,44 @@ class Query(BaseExpression): if select_mask is None: select_mask = {} select_mask[opts.pk] = {} - # All concrete fields that are not part of the defer mask must be - # loaded. If a relational field is encountered it gets added to the - # mask for it be considered if `select_related` and the cycle continues - # by recursively calling this function. - for field in opts.concrete_fields: + # All concrete fields and related objects that are not part of the + # defer mask must be included. If a relational field is encountered it + # gets added to the mask for it be considered if `select_related` and + # the cycle continues by recursively calling this function. + for field in opts.concrete_fields + opts.related_objects: field_mask = mask.pop(field.name, None) - field_att_mask = mask.pop(field.attname, None) + field_att_mask = None + if field_attname := getattr(field, "attname", None): + field_att_mask = mask.pop(field_attname, None) if field_mask is None and field_att_mask is None: select_mask.setdefault(field, {}) elif field_mask: if not field.is_relation: raise FieldError(next(iter(field_mask))) + # Virtual fields such as many-to-many and generic foreign keys + # cannot be effectively deferred. Historically, they were + # allowed to be passed to QuerySet.defer(). Ignore such field + # references until a layer of validation at mask alteration + # time is eventually implemented. + if field.many_to_many: + continue field_select_mask = select_mask.setdefault(field, {}) - related_model = field.remote_field.model._meta.concrete_model + related_model = field.related_model._meta.concrete_model self._get_defer_select_mask( related_model._meta, field_mask, field_select_mask ) - # Remaining defer entries must be references to reverse relationships. - # The following code is expected to raise FieldError if it encounters - # a malformed defer entry. + # Remaining defer entries must be references to filtered relations + # otherwise they are surfaced as missing field errors. for field_name, field_mask in mask.items(): if filtered_relation := self._filtered_relations.get(field_name): relation = opts.get_field(filtered_relation.relation_name) field_select_mask = select_mask.setdefault((field_name, relation), {}) + related_model = relation.related_model._meta.concrete_model + self._get_defer_select_mask( + related_model._meta, field_mask, field_select_mask + ) else: - relation = opts.get_field(field_name) - # While virtual fields such as many-to-many and generic foreign - # keys cannot be effectively deferred we've historically - # allowed them to be passed to QuerySet.defer(). Ignore such - # field references until a layer of validation at mask - # alteration time will be implemented eventually. - if not hasattr(relation, "field"): - continue - field_select_mask = select_mask.setdefault(relation, {}) - related_model = relation.related_model._meta.concrete_model - self._get_defer_select_mask( - related_model._meta, field_mask, field_select_mask - ) + opts.get_field(field_name) return select_mask def _get_only_select_mask(self, opts, mask, select_mask=None): |
