mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
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:
committed by
Mariusz Felisiak
parent
069bee7c12
commit
4137fc2efc
@@ -581,6 +581,13 @@ class BaseDatabaseOperations:
|
||||
"""
|
||||
pass
|
||||
|
||||
def conditional_expression_supported_in_where_clause(self, expression):
|
||||
"""
|
||||
Return True, if the conditional expression is supported in the WHERE
|
||||
clause.
|
||||
"""
|
||||
return True
|
||||
|
||||
def combine_expression(self, connector, sub_expressions):
|
||||
"""
|
||||
Combine a list of subexpressions into a single expression, using
|
||||
|
||||
@@ -6,6 +6,8 @@ from functools import lru_cache
|
||||
from django.conf import settings
|
||||
from django.db.backends.base.operations import BaseDatabaseOperations
|
||||
from django.db.backends.utils import strip_quotes, truncate_name
|
||||
from django.db.models.expressions import Exists, ExpressionWrapper
|
||||
from django.db.models.query_utils import Q
|
||||
from django.db.utils import DatabaseError
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
@@ -607,3 +609,14 @@ END;
|
||||
if fields:
|
||||
return self.connection.features.max_query_params // len(fields)
|
||||
return len(objs)
|
||||
|
||||
def conditional_expression_supported_in_where_clause(self, expression):
|
||||
"""
|
||||
Oracle supports only EXISTS(...) or filters in the WHERE clause, others
|
||||
must be compared with True.
|
||||
"""
|
||||
if isinstance(expression, Exists):
|
||||
return True
|
||||
if isinstance(expression, ExpressionWrapper) and isinstance(expression.expression, Q):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1229,6 +1229,16 @@ class Query(BaseExpression):
|
||||
"""
|
||||
if isinstance(filter_expr, dict):
|
||||
raise FieldError("Cannot parse keyword query as dict")
|
||||
if hasattr(filter_expr, 'resolve_expression') and getattr(filter_expr, 'conditional', False):
|
||||
if connections[DEFAULT_DB_ALIAS].ops.conditional_expression_supported_in_where_clause(filter_expr):
|
||||
condition = filter_expr.resolve_expression(self)
|
||||
else:
|
||||
# Expression is not supported in the WHERE clause, add
|
||||
# comparison with True.
|
||||
condition = self.build_lookup(['exact'], filter_expr.resolve_expression(self), True)
|
||||
clause = self.where_class()
|
||||
clause.add(condition, AND)
|
||||
return clause, []
|
||||
arg, value = filter_expr
|
||||
if not arg:
|
||||
raise FieldError("Cannot parse keyword query %r" % arg)
|
||||
|
||||
Reference in New Issue
Block a user