Used simpler queries for m2m clearing when possible.

Refs #21169
This commit is contained in:
Anssi Kääriäinen 2013-10-09 15:29:14 +03:00
parent 17c3997f68
commit 52015b963d
3 changed files with 32 additions and 15 deletions

View File

@ -580,17 +580,20 @@ def create_many_related_manager(superclass, rel):
) )
do_not_call_in_templates = True do_not_call_in_templates = True
def _build_clear_filters(self, qs): def _build_remove_filters(self, removed_vals):
filters = Q(**{ filters = Q(**{self.source_field_name: self.related_val})
self.source_field_name: self.related_val, # No need to add a subquery condition if removed_vals is a QuerySet without
'%s__in' % self.target_field_name: qs # filters.
}) removed_vals_filters = (not isinstance(removed_vals, QuerySet) or
removed_vals._has_filters())
if removed_vals_filters:
filters &= Q(**{'%s__in' % self.target_field_name: removed_vals})
if self.symmetrical: if self.symmetrical:
filters |= Q(**{ symmetrical_filters = Q(**{self.target_field_name: self.related_val})
self.target_field_name: self.related_val, if removed_vals_filters:
'%s__in' % self.source_field_name: qs symmetrical_filters &= Q(
}) **{'%s__in' % self.source_field_name: removed_vals})
filters |= symmetrical_filters
return filters return filters
def get_queryset(self): def get_queryset(self):
@ -654,7 +657,7 @@ def create_many_related_manager(superclass, rel):
signals.m2m_changed.send(sender=self.through, action="pre_clear", signals.m2m_changed.send(sender=self.through, action="pre_clear",
instance=self.instance, reverse=self.reverse, instance=self.instance, reverse=self.reverse,
model=self.model, pk_set=None, using=db) model=self.model, pk_set=None, using=db)
filters = self._build_clear_filters(self.using(db)) filters = self._build_remove_filters(super(ManyRelatedManager, self).get_queryset().using(db))
self.through._default_manager.using(db).filter(filters).delete() self.through._default_manager.using(db).filter(filters).delete()
signals.m2m_changed.send(sender=self.through, action="post_clear", signals.m2m_changed.send(sender=self.through, action="post_clear",
@ -763,10 +766,13 @@ def create_many_related_manager(superclass, rel):
signals.m2m_changed.send(sender=self.through, action="pre_remove", signals.m2m_changed.send(sender=self.through, action="pre_remove",
instance=self.instance, reverse=self.reverse, instance=self.instance, reverse=self.reverse,
model=self.model, pk_set=old_ids, using=db) model=self.model, pk_set=old_ids, using=db)
target_model_qs = super(ManyRelatedManager, self).get_queryset()
old_vals_qs = self.using(db).filter(**{ if target_model_qs._has_filters():
'%s__in' % self.target_field.related_field.attname: old_ids}) old_vals = target_model_qs.using(db).filter(**{
filters = self._build_clear_filters(old_vals_qs) '%s__in' % self.target_field.related_field.attname: old_ids})
else:
old_vals = old_ids
filters = self._build_remove_filters(old_vals)
self.through._default_manager.using(db).filter(filters).delete() self.through._default_manager.using(db).filter(filters).delete()
signals.m2m_changed.send(sender=self.through, action="post_remove", signals.m2m_changed.send(sender=self.through, action="post_remove",

View File

@ -1026,6 +1026,14 @@ class QuerySet(object):
# If we have a new hint for an existing key, overwrite with the new value. # If we have a new hint for an existing key, overwrite with the new value.
self._hints.update(hints) self._hints.update(hints)
def _has_filters(self):
"""
Checks if this QuerySet has any filtering going on. Note that this
isn't equivalent for checking if all objects are present in results,
for example qs[1:]._has_filters() -> True.
"""
return self.query.has_filters()
class InstanceCheckMeta(type): class InstanceCheckMeta(type):
def __instancecheck__(self, instance): def __instancecheck__(self, instance):

View File

@ -430,6 +430,9 @@ class Query(object):
return number return number
def has_filters(self):
return self.where or self.having
def has_results(self, using): def has_results(self, using):
q = self.clone() q = self.clone()
if not q.distinct: if not q.distinct: