diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index e3e3585a8e..b30c6a355c 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1045,17 +1045,27 @@ class Query(object): self.promote_alias(table) self.where.add((alias, col, field, lookup_type, value), connector) + if negate: for alias in join_list: self.promote_alias(alias) - if final > 1 and lookup_type != 'isnull': - for alias in join_list: - if self.alias_map[alias] == self.LOUTER: - j_col = self.alias_map[alias][RHS_JOIN_COL] - entry = Node([(alias, j_col, None, 'isnull', True)]) - entry.negate() - self.where.add(entry, AND) - break + if lookup_type != 'isnull': + if final > 1: + for alias in join_list: + if self.alias_map[alias][JOIN_TYPE] == self.LOUTER: + j_col = self.alias_map[alias][RHS_JOIN_COL] + entry = Node([(alias, j_col, None, 'isnull', True)]) + entry.negate() + self.where.add(entry, AND) + break + elif not (lookup_type == 'in' and not value): + # Leaky abstraction artifact: We have to specifically + # exclude the "foo__in=[]" case from this handling, because + # it's short-circuited in the Where class. + entry = Node([(alias, col, field, 'isnull', True)]) + entry.negate() + self.where.add(entry, AND) + if can_reuse is not None: can_reuse.update(join_list) diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index 8059798f86..d7c8fba9c6 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -756,5 +756,11 @@ select_related(). We used to return the parent's Detail record here by mistake. >>> obj.person.details.data u'd2' +Bug #7076 -- excluding shouldn't eliminate NULL entries. +>>> Item.objects.exclude(modified=time1).order_by('name') +[, , ] +>>> Tag.objects.exclude(parent__name=t1.name) +[, , ] + """}