1
0
mirror of https://github.com/django/django.git synced 2025-10-29 16:46:11 +00:00

Fixed #19547 -- Caching of related instances.

When &'ing or |'ing querysets, wrong values could be cached, and crashes
could happen.

Thanks Marc Tamlyn for figuring out the problem and writing the patch.
This commit is contained in:
Aymeric Augustin
2013-01-02 21:42:52 +01:00
parent 695b2089e7
commit 07fbc6ae0e
5 changed files with 79 additions and 9 deletions

View File

@@ -44,7 +44,7 @@ class QuerySet(object):
self._for_write = False
self._prefetch_related_lookups = []
self._prefetch_done = False
self._known_related_object = None # (attname, rel_obj)
self._known_related_objects = {} # {rel_field, {pk: rel_obj}}
########################
# PYTHON MAGIC METHODS #
@@ -221,6 +221,7 @@ class QuerySet(object):
if isinstance(other, EmptyQuerySet):
return other._clone()
combined = self._clone()
combined._merge_known_related_objects(other)
combined.query.combine(other.query, sql.AND)
return combined
@@ -229,6 +230,7 @@ class QuerySet(object):
combined = self._clone()
if isinstance(other, EmptyQuerySet):
return combined
combined._merge_known_related_objects(other)
combined.query.combine(other.query, sql.OR)
return combined
@@ -289,10 +291,9 @@ class QuerySet(object):
init_list.append(field.attname)
model_cls = deferred_class_factory(self.model, skip)
# Cache db, model and known_related_object outside the loop
# Cache db and model outside the loop
db = self.db
model = self.model
kro_attname, kro_instance = self._known_related_object or (None, None)
compiler = self.query.get_compiler(using=db)
if fill_cache:
klass_info = get_klass_info(model, max_depth=max_depth,
@@ -323,9 +324,16 @@ class QuerySet(object):
for i, aggregate in enumerate(aggregate_select):
setattr(obj, aggregate, row[i + aggregate_start])
# Add the known related object to the model, if there is one
if kro_instance:
setattr(obj, kro_attname, kro_instance)
# Add the known related objects to the model, if there are any
if self._known_related_objects:
for field, rel_objs in self._known_related_objects.items():
pk = getattr(obj, field.get_attname())
try:
rel_obj = rel_objs[pk]
except KeyError:
pass # may happen in qs1 | qs2 scenarios
else:
setattr(obj, field.name, rel_obj)
yield obj
@@ -902,7 +910,7 @@ class QuerySet(object):
c = klass(model=self.model, query=query, using=self._db)
c._for_write = self._for_write
c._prefetch_related_lookups = self._prefetch_related_lookups[:]
c._known_related_object = self._known_related_object
c._known_related_objects = self._known_related_objects
c.__dict__.update(kwargs)
if setup and hasattr(c, '_setup_query'):
c._setup_query()
@@ -942,6 +950,13 @@ class QuerySet(object):
"""
pass
def _merge_known_related_objects(self, other):
"""
Keep track of all known related objects from either QuerySet instance.
"""
for field, objects in other._known_related_objects.items():
self._known_related_objects.setdefault(field, {}).update(objects)
def _setup_aggregate_query(self, aggregates):
"""
Prepare the query for computing a result that contains aggregate annotations.