From 60c3e55b1f7c87ef9fa7c5ffdaf6e8dba5ab7043 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 26 May 2006 23:41:43 +0000 Subject: [PATCH] Fixed bug with QuerySet.exclude() failing to do negations on Q objects, and at the same time generalised exclude/QNot so that they work for 'external' Q objects i.e. ones that simply have 'get_sql' defined. git-svn-id: http://code.djangoproject.com/svn/django/trunk@2997 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/query.py | 19 +++++++++++++------ tests/modeltests/or_lookups/models.py | 4 ++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 1107269235..91ae294a62 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -291,22 +291,26 @@ class QuerySet(object): def filter(self, *args, **kwargs): "Returns a new QuerySet instance with the args ANDed to the existing set." - return self._filter_or_exclude(Q, *args, **kwargs) + return self._filter_or_exclude(None, *args, **kwargs) def exclude(self, *args, **kwargs): "Returns a new QuerySet instance with NOT (args) ANDed to the existing set." return self._filter_or_exclude(QNot, *args, **kwargs) - def _filter_or_exclude(self, qtype, *args, **kwargs): + def _filter_or_exclude(self, mapper, *args, **kwargs): + # mapper is a callable used to transform Q objects, + # or None for identity transform + if mapper is None: + mapper = lambda x: x if len(args) > 0 or len(kwargs) > 0: assert self._limit is None and self._offset is None, \ "Cannot filter a query once a slice has been taken." clone = self._clone() if len(kwargs) > 0: - clone._filters = clone._filters & qtype(**kwargs) + clone._filters = clone._filters & mapper(Q(**kwargs)) if len(args) > 0: - clone._filters = clone._filters & reduce(operator.and_, args) + clone._filters = clone._filters & reduce(operator.and_, map(mapper, args)) return clone def complex_filter(self, filter_obj): @@ -318,7 +322,7 @@ class QuerySet(object): if hasattr(filter_obj, 'get_sql'): return self._filter_or_exclude(None, filter_obj) else: - return self._filter_or_exclude(Q, **filter_obj) + return self._filter_or_exclude(None, **filter_obj) def select_related(self, true_or_false=True): "Returns a new QuerySet instance with '_select_related' modified." @@ -582,9 +586,12 @@ class Q(object): class QNot(Q): "Encapsulates NOT (...) queries as objects" + def __init__(self, q): + "Creates a negation of the q object passed in." + self.q = q def get_sql(self, opts): - tables, joins, where, params = super(QNot, self).get_sql(opts) + tables, joins, where, params = self.q.get_sql(opts) where2 = ['(NOT (%s))' % " AND ".join(where)] return tables, joins, where2, params diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py index a07dacdd5a..ef4d4dc5ca 100644 --- a/tests/modeltests/or_lookups/models.py +++ b/tests/modeltests/or_lookups/models.py @@ -89,6 +89,10 @@ Hello and goodbye >>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2]) {1: Hello} +# Demonstrating exclude with a Q object +>>> Article.objects.exclude(Q(headline__startswith='Hello')) +[Goodbye] + # The 'complex_filter' method supports framework features such as # 'limit_choices_to' which normally take a single dictionary of lookup arguments # but need to support arbitrary queries via Q objects too.