Skip to content

Commit

Permalink
Refs #29908 -- Optimized known related objects assignment.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
charettes committed Nov 17, 2018
1 parent f436c82 commit 0cf85e6
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 13 deletions.
16 changes: 12 additions & 4 deletions django/db/models/fields/related_descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class Child(Model):
``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
Expand Down Expand Up @@ -581,10 +582,17 @@ def _apply_rel_filters(self, queryset):
# 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

Expand Down
16 changes: 7 additions & 9 deletions django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,12 @@ def __iter__(self):
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])
Expand All @@ -80,11 +78,11 @@ def __iter__(self):
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:
Expand Down

0 comments on commit 0cf85e6

Please sign in to comment.