Ejemplo n.º 1
0
 def _rec_convert(ids):
     if comodel == model:
         return ids
     return select_from_where(
         cr, rel_id1, rel_table, rel_id2, ids, operator)
Ejemplo n.º 2
0
    def parse(self, cr, uid, context):
        """ Transform the leaves of the expression

            The principle is to pop elements from a leaf stack one at a time.
            Each leaf is processed. The processing is a if/elif list of various
            cases that appear in the leafs (many2one, function fields, ...).
            Two things can happen as a processing result:
            - the leaf has been modified and/or new leafs have to be introduced
              in the expression; they are pushed into the leaf stack, to be
              processed right after
            - the leaf is added to the result

            Some internal var explanation:
                :var list path: left operand seen as a sequence of field names
                    ("foo.bar" -> ["foo", "bar"])
                :var obj model: model object, model containing the field
                    (the name provided in the left operand)
                :var obj field: the field corresponding to `path[0]`
                :var obj column: the column corresponding to `path[0]`
                :var obj comodel: relational model of field (field.comodel)
                    (res_partner.bank_ids -> res.partner.bank)
        """

        self.result = []
        self.stack = [ExtendedLeaf(self, leaf, self.root_model)
                      for leaf in self.expression]

        # process from right to left; expression is from left to right
        self.stack.reverse()

        while self.stack:
            # Get the next leaf to process
            leaf = self.pop()

            left = leaf.left
            right = leaf.right
            operator = leaf.operator
            path = leaf.path

            model = leaf.model
            field = model._fields.get(path[0])
            column = model._columns.get(path[0])
            comodel = model.pool.get(getattr(field, 'comodel_name', None))

            # ----------------------------------------
            # SIMPLE CASE
            # 1. leaf is an operator
            # 2. leaf is a true/false leaf
            # -> add directly to result
            # ----------------------------------------

            if leaf.is_operator() or leaf.is_true_leaf(
            ) or leaf.is_false_leaf():
                self.push_result(leaf)

            # ----------------------------------------
            # FIELD NOT FOUND
            # -> from inherits'd fields -> work on the related model, and add
            #    a join condition
            # -> ('id', 'child_of', '..') -> use a 'to_ids'
            # -> but is one on the _log_access special fields, add directly to
            #    result
            #    TODO: make these fields explicitly available in self.columns
            #    instead!
            # -> else: crash
            # ----------------------------------------

            elif not column and path[0] in model._inherit_fields:
                # comments about inherits'd fields
                #  { 'field_name': ('parent_model', 'm2o_field_to_reach_parent'
                # field_column_obj, origina_parent_model), ... }
                next_model = model.pool[model._inherit_fields[path[0]][0]]
                leaf.add_join_context(
                    next_model, model._inherits[
                        next_model._name], 'id', model._inherits[
                        next_model._name])
                self.push(leaf)

            elif left == 'id' and operator == 'child_of':
                ids2 = self.to_ids(right, model)
                dom = self.child_of_domain(left, ids2, model)
                for dom_leaf in reversed(dom):
                    self.push_new_leaf(leaf, dom_leaf, model)

            elif not column and path[0] in MAGIC_COLUMNS:
                self.push_result(leaf)

            elif not field:
                raise ValueError(
                    "Invalid field %r in leaf %r" % (left, str(leaf)))

            # ----------------------------------------
            # PATH SPOTTED
            # -> many2one or one2many with _auto_join:
            #    - add a join, then jump into linked column: column.remaining
            #      on
            #      src_table is replaced by remaining on dst_table, and set for
            #      re-evaluation
            #    - if a domain is defined on the column, add it into evaluation
            #      on the relational table
            # -> many2one, many2many, one2many: replace by an equivalent
            #      computed
            #    domain, given by recursively searching on the remaining of the
            #      path
            # -> note: hack about columns.property should not be necessary
            #      anymore
            #    as after transforming the column, it will go through this loop
            #      once again
            # ----------------------------------------

            elif column._type == 'one2many':
                parser = LeafParserOne2many(leaf, column)
                parser.parse()

            elif (
                len(path) > 1 and
                column._type == 'many2one' and
                is_stored_column(column)
            ):
                # res_partner.state_id = res_partner__state_id.id
                leaf.add_join_context(comodel, path[0], 'id', path[0])
                self.push_new_leaf(leaf, (path[1], operator, right), comodel)

            elif (
                len(path) > 1 and
                column._type == 'many2many' and
                is_stored_column(column)
            ):
                leaf.add_join_context_m2m(
                    comodel,
                    column._rel,
                    column._id1,
                    column._id2,
                    path[0],
                )

                self.push_new_leaf(
                    leaf, (path[1], operator, right), comodel)

            elif len(path) > 1 and column._type == 'many2one':
                # Many2one not stored fields
                right_ids = comodel.search(
                    cr, uid, [(path[1], operator, right)], context=context)
                leaf.leaf = (path[0], 'in', right_ids)
                self.push(leaf)

            elif len(path) > 1 and column._type == 'many2many':
                # One2many not stored fields
                right_ids = comodel.search(
                    cr, uid, [(path[1], operator, right)], context=context)
                table_ids = model.search(
                    cr, uid, [(path[0], 'in', right_ids)],
                    context=dict(context, active_test=False))
                leaf.leaf = ('id', 'in', table_ids)
                self.push(leaf)

            elif not column:
                # Non-stored field should provide an implementation of search.
                if not field.search:
                    # field does not support search!
                    _logger.error(
                        "Non-stored field %s cannot be searched.", field)
                    if _logger.isEnabledFor(logging.DEBUG):
                        _logger.debug(''.join(traceback.format_stack()))
                    # Ignore it: generate a dummy leaf.
                    domain = []
                else:
                    # Let the field generate a domain.
                    recs = model.browse(cr, uid, [], context)
                    domain = field.determine_domain(recs, operator, right)

                if not domain:
                    leaf.leaf = TRUE_LEAF
                    self.push(leaf)
                else:
                    for elem in reversed(domain):
                        self.push_new_leaf(leaf, elem, model)

            # -------------------------------------------------
            # FUNCTION FIELD
            # -> not stored: error if no _fnct_search, otherwise handle
            # the result domain
            # -> stored: management done in the remaining of parsing
            # -------------------------------------------------

            elif isinstance(column, fields.function) and not column.store:
                # this is a function field that is not stored
                if not column._fnct_search:
                    _logger.error(
                        "Field '%s' (%s) can not be searched: "
                        "non-stored function field without fnct_search",
                        column.string, left)
                    # avoid compiling stack trace if not needed
                    if _logger.isEnabledFor(logging.DEBUG):
                        _logger.debug(''.join(traceback.format_stack()))
                    # ignore it: generate a dummy leaf
                    fct_domain = []
                else:
                    fct_domain = column.search(
                        cr, uid, model, left, [leaf.leaf], context=context)

                if not fct_domain:
                    leaf.leaf = TRUE_LEAF
                    self.push(leaf)
                else:
                    # we assume that the expression is valid
                    # we create a dummy leaf for forcing the parsing of the
                    # resulting expression
                    for domain_element in reversed(fct_domain):
                        self.push_new_leaf(leaf, domain_element, model)

            # -------------------------------------------------
            # RELATIONAL FIELDS
            # -------------------------------------------------

            # Applying recursivity on field(one2many)
            elif column._type == 'one2many' and operator == 'child_of':
                ids2 = self.to_ids(right, comodel)
                if column._obj != model._name:
                    dom = self.child_of_domain(left, ids2, comodel)
                else:
                    dom = self.child_of_domain('id', ids2, model, parent=left)
                for dom_leaf in reversed(dom):
                    self.push_new_leaf(leaf, dom_leaf, model)

            elif column._type == 'many2many':
                rel_table, rel_id1, rel_id2 = column._sql_names(model)
                # FIXME
                if operator == 'child_of':
                    def _rec_convert(ids):
                        if comodel == model:
                            return ids
                        return select_from_where(
                            cr, rel_id1, rel_table, rel_id2, ids, operator)

                    ids2 = self.to_ids(right, comodel)
                    dom = self.child_of_domain('id', ids2, comodel)
                    ids2 = comodel.search(cr, uid, dom, context=context)
                    self.push_new_leaf(
                        leaf, ('id', 'in', _rec_convert(ids2)), model)
                else:
                    call_null_m2m = True
                    if right is not False:
                        if isinstance(right, basestring):
                            res_ids = [x[0] for x in comodel.name_search(
                                cr, uid, right, [], operator, context=context)]
                            if res_ids:
                                operator = 'in'
                        else:
                            if not isinstance(right, list):
                                res_ids = [right]
                            else:
                                res_ids = right
                        if not res_ids:
                            if operator in ['like', 'ilike', 'in', '=']:
                                # no result found with given search criteria
                                call_null_m2m = False
                                self.push_new_leaf(leaf, FALSE_LEAF, model)
                            else:
                                # operator changed because ids are directly
                                # related to main object
                                operator = 'in'
                        else:
                            call_null_m2m = False
                            m2m_op = (
                                'not in' if operator in NEGATIVE_TERM_OPERATORS
                                else 'in')
                            self.push_new_leaf(
                                leaf, (
                                    'id',
                                    m2m_op,
                                    select_from_where(
                                        cr,
                                        rel_id1,
                                        rel_table,
                                        rel_id2,
                                        res_ids,
                                        operator
                                    ) or [0]),
                                model)

                    if call_null_m2m:
                        m2m_op = (
                            'in' if operator in NEGATIVE_TERM_OPERATORS
                            else 'not in')
                        self.push_new_leaf(
                            leaf, (
                                'id',
                                m2m_op,
                                select_distinct_from_where_not_null(
                                    cr, rel_id1, rel_table)
                            ), model)

            elif column._type == 'many2one':
                if operator == 'child_of':
                    ids2 = self.to_ids(right, comodel)
                    if column._obj != model._name:
                        dom = self.child_of_domain(left, ids2, comodel)
                    else:
                        dom = self.child_of_domain(
                            'id', ids2, model, parent=left)
                    for dom_leaf in reversed(dom):
                        self.push_new_leaf(leaf, dom_leaf, model)
                else:

                    # resolve string-based m2o criterion into IDs
                    if isinstance(
                        right, basestring) or right and isinstance(
                        right, (tuple, list)) and all(
                        isinstance(
                            item, basestring) for item in right):
                        self.push_new_leaf(
                            leaf,
                            self._get_many2one_expression(
                                comodel, left, right, operator),
                            model)
                    else:
                        # right == [] or right == False and all other cases are
                        # handled by __leaf_to_sql()
                        self.push_result(leaf)

            # -------------------------------------------------
            # OTHER FIELDS
            # -> datetime fields: manage time part of the datetime
            #    column when it is not there
            # -> manage translatable fields
            # -------------------------------------------------

            else:
                if column._type == 'datetime' and right and len(right) == 10:
                    if operator in ('>', '<='):
                        right += ' 23:59:59'
                    else:
                        right += ' 00:00:00'
                    self.push_new_leaf(leaf, (left, operator, right), model)

                elif column.translate and right:
                    need_wildcard = operator in (
                        'like', 'ilike', 'not like', 'not ilike')
                    sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(
                        operator, operator)
                    if need_wildcard:
                        right = '%%%s%%' % right

                    inselect_operator = 'inselect'
                    if sql_operator in NEGATIVE_TERM_OPERATORS:
                        # negate operator (fix lp:1071710)
                        sql_operator = sql_operator[
                            4:] if sql_operator[:3] == 'not' else '='
                        inselect_operator = 'not inselect'

                    unaccent = self._unaccent if sql_operator.endswith(
                        'like') else lambda x: x

                    instr = unaccent('%s')

                    if sql_operator == 'in':
                        # params will be flatten by to_sql() => expand the
                        # placeholders
                        instr = '(%s)' % ', '.join(['%s'] * len(right))

                    subselect = """WITH temp_irt_current (id, name) as (
                            SELECT ct.id, coalesce(it.value,ct.{quote_left})
                            FROM {current_table} ct
                            LEFT JOIN ir_translation it ON (it.name = %s and
                                        it.lang = %s and
                                        it.type = %s and
                                        it.res_id = ct.id and
                                        it.value != '')
                            )
                            SELECT id FROM temp_irt_current
                            WHERE {name} {operator} {right} order by name
                            """.format(
                                current_table=model._table,
                                quote_left=_quote(left),
                                name=unaccent('name'),
                                operator=sql_operator,
                                right=instr)

                    params = (
                        model._name + ',' + left,
                        context.get('lang') or 'en_US',
                        'model',
                        right,
                    )
                    self.push_new_leaf(
                        leaf,
                        ('id', inselect_operator, (subselect, params)),
                        model)

                else:
                    self.push_result(leaf)

        # ----------------------------------------
        # END OF PARSING FULL DOMAIN
        # -> generate joins
        # ----------------------------------------

        joins = set()
        for leaf in self.result:
            joins |= set(leaf.get_join_conditions())
        self.joins = list(joins)
Ejemplo n.º 3
0
 def _rec_convert(ids):
     if relational_model == working_model:
         return ids
     return select_from_where(cr, rel_id1, rel_table, rel_id2,
                              ids, operator)
Ejemplo n.º 4
0
def parse(self, cr, uid, context):
    """ Transform the leaves of the expression

        The principle is to pop elements from a leaf stack one at a time.
        Each leaf is processed. The processing is a if/elif list of various
        cases that appear in the leafs (many2one, function fields, ...).
        Two things can happen as a processing result:
        - the leaf has been modified and/or new leafs have to be introduced
          in the expression; they are pushed into the leaf stack, to be
          processed right after
        - the leaf is added to the result

        Some internal var explanation:
            :var obj working_model: model object, model containing the field
                (the name provided in the left operand)
            :var list field_path: left operand seen as a path
                 (foo.bar -> [foo, bar])
            :var obj relational_model: relational model of a field (field._obj)
                ex: res_partner.bank_ids -> res.partner.bank
    """

    def to_ids(value, relational_model, context=None, limit=None):
        """
        Normalize a single id or name, or a list of those,
        into a list of ids
        :param {int,long,basestring,list,tuple} value:
            if int, long -> return [value]
            if basestring, convert it into a list of basestrings, then
            if list of basestring ->
                perform a name_search on relational_model for each name
                return the list of related ids
        """
        names = []
        if isinstance(value, basestring):
            names = [value]
        elif value and isinstance(value, (tuple, list)) and \
                all(isinstance(item, basestring) for item in value):
            names = value
        elif isinstance(value, (int, long)):
            return [value]
        if names:
            name_get_list = [name_get[0] for name in names for name_get in
                             relational_model.name_search(
                                 cr, uid, name, [], 'ilike', context=context,
                                 limit=limit)]
            return list(set(name_get_list))
        return list(value)

    def child_of_domain(left, ids, left_model, parent=None, prefix='',
                        context=None):
        """
        Return a domain implementing the child_of operator for
        [(left,child_of,ids)], either as a range using the parent_left/right
        tree lookup fields (when available),
        or as an expanded [(left,in,child_ids)]
        """
        if left_model._parent_store and (not left_model.pool._init):
            # TODO: Improve joins implemented for many with '.', replace by:
            # doms += ['&',(prefix+'.parent_left','<',o.parent_right),
            # (prefix+'.parent_left','>=',o.parent_left)]
            doms = []
            for o in left_model.browse(cr, uid, ids, context=context):
                if doms:
                    doms.insert(0, OR_OPERATOR)
                doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right),
                         ('parent_left', '>=', o.parent_left)]
            if prefix:
                return [(left, 'in', left_model.search(
                    cr, uid, doms, context=context))]
            return doms
        else:
            def recursive_children(ids, model, parent_field):
                if not ids:
                    return []
                ids2 = model.search(cr, uid, [(parent_field, 'in', ids)],
                                    context=context)
                return ids + recursive_children(ids2, model, parent_field)
            return [(left, 'in', recursive_children(
                ids, left_model, parent or left_model._parent_name))]

    def pop():
        """ Pop a leaf to process. """
        return self.stack.pop()

    def push(leaf):
        """ Push a leaf to be processed right after. """
        self.stack.append(leaf)

    def push_result(leaf):
        """ Push a leaf to the results. This leaf has been fully processed
            and validated. """
        self.result.append(leaf)

    self.result = []
    self.stack = [ExtendedLeaf(leaf, self.root_model)
                  for leaf in self.expression]
    # process from right to left; expression is from left to right
    self.stack.reverse()

    while self.stack:
        # Get the next leaf to process
        leaf = pop()

        # Get working variables
        working_model = leaf.model
        if leaf.is_operator():
            left, operator, right = leaf.leaf, None, None
        elif leaf.is_true_leaf() or leaf.is_false_leaf():
            # because we consider left as a string
            left, operator, right = ('%s' % leaf.leaf[0], leaf.leaf[1],
                                     leaf.leaf[2])
        else:
            left, operator, right = leaf.leaf
        field_path = left.split('.', 1)
        field = working_model._columns.get(field_path[0])
        # Neova Health BEGIN
        if not working_model._columns.get(field_path[0]) and \
                field_path[0] == 'id':
            # field 'id' normally is not in the _columns
            # the problem appeared with call
            # search('t4clinical.task.base', [
            # ('responsible_user_ids','in',uid)])
            # -- returned [], due to this issue was looking for
            # t4clinical.task.base ids in project.task ids
            field = fields.integer('fake id field. quick fix')
            field._obj = working_model._name
        # Neova Health END
        if field and field._obj:
            relational_model = working_model.pool.get(field._obj)
        else:
            relational_model = None

        # ----------------------------------------
        # SIMPLE CASE
        # 1. leaf is an operator
        # 2. leaf is a true/false leaf
        # -> add directly to result
        # ----------------------------------------

        if leaf.is_operator() or leaf.is_true_leaf() or leaf.is_false_leaf():
            push_result(leaf)

        # ----------------------------------------
        # FIELD NOT FOUND
        # -> from inherits'd fields -> work on the related model, and add
        #    a join condition
        # -> ('id', 'child_of', '..') -> use a 'to_ids'
        # -> but is one on the _log_access special fields, add directly to
        #    result
        # TODO: make these fields explicitly available in self.columns instead!
        # -> else: crash
        # ----------------------------------------

        elif not field and field_path[0] in working_model._inherit_fields:
            # comments about inherits'd fields
            #  { 'field_name': ('parent_model', 'm2o_field_to_reach_parent',
            #                    field_column_obj, origina_parent_model), ... }
            next_model = working_model.pool.get(
                working_model._inherit_fields[field_path[0]][0])
            leaf.add_join_context(
                next_model, working_model._inherits[next_model._name], 'id',
                working_model._inherits[next_model._name])
            push(leaf)

        elif left == 'id' and operator == 'child_of':
            ids2 = to_ids(right, working_model, context)
            dom = child_of_domain(left, ids2, working_model)
            for dom_leaf in reversed(dom):
                new_leaf = create_substitution_leaf(leaf, dom_leaf,
                                                    working_model)
                push(new_leaf)

        elif not field and field_path[0] in MAGIC_COLUMNS:
            push_result(leaf)

        elif not field:
            raise ValueError("Invalid field %r in leaf %r" % (left, str(leaf)))

        # ----------------------------------------
        # PATH SPOTTED
        # -> many2one or one2many with _auto_join:
        #    - add a join, then jump into linked field: field.remaining on
        #      src_table is replaced by remaining on dst_table,
        #      and set for re-evaluation
        #    - if a domain is defined on the field, add it into evaluation
        #      on the relational table
        # -> many2one, many2many, one2many: replace by an equivalent computed
        #    domain, given by recursively searching on the path's remaining
        # -> note: hack about fields.property should not be necessary anymore
        #    as after transforming the field, it will go through this loop
        #    once again
        # ----------------------------------------

        elif len(field_path) > 1 and field._type == 'many2one' and \
                field._auto_join:
            # res_partner.state_id = res_partner__state_id.id
            leaf.add_join_context(relational_model, field_path[0], 'id',
                                  field_path[0])
            push(create_substitution_leaf(
                leaf, (field_path[1], operator, right), relational_model))

        elif len(field_path) > 1 and field._type == 'one2many' and \
                field._auto_join:
            # res_partner.id = res_partner__bank_ids.partner_id
            leaf.add_join_context(relational_model, 'id', field._fields_id,
                                  field_path[0])
            domain = field._domain(working_model) if callable(field._domain) \
                else field._domain
            push(create_substitution_leaf(
                leaf, (field_path[1], operator, right), relational_model))
            if domain:
                domain = normalize_domain(domain)
                for elem in reversed(domain):
                    push(create_substitution_leaf(leaf, elem,
                                                  relational_model))
                push(create_substitution_leaf(leaf, AND_OPERATOR,
                                              relational_model))

        elif len(field_path) > 1 and field._auto_join:
            raise NotImplementedError('_auto_join attribute not supported on '
                                      'many2many field %s' % left)

        elif len(field_path) > 1 and field._type == 'many2one':
            right_ids = relational_model.search(
                cr, uid, [(field_path[1], operator, right)], context=context)
            leaf.leaf = (field_path[0], 'in', right_ids)
            push(leaf)

        # Making search easier when there is a left operand
        # as field.o2m or field.m2m
        elif len(field_path) > 1 and field._type in ['many2many', 'one2many']:
            right_ids = relational_model.search(
                cr, uid, [(field_path[1], operator, right)], context=context)
            table_ids = working_model.search(
                cr, uid, [(field_path[0], 'in', right_ids)],
                context=dict(context, active_test=False))
            leaf.leaf = ('id', 'in', table_ids)
            push(leaf)

        # -------------------------------------------------
        # FUNCTION FIELD
        # -> not stored: error if no _fnct_search,
        #    otherwise handle the result domain
        # -> stored: management done in the remaining of parsing
        # -------------------------------------------------

        elif isinstance(field, fields.function) and not field.store \
                and not field._fnct_search:
            # this is a function field that is not stored
            # the function field doesn't provide a search function and
            # doesn't store values in the database, so we must ignore it:
            # we generate a dummy leaf.
            leaf.leaf = TRUE_LEAF
            _logger.error(
                "The field '%s' (%s) can not be searched: non-stored "
                "function field without fnct_search",
                field.string, left)
            # avoid compiling stack trace if not needed
            if _logger.isEnabledFor(logging.DEBUG):
                _logger.debug(''.join(traceback.format_stack()))
            push(leaf)

        elif isinstance(field, fields.function) and not field.store:
            # this is a function field that is not stored
            fct_domain = field.search(cr, uid, working_model, left,
                                      [leaf.leaf], context=context)
            if not fct_domain:
                leaf.leaf = TRUE_LEAF
                push(leaf)
            else:
                # we assume that the expression is valid
                # we create a dummy leaf for forcing the parsing of the
                # resulting expression
                for domain_element in reversed(fct_domain):
                    push(create_substitution_leaf(leaf, domain_element,
                                                  working_model))
                # self.push(
                # create_substitution_leaf(leaf, TRUE_LEAF, working_model))
                # self.push(
                # create_substitution_leaf(leaf, AND_OPERATOR, working_model))

        # -------------------------------------------------
        # RELATIONAL FIELDS
        # -------------------------------------------------

        # Applying recursivity on field(one2many)
        elif field._type == 'one2many' and operator == 'child_of':
            ids2 = to_ids(right, relational_model, context)
            if field._obj != working_model._name:
                dom = child_of_domain(left, ids2, relational_model,
                                      prefix=field._obj)
            else:
                dom = child_of_domain('id', ids2, working_model, parent=left)
            for dom_leaf in reversed(dom):
                push(create_substitution_leaf(leaf, dom_leaf, working_model))

        elif field._type == 'one2many':
            call_null = True

            if right is not False:
                if isinstance(right, basestring):
                    ids2 = [x[0] for x in relational_model.name_search(
                        cr, uid, right, [], operator, context=context,
                        limit=None)
                    ]
                    if ids2:
                        operator = 'in'
                else:
                    if not isinstance(right, list):
                        ids2 = [right]
                    else:
                        ids2 = right
                if not ids2:
                    if operator in ['like', 'ilike', 'in', '=']:
                        # no result found with given search criteria
                        call_null = False
                        push(create_substitution_leaf(leaf, FALSE_LEAF,
                                                      working_model))
                else:
                    ids2 = select_from_where(cr, field._fields_id,
                                             relational_model._table, 'id',
                                             ids2, operator)
                    if ids2:
                        call_null = False
                        o2m_op = 'not in' if operator in \
                            NEGATIVE_TERM_OPERATORS else 'in'
                        push(create_substitution_leaf(
                            leaf, ('id', o2m_op, ids2), working_model))

            if call_null:
                o2m_op = 'in' if operator in \
                    NEGATIVE_TERM_OPERATORS else 'not in'
                push(create_substitution_leaf(
                    leaf, ('id', o2m_op,
                           select_distinct_from_where_not_null(
                               cr, field._fields_id, relational_model._table)),
                    working_model))

        elif field._type == 'many2many':
            rel_table, rel_id1, rel_id2 = field._sql_names(working_model)
            # FIXME
            if operator == 'child_of':
                def _rec_convert(ids):
                    if relational_model == working_model:
                        return ids
                    return select_from_where(cr, rel_id1, rel_table, rel_id2,
                                             ids, operator)

                ids2 = to_ids(right, relational_model, context)
                dom = child_of_domain('id', ids2, relational_model)
                ids2 = relational_model.search(cr, uid, dom, context=context)
                push(create_substitution_leaf(leaf,
                                              ('id', 'in', _rec_convert(ids2)),
                                              working_model))
            else:
                call_null_m2m = True
                if right is not False:
                    if isinstance(right, basestring):
                        res_ids = [x[0] for x in relational_model.name_search(
                            cr, uid, right, [], operator, context=context)]
                        if res_ids:
                            operator = 'in'
                    else:
                        if not isinstance(right, list):
                            res_ids = [right]
                        else:
                            res_ids = right
                    if not res_ids:
                        if operator in ['like', 'ilike', 'in', '=']:
                            # no result found with given search criteria
                            call_null_m2m = False
                            push(create_substitution_leaf(leaf, FALSE_LEAF,
                                                          working_model))
                        else:
                            # operator changed because ids are directly related
                            # to main object
                            operator = 'in'
                    else:
                        call_null_m2m = False
                        m2m_op = 'not in' if operator in \
                            NEGATIVE_TERM_OPERATORS else 'in'
                        push(create_substitution_leaf(
                            leaf, ('id', m2m_op,
                                   select_from_where(
                                       cr, rel_id1, rel_table, rel_id2,
                                       res_ids, operator) or [0]),
                            working_model))

                if call_null_m2m:
                    m2m_op = 'in' if operator in \
                        NEGATIVE_TERM_OPERATORS else 'not in'
                    push(create_substitution_leaf(
                        leaf,
                        ('id', m2m_op, select_distinct_from_where_not_null(
                            cr, rel_id1, rel_table)), working_model))

        elif field._type == 'many2one':
            if operator == 'child_of':
                ids2 = to_ids(right, relational_model, context)
                if field._obj != working_model._name:
                    dom = child_of_domain(left, ids2, relational_model,
                                          prefix=field._obj)
                else:
                    dom = child_of_domain('id', ids2, working_model,
                                          parent=left)
                for dom_leaf in reversed(dom):
                    push(create_substitution_leaf(leaf, dom_leaf,
                                                  working_model))
            else:
                def _get_expression(relational_model, cr, uid, left, right,
                                    operator, context=None):
                    if context is None:
                        context = {}
                    c = context.copy()
                    c['active_test'] = False
                    # Special treatment to ill-formed domains
                    operator = (operator in ['<', '>', '<=', '>=']) and 'in' \
                        or operator

                    dict_op = {'not in': '!=', 'in': '=',
                               '=': 'in', '!=': 'not in'}
                    if isinstance(right, tuple):
                        right = list(right)
                    if (not isinstance(right, list)) and \
                            operator in ['not in', 'in']:
                        operator = dict_op[operator]
                    elif isinstance(right, list) and operator in ['!=', '=']:
                        # for domain (FIELD,'=',['value1','value2'])
                        operator = dict_op[operator]
                    res_ids = [x[0] for x in relational_model.name_search(
                        cr, uid, right, [], operator, limit=None, context=c)]
                    if operator in NEGATIVE_TERM_OPERATORS:
                        # TODO this should not be appended if False in 'right'
                        res_ids.append(False)
                    return left, 'in', res_ids
                # resolve string-based m2o criterion into IDs
                if isinstance(right, basestring) or right and \
                        isinstance(right, (tuple, list)) and \
                        all(isinstance(item, basestring) for item in right):
                    push(create_substitution_leaf(
                        leaf,
                        _get_expression(relational_model, cr, uid, left, right,
                                        operator, context=context),
                        working_model))
                else:
                    # right == [] or right == False and all other cases
                    # are handled by __leaf_to_sql()
                    push_result(leaf)

        # -------------------------------------------------
        # OTHER FIELDS
        # -> datetime fields: manage time part of the datetime
        #    field when it is not there
        # -> manage translatable fields
        # -------------------------------------------------

        else:
            if field._type == 'datetime' and right and len(right) == 10:
                if operator in ('>', '>='):
                    right += ' 00:00:00'
                elif operator in ('<', '<='):
                    right += ' 23:59:59'
                push(create_substitution_leaf(leaf, (left, operator, right),
                                              working_model))

            elif field.translate and right:
                need_wildcard = operator in ('like', 'ilike', 'not like',
                                             'not ilike')
                sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(
                    operator, operator)
                if need_wildcard:
                    right = '%%%s%%' % right

                inselect_operator = 'inselect'
                if sql_operator in NEGATIVE_TERM_OPERATORS:
                    # negate operator (fix lp:1071710)
                    if sql_operator[:3] == 'not':
                        sql_operator = sql_operator[4:]
                    else:
                        sql_operator = '='
                    inselect_operator = 'not inselect'

                unaccent = self._unaccent if sql_operator.endswith('like') \
                    else lambda x: x

                trans_left = unaccent('value')
                quote_left = unaccent(_quote(left))
                instr = unaccent('%s')

                if sql_operator == 'in':
                    # params will be flatten by to_sql() =>
                    # expand the placeholders
                    instr = '(%s)' % ', '.join(['%s'] * len(right))

                subselect = """(SELECT res_id
                                  FROM ir_translation
                                 WHERE name = %s
                                   AND lang = %s
                                   AND type = %s
                                   AND {trans_left} {operator} {right}
                               ) UNION (
                                SELECT id
                                  FROM "{table}"
                                 WHERE {left} {operator} {right}
                               )
                            """.format(trans_left=trans_left,
                                       operator=sql_operator, right=instr,
                                       table=working_model._table,
                                       left=quote_left)

                params = (
                    working_model._name + ',' + left,
                    context.get('lang') or 'en_US',
                    'model',
                    right,
                    right,
                )
                push(create_substitution_leaf(leaf, ('id', inselect_operator,
                                                     (subselect, params)
                                                     ), working_model))

            else:
                push_result(leaf)

    # ----------------------------------------
    # END OF PARSING FULL DOMAIN
    # -> generate joins
    # ----------------------------------------

    joins = set()
    for leaf in self.result:
        joins |= set(leaf.get_join_conditions())
    self.joins = list(joins)
 def _rec_convert(ids):
     if relational_model == working_model:
         return ids
     return select_from_where(cr, rel_id1, rel_table, rel_id2,
                              ids, operator)
def parse(self, cr, uid, context):
    """
    Transform the leaves of the expression

    The principle is to pop elements from a leaf stack one at a time.
    Each leaf is processed. The processing is a if/elif list of various
    cases that appear in the leafs (many2one, function fields, ...).
    Two things can happen as a processing result:
    - the leaf has been modified and/or new leafs have to be introduced
      in the expression; they are pushed into the leaf stack, to be
      processed right after
    - the leaf is added to the result

    Some internal var explanation:
        :var obj working_model: model object, model containing the field
            (the name provided in the left operand)
        :var list field_path: left operand seen as a path
             (foo.bar -> [foo, bar])
        :var obj relational_model: relational model of a field (field._obj)
            ex: res_partner.bank_ids -> res.partner.bank
    """

    def to_ids(value, relational_model, context=None, limit=None):
        """
        Normalize a single id or name, or a list of those,
        into a list of ids
        :param {int,long,basestring,list,tuple} value:
            if int, long -> return [value]
            if basestring, convert it into a list of basestrings, then
            if list of basestring ->
                perform a name_search on relational_model for each name
                return the list of related ids
        """
        names = []
        if isinstance(value, basestring):
            names = [value]
        elif value and isinstance(value, (tuple, list)) and \
                all(isinstance(item, basestring) for item in value):
            names = value
        elif isinstance(value, (int, long)):
            return [value]
        if names:
            name_get_list = [name_get[0] for name in names for name_get in
                             relational_model.name_search(
                                 cr, uid, name, [], 'ilike', context=context,
                                 limit=limit)]
            return list(set(name_get_list))
        return list(value)

    def child_of_domain(left, ids, left_model, parent=None, prefix='',
                        context=None):
        """
        Return a domain implementing the child_of operator for
        [(left,child_of,ids)], either as a range using the parent_left/right
        tree lookup fields (when available),
        or as an expanded [(left,in,child_ids)]
        """
        if left_model._parent_store and (not left_model.pool._init):
            # TODO: Improve joins implemented for many with '.', replace by:
            # doms += ['&',(prefix+'.parent_left','<',o.parent_right),
            # (prefix+'.parent_left','>=',o.parent_left)]
            doms = []
            for o in left_model.browse(cr, uid, ids, context=context):
                if doms:
                    doms.insert(0, OR_OPERATOR)
                doms += [AND_OPERATOR, ('parent_left', '<', o.parent_right),
                         ('parent_left', '>=', o.parent_left)]
            if prefix:
                return [(left, 'in', left_model.search(
                    cr, uid, doms, context=context))]
            return doms
        else:
            def recursive_children(ids, model, parent_field):
                if not ids:
                    return []
                ids2 = model.search(cr, uid, [(parent_field, 'in', ids)],
                                    context=context)
                return ids + recursive_children(ids2, model, parent_field)
            return [(left, 'in', recursive_children(
                ids, left_model, parent or left_model._parent_name))]

    def pop():
        """ Pop a leaf to process. """
        return self.stack.pop()

    def push(leaf):
        """ Push a leaf to be processed right after. """
        self.stack.append(leaf)

    def push_result(leaf):
        """ Push a leaf to the results. This leaf has been fully processed
            and validated. """
        self.result.append(leaf)

    self.result = []
    self.stack = [ExtendedLeaf(leaf, self.root_model)
                  for leaf in self.expression]
    # process from right to left; expression is from left to right
    self.stack.reverse()

    while self.stack:
        # Get the next leaf to process
        leaf = pop()

        # Get working variables
        working_model = leaf.model
        if leaf.is_operator():
            left, operator, right = leaf.leaf, None, None
        elif leaf.is_true_leaf() or leaf.is_false_leaf():
            # because we consider left as a string
            left, operator, right = ('%s' % leaf.leaf[0], leaf.leaf[1],
                                     leaf.leaf[2])
        else:
            left, operator, right = leaf.leaf
        field_path = left.split('.', 1)
        field = working_model._columns.get(field_path[0])
        # Neova Health BEGIN
        if not working_model._columns.get(field_path[0]) and \
                field_path[0] == 'id':
            # field 'id' normally is not in the _columns
            # the problem appeared with call
            # search('t4clinical.task.base', [
            # ('responsible_user_ids','in',uid)])
            # -- returned [], due to this issue was looking for
            # t4clinical.task.base ids in project.task ids
            field = fields.integer('fake id field. quick fix')
            field._obj = working_model._name
        # Neova Health END
        if field and field._obj:
            relational_model = working_model.pool.get(field._obj)
        else:
            relational_model = None

        # ----------------------------------------
        # SIMPLE CASE
        # 1. leaf is an operator
        # 2. leaf is a true/false leaf
        # -> add directly to result
        # ----------------------------------------

        if leaf.is_operator() or leaf.is_true_leaf() or leaf.is_false_leaf():
            push_result(leaf)

        # ----------------------------------------
        # FIELD NOT FOUND
        # -> from inherits'd fields -> work on the related model, and add
        #    a join condition
        # -> ('id', 'child_of', '..') -> use a 'to_ids'
        # -> but is one on the _log_access special fields, add directly to
        #    result
        # TODO: make these fields explicitly available in self.columns instead!
        # -> else: crash
        # ----------------------------------------

        elif not field and field_path[0] in working_model._inherit_fields:
            # comments about inherits'd fields
            #  { 'field_name': ('parent_model', 'm2o_field_to_reach_parent',
            #                    field_column_obj, origina_parent_model), ... }
            next_model = working_model.pool.get(
                working_model._inherit_fields[field_path[0]][0])
            leaf.add_join_context(
                next_model, working_model._inherits[next_model._name], 'id',
                working_model._inherits[next_model._name])
            push(leaf)

        elif left == 'id' and operator == 'child_of':
            ids2 = to_ids(right, working_model, context)
            dom = child_of_domain(left, ids2, working_model)
            for dom_leaf in reversed(dom):
                new_leaf = create_substitution_leaf(leaf, dom_leaf,
                                                    working_model)
                push(new_leaf)

        elif not field and field_path[0] in MAGIC_COLUMNS:
            push_result(leaf)

        elif not field:
            raise ValueError("Invalid field %r in leaf %r" % (left, str(leaf)))

        # ----------------------------------------
        # PATH SPOTTED
        # -> many2one or one2many with _auto_join:
        #    - add a join, then jump into linked field: field.remaining on
        #      src_table is replaced by remaining on dst_table,
        #      and set for re-evaluation
        #    - if a domain is defined on the field, add it into evaluation
        #      on the relational table
        # -> many2one, many2many, one2many: replace by an equivalent computed
        #    domain, given by recursively searching on the path's remaining
        # -> note: hack about fields.property should not be necessary anymore
        #    as after transforming the field, it will go through this loop
        #    once again
        # ----------------------------------------

        elif len(field_path) > 1 and field._type == 'many2one' and \
                field._auto_join:
            # res_partner.state_id = res_partner__state_id.id
            leaf.add_join_context(relational_model, field_path[0], 'id',
                                  field_path[0])
            push(create_substitution_leaf(
                leaf, (field_path[1], operator, right), relational_model))

        elif len(field_path) > 1 and field._type == 'one2many' and \
                field._auto_join:
            # res_partner.id = res_partner__bank_ids.partner_id
            leaf.add_join_context(relational_model, 'id', field._fields_id,
                                  field_path[0])
            domain = field._domain(working_model) if callable(field._domain) \
                else field._domain
            push(create_substitution_leaf(
                leaf, (field_path[1], operator, right), relational_model))
            if domain:
                domain = normalize_domain(domain)
                for elem in reversed(domain):
                    push(create_substitution_leaf(leaf, elem,
                                                  relational_model))
                push(create_substitution_leaf(leaf, AND_OPERATOR,
                                              relational_model))

        elif len(field_path) > 1 and field._auto_join:
            raise NotImplementedError('_auto_join attribute not supported on '
                                      'many2many field %s' % left)

        elif len(field_path) > 1 and field._type == 'many2one':
            right_ids = relational_model.search(
                cr, uid, [(field_path[1], operator, right)], context=context)
            leaf.leaf = (field_path[0], 'in', right_ids)
            push(leaf)

        # Making search easier when there is a left operand
        # as field.o2m or field.m2m
        elif len(field_path) > 1 and field._type in ['many2many', 'one2many']:
            right_ids = relational_model.search(
                cr, uid, [(field_path[1], operator, right)], context=context)
            table_ids = working_model.search(
                cr, uid, [(field_path[0], 'in', right_ids)],
                context=dict(context, active_test=False))
            leaf.leaf = ('id', 'in', table_ids)
            push(leaf)

        # -------------------------------------------------
        # FUNCTION FIELD
        # -> not stored: error if no _fnct_search,
        #    otherwise handle the result domain
        # -> stored: management done in the remaining of parsing
        # -------------------------------------------------

        elif isinstance(field, fields.function) and not field.store \
                and not field._fnct_search:
            # this is a function field that is not stored
            # the function field doesn't provide a search function and
            # doesn't store values in the database, so we must ignore it:
            # we generate a dummy leaf.
            leaf.leaf = TRUE_LEAF
            _logger.error(
                "The field '%s' (%s) can not be searched: non-stored "
                "function field without fnct_search",
                field.string, left)
            # avoid compiling stack trace if not needed
            if _logger.isEnabledFor(logging.DEBUG):
                _logger.debug(''.join(traceback.format_stack()))
            push(leaf)

        elif isinstance(field, fields.function) and not field.store:
            # this is a function field that is not stored
            fct_domain = field.search(cr, uid, working_model, left,
                                      [leaf.leaf], context=context)
            if not fct_domain:
                leaf.leaf = TRUE_LEAF
                push(leaf)
            else:
                # we assume that the expression is valid
                # we create a dummy leaf for forcing the parsing of the
                # resulting expression
                for domain_element in reversed(fct_domain):
                    push(create_substitution_leaf(leaf, domain_element,
                                                  working_model))
                # self.push(
                # create_substitution_leaf(leaf, TRUE_LEAF, working_model))
                # self.push(
                # create_substitution_leaf(leaf, AND_OPERATOR, working_model))

        # -------------------------------------------------
        # RELATIONAL FIELDS
        # -------------------------------------------------

        # Applying recursivity on field(one2many)
        elif field._type == 'one2many' and operator == 'child_of':
            ids2 = to_ids(right, relational_model, context)
            if field._obj != working_model._name:
                dom = child_of_domain(left, ids2, relational_model,
                                      prefix=field._obj)
            else:
                dom = child_of_domain('id', ids2, working_model, parent=left)
            for dom_leaf in reversed(dom):
                push(create_substitution_leaf(leaf, dom_leaf, working_model))

        elif field._type == 'one2many':
            call_null = True

            if right is not False:
                if isinstance(right, basestring):
                    ids2 = [x[0] for x in relational_model.name_search(
                        cr, uid, right, [], operator, context=context,
                        limit=None)
                    ]
                    if ids2:
                        operator = 'in'
                else:
                    if not isinstance(right, list):
                        ids2 = [right]
                    else:
                        ids2 = right
                if not ids2:
                    if operator in ['like', 'ilike', 'in', '=']:
                        # no result found with given search criteria
                        call_null = False
                        push(create_substitution_leaf(leaf, FALSE_LEAF,
                                                      working_model))
                else:
                    ids2 = select_from_where(cr, field._fields_id,
                                             relational_model._table, 'id',
                                             ids2, operator)
                    if ids2:
                        call_null = False
                        o2m_op = 'not in' if operator in \
                            NEGATIVE_TERM_OPERATORS else 'in'
                        push(create_substitution_leaf(
                            leaf, ('id', o2m_op, ids2), working_model))

            if call_null:
                o2m_op = 'in' if operator in \
                    NEGATIVE_TERM_OPERATORS else 'not in'
                push(create_substitution_leaf(
                    leaf, ('id', o2m_op,
                           select_distinct_from_where_not_null(
                               cr, field._fields_id, relational_model._table)),
                    working_model))

        elif field._type == 'many2many':
            rel_table, rel_id1, rel_id2 = field._sql_names(working_model)
            # FIXME
            if operator == 'child_of':
                def _rec_convert(ids):
                    if relational_model == working_model:
                        return ids
                    return select_from_where(cr, rel_id1, rel_table, rel_id2,
                                             ids, operator)

                ids2 = to_ids(right, relational_model, context)
                dom = child_of_domain('id', ids2, relational_model)
                ids2 = relational_model.search(cr, uid, dom, context=context)
                push(create_substitution_leaf(leaf,
                                              ('id', 'in', _rec_convert(ids2)),
                                              working_model))
            else:
                call_null_m2m = True
                if right is not False:
                    if isinstance(right, basestring):
                        res_ids = [x[0] for x in relational_model.name_search(
                            cr, uid, right, [], operator, context=context)]
                        if res_ids:
                            operator = 'in'
                    else:
                        if not isinstance(right, list):
                            res_ids = [right]
                        else:
                            res_ids = right
                    if not res_ids:
                        if operator in ['like', 'ilike', 'in', '=']:
                            # no result found with given search criteria
                            call_null_m2m = False
                            push(create_substitution_leaf(leaf, FALSE_LEAF,
                                                          working_model))
                        else:
                            # operator changed because ids are directly related
                            # to main object
                            operator = 'in'
                    else:
                        call_null_m2m = False
                        m2m_op = 'not in' if operator in \
                            NEGATIVE_TERM_OPERATORS else 'in'
                        push(create_substitution_leaf(
                            leaf, ('id', m2m_op,
                                   select_from_where(
                                       cr, rel_id1, rel_table, rel_id2,
                                       res_ids, operator) or [0]),
                            working_model))

                if call_null_m2m:
                    m2m_op = 'in' if operator in \
                        NEGATIVE_TERM_OPERATORS else 'not in'
                    push(create_substitution_leaf(
                        leaf,
                        ('id', m2m_op, select_distinct_from_where_not_null(
                            cr, rel_id1, rel_table)), working_model))

        elif field._type == 'many2one':
            if operator == 'child_of':
                ids2 = to_ids(right, relational_model, context)
                if field._obj != working_model._name:
                    dom = child_of_domain(left, ids2, relational_model,
                                          prefix=field._obj)
                else:
                    dom = child_of_domain('id', ids2, working_model,
                                          parent=left)
                for dom_leaf in reversed(dom):
                    push(create_substitution_leaf(leaf, dom_leaf,
                                                  working_model))
            else:
                def _get_expression(relational_model, cr, uid, left, right,
                                    operator, context=None):
                    if context is None:
                        context = {}
                    c = context.copy()
                    c['active_test'] = False
                    # Special treatment to ill-formed domains
                    operator = (operator in ['<', '>', '<=', '>=']) and 'in' \
                        or operator

                    dict_op = {'not in': '!=', 'in': '=',
                               '=': 'in', '!=': 'not in'}
                    if isinstance(right, tuple):
                        right = list(right)
                    if (not isinstance(right, list)) and \
                            operator in ['not in', 'in']:
                        operator = dict_op[operator]
                    elif isinstance(right, list) and operator in ['!=', '=']:
                        # for domain (FIELD,'=',['value1','value2'])
                        operator = dict_op[operator]
                    res_ids = [x[0] for x in relational_model.name_search(
                        cr, uid, right, [], operator, limit=None, context=c)]
                    if operator in NEGATIVE_TERM_OPERATORS:
                        # TODO this should not be appended if False in 'right'
                        res_ids.append(False)
                    return left, 'in', res_ids
                # resolve string-based m2o criterion into IDs
                if isinstance(right, basestring) or right and \
                        isinstance(right, (tuple, list)) and \
                        all(isinstance(item, basestring) for item in right):
                    push(create_substitution_leaf(
                        leaf,
                        _get_expression(relational_model, cr, uid, left, right,
                                        operator, context=context),
                        working_model))
                else:
                    # right == [] or right == False and all other cases
                    # are handled by __leaf_to_sql()
                    push_result(leaf)

        # -------------------------------------------------
        # OTHER FIELDS
        # -> datetime fields: manage time part of the datetime
        #    field when it is not there
        # -> manage translatable fields
        # -------------------------------------------------

        else:
            if field._type == 'datetime' and right and len(right) == 10:
                if operator in ('>', '>='):
                    right += ' 00:00:00'
                elif operator in ('<', '<='):
                    right += ' 23:59:59'
                push(create_substitution_leaf(leaf, (left, operator, right),
                                              working_model))

            elif field.translate and right:
                need_wildcard = operator in ('like', 'ilike', 'not like',
                                             'not ilike')
                sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(
                    operator, operator)
                if need_wildcard:
                    right = '%%%s%%' % right

                inselect_operator = 'inselect'
                if sql_operator in NEGATIVE_TERM_OPERATORS:
                    # negate operator (fix lp:1071710)
                    if sql_operator[:3] == 'not':
                        sql_operator = sql_operator[4:]
                    else:
                        sql_operator = '='
                    inselect_operator = 'not inselect'

                unaccent = self._unaccent if sql_operator.endswith('like') \
                    else lambda x: x

                trans_left = unaccent('value')
                quote_left = unaccent(_quote(left))
                instr = unaccent('%s')

                if sql_operator == 'in':
                    # params will be flatten by to_sql() =>
                    # expand the placeholders
                    instr = '(%s)' % ', '.join(['%s'] * len(right))

                subselect = """(SELECT res_id
                                  FROM ir_translation
                                 WHERE name = %s
                                   AND lang = %s
                                   AND type = %s
                                   AND {trans_left} {operator} {right}
                               ) UNION (
                                SELECT id
                                  FROM "{table}"
                                 WHERE {left} {operator} {right}
                               )
                            """.format(trans_left=trans_left,
                                       operator=sql_operator, right=instr,
                                       table=working_model._table,
                                       left=quote_left)

                params = (
                    working_model._name + ',' + left,
                    context.get('lang') or 'en_US',
                    'model',
                    right,
                    right,
                )
                push(create_substitution_leaf(leaf, ('id', inselect_operator,
                                                     (subselect, params)
                                                     ), working_model))

            else:
                push_result(leaf)

    # ----------------------------------------
    # END OF PARSING FULL DOMAIN
    # -> generate joins
    # ----------------------------------------

    joins = set()
    for leaf in self.result:
        joins |= set(leaf.get_join_conditions())
    self.joins = list(joins)