From 9cdd49c1e0cbe8621d162190c43284548e4b0002 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Mon, 27 Feb 2006 22:51:36 +0000 Subject: [PATCH] Added QuerySet.exclude() which does the opposite of QuerySet.filter(). As a side effect, the "ne" lookup type no longer exists. This fixes #966. git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2422 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/ado_mssql/base.py | 1 - django/db/backends/mysql/base.py | 1 - django/db/backends/postgresql/base.py | 1 - django/db/backends/sqlite3/base.py | 1 - django/db/models/manager.py | 3 +++ django/db/models/query.py | 19 +++++++++++++++++-- tests/modeltests/lookup/models.py | 8 ++++++++ 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index 7c54c7b8be..b4caebd412 100644 --- a/django/db/backends/ado_mssql/base.py +++ b/django/db/backends/ado_mssql/base.py @@ -119,7 +119,6 @@ OPERATOR_MAPPING = { 'iexact': 'LIKE %s', 'contains': 'LIKE %s', 'icontains': 'LIKE %s', - 'ne': '!= %s', 'gt': '> %s', 'gte': '>= %s', 'lt': '< %s', diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index c4cd36606b..04d027e148 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -132,7 +132,6 @@ OPERATOR_MAPPING = { 'iexact': 'LIKE %s', 'contains': 'LIKE BINARY %s', 'icontains': 'LIKE %s', - 'ne': '!= %s', 'gt': '> %s', 'gte': '>= %s', 'lt': '< %s', diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 4b186975eb..47fb8e621c 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -107,7 +107,6 @@ OPERATOR_MAPPING = { 'iexact': 'ILIKE %s', 'contains': 'LIKE %s', 'icontains': 'ILIKE %s', - 'ne': '!= %s', 'gt': '> %s', 'gte': '>= %s', 'lt': '< %s', diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 841185c8dd..5b57925256 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -131,7 +131,6 @@ OPERATOR_MAPPING = { 'iexact': "LIKE %s ESCAPE '\\'", 'contains': "LIKE %s ESCAPE '\\'", 'icontains': "LIKE %s ESCAPE '\\'", - 'ne': '!= %s', 'gt': '> %s', 'gte': '>= %s', 'lt': '< %s', diff --git a/django/db/models/manager.py b/django/db/models/manager.py index f266d06827..e21af1ada5 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -69,6 +69,9 @@ class Manager(object): def filter(self, *args, **kwargs): return self.get_query_set().filter(*args, **kwargs) + def exclude(self, *args, **kwargs): + return self.get_query_set().exclude(*args, **kwargs) + def in_bulk(self, *args, **kwargs): return self.get_query_set().in_bulk(*args, **kwargs) diff --git a/django/db/models/query.py b/django/db/models/query.py index 9c4a83f0c3..cfd86892fd 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -244,9 +244,16 @@ 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) + + def exclude(self, *args, **kwargs): + "Returns a new QuerySet instance with NOT (arsg) ANDed to the existing set." + return self._filter_or_exclude(QNot, *args, **kwargs) + + def _filter_or_exclude(self, qtype, *args, **kwargs): clone = self._clone() if len(kwargs) > 0: - clone._filters = clone._filters & Q(**kwargs) + clone._filters = clone._filters & qtype(**kwargs) if len(args) > 0: clone._filters = clone._filters & reduce(operator.and_, args) return clone @@ -490,7 +497,7 @@ class QOr(QOperator): else: raise TypeError, other -class Q: +class Q(object): "Encapsulates queries as objects that can be combined logically." def __init__(self, **kwargs): self.kwargs = kwargs @@ -504,6 +511,14 @@ class Q: def get_sql(self, opts): return parse_lookup(self.kwargs.items(), opts) +class QNot(Q): + "Encapsulates NOT (...) queries as objects" + + def get_sql(self, opts): + tables, joins, where, params = super(QNot, self).get_sql(opts) + where2 = ['(NOT (%s))' % " AND ".join(where)] + return tables, joins, where2, params + def get_where_clause(lookup_type, table_prefix, field_name, value): if table_prefix.endswith('.'): table_prefix = backend.quote_name(table_prefix[:-1])+'.' diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index cc089493d5..2aaf626e36 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -170,4 +170,12 @@ Article 1 [Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] >>> Article.objects.filter(headline__startswith='Article%') [Article% with percent sign] + +# exclude() is the opposite of filter() when doing lookups: +>>> Article.objects.filter(headline__contains='Article').exclude(headline__contains='with') +[Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +>>> Article.objects.exclude(headline__startswith="Article_") +[Article% with percent sign, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +>>> Article.objects.exclude(headline="Article 7") +[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 1]cl """