diff -ruN django.orig/db/backends/mysql/base.py django/db/backends/mysql/base.py
old
|
new
|
|
258 | 258 | class DatabaseWrapper(BaseDatabaseWrapper): |
259 | 259 | vendor = 'mysql' |
260 | 260 | operators = { |
| 261 | 'ne': '<> %s', |
261 | 262 | 'exact': '= %s', |
262 | 263 | 'iexact': 'LIKE %s', |
263 | 264 | 'contains': 'LIKE BINARY %s', |
diff -ruN django.orig/db/backends/oracle/base.py django/db/backends/oracle/base.py
old
|
new
|
|
384 | 384 | operators = _UninitializedOperatorsDescriptor() |
385 | 385 | |
386 | 386 | _standard_operators = { |
| 387 | 'ne': '<> %s', |
387 | 388 | 'exact': '= %s', |
388 | 389 | 'iexact': '= UPPER(%s)', |
389 | 390 | 'contains': "LIKE TRANSLATE(%s USING NCHAR_CS) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", |
diff -ruN django.orig/db/backends/postgresql/base.py django/db/backends/postgresql/base.py
old
|
new
|
|
87 | 87 | class DatabaseWrapper(BaseDatabaseWrapper): |
88 | 88 | vendor = 'postgresql' |
89 | 89 | operators = { |
| 90 | 'ne': '<> %s', |
90 | 91 | 'exact': '= %s', |
91 | 92 | 'iexact': '= UPPER(%s)', |
92 | 93 | 'contains': 'LIKE %s', |
diff -ruN django.orig/db/backends/postgresql_psycopg2/base.py django/db/backends/postgresql_psycopg2/base.py
old
|
new
|
|
84 | 84 | class DatabaseWrapper(BaseDatabaseWrapper): |
85 | 85 | vendor = 'postgresql' |
86 | 86 | operators = { |
| 87 | 'ne': '<> %s', |
87 | 88 | 'exact': '= %s', |
88 | 89 | 'iexact': '= UPPER(%s)', |
89 | 90 | 'contains': 'LIKE %s', |
diff -ruN django.orig/db/backends/sqlite3/base.py django/db/backends/sqlite3/base.py
old
|
new
|
|
167 | 167 | # being escaped has a percent or underscore in it. |
168 | 168 | # See http://www.sqlite.org/lang_expr.html for an explanation. |
169 | 169 | operators = { |
| 170 | 'ne': '<> %s', |
170 | 171 | 'exact': '= %s', |
171 | 172 | 'iexact': "LIKE %s ESCAPE '\\'", |
172 | 173 | 'contains': "LIKE %s ESCAPE '\\'", |
diff -ruN django.orig/db/models/fields/__init__.py django/db/models/fields/__init__.py
old
|
new
|
|
288 | 288 | 'endswith', 'iendswith', 'isnull' |
289 | 289 | ): |
290 | 290 | return value |
291 | | elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): |
| 291 | elif lookup_type in ('ne', 'exact', 'gt', 'gte', 'lt', 'lte'): |
292 | 292 | return self.get_prep_value(value) |
293 | | elif lookup_type in ('range', 'in'): |
| 293 | elif lookup_type in ('range', 'in', 'notin'): |
294 | 294 | return [self.get_prep_value(v) for v in value] |
295 | 295 | elif lookup_type == 'year': |
296 | 296 | try: |
… |
… |
|
319 | 319 | |
320 | 320 | if lookup_type in ('regex', 'iregex', 'month', 'day', 'week_day', 'search'): |
321 | 321 | return [value] |
322 | | elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): |
| 322 | elif lookup_type in ('ne', 'exact', 'gt', 'gte', 'lt', 'lte'): |
323 | 323 | return [self.get_db_prep_value(value, connection=connection, prepared=prepared)] |
324 | | elif lookup_type in ('range', 'in'): |
| 324 | elif lookup_type in ('range', 'in', 'notin'): |
325 | 325 | return [self.get_db_prep_value(v, connection=connection, prepared=prepared) for v in value] |
326 | 326 | elif lookup_type in ('contains', 'icontains'): |
327 | 327 | return ["%%%s%%" % connection.ops.prep_for_like_query(value)] |
diff -ruN django.orig/db/models/fields/related.py django/db/models/fields/related.py
old
|
new
|
|
132 | 132 | # get_(next/prev)_by_date work; other lookups are not allowed since that |
133 | 133 | # gets messy pretty quick. This is a good candidate for some refactoring |
134 | 134 | # in the future. |
135 | | if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: |
| 135 | if lookup_type in ['ne', 'exact', 'gt', 'lt', 'gte', 'lte']: |
136 | 136 | return self._pk_trace(value, 'get_prep_lookup', lookup_type) |
137 | | if lookup_type in ('range', 'in'): |
| 137 | if lookup_type in ('range', 'in', 'notin'): |
138 | 138 | return [self._pk_trace(v, 'get_prep_lookup', lookup_type) for v in value] |
139 | 139 | elif lookup_type == 'isnull': |
140 | 140 | return [] |
… |
… |
|
160 | 160 | # get_(next/prev)_by_date work; other lookups are not allowed since that |
161 | 161 | # gets messy pretty quick. This is a good candidate for some refactoring |
162 | 162 | # in the future. |
163 | | if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: |
| 163 | if lookup_type in ['ne', 'exact', 'gt', 'lt', 'gte', 'lte']: |
164 | 164 | return [self._pk_trace(value, 'get_db_prep_lookup', lookup_type, |
165 | 165 | connection=connection, prepared=prepared)] |
166 | | if lookup_type in ('range', 'in'): |
| 166 | if lookup_type in ('range', 'in', 'notin'): |
167 | 167 | return [self._pk_trace(v, 'get_db_prep_lookup', lookup_type, |
168 | 168 | connection=connection, prepared=prepared) |
169 | 169 | for v in value] |
… |
… |
|
204 | 204 | else: |
205 | 205 | field = field.rel.to._meta.pk |
206 | 206 | |
207 | | if lookup_type in ('range', 'in'): |
| 207 | if lookup_type in ('range', 'in', 'notin'): |
208 | 208 | v = [v] |
209 | 209 | v = getattr(field, prep_func)(lookup_type, v, **kwargs) |
210 | 210 | if isinstance(v, list): |
diff -ruN django.orig/db/models/sql/constants.py django/db/models/sql/constants.py
old
|
new
|
|
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 | 'ne', 'exact', '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 -ruN django.orig/db/models/sql/query.py django/db/models/sql/query.py
old
|
new
|
|
1039 | 1039 | # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all |
1040 | 1040 | # uses of None as a query value. |
1041 | 1041 | if value is None: |
1042 | | if lookup_type != 'exact': |
| 1042 | if lookup_type not in ('exact', 'ne'): |
1043 | 1043 | raise ValueError("Cannot use None as a query value") |
| 1044 | value = lookup_type == 'exact' |
1044 | 1045 | lookup_type = 'isnull' |
1045 | | value = True |
1046 | 1046 | elif callable(value): |
1047 | 1047 | value = value() |
1048 | 1048 | elif hasattr(value, 'evaluate'): |
… |
… |
|
1143 | 1143 | entry.negate() |
1144 | 1144 | self.where.add(entry, AND) |
1145 | 1145 | break |
1146 | | if not (lookup_type == 'in' |
| 1146 | if not (lookup_type in ('in', 'notin') |
1147 | 1147 | and not hasattr(value, 'as_sql') |
1148 | 1148 | and not hasattr(value, '_as_sql') |
1149 | 1149 | and not value) and field.null: |
… |
… |
|
1151 | 1151 | # exclude the "foo__in=[]" case from this handling, because |
1152 | 1152 | # it's short-circuited in the Where class. |
1153 | 1153 | # We also need to handle the case where a subquery is provided |
1154 | | self.where.add((Constraint(alias, col, None), 'isnull', False), AND) |
| 1154 | self.where.add((Constraint(alias, col, None), 'isnull', lookup_type == 'notin'), AND) |
1155 | 1155 | |
1156 | 1156 | if can_reuse is not None: |
1157 | 1157 | can_reuse.update(join_list) |
diff -ruN django.orig/db/models/sql/where.py django/db/models/sql/where.py
old
|
new
|
|
163 | 163 | else: |
164 | 164 | extra = '' |
165 | 165 | |
166 | | if (len(params) == 1 and params[0] == '' and lookup_type == 'exact' |
| 166 | if (len(params) == 1 and params[0] == '' and lookup_type in ('exact', 'ne') |
167 | 167 | and connection.features.interprets_empty_strings_as_nulls): |
| 168 | value_annot = lookup_type == 'exact' |
168 | 169 | lookup_type = 'isnull' |
169 | | value_annot = True |
170 | 170 | |
171 | 171 | if lookup_type in connection.operators: |
172 | 172 | format = "%s %%s %%s" % (connection.ops.lookup_cast(lookup_type),) |
… |
… |
|
174 | 174 | connection.operators[lookup_type] % cast_sql, |
175 | 175 | extra), params) |
176 | 176 | |
177 | | if lookup_type == 'in': |
| 177 | if lookup_type in ('in', 'notin'): |
178 | 178 | if not value_annot: |
179 | 179 | raise EmptyResultSet |
| 180 | if lookup_type == 'in': |
| 181 | op = 'IN' |
| 182 | else: |
| 183 | op = 'NOT IN' |
180 | 184 | if extra: |
181 | | return ('%s IN %s' % (field_sql, extra), params) |
| 185 | return ('%s %s %s' % (field_sql, op, extra), params) |
182 | 186 | max_in_list_size = connection.ops.max_in_list_size() |
183 | 187 | if max_in_list_size and len(params) > max_in_list_size: |
184 | 188 | # Break up the params list into an OR of manageable chunks. |
… |
… |
|
186 | 190 | for offset in xrange(0, len(params), max_in_list_size): |
187 | 191 | if offset > 0: |
188 | 192 | in_clause_elements.append(' OR ') |
189 | | in_clause_elements.append('%s IN (' % field_sql) |
| 193 | in_clause_elements.append('%s %s (' % (field_sql, op)) |
190 | 194 | group_size = min(len(params) - offset, max_in_list_size) |
191 | 195 | param_group = ', '.join(repeat('%s', group_size)) |
192 | 196 | in_clause_elements.append(param_group) |
… |
… |
|
194 | 198 | in_clause_elements.append(')') |
195 | 199 | return ''.join(in_clause_elements), params |
196 | 200 | else: |
197 | | return ('%s IN (%s)' % (field_sql, |
| 201 | return ('%s %s (%s)' % (field_sql, op, |
198 | 202 | ', '.join(repeat('%s', len(params)))), |
199 | 203 | params) |
200 | 204 | elif lookup_type in ('range', 'year'): |