From e07a457c0054fa20dcbbf5bf41bc07191bdaa895 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Mon, 28 Apr 2008 04:29:06 +0000 Subject: [PATCH] Fixed #7096 -- The simplifications in [7461] weren't complete. They broke multi-component exclude() calls. Fixed that. git-svn-id: http://code.djangoproject.com/svn/django/trunk@7493 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/sql/query.py | 17 +++++++++++------ tests/regressiontests/queries/models.py | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 59b2ebdd68..bfed984953 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -895,9 +895,15 @@ class Query(object): Add a single filter to the query. The 'filter_expr' is a pair: (filter_string, value). E.g. ('name__contains', 'fred') - If 'negate' is True, this is an exclude() filter. If 'trim' is True, we - automatically trim the final join group (used internally when - constructing nested queries). + If 'negate' is True, this is an exclude() filter. It's important to + note that this method does not negate anything in the where-clause + object when inserting the filter constraints. This is because negated + filters often require multiple calls to add_filter() and the negation + should only happen once. So the caller is responsible for this (the + caller will normally be add_q(), so that as an example). + + If 'trim' is True, we automatically trim the final join group (used + internally when constructing nested queries). If 'can_reuse' is a set, we are processing a component of a multi-component filter (e.g. filter(Q1, Q2)). In this case, 'can_reuse' @@ -1001,7 +1007,6 @@ class Query(object): self.where.add((alias, col, field, lookup_type, value), connector) if negate: - self.where.negate() for alias in join_list: self.promote_alias(alias) if final > 1 and lookup_type != 'isnull': @@ -1039,12 +1044,12 @@ class Query(object): self.where.start_subtree(connector) self.add_q(child, used_aliases) self.where.end_subtree() - if q_object.negated: - self.where.children[-1].negate() else: self.add_filter(child, connector, q_object.negated, can_reuse=used_aliases) connector = q_object.connector + if q_object.negated: + self.where.negate() if subtree: self.where.end_subtree() diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index 5529bced8b..e108c46352 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -658,5 +658,24 @@ Bug #7098 -- Make sure semi-deprecated ordering by related models syntax still works. >>> Item.objects.values('note__note').order_by('queries_note.note', 'id') [{'note__note': u'n2'}, {'note__note': u'n3'}, {'note__note': u'n3'}, {'note__note': u'n3'}] + +Bug #7096 -- Make sure exclude() with multiple conditions continues to work. +>>> Tag.objects.filter(parent=t1, name='t3').order_by('name') +[] +>>> Tag.objects.exclude(parent=t1, name='t3').order_by('name') +[, , , ] +>>> Item.objects.exclude(tags__name='t1', name='one').order_by('name').distinct() +[, , ] +>>> Item.objects.filter(name__in=['three', 'four']).exclude(tags__name='t1').order_by('name') +[, ] + +More twisted cases, involving nested negations. +>>> Item.objects.exclude(~Q(tags__name='t1', name='one')) +[] +>>> Item.objects.filter(~Q(tags__name='t1', name='one'), name='two') +[] +>>> Item.objects.exclude(~Q(tags__name='t1', name='one'), name='two') +[, , ] + """}