Fixed #25367 -- Allowed boolean expressions in QuerySet.filter() and exclude().

This allows using expressions that have an output_field that is a
BooleanField to be used directly in a queryset filters, or in the
When() clauses of a Case() expression.

Thanks Josh Smeaton, Tim Graham, Simon Charette, Mariusz Felisiak, and
Adam Johnson for reviews.

Co-Authored-By: NyanKiyoshi <hello@vanille.bid>
This commit is contained in:
Matthew Schinckel 2017-02-27 19:31:52 +10:30 committed by Mariusz Felisiak
parent 069bee7c12
commit 4137fc2efc
10 changed files with 184 additions and 23 deletions

View file

@ -90,6 +90,8 @@ class Combinable:
return self._combine(other, self.POW, False)
def __and__(self, other):
if getattr(self, 'conditional', False) and getattr(other, 'conditional', False):
return Q(self) & Q(other)
raise NotImplementedError(
"Use .bitand() and .bitor() for bitwise logical operations."
)
@ -104,6 +106,8 @@ class Combinable:
return self._combine(other, self.BITRIGHTSHIFT, False)
def __or__(self, other):
if getattr(self, 'conditional', False) and getattr(other, 'conditional', False):
return Q(self) | Q(other)
raise NotImplementedError(
"Use .bitand() and .bitor() for bitwise logical operations."
)
@ -245,6 +249,10 @@ class BaseExpression:
])
return c
@property
def conditional(self):
return isinstance(self.output_field, fields.BooleanField)
@property
def field(self):
return self.output_field
@ -873,12 +881,17 @@ class ExpressionWrapper(Expression):
class When(Expression):
template = 'WHEN %(condition)s THEN %(result)s'
# This isn't a complete conditional expression, must be used in Case().
conditional = False
def __init__(self, condition=None, then=None, **lookups):
if lookups and condition is None:
condition, lookups = Q(**lookups), None
if condition is None or not getattr(condition, 'conditional', False) or lookups:
raise TypeError("__init__() takes either a Q object or lookups as keyword arguments")
raise TypeError(
'When() supports a Q object, a boolean expression, or lookups '
'as a condition.'
)
if isinstance(condition, Q) and not condition:
raise ValueError("An empty Q() can't be used as a When() condition.")
super().__init__(output_field=None)
@ -1090,6 +1103,7 @@ class Exists(Subquery):
class OrderBy(BaseExpression):
template = '%(expression)s %(ordering)s'
conditional = False
def __init__(self, expression, descending=False, nulls_first=False, nulls_last=False):
if nulls_first and nulls_last: