Simplified conditions, built by Q objects, if many conditions are joined in same mode

This commit is contained in:
M1ha 2018-12-11 18:04:08 +05:00
parent 12463c61b8
commit 0c92e2ac74

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import six import six
import pytz import pytz
from copy import copy from copy import copy, deepcopy
from math import ceil from math import ceil
from .utils import comma_join from .utils import comma_join
@ -170,6 +170,11 @@ class FOV(object):
def to_sql(self, model_cls): def to_sql(self, model_cls):
return self._operator.to_sql(model_cls, self._field_name, self._value) return self._operator.to_sql(model_cls, self._field_name, self._value)
def __deepcopy__(self, memodict={}):
res = copy(self)
res._value = deepcopy(self._value)
return res
class Q(object): class Q(object):
@ -178,8 +183,7 @@ class Q(object):
def __init__(self, **filter_fields): def __init__(self, **filter_fields):
self._fovs = [self._build_fov(k, v) for k, v in six.iteritems(filter_fields)] self._fovs = [self._build_fov(k, v) for k, v in six.iteritems(filter_fields)]
self._l_child = None self._children = []
self._r_child = None
self._negate = False self._negate = False
self._mode = self.AND_MODE self._mode = self.AND_MODE
@ -189,14 +193,22 @@ class Q(object):
Checks if there are any conditions in Q object Checks if there are any conditions in Q object
:return: Boolean :return: Boolean
""" """
return not bool(self._fovs or self._l_child or self._r_child) return not bool(self._fovs or self._children)
@classmethod @classmethod
def _construct_from(cls, l_child, r_child, mode): def _construct_from(cls, l_child, r_child, mode):
q = Q() if mode == l_child._mode:
q._l_child = l_child q = deepcopy(l_child)
q._r_child = r_child q._children.append(deepcopy(r_child))
q._mode = mode # AND/OR elif mode == r_child._mode:
q = deepcopy(r_child)
q._children.append(deepcopy(l_child))
else:
# Different modes
q = Q()
q._children = [l_child, r_child]
q._mode = mode # AND/OR
return q return q
def _build_fov(self, key, value): def _build_fov(self, key, value):
@ -207,15 +219,23 @@ class Q(object):
return FOV(field_name, operator, value) return FOV(field_name, operator, value)
def to_sql(self, model_cls): def to_sql(self, model_cls):
condition_sql = []
if self._fovs: if self._fovs:
sql = ' {} '.format(self._mode).join(fov.to_sql(model_cls) for fov in self._fovs) condition_sql.extend([fov.to_sql(model_cls) for fov in self._fovs])
elif self._l_child and self._r_child:
sql = '({}) {} ({})'.format(self._l_child.to_sql(model_cls), self._mode, self._r_child.to_sql(model_cls)) if self._children:
elif self._l_child or self._r_child: condition_sql.extend([child.to_sql(model_cls) for child in self._children if child])
# Return existing condition
sql = (self._l_child or self._r_child).to_sql(model_cls) if not condition_sql:
else: # Empty Q() object returns everything
sql = '1' sql = '1'
elif len(condition_sql) == 1:
# Skip not needed brackets over single condition
sql = condition_sql[0]
else:
# Each condition must be enclosed in brackets, or order of operations may be wrong
sql = '(%s)' % ') {} ('.format(self._mode).join(condition_sql)
if self._negate: if self._negate:
sql = 'NOT (%s)' % sql sql = 'NOT (%s)' % sql
@ -236,6 +256,17 @@ class Q(object):
def __bool__(self): def __bool__(self):
return not self.is_empty return not self.is_empty
def __deepcopy__(self, memodict={}):
q = Q()
q._fovs = [deepcopy(fov) for fov in self._fovs]
q._negate = self._negate
q._mode = self._mode
if self._children:
q._children = [deepcopy(child) for child in self._children]
return q
@six.python_2_unicode_compatible @six.python_2_unicode_compatible
class QuerySet(object): class QuerySet(object):