1
0
mirror of https://github.com/django/django.git synced 2025-10-31 09:41:08 +00:00

[1.5.x] 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.

Backport of 07fbc6a.
This commit is contained in:
Aymeric Augustin
2013-01-02 21:42:52 +01:00
parent da2cdd3a0f
commit 056ace0f39
5 changed files with 79 additions and 9 deletions

View File

@@ -497,7 +497,7 @@ class ForeignRelatedObjectsDescriptor(object):
except (AttributeError, KeyError):
db = self._db or router.db_for_read(self.model, instance=self.instance)
qs = super(RelatedManager, self).get_query_set().using(db).filter(**self.core_filters)
qs._known_related_object = (rel_field.name, self.instance)
qs._known_related_objects = {rel_field: {self.instance.pk: self.instance}}
return qs
def get_prefetch_query_set(self, instances):

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.