diff -ruN django.orig/db/backends/mysql/base.py django/db/backends/mysql/base.py
--- django.orig/db/backends/mysql/base.py	2011-02-01 08:42:52.000000000 -0600
+++ django/db/backends/mysql/base.py	2011-10-30 17:31:03.000000000 -0600
@@ -258,6 +258,7 @@
 class DatabaseWrapper(BaseDatabaseWrapper):
     vendor = 'mysql'
     operators = {
+        'ne': '<> %s',
         'exact': '= %s',
         'iexact': 'LIKE %s',
         'contains': 'LIKE BINARY %s',
diff -ruN django.orig/db/backends/oracle/base.py django/db/backends/oracle/base.py
--- django.orig/db/backends/oracle/base.py	2011-03-08 16:41:25.000000000 -0600
+++ django/db/backends/oracle/base.py	2011-10-30 17:31:03.000000000 -0600
@@ -384,6 +384,7 @@
     operators = _UninitializedOperatorsDescriptor()
 
     _standard_operators = {
+        'ne': '<> %s',
         'exact': '= %s',
         'iexact': '= UPPER(%s)',
         '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
--- django.orig/db/backends/postgresql/base.py	2010-11-09 10:46:42.000000000 -0600
+++ django/db/backends/postgresql/base.py	2011-10-30 17:31:03.000000000 -0600
@@ -87,6 +87,7 @@
 class DatabaseWrapper(BaseDatabaseWrapper):
     vendor = 'postgresql'
     operators = {
+        'ne': '<> %s',
         'exact': '= %s',
         'iexact': '= UPPER(%s)',
         'contains': 'LIKE %s',
diff -ruN django.orig/db/backends/postgresql_psycopg2/base.py django/db/backends/postgresql_psycopg2/base.py
--- django.orig/db/backends/postgresql_psycopg2/base.py	2010-11-09 10:46:42.000000000 -0600
+++ django/db/backends/postgresql_psycopg2/base.py	2011-10-30 17:31:03.000000000 -0600
@@ -84,6 +84,7 @@
 class DatabaseWrapper(BaseDatabaseWrapper):
     vendor = 'postgresql'
     operators = {
+        'ne': '<> %s',
         'exact': '= %s',
         'iexact': '= UPPER(%s)',
         'contains': 'LIKE %s',
diff -ruN django.orig/db/backends/sqlite3/base.py django/db/backends/sqlite3/base.py
--- django.orig/db/backends/sqlite3/base.py	2011-05-10 07:28:29.000000000 -0500
+++ django/db/backends/sqlite3/base.py	2011-10-30 17:31:03.000000000 -0600
@@ -167,6 +167,7 @@
     # being escaped has a percent or underscore in it.
     # See http://www.sqlite.org/lang_expr.html for an explanation.
     operators = {
+        'ne': '<> %s',
         'exact': '= %s',
         'iexact': "LIKE %s ESCAPE '\\'",
         'contains': "LIKE %s ESCAPE '\\'",
diff -ruN django.orig/db/models/fields/__init__.py django/db/models/fields/__init__.py
--- django.orig/db/models/fields/__init__.py	2011-09-09 20:08:24.000000000 -0500
+++ django/db/models/fields/__init__.py	2011-10-30 17:31:03.000000000 -0600
@@ -288,9 +288,9 @@
                 'endswith', 'iendswith', 'isnull'
             ):
             return value
-        elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
+        elif lookup_type in ('ne', 'exact', 'gt', 'gte', 'lt', 'lte'):
             return self.get_prep_value(value)
-        elif lookup_type in ('range', 'in'):
+        elif lookup_type in ('range', 'in', 'notin'):
             return [self.get_prep_value(v) for v in value]
         elif lookup_type == 'year':
             try:
@@ -319,9 +319,9 @@
 
         if lookup_type in ('regex', 'iregex', 'month', 'day', 'week_day', 'search'):
             return [value]
-        elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
+        elif lookup_type in ('ne', 'exact', 'gt', 'gte', 'lt', 'lte'):
             return [self.get_db_prep_value(value, connection=connection, prepared=prepared)]
-        elif lookup_type in ('range', 'in'):
+        elif lookup_type in ('range', 'in', 'notin'):
             return [self.get_db_prep_value(v, connection=connection, prepared=prepared) for v in value]
         elif lookup_type in ('contains', 'icontains'):
             return ["%%%s%%" % connection.ops.prep_for_like_query(value)]
diff -ruN django.orig/db/models/fields/related.py django/db/models/fields/related.py
--- django.orig/db/models/fields/related.py	2011-03-09 19:15:19.000000000 -0600
+++ django/db/models/fields/related.py	2011-10-30 17:31:03.000000000 -0600
@@ -132,9 +132,9 @@
         # get_(next/prev)_by_date work; other lookups are not allowed since that
         # gets messy pretty quick. This is a good candidate for some refactoring
         # in the future.
-        if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']:
+        if lookup_type in ['ne', 'exact', 'gt', 'lt', 'gte', 'lte']:
             return self._pk_trace(value, 'get_prep_lookup', lookup_type)
-        if lookup_type in ('range', 'in'):
+        if lookup_type in ('range', 'in', 'notin'):
             return [self._pk_trace(v, 'get_prep_lookup', lookup_type) for v in value]
         elif lookup_type == 'isnull':
             return []
@@ -160,10 +160,10 @@
         # get_(next/prev)_by_date work; other lookups are not allowed since that
         # gets messy pretty quick. This is a good candidate for some refactoring
         # in the future.
-        if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']:
+        if lookup_type in ['ne', 'exact', 'gt', 'lt', 'gte', 'lte']:
             return [self._pk_trace(value, 'get_db_prep_lookup', lookup_type,
                             connection=connection, prepared=prepared)]
-        if lookup_type in ('range', 'in'):
+        if lookup_type in ('range', 'in', 'notin'):
             return [self._pk_trace(v, 'get_db_prep_lookup', lookup_type,
                             connection=connection, prepared=prepared)
                     for v in value]
@@ -204,7 +204,7 @@
             else:
                 field = field.rel.to._meta.pk
 
-        if lookup_type in ('range', 'in'):
+        if lookup_type in ('range', 'in', 'notin'):
             v = [v]
         v = getattr(field, prep_func)(lookup_type, v, **kwargs)
         if isinstance(v, list):
diff -ruN django.orig/db/models/sql/constants.py django/db/models/sql/constants.py
--- django.orig/db/models/sql/constants.py	2009-02-07 23:08:06.000000000 -0600
+++ django/db/models/sql/constants.py	2011-10-30 17:31:03.000000000 -0600
@@ -2,8 +2,8 @@
 
 # Valid query types (a dictionary is used for speedy lookups).
 QUERY_TERMS = dict([(x, None) for x in (
-    'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
-    'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
+    'ne', 'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
+    'notin', 'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
     'month', 'day', 'week_day', 'isnull', 'search', 'regex', 'iregex',
     )])
 
diff -ruN django.orig/db/models/sql/query.py django/db/models/sql/query.py
--- django.orig/db/models/sql/query.py	2011-10-29 21:49:32.000000000 -0500
+++ django/db/models/sql/query.py	2011-10-30 17:44:27.000000000 -0600
@@ -1039,10 +1039,10 @@
         # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
         # uses of None as a query value.
         if value is None:
-            if lookup_type != 'exact':
+            if lookup_type not in ('exact', 'ne'):
                 raise ValueError("Cannot use None as a query value")
+            value = lookup_type == 'exact'
             lookup_type = 'isnull'
-            value = True
         elif callable(value):
             value = value()
         elif hasattr(value, 'evaluate'):
@@ -1143,7 +1143,7 @@
                             entry.negate()
                             self.where.add(entry, AND)
                             break
-                if not (lookup_type == 'in'
+                if not (lookup_type in ('in', 'notin')
                             and not hasattr(value, 'as_sql')
                             and not hasattr(value, '_as_sql')
                             and not value) and field.null:
@@ -1151,7 +1151,7 @@
                     # exclude the "foo__in=[]" case from this handling, because
                     # it's short-circuited in the Where class.
                     # We also need to handle the case where a subquery is provided
-                    self.where.add((Constraint(alias, col, None), 'isnull', False), AND)
+                    self.where.add((Constraint(alias, col, None), 'isnull', lookup_type == 'notin'), AND)
 
         if can_reuse is not None:
             can_reuse.update(join_list)
diff -ruN django.orig/db/models/sql/where.py django/db/models/sql/where.py
--- django.orig/db/models/sql/where.py	2010-09-16 14:53:41.000000000 -0500
+++ django/db/models/sql/where.py	2011-10-30 17:42:28.000000000 -0600
@@ -163,10 +163,10 @@
         else:
             extra = ''
 
-        if (len(params) == 1 and params[0] == '' and lookup_type == 'exact'
+        if (len(params) == 1 and params[0] == '' and lookup_type in ('exact', 'ne')
             and connection.features.interprets_empty_strings_as_nulls):
+            value_annot = lookup_type == 'exact'
             lookup_type = 'isnull'
-            value_annot = True
 
         if lookup_type in connection.operators:
             format = "%s %%s %%s" % (connection.ops.lookup_cast(lookup_type),)
@@ -174,11 +174,15 @@
                               connection.operators[lookup_type] % cast_sql,
                               extra), params)
 
-        if lookup_type == 'in':
+        if lookup_type in ('in', 'notin'):
             if not value_annot:
                 raise EmptyResultSet
+            if lookup_type == 'in':
+                op = 'IN'
+            else:
+                op = 'NOT IN'
             if extra:
-                return ('%s IN %s' % (field_sql, extra), params)
+                return ('%s %s %s' % (field_sql, op, extra), params)
             max_in_list_size = connection.ops.max_in_list_size()
             if max_in_list_size and len(params) > max_in_list_size:
                 # Break up the params list into an OR of manageable chunks.
@@ -186,7 +190,7 @@
                 for offset in xrange(0, len(params), max_in_list_size):
                     if offset > 0:
                         in_clause_elements.append(' OR ')
-                    in_clause_elements.append('%s IN (' % field_sql)
+                    in_clause_elements.append('%s %s (' % (field_sql, op))
                     group_size = min(len(params) - offset, max_in_list_size)
                     param_group = ', '.join(repeat('%s', group_size))
                     in_clause_elements.append(param_group)
@@ -194,7 +198,7 @@
                 in_clause_elements.append(')')
                 return ''.join(in_clause_elements), params
             else:
-                return ('%s IN (%s)' % (field_sql,
+                return ('%s %s (%s)' % (field_sql, op,
                                         ', '.join(repeat('%s', len(params)))),
                         params)
         elif lookup_type in ('range', 'year'):
