summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2018-11-16 17:23:12 -0500
committerSimon Charette <charette.s@gmail.com>2018-11-17 18:56:11 -0500
commit0cf85e6b074794ac91857aa097f0b3dc3e6d9468 (patch)
treeda1b84c29896dac2041f8d2dad9926d706dc9db7
parentf436c82637dafa3a9abbd65a3be77bf7ad431213 (diff)
Refs #29908 -- Optimized known related objects assignment.
Since CPython implements a C level attrgetter(*attrs) it even outperforms the most common case of a single known related object since the resulting attribute values tuple is built in C.
-rw-r--r--django/db/models/fields/related_descriptors.py16
-rw-r--r--django/db/models/query.py16
2 files changed, 19 insertions, 13 deletions
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index e3c74cbcfa..8b9ddf0fb4 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -63,6 +63,7 @@ and two directions (forward and reverse) for a total of six combinations.
``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
"""
+from django.core.exceptions import FieldError
from django.db import connections, router, transaction
from django.db.models import Q, signals
from django.db.models.query import QuerySet
@@ -581,10 +582,17 @@ def create_reverse_many_to_one_manager(superclass, rel):
# that abuse create_reverse_many_to_one_manager() with reverse
# one-to-many relationships instead and break known related
# objects assignment.
- rel_obj_id = tuple([
- getattr(self.instance, target_field.attname)
- for target_field in self.field.get_path_info()[-1].target_fields
- ])
+ try:
+ target_field = self.field.target_field
+ except FieldError:
+ # The relationship has multiple target fields. Use a tuple
+ # for related object id.
+ rel_obj_id = tuple([
+ getattr(self.instance, target_field.attname)
+ for target_field in self.field.get_path_info()[-1].target_fields
+ ])
+ else:
+ rel_obj_id = getattr(self.instance, target_field.attname)
queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}}
return queryset
diff --git a/django/db/models/query.py b/django/db/models/query.py
index b40c768f5c..85c695d298 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -62,14 +62,12 @@ class ModelIterable(BaseIterable):
for f in select[model_fields_start:model_fields_end]]
related_populators = get_related_populators(klass_info, select, db)
known_related_objects = [
- (field, related_objs, [
- operator.attrgetter(
- field.attname
- if from_field == 'self' else
- queryset.model._meta.get_field(from_field).attname
- )
+ (field, related_objs, operator.attrgetter(*[
+ field.attname
+ if from_field == 'self' else
+ queryset.model._meta.get_field(from_field).attname
for from_field in field.from_fields
- ]) for field, related_objs in queryset._known_related_objects.items()
+ ])) for field, related_objs in queryset._known_related_objects.items()
]
for row in compiler.results_iter(results):
obj = model_cls.from_db(db, init_list, row[model_fields_start:model_fields_end])
@@ -80,11 +78,11 @@ class ModelIterable(BaseIterable):
setattr(obj, attr_name, row[col_pos])
# Add the known related objects to the model.
- for field, rel_objs, rel_getters in known_related_objects:
+ for field, rel_objs, rel_getter in known_related_objects:
# Avoid overwriting objects loaded by, e.g., select_related().
if field.is_cached(obj):
continue
- rel_obj_id = tuple([rel_getter(obj) for rel_getter in rel_getters])
+ rel_obj_id = rel_getter(obj)
try:
rel_obj = rel_objs[rel_obj_id]
except KeyError: