Index: django/db/models/sql/query.py
===================================================================
--- django/db/models/sql/query.py	(revision 8053)
+++ django/db/models/sql/query.py	(working copy)
@@ -986,6 +986,34 @@
             self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1,
                     used, next, restricted, new_nullable, dupe_set)
 
+    def parse_filter_expr(self, filter_expr):
+        """
+        Parses the given filter expression and returns a tuple containing
+        the field names, the lookup type and the value.
+        """
+        arg, value = filter_expr
+        parts = arg.split(LOOKUP_SEP)
+        if not parts:
+            raise FieldError("Cannot parse keyword query %r" % arg)
+
+        # Work out the lookup type and remove it from 'parts', if necessary.
+        if len(parts) == 1 or parts[-1] not in self.query_terms:
+            lookup_type = 'exact'
+        else:
+            lookup_type = parts.pop()
+
+        # 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':
+                raise ValueError("Cannot use None as a query value")
+            lookup_type = 'isnull'
+            value = True
+        elif callable(value):
+            value = value()
+
+        return (parts, lookup_type, value)
+
     def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
             can_reuse=None):
         """
@@ -1008,37 +1036,24 @@
         if we would otherwise force the creation of new aliases for a join
         (needed for nested Q-filters). The set is updated by this method.
         """
-        arg, value = filter_expr
-        parts = arg.split(LOOKUP_SEP)
-        if not parts:
-            raise FieldError("Cannot parse keyword query %r" % arg)
+        names, lookup_type, value = self.parse_filter_expr(filter_expr)
 
-        # Work out the lookup type and remove it from 'parts', if necessary.
-        if len(parts) == 1 or parts[-1] not in self.query_terms:
-            lookup_type = 'exact'
-        else:
-            lookup_type = parts.pop()
-
-        # 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':
-                raise ValueError("Cannot use None as a query value")
-            lookup_type = 'isnull'
-            value = True
-        elif callable(value):
-            value = value()
-
         opts = self.get_meta()
         alias = self.get_initial_alias()
         allow_many = trim or not negate
 
         try:
-            field, target, opts, join_list, last = self.setup_joins(parts, opts,
+            field, target, opts, join_list, last = self.setup_joins(names, opts,
                     alias, True, allow_many, can_reuse=can_reuse)
         except MultiJoin, e:
-            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]))
+            self.split_exclude(filter_expr, LOOKUP_SEP.join(names[:e.level]))
             return
+
+        self.setup_where(field, target, join_list, last, connector, lookup_type, 
+            value, negate, trim, can_reuse)
+
+    def setup_where(self, field, target, join_list, last, connector, 
+        lookup_type, value, negate, trim, can_reuse):
         final = len(join_list)
         penultimate = last.pop()
         if penultimate == final:
@@ -1164,6 +1179,140 @@
         if subtree:
             self.where.end_subtree()
 
+    def setup_join(self, opts, field, model, direct, m2m, name, joins,
+        exclusions, dupe_set, alias, dupe_multis, can_reuse=None):
+        """
+        Compute a single table join.
+        """
+        int_alias = None
+        
+        if model:
+            # The field lives on a base class of the current model.
+            for int_model in opts.get_base_chain(model):
+                lhs_col = opts.parents[int_model].column
+                dedupe = lhs_col in opts.duplicate_targets
+                if dedupe:
+                    exclusions.update(self.dupe_avoidance.get(
+                            (id(opts), lhs_col), ()))
+                    dupe_set.add((opts, lhs_col))
+                opts = int_model._meta
+                alias = self.join((alias, opts.db_table, lhs_col,
+                        opts.pk.column), exclusions=exclusions)
+                joins.append(alias)
+                exclusions.add(alias)
+                for (dupe_opts, dupe_col) in dupe_set:
+                    self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
+        cached_data = opts._join_cache.get(name)
+        orig_opts = opts
+        dupe_col = direct and field.column or field.field.column
+        dedupe = dupe_col in opts.duplicate_targets
+        if dupe_set or dedupe:
+            if dedupe:
+                dupe_set.add((opts, dupe_col))
+            exclusions.update(self.dupe_avoidance.get((id(opts), dupe_col),
+                    ()))
+
+        if direct:
+            if m2m:
+                # Many-to-many field defined on the current model.
+                if cached_data:
+                    (table1, from_col1, to_col1, table2, from_col2,
+                            to_col2, opts, target) = cached_data
+                else:
+                    table1 = field.m2m_db_table()
+                    from_col1 = opts.pk.column
+                    to_col1 = field.m2m_column_name()
+                    opts = field.rel.to._meta
+                    table2 = opts.db_table
+                    from_col2 = field.m2m_reverse_name()
+                    to_col2 = opts.pk.column
+                    target = opts.pk
+                    orig_opts._join_cache[name] = (table1, from_col1,
+                            to_col1, table2, from_col2, to_col2, opts,
+                            target)
+
+                int_alias = self.join((alias, table1, from_col1, to_col1),
+                        dupe_multis, exclusions, nullable=True,
+                        reuse=can_reuse)
+                alias = self.join((int_alias, table2, from_col2, to_col2),
+                        dupe_multis, exclusions, nullable=True,
+                        reuse=can_reuse)
+                joins.extend([int_alias, alias])
+            elif field.rel:
+                # One-to-one or many-to-one field
+                if cached_data:
+                    (table, from_col, to_col, opts, target) = cached_data
+                else:
+                    opts = field.rel.to._meta
+                    target = field.rel.get_related_field()
+                    table = opts.db_table
+                    from_col = field.column
+                    to_col = target.column
+                    orig_opts._join_cache[name] = (table, from_col, to_col,
+                            opts, target)
+                alias = self.join((alias, table, from_col, to_col),
+                        exclusions=exclusions, nullable=field.null)
+                joins.append(alias)
+            else:
+                # Non-relation fields.
+                target = field
+                return True, int_alias, alias, field, target, opts
+        else:
+            orig_field = field
+            field = field.field
+            if m2m:
+                # Many-to-many field defined on the target model.
+                if cached_data:
+                    (table1, from_col1, to_col1, table2, from_col2,
+                            to_col2, opts, target) = cached_data
+                else:
+                    table1 = field.m2m_db_table()
+                    from_col1 = opts.pk.column
+                    to_col1 = field.m2m_reverse_name()
+                    opts = orig_field.opts
+                    table2 = opts.db_table
+                    from_col2 = field.m2m_column_name()
+                    to_col2 = opts.pk.column
+                    target = opts.pk
+                    orig_opts._join_cache[name] = (table1, from_col1,
+                            to_col1, table2, from_col2, to_col2, opts,
+                            target)
+
+                int_alias = self.join((alias, table1, from_col1, to_col1),
+                        dupe_multis, exclusions, nullable=True,
+                        reuse=can_reuse)
+                alias = self.join((int_alias, table2, from_col2, to_col2),
+                        dupe_multis, exclusions, nullable=True,
+                        reuse=can_reuse)
+                joins.extend([int_alias, alias])
+            else:
+                # One-to-many field (ForeignKey defined on the target model)
+                if cached_data:
+                    (table, from_col, to_col, opts, target) = cached_data
+                else:
+                    local_field = opts.get_field_by_name(
+                            field.rel.field_name)[0]
+                    opts = orig_field.opts
+                    table = opts.db_table
+                    from_col = local_field.column
+                    to_col = field.column
+                    target = opts.pk
+                    orig_opts._join_cache[name] = (table, from_col, to_col,
+                            opts, target)
+
+                alias = self.join((alias, table, from_col, to_col),
+                        dupe_multis, exclusions, nullable=True,
+                        reuse=can_reuse)
+                joins.append(alias)
+
+        for (dupe_opts, dupe_col) in dupe_set:
+            if int_alias is None:
+                self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
+            else:
+                self.update_dupe_avoidance(dupe_opts, dupe_col, int_alias)
+
+        return False, int_alias, alias, field, target, opts
+
     def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
             allow_explicit_fk=False, can_reuse=None):
         """
@@ -1184,11 +1333,10 @@
         last = [0]
         dupe_set = set()
         exclusions = set()
+        int_alias = None
         for pos, name in enumerate(names):
-            try:
+            if int_alias:
                 exclusions.add(int_alias)
-            except NameError:
-                pass
             exclusions.add(alias)
             last.append(len(joins))
             if name == 'pk':
@@ -1213,132 +1361,13 @@
                 for alias in joins:
                     self.unref_alias(alias)
                 raise MultiJoin(pos + 1)
-            if model:
-                # The field lives on a base class of the current model.
-                for int_model in opts.get_base_chain(model):
-                    lhs_col = opts.parents[int_model].column
-                    dedupe = lhs_col in opts.duplicate_targets
-                    if dedupe:
-                        exclusions.update(self.dupe_avoidance.get(
-                                (id(opts), lhs_col), ()))
-                        dupe_set.add((opts, lhs_col))
-                    opts = int_model._meta
-                    alias = self.join((alias, opts.db_table, lhs_col,
-                            opts.pk.column), exclusions=exclusions)
-                    joins.append(alias)
-                    exclusions.add(alias)
-                    for (dupe_opts, dupe_col) in dupe_set:
-                        self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
-            cached_data = opts._join_cache.get(name)
-            orig_opts = opts
-            dupe_col = direct and field.column or field.field.column
-            dedupe = dupe_col in opts.duplicate_targets
-            if dupe_set or dedupe:
-                if dedupe:
-                    dupe_set.add((opts, dupe_col))
-                exclusions.update(self.dupe_avoidance.get((id(opts), dupe_col),
-                        ()))
 
-            if direct:
-                if m2m:
-                    # Many-to-many field defined on the current model.
-                    if cached_data:
-                        (table1, from_col1, to_col1, table2, from_col2,
-                                to_col2, opts, target) = cached_data
-                    else:
-                        table1 = field.m2m_db_table()
-                        from_col1 = opts.pk.column
-                        to_col1 = field.m2m_column_name()
-                        opts = field.rel.to._meta
-                        table2 = opts.db_table
-                        from_col2 = field.m2m_reverse_name()
-                        to_col2 = opts.pk.column
-                        target = opts.pk
-                        orig_opts._join_cache[name] = (table1, from_col1,
-                                to_col1, table2, from_col2, to_col2, opts,
-                                target)
-
-                    int_alias = self.join((alias, table1, from_col1, to_col1),
-                            dupe_multis, exclusions, nullable=True,
-                            reuse=can_reuse)
-                    alias = self.join((int_alias, table2, from_col2, to_col2),
-                            dupe_multis, exclusions, nullable=True,
-                            reuse=can_reuse)
-                    joins.extend([int_alias, alias])
-                elif field.rel:
-                    # One-to-one or many-to-one field
-                    if cached_data:
-                        (table, from_col, to_col, opts, target) = cached_data
-                    else:
-                        opts = field.rel.to._meta
-                        target = field.rel.get_related_field()
-                        table = opts.db_table
-                        from_col = field.column
-                        to_col = target.column
-                        orig_opts._join_cache[name] = (table, from_col, to_col,
-                                opts, target)
-
-                    alias = self.join((alias, table, from_col, to_col),
-                            exclusions=exclusions, nullable=field.null)
-                    joins.append(alias)
-                else:
-                    # Non-relation fields.
-                    target = field
-                    break
-            else:
-                orig_field = field
-                field = field.field
-                if m2m:
-                    # Many-to-many field defined on the target model.
-                    if cached_data:
-                        (table1, from_col1, to_col1, table2, from_col2,
-                                to_col2, opts, target) = cached_data
-                    else:
-                        table1 = field.m2m_db_table()
-                        from_col1 = opts.pk.column
-                        to_col1 = field.m2m_reverse_name()
-                        opts = orig_field.opts
-                        table2 = opts.db_table
-                        from_col2 = field.m2m_column_name()
-                        to_col2 = opts.pk.column
-                        target = opts.pk
-                        orig_opts._join_cache[name] = (table1, from_col1,
-                                to_col1, table2, from_col2, to_col2, opts,
-                                target)
-
-                    int_alias = self.join((alias, table1, from_col1, to_col1),
-                            dupe_multis, exclusions, nullable=True,
-                            reuse=can_reuse)
-                    alias = self.join((int_alias, table2, from_col2, to_col2),
-                            dupe_multis, exclusions, nullable=True,
-                            reuse=can_reuse)
-                    joins.extend([int_alias, alias])
-                else:
-                    # One-to-many field (ForeignKey defined on the target model)
-                    if cached_data:
-                        (table, from_col, to_col, opts, target) = cached_data
-                    else:
-                        local_field = opts.get_field_by_name(
-                                field.rel.field_name)[0]
-                        opts = orig_field.opts
-                        table = opts.db_table
-                        from_col = local_field.column
-                        to_col = field.column
-                        target = opts.pk
-                        orig_opts._join_cache[name] = (table, from_col, to_col,
-                                opts, target)
-
-                    alias = self.join((alias, table, from_col, to_col),
-                            dupe_multis, exclusions, nullable=True,
-                            reuse=can_reuse)
-                    joins.append(alias)
-
-            for (dupe_opts, dupe_col) in dupe_set:
-                try:
-                    self.update_dupe_avoidance(dupe_opts, dupe_col, int_alias)
-                except NameError:
-                    self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
-
+            final, int_alias, alias, field, target, opts = self.setup_join(opts, 
+                field, model, direct, m2m, name, joins, exclusions,
+                dupe_set, alias, dupe_multis, can_reuse=can_reuse)
+            if final:
+                break
+        
         if pos != len(names) - 1:
             raise FieldError("Join on field %r not permitted." % name)
 
