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:
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user