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 | |
| 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')
| -rw-r--r-- | django/db/models/sql/compiler.py | 7 | ||||
| -rw-r--r-- | django/db/models/sql/query.py | 46 |
2 files changed, 26 insertions, 27 deletions
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 7541817ba0..7377e555c3 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1259,11 +1259,10 @@ class SQLCompiler: ] for related_object, related_field, model in related_fields: if not select_related_descend( - related_field, + related_object, restricted, requested, select_mask, - reverse=True, ): continue @@ -1280,7 +1279,7 @@ class SQLCompiler: "model": model, "field": related_field, "reverse": True, - "local_setter": related_field.remote_field.set_cached_value, + "local_setter": related_object.set_cached_value, "remote_setter": related_field.set_cached_value, "from_parent": from_parent, } @@ -1296,7 +1295,7 @@ class SQLCompiler: select_fields.append(len(select)) select.append((col, None)) klass_info["select_fields"] = select_fields - next = requested.get(related_field.related_query_name(), {}) + next = requested.get(related_field_name, {}) next_klass_infos = self.get_related_selections( select, related_select_mask, 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): |
