diff -urN Django-1.4b1.orig/django/db/models/sql/compiler.py Django-1.4b1/django/db/models/sql/compiler.py
--- Django-1.4b1.orig/django/db/models/sql/compiler.py	2012-02-16 05:03:47.000000000 +0100
+++ Django-1.4b1/django/db/models/sql/compiler.py	2012-03-04 23:39:03.982386400 +0100
@@ -456,8 +456,8 @@
         """
         if not alias:
             alias = self.query.get_initial_alias()
-        field, target, opts, joins, _, _ = self.query.setup_joins(pieces,
-                opts, alias, False)
+        field, target, opts, joins, _, _, _ = self.query.setup_joins(
+                pieces, opts, alias, False)
         alias = joins[-1]
         col = target.column
         if not field.rel:
@@ -511,7 +511,7 @@
             if not self.query.alias_refcount[alias]:
                 continue
             try:
-                name, alias, join_type, lhs, lhs_col, col, nullable = self.query.alias_map[alias]
+                name, alias, join_type, lhs, lhs_col, col, nullable, demoted_join = self.query.alias_map[alias]
             except KeyError:
                 # Extra tables can end up in self.tables, but not in the
                 # alias_map if they aren't in a join. That's OK. We skip them.
diff -urN Django-1.4b1.orig/django/db/models/sql/constants.py Django-1.4b1/django/db/models/sql/constants.py
--- Django-1.4b1.orig/django/db/models/sql/constants.py	2012-02-16 05:03:47.000000000 +0100
+++ Django-1.4b1/django/db/models/sql/constants.py	2012-03-04 23:44:11.914999200 +0100
@@ -24,6 +24,7 @@
 LHS_JOIN_COL = 4
 RHS_JOIN_COL = 5
 NULLABLE = 6
+DEMOTED_JOIN = 7
 
 # How many results to expect from a cursor.execute call
 MULTI = 'multi'
diff -urN Django-1.4b1.orig/django/db/models/sql/expressions.py Django-1.4b1/django/db/models/sql/expressions.py
--- Django-1.4b1.orig/django/db/models/sql/expressions.py	2012-02-16 05:03:47.000000000 +0100
+++ Django-1.4b1/django/db/models/sql/expressions.py	2012-03-04 23:40:00.806636600 +0100
@@ -44,7 +44,7 @@
             self.cols[node] = query.aggregate_select[node.name]
         else:
             try:
-                field, source, opts, join_list, last, _ = query.setup_joins(
+                field, source, opts, join_list, last, _, _ = query.setup_joins(
                     field_list, query.get_meta(),
                     query.get_initial_alias(), False)
                 col, _, join_list = query.trim_joins(source, join_list, last, False)
diff -urN Django-1.4b1.orig/django/db/models/sql/query.py Django-1.4b1/django/db/models/sql/query.py
--- Django-1.4b1.orig/django/db/models/sql/query.py	2012-02-16 05:03:47.000000000 +0100
+++ Django-1.4b1/django/db/models/sql/query.py	2012-03-05 14:28:58.878556000 +0100
@@ -504,6 +504,13 @@
                 # the trimming of unnecessary tables.
                 if self.alias_refcount.get(alias) or rhs.alias_refcount.get(alias):
                     self.promote_alias(alias, True)
+            # Find aliases that are contained both in rhs and lhs
+            # if one of them is demoted then promote lhs alias to LOUTER..
+            common_tables = (l_tables & r_tables)
+            for alias in common_tables:
+                if alias in self.alias_map and alias in rhs.alias_map and \
+                  self.alias_map[alias][DEMOTED_JOIN] != rhs.alias_map[alias][DEMOTED_JOIN]:
+                    self.promote_alias(alias, True)
 
         # Now relabel a copy of the rhs where-clause and add it to the current
         # one.
@@ -696,10 +703,21 @@
                 self.alias_map[alias][JOIN_TYPE] != self.LOUTER):
             data = list(self.alias_map[alias])
             data[JOIN_TYPE] = self.LOUTER
+            data[DEMOTED_JOIN] = False
             self.alias_map[alias] = tuple(data)
             return True
         return False
 
+    def demote_alias(self, alias, change_join_type=False):
+        """
+        Demotes the join type of an alias to an inner join.
+        """
+        data = list(self.alias_map[alias])
+        if change_join_type:
+            data[JOIN_TYPE] = self.INNER
+        data[DEMOTED_JOIN] = True
+        self.alias_map[alias] = tuple(data)
+
     def promote_alias_chain(self, chain, must_promote=False):
         """
         Walks along a chain of aliases, promoting the first nullable join and
@@ -912,15 +930,21 @@
 
         # No reuse is possible, so we need a new alias.
         alias, _ = self.table_alias(table, True)
+
         if not lhs:
             # Not all tables need to be joined to anything. No join type
             # means the later columns are ignored.
             join_type = None
         elif promote or outer_if_first:
             join_type = self.LOUTER
+        elif lhs in self.alias_map and self.alias_map[lhs][DEMOTED_JOIN]:
+            # If the lhs alias was demoted to INNER
+            # promote it to LOUTER and change join_type for rhs to LOUTER 
+            self.promote_alias(lhs, True)
+            join_type = self.LOUTER
         else:
             join_type = self.INNER
-        join = (table, alias, join_type, lhs, lhs_col, col, nullable)
+        join = (table, alias, join_type, lhs, lhs_col, col, nullable, False)
         self.alias_map[alias] = join
         if t_ident in self.join_map:
             self.join_map[t_ident] += (alias,)
@@ -1007,7 +1031,7 @@
             #   - this is an annotation over a model field
             # then we need to explore the joins that are required.
 
-            field, source, opts, join_list, last, _ = self.setup_joins(
+            field, source, opts, join_list, last, _, allow_trim_join = self.setup_joins(
                 field_list, opts, self.get_initial_alias(), False)
 
             # Process the join chain to see if it can be trimmed
@@ -1119,7 +1143,7 @@
         allow_many = trim or not negate
 
         try:
-            field, target, opts, join_list, last, extra_filters = self.setup_joins(
+            field, target, opts, join_list, last, extra_filters, allow_trim_join = self.setup_joins(
                     parts, opts, alias, True, allow_many, allow_explicit_fk=True,
                     can_reuse=can_reuse, negate=negate,
                     process_extras=process_extras)
@@ -1139,9 +1163,22 @@
             self.promote_alias_chain(join_list)
             join_promote = True
 
+        # If we have a one2one or many2one field, we can trim the left outer
+        # join from the end of a list of joins.
+        # In order to do this, we convert alias join type back to INNER, if possible,
+        # and trim_joins called later will do the strip for us.
+        # We have to be carefull when changing the join type to INNER, because
+        # the alias_map may already contain LOUTER join for this alias setup 
+        # by some earlier applied filter.
+        # The join type can be changed to INNER by the demote_alias method only if
+        # it was set to LOUTER by a call to the promote_alias_chain above.
+        nonnull_comparison = lookup_type == 'isnull'
+        if nonnull_comparison and allow_trim_join and field.rel:
+                self.demote_alias(join_list[-1], change_join_type=join_promote)
+                nonnull_comparison = False
+
         # Process the join list to see if we can remove any inner joins from
         # the far end (fewer tables in a query is better).
-        nonnull_comparison = (lookup_type == 'isnull' and value is False)
         col, alias, join_list = self.trim_joins(target, join_list, last, trim,
                 nonnull_comparison)
 
@@ -1295,8 +1332,10 @@
         dupe_set = set()
         exclusions = set()
         extra_filters = []
+        allow_trim_join = True
         int_alias = None
         for pos, name in enumerate(names):
+            allow_trim_join = True
             if int_alias is not None:
                 exclusions.add(int_alias)
             exclusions.add(alias)
@@ -1318,6 +1357,11 @@
                     raise FieldError("Cannot resolve keyword %r into field. "
                             "Choices are: %s" % (name, ", ".join(names)))
 
+            # presence of indirect field in the filter requires
+            # left outer join for isnull
+            if not direct and allow_trim_join:
+                allow_trim_join = False
+
             if not allow_many and (m2m or not direct):
                 for alias in joins:
                     self.unref_alias(alias)
@@ -1479,7 +1523,7 @@
             else:
                 raise FieldError("Join on field %r not permitted." % name)
 
-        return field, target, opts, joins, last, extra_filters
+        return field, target, opts, joins, last, extra_filters, allow_trim_join
 
     def trim_joins(self, target, join_list, last, trim, nonnull_check=False):
         """
@@ -1648,7 +1692,7 @@
 
         try:
             for name in field_names:
-                field, target, u2, joins, u3, u4 = self.setup_joins(
+                field, target, u2, joins, u3, u4, allow_trim_join = self.setup_joins(
                         name.split(LOOKUP_SEP), opts, alias, False, allow_m2m,
                         True)
                 final_alias = joins[-1]
@@ -1930,7 +1974,7 @@
         """
         opts = self.model._meta
         alias = self.get_initial_alias()
-        field, col, opts, joins, last, extra = self.setup_joins(
+        field, col, opts, joins, last, extra, allow_trim_join = self.setup_joins(
                 start.split(LOOKUP_SEP), opts, alias, False)
         select_col = self.alias_map[joins[1]][LHS_JOIN_COL]
         select_alias = alias
diff -urN Django-1.4b1.orig/tests/regressiontests/queries/tests.py Django-1.4b1/tests/regressiontests/queries/tests.py
--- Django-1.4b1.orig/tests/regressiontests/queries/tests.py	2012-02-16 05:03:44.000000000 +0100
+++ Django-1.4b1/tests/regressiontests/queries/tests.py	2012-03-05 13:53:49.309610800 +0100
@@ -842,6 +842,239 @@
         )
         Tag._meta.ordering = original_ordering
 
+    def test_ticket10790(self):
+
+        """
+            Querying direct fields with isnull should trim the left outer join.
+           It also should not create INNER JOIN.
+        """
+        q = Tag.objects.filter(parent__isnull=True)
+
+        self.assertQuerysetEqual(q, ['<Tag: t1>'])
+        self.assertTrue('JOIN' not in str(q.query))
+
+        q = Tag.objects.filter(parent__isnull=False)
+
+        self.assertQuerysetEqual(
+            q,
+            ['<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
+        )
+        self.assertTrue('JOIN' not in str(q.query))
+
+        q = Tag.objects.exclude(parent__isnull=True)
+        self.assertQuerysetEqual(
+            q,
+            ['<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
+        )
+        self.assertTrue('JOIN' not in str(q.query))
+
+        q = Tag.objects.exclude(parent__isnull=False)
+        self.assertQuerysetEqual( q, ['<Tag: t1>'])
+        self.assertTrue('JOIN' not in str(q.query))
+
+        q = Tag.objects.exclude(parent__parent__isnull=False)
+
+        self.assertQuerysetEqual(
+            q,
+            ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>'],
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1)
+        self.assertTrue('INNER JOIN' not in str(q.query))
+
+        """
+            Querying across several tables should strip only the last outer join,
+           while preserving the preceeding inner joins.
+        """
+        q = Tag.objects.filter(parent__parent__isnull=False)
+
+        self.assertQuerysetEqual(
+            q,
+            ['<Tag: t4>', '<Tag: t5>'],
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+
+        """
+            Querying without isnull should not convert anything to left outer join.
+        """
+        # querying across the tables without isnull should strip the inner join
+        q = Tag.objects.filter(parent__parent = self.t1)
+        self.assertQuerysetEqual(
+            q,
+            ['<Tag: t4>', '<Tag: t5>'],
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+
+        """
+            Querying via indirect fields should populate the left outer join
+        """
+        q = NamedCategory.objects.filter(tag__isnull=True)
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1)
+        # join to dumbcategory ptr_id
+        self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+        self.assertQuerysetEqual( q, [])
+
+        """
+            Querying across several tables should strip only the last join, while 
+           preserving the preceding left outer joins.
+        """
+        q = NamedCategory.objects.filter(tag__parent__isnull=True)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1)
+        self.assertQuerysetEqual( q, ['<NamedCategory: NamedCategory object>'])
+
+        """
+            Querying across m2m field should not strip the m2m table from join.
+        """
+        q = Author.objects.filter(item__tags__isnull=True)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a2>', '<Author: a3>'],
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 2)
+        self.assertTrue('INNER JOIN' not in str(q.query))
+
+        q = Author.objects.filter(item__tags__parent__isnull=True)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a3>'],
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 3)
+        self.assertTrue('INNER JOIN' not in str(q.query))
+
+        """
+            Querying with isnull=False across m2m field should not create outer joins
+        """
+        q = Author.objects.filter(item__tags__isnull=False)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a1>', '<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a4>']
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 2)
+
+        q = Author.objects.filter(item__tags__parent__isnull=False)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a1>', '<Author: a2>', '<Author: a4>']
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 3)
+
+        q = Author.objects.filter(item__tags__parent__parent__isnull=False)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a4>']
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 4)
+
+        """
+            Querying with isnull=True across m2m field should not create inner joins
+           and strip last outer join
+        """
+        q = Author.objects.filter(item__tags__parent__parent__isnull=True)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a1>', '<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a2>', '<Author: a3>']
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 4)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 0)
+
+        q = Author.objects.filter(item__tags__parent__isnull=True)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a3>']
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 3)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 0)
+
+        """
+            Reverse querying with isnull should not strip the join
+        """
+        q = Author.objects.filter(item__isnull=True)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a3>']
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 0)
+
+        q = Author.objects.filter(item__isnull=False)
+        self.assertQuerysetEqual(
+            q,
+            ['<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a4>']
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+
+        """
+            Querying with combined q-objects should also strip the left outer join
+        """
+        q = Tag.objects.filter(Q(parent__isnull=True)|Q(parent=self.t1))
+        self.assertQuerysetEqual(
+            q,
+            ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+        )
+        self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q.query).count('INNER JOIN') == 0)
+
+        """
+            Combining queries should not re-populate the left outer join
+        """
+        q1 = Tag.objects.filter(parent__isnull=True)
+        q2 = Tag.objects.filter(parent__isnull=False)
+
+        q3 = q1|q2
+        self.assertQuerysetEqual(
+            q3,
+            ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
+        )
+        self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+        q3 = q1&q2
+        self.assertQuerysetEqual( q3, [])
+        self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+        q2 = Tag.objects.filter(parent = self.t1)
+        q3 = q1|q2
+        self.assertQuerysetEqual(
+            q3,
+            ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+        )
+        self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+        q3 = q2|q1
+        self.assertQuerysetEqual(
+            q3,
+            ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+        )
+        self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 0)
+        self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+        q1 = Tag.objects.filter(parent__isnull=True)
+        q2 = Tag.objects.filter(parent__parent__isnull=True)
+
+        q3 = q1|q2
+        self.assertQuerysetEqual(
+            q3,
+            ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+        )
+        self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 1)
+        self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+        q3 = q2|q1
+        self.assertQuerysetEqual(
+            q3,
+            ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+        )
+        self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 1)
+        self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
 class Queries2Tests(TestCase):
     def setUp(self):
         Number.objects.create(num=4)
