summaryrefslogtreecommitdiff
path: root/django/db/models/sql
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2024-04-05 23:20:41 -0400
committernessita <124304+nessita@users.noreply.github.com>2024-04-23 13:17:17 -0300
commit195d885ca01b14e3ce9a1881c3b8f7074f953736 (patch)
tree559f8ababd0bb79626a0fa20c00a4d1bf6827a48 /django/db/models/sql
parent83f5478225588f31e7cbbfed63a4a2b936abc03f (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.py7
-rw-r--r--django/db/models/sql/query.py46
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):