diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 830808b..b47e4e6 100644
a
|
b
|
class DatabaseOperations(BaseDatabaseOperations):
|
307 | 307 | class DatabaseWrapper(BaseDatabaseWrapper): |
308 | 308 | vendor = 'mysql' |
309 | 309 | operators = { |
| 310 | 'ne': '<> %s', |
310 | 311 | 'exact': '= %s', |
311 | 312 | 'iexact': 'LIKE %s', |
312 | 313 | 'contains': 'LIKE BINARY %s', |
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 105730c..1a712f6 100644
a
|
b
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
407 | 407 | operators = _UninitializedOperatorsDescriptor() |
408 | 408 | |
409 | 409 | _standard_operators = { |
| 410 | 'ne': '<> %s', |
410 | 411 | 'exact': '= %s', |
411 | 412 | 'iexact': '= UPPER(%s)', |
412 | 413 | 'contains': "LIKE TRANSLATE(%s USING NCHAR_CS) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", |
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index a77eccf..0919611 100644
a
|
b
|
class DatabaseFeatures(BaseDatabaseFeatures):
|
97 | 97 | class DatabaseWrapper(BaseDatabaseWrapper): |
98 | 98 | vendor = 'postgresql' |
99 | 99 | operators = { |
| 100 | 'ne': '<> %s', |
100 | 101 | 'exact': '= %s', |
101 | 102 | 'iexact': '= UPPER(%s)', |
102 | 103 | 'contains': 'LIKE %s', |
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index b5d38bb..e6ad1cd 100644
a
|
b
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
204 | 204 | # being escaped has a percent or underscore in it. |
205 | 205 | # See http://www.sqlite.org/lang_expr.html for an explanation. |
206 | 206 | operators = { |
| 207 | 'ne': '<> %s', |
207 | 208 | 'exact': '= %s', |
208 | 209 | 'iexact': "LIKE %s ESCAPE '\\'", |
209 | 210 | 'contains': "LIKE %s ESCAPE '\\'", |
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 04b13aa..851b2ec 100644
a
|
b
|
class Field(object):
|
306 | 306 | 'endswith', 'iendswith', 'isnull' |
307 | 307 | ): |
308 | 308 | return value |
309 | | elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): |
| 309 | elif lookup_type in ('exact', 'ne', 'gt', 'gte', 'lt', 'lte'): |
310 | 310 | return self.get_prep_value(value) |
311 | | elif lookup_type in ('range', 'in'): |
| 311 | elif lookup_type in ('range', 'in', 'notin'): |
312 | 312 | return [self.get_prep_value(v) for v in value] |
313 | 313 | elif lookup_type == 'year': |
314 | 314 | try: |
… |
… |
class Field(object):
|
342 | 342 | if lookup_type in ('regex', 'iregex', 'month', 'day', 'week_day', |
343 | 343 | 'search'): |
344 | 344 | return [value] |
345 | | elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): |
| 345 | elif lookup_type in ('exact', 'ne', 'gt', 'gte', 'lt', 'lte'): |
346 | 346 | return [self.get_db_prep_value(value, connection=connection, |
347 | 347 | prepared=prepared)] |
348 | | elif lookup_type in ('range', 'in'): |
| 348 | elif lookup_type in ('range', 'in', 'notin'): |
349 | 349 | return [self.get_db_prep_value(v, connection=connection, |
350 | 350 | prepared=prepared) for v in value] |
351 | 351 | elif lookup_type in ('contains', 'icontains'): |
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index ad05c83..8fba124 100644
a
|
b
|
class RelatedField(object):
|
133 | 133 | # get_(next/prev)_by_date work; other lookups are not allowed since that |
134 | 134 | # gets messy pretty quick. This is a good candidate for some refactoring |
135 | 135 | # in the future. |
136 | | if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: |
| 136 | if lookup_type in ['exact', 'ne', 'gt', 'lt', 'gte', 'lte']: |
137 | 137 | return self._pk_trace(value, 'get_prep_lookup', lookup_type) |
138 | | if lookup_type in ('range', 'in'): |
| 138 | if lookup_type in ('range', 'in', 'notin'): |
139 | 139 | return [self._pk_trace(v, 'get_prep_lookup', lookup_type) for v in value] |
140 | 140 | elif lookup_type == 'isnull': |
141 | 141 | return [] |
… |
… |
class RelatedField(object):
|
161 | 161 | # get_(next/prev)_by_date work; other lookups are not allowed since that |
162 | 162 | # gets messy pretty quick. This is a good candidate for some refactoring |
163 | 163 | # in the future. |
164 | | if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: |
| 164 | if lookup_type in ['exact', 'ne', 'gt', 'lt', 'gte', 'lte']: |
165 | 165 | return [self._pk_trace(value, 'get_db_prep_lookup', lookup_type, |
166 | 166 | connection=connection, prepared=prepared)] |
167 | | if lookup_type in ('range', 'in'): |
| 167 | if lookup_type in ('range', 'in', 'notin'): |
168 | 168 | return [self._pk_trace(v, 'get_db_prep_lookup', lookup_type, |
169 | 169 | connection=connection, prepared=prepared) |
170 | 170 | for v in value] |
… |
… |
class RelatedField(object):
|
205 | 205 | else: |
206 | 206 | field = field.rel.to._meta.pk |
207 | 207 | |
208 | | if lookup_type in ('range', 'in'): |
| 208 | if lookup_type in ('range', 'in', 'notin'): |
209 | 209 | v = [v] |
210 | 210 | v = getattr(field, prep_func)(lookup_type, v, **kwargs) |
211 | 211 | if isinstance(v, list): |
diff --git a/django/db/models/sql/constants.py b/django/db/models/sql/constants.py
index 63c704f..0ba218c 100644
a
|
b
|
import re
|
2 | 2 | |
3 | 3 | # Valid query types (a dictionary is used for speedy lookups). |
4 | 4 | QUERY_TERMS = dict([(x, None) for x in ( |
5 | | 'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in', |
6 | | 'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year', |
| 5 | 'exact', 'ne', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in', |
| 6 | 'notin', 'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year', |
7 | 7 | 'month', 'day', 'week_day', 'isnull', 'search', 'regex', 'iregex', |
8 | 8 | )]) |
9 | 9 | |
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 4891855..f6fc472 100644
a
|
b
|
class Query(object):
|
1071 | 1071 | # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all |
1072 | 1072 | # uses of None as a query value. |
1073 | 1073 | if value is None: |
1074 | | if lookup_type != 'exact': |
| 1074 | if lookup_type not in ('exact', 'ne'): |
1075 | 1075 | raise ValueError("Cannot use None as a query value") |
| 1076 | value = lookup_type == 'exact' |
1076 | 1077 | lookup_type = 'isnull' |
1077 | | value = True |
1078 | 1078 | elif callable(value): |
1079 | 1079 | value = value() |
1080 | 1080 | elif isinstance(value, ExpressionNode): |
… |
… |
class Query(object):
|
1179 | 1179 | entry.negate() |
1180 | 1180 | self.where.add(entry, AND) |
1181 | 1181 | break |
1182 | | if not (lookup_type == 'in' |
| 1182 | if not (lookup_type in ('in', 'notin') |
1183 | 1183 | and not hasattr(value, 'as_sql') |
1184 | 1184 | and not hasattr(value, '_as_sql') |
1185 | 1185 | and not value) and field.null: |
… |
… |
class Query(object):
|
1187 | 1187 | # exclude the "foo__in=[]" case from this handling, because |
1188 | 1188 | # it's short-circuited in the Where class. |
1189 | 1189 | # We also need to handle the case where a subquery is provided |
1190 | | self.where.add((Constraint(alias, col, None), 'isnull', False), AND) |
| 1190 | self.where.add((Constraint(alias, col, None), 'isnull', lookup_type == 'notin'), AND) |
1191 | 1191 | |
1192 | 1192 | if can_reuse is not None: |
1193 | 1193 | can_reuse.update(join_list) |
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 1455ba6..e1aa6c3 100644
a
|
b
|
class WhereNode(tree.Node):
|
165 | 165 | else: |
166 | 166 | extra = '' |
167 | 167 | |
168 | | if (len(params) == 1 and params[0] == '' and lookup_type == 'exact' |
| 168 | if (len(params) == 1 and params[0] == '' and lookup_type in ('exact', 'ne') |
169 | 169 | and connection.features.interprets_empty_strings_as_nulls): |
| 170 | value_annot = lookup_type == 'exact' |
170 | 171 | lookup_type = 'isnull' |
171 | | value_annot = True |
172 | 172 | |
173 | 173 | if lookup_type in connection.operators: |
174 | 174 | format = "%s %%s %%s" % (connection.ops.lookup_cast(lookup_type),) |
… |
… |
class WhereNode(tree.Node):
|
176 | 176 | connection.operators[lookup_type] % cast_sql, |
177 | 177 | extra), params) |
178 | 178 | |
179 | | if lookup_type == 'in': |
| 179 | if lookup_type in ('in', 'notin'): |
180 | 180 | if not value_annot: |
181 | 181 | raise EmptyResultSet |
| 182 | if lookup_type == 'in': |
| 183 | op = 'IN' |
| 184 | else: |
| 185 | op = 'NOT IN' |
182 | 186 | if extra: |
183 | | return ('%s IN %s' % (field_sql, extra), params) |
| 187 | return ('%s %s %s' % (field_sql, op, extra), params) |
184 | 188 | max_in_list_size = connection.ops.max_in_list_size() |
185 | 189 | if max_in_list_size and len(params) > max_in_list_size: |
186 | 190 | # Break up the params list into an OR of manageable chunks. |
… |
… |
class WhereNode(tree.Node):
|
188 | 192 | for offset in xrange(0, len(params), max_in_list_size): |
189 | 193 | if offset > 0: |
190 | 194 | in_clause_elements.append(' OR ') |
191 | | in_clause_elements.append('%s IN (' % field_sql) |
| 195 | in_clause_elements.append('%s %s (' % (field_sql, op)) |
192 | 196 | group_size = min(len(params) - offset, max_in_list_size) |
193 | 197 | param_group = ', '.join(repeat('%s', group_size)) |
194 | 198 | in_clause_elements.append(param_group) |
… |
… |
class WhereNode(tree.Node):
|
196 | 200 | in_clause_elements.append(')') |
197 | 201 | return ''.join(in_clause_elements), params |
198 | 202 | else: |
199 | | return ('%s IN (%s)' % (field_sql, |
| 203 | return ('%s %s (%s)' % (field_sql, op, |
200 | 204 | ', '.join(repeat('%s', len(params)))), |
201 | 205 | params) |
202 | 206 | elif lookup_type in ('range', 'year'): |