From 0cf85e6b074794ac91857aa097f0b3dc3e6d9468 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Fri, 16 Nov 2018 17:23:12 -0500 Subject: [PATCH] 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. --- django/db/models/fields/related_descriptors.py | 16 ++++++++++++---- django/db/models/query.py | 16 +++++++--------- 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 e3c74cbcfaa..8b9ddf0fb43 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 b40c768f5c8..85c695d298d 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: