Exemple #1
0
 def test_30_normalize_domain(self):
     expression = openerp.osv.expression
     norm_domain = domain = ['&', (1, '=', 1), ('a', '=', 'b')]
     assert norm_domain == expression.normalize_domain(domain), "Normalized domains should be left untouched"
     domain = [('x', 'in', ['y', 'z']), ('a.v', '=', 'e'), '|', '|', ('a', '=', 'b'), '!', ('c', '>', 'd'), ('e', '!=', 'f'), ('g', '=', 'h')]
     norm_domain = ['&', '&', '&'] + domain
     assert norm_domain == expression.normalize_domain(domain), "Non-normalized domains should be properly normalized"
Exemple #2
0
    def _needaction_domain_get(self, cr, uid, context=None):
        """ Returns the domain to filter records that require an action
            :return: domain or False is no action
        """
        # get domain from parent object if exists
        dom = super(res_partner_needaction, self)._needaction_domain_get(
            cr, uid, context=context)
        obj = self.pool['ir.model.data']
        followers = obj.get_object(
            cr, uid,
            'account_streamline',
            'group_account_creators'
        ).message_follower_ids

        user = self.pool['res.users'].browse(cr, uid, uid, context=context)

        if user.partner_id in followers:
            mydom = [
                '|', ('supplier_account_check', '=', True),
                ('customer_account_check', '=', True),
            ]

            dom = expression.OR([
                expression.normalize_domain(mydom),
                expression.normalize_domain(dom)
            ])

        return dom
 def test_30_normalize_domain(self):
     expression = openerp.osv.expression
     norm_domain = domain = ['&', (1, '=', 1), ('a', '=', 'b')]
     assert norm_domain == expression.normalize_domain(domain), "Normalized domains should be left untouched"
     domain = [('x', 'in', ['y', 'z']), ('a.v', '=', 'e'), '|', '|', ('a', '=', 'b'), '!', ('c', '>', 'd'), ('e', '!=', 'f'), ('g', '=', 'h')]
     norm_domain = ['&', '&', '&'] + domain
     assert norm_domain == expression.normalize_domain(domain), "Non-normalized domains should be properly normalized"
    def message_read(
        self, cr, uid, ids=None, domain=None, message_unload_ids=None,
        thread_level=0, context=None, parent_id=False, limit=None
    ):
        """Override this function to include account.move.line notifications
        within account.move notification lists.

        :todo This applies to every mail.message object; maybe find a better
        solution that doesn't involve modifying objects globally.
        """

        # Example domain: [['model', '=', 'account.move'], ['res_id', '=', 7]]

        # Avoid recursion...
        if domain and ['model', '=', 'account.move.line'] not in domain:

            am_obj = self.pool['account.move']

            line_domain = []

            # Look for a "res_id = X" domain part.
            for domain_index, domain_part in enumerate(domain):
                if (domain_index < len(domain) - 1 and
                    domain_part == ['model', '=', 'account.move']
                ):
                    next_part = domain[domain_index + 1]
                    if next_part[0] == 'res_id' and next_part[1] == '=':

                        move_id = next_part[2]
                        line_ids = am_obj.read(
                            cr, uid,
                            move_id,
                            ['line_id'],
                            context=context
                        )['line_id']

                        line_domain = [
                            ('model', '=', 'account.move.line'),
                            ('res_id', 'in', line_ids)
                        ]

            if line_domain:
                domain = expression.OR([
                    expression.normalize_domain(line_domain),
                    expression.normalize_domain(domain)
                ])

                # Make sure our domain is applied. When "ids" is set, the
                # domain is ignored.
                ids = None

        return super(mail_message, self).message_read(
            cr, uid, ids=ids, domain=domain,
            message_unload_ids=message_unload_ids, thread_level=thread_level,
            context=context, parent_id=parent_id, limit=limit
        )
Exemple #5
0
    def _create_or_combine_sharing_rule(self, cr, current_user, wizard_data, group_id, model_id, domain, restrict=False, rule_name=None, context=None):
        """Add a new ir.rule entry for model_id and domain on the target group_id.
           If ``restrict`` is True, instead of adding a rule, the domain is
           combined with AND operator with all existing rules in the group, to implement
           an additional restriction (as of 6.1, multiple rules in the same group are
           OR'ed by default, so a restriction must alter all existing rules)

           This is necessary because the personal rules of the user that is sharing
           are first copied to the new share group. Afterwards the filters used for
           sharing are applied as an additional layer of rules, which are likely to
           apply to the same model. The default rule algorithm would OR them (as of 6.1),
           which would result in a combined set of permission that could be larger
           than those of the user that is sharing! Hence we must forcefully AND the
           rules at this stage.
           One possibly undesirable effect can appear when sharing with a
           pre-existing group, in which case altering pre-existing rules would not
           be desired. This is addressed in the portal module.
           """
        if rule_name is None:
            rule_name = _('Sharing filter created by user %s (%s) for group %s') % \
                            (current_user.name, current_user.login, group_id)
        rule_obj = self.pool.get('ir.rule')
        rule_ids = rule_obj.search(cr, UID_ROOT, [('groups', 'in', group_id), ('model_id', '=', model_id)])
        if rule_ids:
            for rule in rule_obj.browse(cr, UID_ROOT, rule_ids, context=context):
                if rule.domain_force == domain:
                    # don't create it twice!
                    if restrict:
                        continue
                    else:
                        _logger.debug("Ignoring sharing rule on model %s with domain: %s the same rule exists already", model_id, domain)
                        return
                if restrict:
                    # restricting existing rules is done by adding the clause
                    # with an AND, but we can't alter the rule if it belongs to
                    # other groups, so we duplicate if needed
                    rule = self._check_personal_rule_or_duplicate(cr, group_id, rule, context=context)
                    eval_ctx = rule_obj._eval_context_for_combinations()
                    org_domain = expression.normalize_domain(eval(rule.domain_force, eval_ctx))
                    new_clause = expression.normalize_domain(eval(domain, eval_ctx))
                    combined_domain = expression.AND([new_clause, org_domain])
                    rule.write({'domain_force': combined_domain, 'name': rule.name + _('(Modified)')})
                    _logger.debug("Combining sharing rule %s on model %s with domain: %s", rule.id, model_id, domain)
        if not rule_ids or not restrict:
            # Adding the new rule in the group is ok for normal cases, because rules
            # in the same group and for the same model will be combined with OR
            # (as of v6.1), so the desired effect is achieved.
            rule_obj.create(cr, UID_ROOT, {
                'name': rule_name,
                'model_id': model_id,
                'domain_force': domain,
                'groups': [(4,group_id)]
                })
            _logger.debug("Created sharing rule on model %s with domain: %s", model_id, domain)
Exemple #6
0
    def _create_or_combine_sharing_rule(self, cr, current_user, wizard_data, group_id, model_id, domain, restrict=False, rule_name=None, context=None):
        """Add a new ir.rule entry for model_id and domain on the target group_id.
           If ``restrict`` is True, instead of adding a rule, the domain is
           combined with AND operator with all existing rules in the group, to implement
           an additional restriction (as of 6.1, multiple rules in the same group are
           OR'ed by default, so a restriction must alter all existing rules)

           This is necessary because the personal rules of the user that is sharing
           are first copied to the new share group. Afterwards the filters used for
           sharing are applied as an additional layer of rules, which are likely to
           apply to the same model. The default rule algorithm would OR them (as of 6.1),
           which would result in a combined set of permission that could be larger
           than those of the user that is sharing! Hence we must forcefully AND the
           rules at this stage.
           One possibly undesirable effect can appear when sharing with a
           pre-existing group, in which case altering pre-existing rules would not
           be desired. This is addressed in the portal module.
           """
        if rule_name is None:
            rule_name = _('Sharing filter created by user %s (%s) for group %s') % \
                            (current_user.name, current_user.login, group_id)
        rule_obj = self.pool.get('ir.rule')
        rule_ids = rule_obj.search(cr, UID_ROOT, [('groups', 'in', group_id), ('model_id', '=', model_id)])
        if rule_ids:
            for rule in rule_obj.browse(cr, UID_ROOT, rule_ids, context=context):
                if rule.domain_force == domain:
                    # don't create it twice!
                    if restrict:
                        continue
                    else:
                        _logger.debug("Ignoring sharing rule on model %s with domain: %s the same rule exists already", model_id, domain)
                        return
                if restrict:
                    # restricting existing rules is done by adding the clause
                    # with an AND, but we can't alter the rule if it belongs to
                    # other groups, so we duplicate if needed
                    rule = self._check_personal_rule_or_duplicate(cr, group_id, rule, context=context)
                    eval_ctx = rule_obj._eval_context_for_combinations()
                    org_domain = expression.normalize_domain(eval(rule.domain_force, eval_ctx))
                    new_clause = expression.normalize_domain(eval(domain, eval_ctx))
                    combined_domain = expression.AND([new_clause, org_domain])
                    rule.write({'domain_force': combined_domain, 'name': rule.name + _('(Modified)')})
                    _logger.debug("Combining sharing rule %s on model %s with domain: %s", rule.id, model_id, domain)
        if not rule_ids or not restrict:
            # Adding the new rule in the group is ok for normal cases, because rules
            # in the same group and for the same model will be combined with OR
            # (as of v6.1), so the desired effect is achieved.
            rule_obj.create(cr, UID_ROOT, {
                'name': rule_name,
                'model_id': model_id,
                'domain_force': domain,
                'groups': [(4,group_id)]
                })
            _logger.debug("Created sharing rule on model %s with domain: %s", model_id, domain)
    def _get_filter_expression(self, cr, uid, args, context=None):
        """Return a expression for additional filtering"""
        orm_model=self.pool.get(self._model)

        applicable_args=[]

        def get_applicable_args(args, index):
            if expression.is_leaf(args[index]):
                #TODO: also check for inherited fields etc
                if ((
                        args[index][0] in orm_model._columns or
                        orm_model._log_access and 
                            args[index][0]  in ['create_date','create_uid',
                            'write_date', 'write_uid']
                    )
                    and
                    args[index][0] not in ['text','model']):
                    return [args[index]], 1
                else:
                    return [], 1
            else:
                op1=get_applicable_args(args, index+1)
                op2=get_applicable_args(args, index+op1[1]+1)
                return (([args[index]] 
                        if len(op1[0]) > 0 and len(op2[0]) > 0 
                        else []) +
                        op1[0] + op2[0],
                        op1[1] + op2[1] + 1
                        )

        if openerp.release.version_info[0] <= 6:
            args=get_applicable_args(expression.normalize(args), 0)[0]
        else:
            args=get_applicable_args(expression.normalize_domain(args), 0)[0]
        return expression.expression(cr, uid, args, orm_model, context)
Exemple #8
0
 def search(self,
            cr,
            user,
            args,
            offset=0,
            limit=None,
            order=None,
            context=None,
            count=False):
     """ Display only standalone contact matching ``args`` or having
     attached contact matching ``args`` """
     if context is None:
         context = {}
     if context.get('search_show_all_positions') is False:
         args = expression.normalize_domain(args)
         attached_contact_args = expression.AND(
             (args, [('contact_type', '=', 'attached')]))
         attached_contact_ids = super(res_partner,
                                      self).search(cr,
                                                   user,
                                                   attached_contact_args,
                                                   context=context)
         args = expression.OR((
             expression.AND(([('contact_type', '=', 'standalone')], args)),
             [('other_contact_ids', 'in', attached_contact_ids)],
         ))
     return super(res_partner, self).search(cr,
                                            user,
                                            args,
                                            offset=offset,
                                            limit=limit,
                                            order=order,
                                            context=context,
                                            count=count)
Exemple #9
0
 def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
     res = {}
     eval_context = self._eval_context(cr, uid)
     for rule in self.browse(cr, uid, ids, context):
         tmp_domain = str(rule.domain_force)
         if tmp_domain.find("%s") > 0:
             tmp_domain = eval(rule.domain_force)
             str_query = str(tmp_domain[0][2])
             cr.execute(str_query % uid)
             tmp_ids = cr.fetchall()
             list_ids = []
             for i in tmp_ids:
                 list_ids.append(i[0])
             res[rule.id] = eval("[('" + tmp_domain[0][0] + "','" +
                                 tmp_domain[0][1] + "'," + str(list_ids) +
                                 ")]")
         elif tmp_domain.find('.sql.query') > 0:
             tmp_domain = eval(rule.domain_force)
             str_query = str(tmp_domain[0][2])
             str_query = str_query.replace('.sql.query', '')
             cr.execute(str_query)
             tmp_ids = cr.fetchall()
             list_ids = []
             for i in tmp_ids:
                 list_ids.append(i[0])
             res[rule.id] = eval("[('" + tmp_domain[0][0] + "','" +
                                 tmp_domain[0][1] + "'," + str(list_ids) +
                                 ")]")
         elif rule.domain_force:
             res[rule.id] = expression.normalize_domain(
                 eval(rule.domain_force, eval_context))
         else:
             res[rule.id] = []
     return res
    def parse_stored(self):
        """
        This function parses the more frequent case.
        The many2one field is stored and the leaf is not complete.
        """
        leaf = self.leaf
        comodel = self.comodel
        path = self.path
        column = self.column

        self.add_inherit_join_context()

        domain = (
            column._domain(self.model) if callable(column._domain)
            else column._domain)

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

        # self.push_new_leaf(leaf, AND_OPERATOR, comodel)

        if domain:
            domain = normalize_domain(domain)

            for elem in reversed(domain):
                self.push_new_leaf(leaf, elem, comodel)

            self.push_new_leaf(leaf, AND_OPERATOR, comodel)
    def get_aml_domain_for_expr(self, expr,
                                date_from, date_to,
                                period_from, period_to,
                                target_move):
        """ Get a domain on account.move.line for an expression.

        Prerequisite: done_parsing() must have been invoked.

        Returns a domain that can be used to search on account.move.line.
        """
        aml_domains = []
        date_domain_by_mode = {}
        for mo in self.ACC_RE.finditer(expr):
            field, mode, account_codes, domain = self._parse_match_object(mo)
            aml_domain = list(domain)
            account_ids = set()
            for account_code in account_codes:
                account_ids.update(self._account_ids_by_code[account_code])
            aml_domain.append(('account_id', 'in', tuple(account_ids)))
            if field == 'crd':
                aml_domain.append(('credit', '>', 0))
            elif field == 'deb':
                aml_domain.append(('debit', '>', 0))
            aml_domains.append(expression.normalize_domain(aml_domain))
            if mode not in date_domain_by_mode:
                date_domain_by_mode[mode] = \
                    self.get_aml_domain_for_dates(date_from, date_to,
                                                  period_from, period_to,
                                                  mode, target_move)
        return expression.OR(aml_domains) + \
            expression.OR(date_domain_by_mode.values())
    def get_aml_domain_for_expr(self, expr,
                                date_from, date_to,
                                period_from, period_to,
                                target_move):
        """ Get a domain on account.move.line for an expression.

        Prerequisite: done_parsing() must have been invoked.

        Returns a domain that can be used to search on account.move.line.
        """
        aml_domains = []
        date_domain_by_mode = {}
        for mo in self.ACC_RE.finditer(expr):
            field, mode, account_codes, domain = self._parse_match_object(mo)
            aml_domain = list(domain)
            account_ids = set()
            for account_code in account_codes:
                account_ids.update(self._account_ids_by_code[account_code])
            aml_domain.append(('account_id', 'in', tuple(account_ids)))
            if field == 'crd':
                aml_domain.append(('credit', '>', 0))
            elif field == 'deb':
                aml_domain.append(('debit', '>', 0))
            aml_domains.append(expression.normalize_domain(aml_domain))
            if mode not in date_domain_by_mode:
                date_domain_by_mode[mode] = \
                    self.get_aml_domain_for_dates(date_from, date_to,
                                                  period_from, period_to,
                                                  mode, target_move)
        return expression.OR(aml_domains) + \
            expression.OR(date_domain_by_mode.values())
Exemple #13
0
 def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
     res = {}
     eval_context = self._eval_context(cr, uid)
     for rule in self.browse(cr, uid, ids, context):
         tmp_domain=str(rule.domain_force)
         if tmp_domain.find("%s")>0:
                 tmp_domain=eval(rule.domain_force)                  
                 str_query=str(tmp_domain[0][2])
                 cr.execute(str_query % uid)
                 tmp_ids=cr.fetchall()
                 list_ids=[]
                 for i in tmp_ids:
                     list_ids.append(i[0])
                 res[rule.id] = eval("[('"+tmp_domain[0][0]+"','"+tmp_domain[0][1]+"',"+str(list_ids)+")]")
         elif tmp_domain.find('.sql.query')>0:
             tmp_domain=eval(rule.domain_force)                  
             str_query=str(tmp_domain[0][2])
             str_query=str_query.replace('.sql.query','')
             cr.execute(str_query)
             tmp_ids=cr.fetchall()
             list_ids=[]
             for i in tmp_ids:
                 list_ids.append(i[0])
             res[rule.id] = eval("[('"+tmp_domain[0][0]+"','"+tmp_domain[0][1]+"',"+str(list_ids)+")]")
         elif rule.domain_force:
             res[rule.id] = expression.normalize_domain(eval(rule.domain_force, eval_context))
         else:
             res[rule.id] = []
     return res
Exemple #14
0
def check_record_rule_cause_error(self, model_name, user, rule, mode="read"):
    """
    Evaluate the rule to check whether it cause the access right error
    or not.
    This function is the combination of 2 functions:
        + domain_get
        + _check_record_rules_result_count
    """
    model_pooler = self.pool[model_name]
    global_domains = []  # list of domains
    group_domains = {}  # map: group -> list of domains
    rule_domain = rule.domain
    dom = expression.normalize_domain(rule_domain)
    for group in rule.groups:
        if group in user.groups_id:
            group_domains.setdefault(group, []).append(dom)
    if not rule.groups:
        global_domains.append(dom)
    # combine global domains and group domains
    if group_domains:
        group_domain = expression.OR(map(expression.OR,
                                         group_domains.values()))
    else:
        group_domain = []
    domain = expression.AND(global_domains + [group_domain])
    if domain:
        # _where_calc is called as superuser. This means that rules can
        # involve objects on which the real uid has no acces rights.
        # This means also there is no implicit restriction (e.g. an object
        # references another object the user can't see).
        query = self.pool.get(model_name)._where_calc(self._cr,
                                                      SUPERUSER_ID,
                                                      domain,
                                                      active_test=False)
        where_clause, where_params, tables = query.where_clause, \
            query.where_clause_params, query.tables
        if where_clause:
            where_clause = ' and ' + ' and '.join(where_clause)
            self._cr.execute(
                'SELECT ' + model_pooler._table + '.id FROM ' +
                ','.join(tables) + ' WHERE ' + model_pooler._table +
                '.id IN %s' + where_clause,
                ([tuple(self._ids)] + where_params))
            result_ids = [x['id'] for x in self._cr.dictfetchall()]

            ids, result_ids = set(self._ids), set(result_ids)
            missing_ids = ids - result_ids
            if missing_ids:
                # Attempt to distinguish record rule
                # restriction vs deleted records,
                # to provide a more specific error message -
                # check if the missinf
                self._cr.execute(
                    'SELECT id FROM ' + model_pooler._table +
                    ' WHERE id IN %s', (tuple(missing_ids), ))
                # the missing ids are (at least partially)
                # hidden by access rules
                if self._cr.rowcount or mode not in ('read', 'unlink'):
                    return True
    return False
 def _domain_force_get(self):
     eval_context = self._eval_context()
     for rule in self:
         if rule.domain_force:
             rule.domain = expression.normalize_domain(
                 eval(rule.domain_force, eval_context))
         else:
             rule.domain = []
Exemple #16
0
 def eval_n(domain):
     '''parse a domain and normalize it'''
     try:
         domain = safe_eval(domain)
     except:
         domain = [expression.FALSE_LEAF]
     return expression.normalize_domain(
         domain or [expression.FALSE_LEAF])
Exemple #17
0
 def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
     res = {}
     eval_context = self._eval_context(cr, uid)
     for rule in self.browse(cr, uid, ids, context):
         if rule.domain_force:
             res[rule.id] = expression.normalize_domain(eval(rule.domain_force, eval_context))
         else:
             res[rule.id] = []
     return res
Exemple #18
0
 def extend(domain):
     for index, item in enumerate(domain):
         if isinstance(item, list):
             field = get_field(item)
             if field.search and not field.related:
                 extension = field.search(get_records(item), *item[1:])
                 domain = domain[:index] + normalize_domain(
                     extension) + domain[index + 1:]
     return domain
Exemple #19
0
 def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
     res = {}
     eval_context = self._eval_context(cr, uid)
     for rule in self.browse(cr, uid, ids, context):
         if rule.domain_force:
             res[rule.id] = expression.normalize_domain(eval(rule.domain_force, eval_context))
         else:
             res[rule.id] = []
     return res
Exemple #20
0
    def _compute_domain(self, cr, uid, model_name, mode="read", context=None):
        context = context or {}
        if mode not in self._MODES:
            raise ValueError('Invalid mode: %r' % (mode, ))

        if uid == SUPERUSER_ID:
            return None
        cr.execute(
            """SELECT r.id
                FROM ir_rule r
                JOIN ir_model m ON (r.model_id = m.id)
                WHERE m.model = %s
                AND r.active is True
                AND r.perm_""" + mode + """
                AND (r.id IN (SELECT rule_group_id FROM rule_group_rel g_rel
                            JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid)
                            WHERE u_rel.uid = %s) OR r.global)""",
            (model_name, uid))
        rule_ids = [x[0] for x in cr.fetchall()]
        if rule_ids:
            # browse user as super-admin root to avoid access errors!
            user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid)
            global_domains = []  # list of domains
            group_domains = {}  # map: group -> list of domains
            context['_compute_domain_rules'] = [
            ]  #Used to show a comprensive rule error message
            for rule in self.browse(cr, SUPERUSER_ID, rule_ids):
                # read 'domain' as UID to have the correct eval context for the rule.
                rule_domain = self.read(cr, uid, rule.id, ['domain'])['domain']
                dom = expression.normalize_domain(rule_domain)
                for group in rule.groups:
                    if group in user.groups_id:
                        group_domains.setdefault(group, []).append(dom)
                if not rule.groups:
                    global_domains.append(dom)
                context['_compute_domain_rules'].append({
                    'id':
                    rule.id,
                    'name':
                    rule.name,
                    'domain':
                    dom,
                    'groups': [g.name for g in rule.groups],
                    'model':
                    rule.model_id.name
                })
            # combine global domains and group domains
            if group_domains:
                group_domain = expression.OR(
                    map(expression.OR, group_domains.values()))
            else:
                group_domain = []
            domain = expression.AND(global_domains + [group_domain])
            return domain
        return []
Exemple #21
0
 def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
     res = {}
     eval_context = self._eval_context(cr, uid)
     for rule in self.browse(cr, uid, ids, context):
         tmp_domain=str(rule.domain_force)            
         if tmp_domain.find("%s")>0:                         
             tmp_domain=eval(rule.domain_force)
             for dom in filter(lambda x: x[2].find("%s")>0,tmp_domain):                    
                 tdom = list(dom)
                 str_query=str(tdom[2])
                 cr.execute(str_query % uid)
                 tmp_ids=cr.fetchall()
                 list_ids=[]
                 for i in tmp_ids:
                     list_ids.append(i[0])
                 tdom[2] = tuple(list_ids)
                 tdom = tuple(tdom)
                 tmp_domain[tmp_domain.index(dom)] = tdom
             res[rule.id] = expression.normalize_domain(eval(str(tmp_domain), eval_context))
             #res[rule.id] = eval(tmp_domain)
                 #res[rule.id] = eval("[('"+tmp_domain[0][0]+"','"+tmp_domain[0][1]+"',"+str(list_ids)+")]")
             
         elif tmp_domain.find('.sql.query')>0:
             tmp_domain=eval(rule.domain_force)
             for dom in filter(lambda x: x[2].find(".sql.query")>0,tmp_domain):
                 tdom = list(dom)
                 str_query=str(tdom[2])                    
                 str_query=str_query.replace('.sql.query','')
                 cr.execute(str_query)
                 tmp_ids=cr.fetchall()
                 list_ids = []
                 for i in tmp_ids:
                     list_ids.append(i[0])
                 tdom[2] = tuple(set(list_ids))
                 tdom = tuple(tdom)
                 tmp_domain[tmp_domain.index(dom)] = tdom
             res[rule.id] = expression.normalize_domain(eval(str(tmp_domain), eval_context))
         elif rule.domain_force:
             res[rule.id] = expression.normalize_domain(eval(rule.domain_force, eval_context))
         else:
             res[rule.id] = []
     return res
 def _domain_move_lines_for_reconciliation(self,
                                           st_line,
                                           excluded_ids=None,
                                           str=False,
                                           additional_domain=None):
     if st_line.prepared_account_id:
         additional_domain = expression.AND([
             expression.normalize_domain(additional_domain)
             if additional_domain else [],
             [('account_id', '=', st_line.prepared_account_id.id)],
         ])
     if st_line.prepared_analytic_account_id:
         additional_domain = expression.AND([
             expression.normalize_domain(additional_domain)
             if additional_domain else [],
             [('analytic_account_id', '=',
               st_line.prepared_analytic_account_id.id)],
         ])
     return super(AccountBankStatementLine, self)\
         ._domain_move_lines_for_reconciliation(
             st_line, excluded_ids=excluded_ids, str=str,
             additional_domain=additional_domain)
Exemple #23
0
    def _domain_get(self, cr, uid, ids, field_name, args, context=None):
        '''combine our domain with all domains to union/complement,
        this works recursively'''
        def eval_n(domain):
            '''parse a domain and normalize it'''
            try:
                domain = safe_eval(domain)
            except:
                domain = [expression.FALSE_LEAF]
            return expression.normalize_domain(
                domain or [expression.FALSE_LEAF])

        result = {}
        for this in self.read(
                cr, uid, ids,
                ['domain_this', 'union_filter_ids', 'complement_filter_ids'],
                context=context):
            domain = eval_n(this['domain_this'])
            for u in self.read(cr, uid, this['union_filter_ids'],
                               ['domain', 'evaluate_always', 'model_id'],
                               context=context):
                if u['evaluate_always']:
                    matching_ids = self.pool[u['model_id']].search(
                        cr, uid, eval_n(u['domain']),
                        context=context)
                    domain = expression.OR([
                        domain,
                        [('id', 'in', matching_ids)],
                    ])
                else:
                    domain = expression.OR([domain, eval_n(u['domain'])])
            for c in self.read(cr, uid, this['complement_filter_ids'],
                               ['domain', 'evaluate_before_negate',
                                'model_id'],
                               context=context):
                if c['evaluate_before_negate']:
                    matching_ids = self.pool[c['model_id']].search(
                        cr, uid, eval_n(c['domain']),
                        context=context)
                    domain = expression.AND([
                        domain,
                        [('id', 'not in', matching_ids)],
                    ])
                else:
                    domain = expression.AND([
                        domain,
                        ['!'] + eval_n(c['domain'])])
            result[this['id']] = str(expression.normalize_domain(domain))
        return result
Exemple #24
0
 def get_aml_domain_for_dates(self, date_from, date_to, period_from,
                              period_to, mode, target_move):
     if period_from and period_to:
         period_ids = self._get_period_ids_for_mode(period_from, period_to,
                                                    mode)
         domain = [('period_id', 'in', period_ids)]
     else:
         if mode == MODE_VARIATION:
             domain = [('date', '>=', date_from), ('date', '<=', date_to)]
         else:
             raise Warning(
                 _("Modes i and e are only applicable for "
                   "fiscal periods"))
     if target_move == 'posted':
         domain.append(('move_id.state', '=', 'posted'))
     return expression.normalize_domain(domain)
 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
     """ Display only standalone contact matching ``args`` or having
     attached contact matching ``args`` """
     if context is None:
         context = {}
     if context.get('search_show_all_positions') is False:
         args = expression.normalize_domain(args)
         attached_contact_args = expression.AND((args, [('contact_type', '=', 'attached')]))
         attached_contact_ids = super(res_partner, self).search(cr, user, attached_contact_args,
                                                                context=context)
         args = expression.OR((
             expression.AND(([('contact_type', '=', 'standalone')], args)),
             [('other_contact_ids', 'in', attached_contact_ids)],
         ))
     return super(res_partner, self).search(cr, user, args, offset=offset, limit=limit,
                                            order=order, context=context, count=count)
 def get_aml_domain_for_dates(self, date_from, date_to,
                              period_from, period_to,
                              mode,
                              target_move):
     if period_from and period_to:
         period_ids = self._get_period_ids_for_mode(
             period_from, period_to, mode)
         domain = [('period_id', 'in', period_ids)]
     else:
         if mode == MODE_VARIATION:
             domain = [('date', '>=', date_from), ('date', '<=', date_to)]
         else:
             raise Warning(_("Modes i and e are only applicable for "
                             "fiscal periods"))
     if target_move == 'posted':
         domain.append(('move_id.state', '=', 'posted'))
     return expression.normalize_domain(domain)
Exemple #27
0
    def _compute_domain(self, cr, uid, model_name, mode="read"):
        if mode not in self._MODES:
            raise ValueError("Invalid mode: %r" % (mode,))

        if uid == SUPERUSER_ID:
            return None
        cr.execute(
            """SELECT r.id
                FROM ir_rule r
                JOIN ir_model m ON (r.model_id = m.id)
                WHERE m.model = %s
                AND r.active is True
                AND r.perm_"""
            + mode
            + """
                AND (r.id IN (SELECT rule_group_id FROM rule_group_rel g_rel
                            JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid)
                            WHERE u_rel.uid = %s) OR r.global)""",
            (model_name, uid),
        )
        rule_ids = [x[0] for x in cr.fetchall()]
        if rule_ids:
            # browse user as super-admin root to avoid access errors!
            user = self.pool.get("res.users").browse(cr, SUPERUSER_ID, uid)
            global_domains = []  # list of domains
            group_domains = {}  # map: group -> list of domains
            for rule in self.browse(cr, SUPERUSER_ID, rule_ids):
                # read 'domain' as UID to have the correct eval context for the rule.
                rule_domain = self.read(cr, uid, [rule.id], ["domain"])[0]["domain"]
                dom = expression.normalize_domain(rule_domain)
                for group in rule.groups:
                    if group in user.groups_id:
                        group_domains.setdefault(group, []).append(dom)
                if not rule.groups:
                    global_domains.append(dom)
            # combine global domains and group domains
            if group_domains:
                group_domain = expression.OR(map(expression.OR, group_domains.values()))
            else:
                group_domain = []
            domain = expression.AND(global_domains + [group_domain])
            return domain
        return []
Exemple #28
0
 def search(self, args, offset=0, limit=None, order=None, count=False):
     """ Display only standalone contact matching ``args`` or having
     attached contact matching ``args`` """
     ctx = self.env.context
     if (ctx.get('search_show_all_positions', {}).get('is_set')
             and not ctx['search_show_all_positions']['set_value']):
         args = expression.normalize_domain(args)
         attached_contact_args = expression.AND(
             (args, [('contact_type', '=', 'attached')]))
         attached_contacts = super(ResPartner,
                                   self).search(attached_contact_args)
         args = expression.OR((
             expression.AND(([('contact_type', '=', 'standalone')], args)),
             [('other_contact_ids', 'in', attached_contacts.ids)],
         ))
     return super(ResPartner, self).search(args,
                                           offset=offset,
                                           limit=limit,
                                           order=order,
                                           count=count)
Exemple #29
0
    def get_move_lines_for_reconciliation(self, excluded_ids=None, str=False, offset=0, limit=None, additional_domain=None, overlook_partner=False):
        """ Return account.move.line records which can be used for bank statement reconciliation.

            :param excluded_ids:
            :param str:
            :param offset:
            :param limit:
            :param additional_domain:
            :param overlook_partner:
        """
        # Domain to fetch registered payments (use case where you encode the payment before you get the bank statement)
        reconciliation_aml_accounts = [self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id]
        domain_reconciliation = ['&', ('statement_id', '=', False), ('account_id', 'in', reconciliation_aml_accounts)]

        # Domain to fetch unreconciled payables/receivables (use case where you close invoices/refunds by reconciling your bank statements)
        domain_matching = [('reconciled', '=', False)]
        if self.partner_id.id or overlook_partner:
            domain_matching = expression.AND([domain_matching, [('account_id.internal_type', 'in', ['payable', 'receivable'])]])
        else:
            # TODO : find out what use case this permits (match a check payment, registered on a journal whose account type is other instead of liquidity)
            domain_matching = expression.AND([domain_matching, [('account_id.reconcile', '=', True)]])

        # Let's add what applies to both
        domain = expression.OR([domain_reconciliation, domain_matching])
        if self.partner_id.id and not overlook_partner:
            domain = expression.AND([domain, [('partner_id', '=', self.partner_id.id)]])

        # Domain factorized for all reconciliation use cases
        ctx = dict(self._context or {})
        ctx['bank_statement_line'] = self
        generic_domain = self.env['account.move.line'].with_context(ctx).domain_move_lines_for_reconciliation(excluded_ids=excluded_ids, str=str)
        domain = expression.AND([domain, generic_domain])

        # Domain from caller
        if additional_domain is None:
            additional_domain = []
        else:
            additional_domain = expression.normalize_domain(additional_domain)
        domain = expression.AND([domain, additional_domain])

        return self.env['account.move.line'].search(domain, offset=offset, limit=limit, order="date_maturity asc, id asc")
    def get_move_lines_for_reconciliation(self, excluded_ids=None, str=False, offset=0, limit=None, additional_domain=None, overlook_partner=False):
        """ Return account.move.line records which can be used for bank statement reconciliation.

            :param excluded_ids:
            :param str:
            :param offset:
            :param limit:
            :param additional_domain:
            :param overlook_partner:
        """
        # Domain to fetch registered payments (use case where you encode the payment before you get the bank statement)
        reconciliation_aml_accounts = [self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id]
        domain_reconciliation = ['&', ('statement_id', '=', False), ('account_id', 'in', reconciliation_aml_accounts)]

        # Domain to fetch unreconciled payables/receivables (use case where you close invoices/refunds by reconciling your bank statements)
        domain_matching = [('reconciled', '=', False)]
        if self.partner_id.id or overlook_partner:
            domain_matching = expression.AND([domain_matching, [('account_id.internal_type', 'in', ['payable', 'receivable'])]])
        else:
            # TODO : find out what use case this permits (match a check payment, registered on a journal whose account type is other instead of liquidity)
            domain_matching = expression.AND([domain_matching, [('account_id.reconcile', '=', True)]])

        # Let's add what applies to both
        domain = expression.OR([domain_reconciliation, domain_matching])
        if self.partner_id.id and not overlook_partner:
            domain = expression.AND([domain, [('partner_id', '=', self.partner_id.id)]])

        # Domain factorized for all reconciliation use cases
        ctx = dict(self._context or {})
        ctx['bank_statement_line'] = self
        generic_domain = self.env['account.move.line'].with_context(ctx).domain_move_lines_for_reconciliation(excluded_ids=excluded_ids, str=str)
        domain = expression.AND([domain, generic_domain])

        # Domain from caller
        if additional_domain is None:
            additional_domain = []
        else:
            additional_domain = expression.normalize_domain(additional_domain)
        domain = expression.AND([domain, additional_domain])

        return self.env['account.move.line'].search(domain, offset=offset, limit=limit, order="date_maturity asc, id asc")
Exemple #31
0
 def _evaluate_get(self, cr, uid, ids, field_name, args, context=None):
     """check if this filter contains references to x2many fields. If so,
     then negation goes wrong in nearly all cases, so we evaluate the
     filter and remove its resulting ids"""
     result = {}
     for this in self.read(cr, uid, ids, ['model_id', 'domain'],
                           context=context):
         result[this['id']] = {
             'evaluate_before_negate': False,
             'evaluate_always': False,
         }
         domain = expression.normalize_domain(
             safe_eval(this['domain'] or 'False') or
             [expression.FALSE_LEAF])
         for arg in domain:
             if not expression.is_leaf(arg) or not isinstance(
                     arg[0], basestring):
                 continue
             current_model = self.pool.get(this['model_id'])
             if not current_model:
                 continue
             has_x2many = False
             has_auto_join = False
             for field_name in arg[0].split('.'):
                 if field_name in MAGIC_COLUMNS:
                     continue
                 field = current_model._all_columns[field_name].column
                 has_x2many |= field._type in self._evaluate_before_negate
                 has_x2many |= isinstance(field, fields.function)
                 has_auto_join |= field._auto_join
                 has_auto_join |= isinstance(field, fields.function)
                 if hasattr(field, '_obj'):
                     current_model = self.pool.get(field._obj)
                 if not current_model or has_x2many and has_auto_join:
                     break
             result[this['id']]['evaluate_before_negate'] |= has_x2many
             result[this['id']]['evaluate_always'] |= has_auto_join
             if result[this['id']]['evaluate_before_negate'] and\
                     result[this['id']]['evaluate_always']:
                 break
     return result
Exemple #32
0
    def extended_init(self, cr, uid, exp, table, context):

        self._unaccent = expression.get_unaccent_wrapper(cr)
        self.joins = []
        self.root_model = table

        # normalize and prepare the expression for parsing
        self.expression = expression.distribute_not(
            expression.normalize_domain(exp))

        # look for real lang from context before parse
        parse_ctx = context.copy()
        if parse_ctx.get('lang', False) and exists_short_code(cr):
            cr.execute("select code from res_lang where short_code = '%s'" %
                       parse_ctx['lang'])
            res = cr.fetchall()
            if res and res[0]:
                parse_ctx.update({'lang': res[0][0]})

        # parse the domain expression
        self.parse(cr, uid, context=parse_ctx)
 def _compute_activity_rule_domain(self, activity_id):
     if self._uid == SUPERUSER_ID or isinstance(self.env.uid,
                                                BaseSuspendSecurityUid):
         return None
     self._cr.execute(
         """SELECT r.id
             FROM activity_record_rule r
             WHERE r.active is True
             AND r.activity_id = %s
             AND (r.id IN (SELECT rule_group_id FROM activity_rule_group_rel g_rel
             JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid)
             WHERE u_rel.uid = %s) OR r.global)""",
         (activity_id, self._uid))
     rule_ids = [x[0] for x in self._cr.fetchall()]
     if rule_ids:
         # browse user as super-admin root to avoid access errors!
         user = self.env['res.users'].sudo().browse([self._uid])
         global_domains = []  # list of domains
         group_domains = {}  # map: group -> list of domains
         for rule in self.sudo().browse(rule_ids):
             # read 'domain' as UID to have the correct eval context for
             # the rule.
             rule_domain = rule.sudo(user=user.id)\
                 .read(['domain'])[0]['domain']
             dom = expression.normalize_domain(rule_domain)
             for group in rule.groups:
                 if group in user.groups_id:
                     group_domains.setdefault(group, []).append(dom)
             if not rule.groups:
                 global_domains.append(dom)
         # combine global domains and group domains
         if group_domains:
             group_domain = expression.OR(
                 map(expression.OR, group_domains.values()))
         else:
             group_domain = []
         domain = expression.AND(global_domains + [group_domain])
         return domain
     return []
    def _get_domain_reconciliation(self, excluded_ids, str, overlook_partner,
                                   additional_domain):

        reconciliation_aml_accounts = [
            self.journal_id.default_credit_account_id.id,
            self.journal_id.default_debit_account_id.id,
        ]
        bank_reconcile_account_allowed_ids =\
            self.journal_id.bank_reconcile_account_allowed_ids.ids or []
        reconciliation_account_all = reconciliation_aml_accounts + \
            bank_reconcile_account_allowed_ids

        domain = [
            '&', ('statement_id', '=', False),
            ('account_id', 'in', reconciliation_account_all)
        ]

        if self.partner_id.id and not overlook_partner:
            domain = expression.AND(
                [domain, [('partner_id', '=', self.partner_id.id)]])

        # Domain factorized for all reconciliation use cases
        ctx = dict(self._context or {})
        ctx['bank_statement_line'] = self
        generic_domain = self.env['account.move.line'].with_context(
            ctx).domain_move_lines_for_reconciliation(
                excluded_ids=excluded_ids, str=str)
        domain = expression.AND([domain, generic_domain])

        # Domain from caller
        if additional_domain is None:
            additional_domain = []
        else:
            additional_domain = expression.normalize_domain(additional_domain)
        domain = expression.AND([domain, additional_domain])

        return domain
 def _restrict_field_access_adjust_field_modifiers(self, field_node,
                                                   modifiers):
     """inject a readonly modifier to make non-writable fields in a form
     readonly"""
     # TODO: this can be fooled by embedded views
     if not self._restrict_field_access_is_field_accessible(
             field_node.attrib['name'], action='write'):
         for modifier, value in [('readonly', True), ('required', False)]:
             domain = modifiers.get(modifier, [])
             if isinstance(domain, list) and domain:
                 domain = expression.normalize_domain(domain)
             elif bool(domain) == value:
                 # readonly/nonrequired anyways
                 return modifiers
             else:
                 domain = []
             restrict_domain = [('restrict_field_access', '=', value)]
             if domain:
                 restrict_domain = expression.OR([
                     restrict_domain,
                     domain
                 ])
             modifiers[modifier] = restrict_domain
     return modifiers
Exemple #36
0
    def __init__(self, cr, uid, exp, table, context):
        """ Initialize expression object and automatically parse the expression
            right after initialization.

            :param exp: expression (using domain ('foo', '=', 'bar' format))
            :param table: root model

            :attr list result: list that will hold the result of the parsing
                as a list of ExtendedLeaf
            :attr list joins: list of join conditions, such as
                (res_country_state."id" = res_partner."state_id")
            :attr root_model: base model for the query
            :attr list expression: the domain expression, that will be
                normalized
                and prepared
        """
        self._unaccent = get_unaccent_wrapper(cr)
        self.joins = []
        self.root_model = table

        # normalize and prepare the expression for parsing
        self.expression = distribute_not(normalize_domain(exp))

        # Keep a dict of table aliases to ensure that no alias
        # of lenght > 64 char are created
        self.table_aliases = {}
        # aliases are of the following form
        # a1, a2, a3 ... a10 ... a999 ...
        self.next_alias_int = 1

        self.cr = cr
        self.uid = uid
        self.context = context

        # parse the domain expression
        self.parse(cr, uid, context=context)
 def _search(self, args, offset=0, limit=None, order=None, count=False,
             access_rights_uid=None):
     if not args:
         return super(RestrictFieldAccessMixin, self)._search(
             args, offset=offset, limit=limit, order=order, count=count,
             access_rights_uid=access_rights_uid)
     args = expression.normalize_domain(args)
     has_inaccessible_field = False
     for term in args:
         if not expression.is_leaf(term):
             continue
         if not self._restrict_field_access_is_field_accessible(
                 term[0], 'read'):
             has_inaccessible_field = True
             break
     if has_inaccessible_field:
         check_self = self if not access_rights_uid else self.sudo(
             access_rights_uid)
         check_self\
             ._restrict_field_access_inject_restrict_field_access_domain(
                 args)
     return super(RestrictFieldAccessMixin, self)._search(
         args, offset=offset, limit=limit, order=order, count=count,
         access_rights_uid=access_rights_uid)
Exemple #38
0
 def get_rule_ids(self, cr, uid, ids, check_uid, model_name, mode="read"):
     if check_uid == SUPERUSER_ID:
         return []
     res_ids = []
     model_pooler = self.pool[model_name]
     cr.execute(
         """
             SELECT r.id
             FROM ir_rule r
             JOIN ir_model m ON (r.model_id = m.id)
             WHERE m.model = %s
             AND r.active is True
             AND r.perm_""" + mode + """
             AND (r.id IN (SELECT rule_group_id FROM rule_group_rel g_rel
                         JOIN res_groups_users_rel u_rel ON (g_rel.group_id = u_rel.gid)
                         WHERE u_rel.uid = %s) OR r.global)""",
         (model_name, check_uid))
     rule_ids = [x[0] for x in cr.fetchall()]
     if rule_ids:
         # browse user as super-admin root to avoid access errors!
         user = self.pool['res.users'].browse(cr, SUPERUSER_ID, check_uid)
         rule_datas = self.pool['ir.rule'].browse(cr, SUPERUSER_ID,
                                                  rule_ids)
         for rule in rule_datas:
             global_domains = []  # list of domains
             # map: group -> list of domains
             group_domains = {}
             # read 'domain' as UID to have the correct eval context for the
             # rule.
             rule_domain = rule.domain
             #                rule_domain = rule_domain['domain']
             dom = expression.normalize_domain(rule_domain)
             for group in rule.groups:
                 if group in user.groups_id:
                     group_domains.setdefault(group, []).append(dom)
             if not rule.groups:
                 global_domains.append(dom)
             # combine global domains and group domains
             if group_domains:
                 group_domain = expression.OR(
                     map(expression.OR, group_domains.values()))
             else:
                 group_domain = []
             domain = expression.AND(global_domains + [group_domain])
             if domain:
                 # _where_calc is called as superuser. This means that rules can
                 # involve objects on which the real uid has no acces rights.
                 # This means also there is no implicit restriction (e.g. an object
                 # references another object the user can't see).
                 query = self.pool.get(model_name)._where_calc(
                     cr, SUPERUSER_ID, domain, active_test=False)
                 where_clause, where_params, tables = query.where_clause, query.where_clause_params, query.tables
                 if where_clause:
                     where_clause = ' and ' + ' and '.join(where_clause)
                     cr.execute(
                         'SELECT ' + model_pooler._table + '.id FROM ' +
                         ','.join(tables) + ' WHERE ' +
                         model_pooler._table + '.id IN %s' + where_clause,
                         ([tuple(ids)] + where_params))
                     returned_ids = [x['id'] for x in cr.dictfetchall()]
                     check_rs = self.profile_check_record_rules_result_count(
                         cr,
                         check_uid,
                         ids,
                         returned_ids,
                         mode,
                         model_pooler,
                         context={})
                     if not check_rs:
                         res_ids.append(rule.id)
     return res_ids
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 _restrict_field_access_inject_restrict_field_access_domain(
         self, domain):
     domain[:] = expression.AND([
         expression.normalize_domain(domain),
         [('credit_limit', '<', 42)]
     ])
Exemple #41
0
def filtered_from_domain(self, domain):
    if not domain or not self:
        return self

    localdict = {
        'time': time,
        'datetime': datetime,
        'relativedelta': relativedelta,
        'context': self._context,
        'uid': self._uid,
        'user': self.env.user
    }
    try:
        if not isinstance(domain, basestring):
            domain = repr(domain)
        domain = normalize_domain(eval(domain, localdict))
    except:
        raise Warning(
            _('Domain not supported for %s filtering: %s') %
            (self._name, domain))

    stack = []

    def preformat(item):
        model = self[0]
        if item[0].split('.')[:-1]:
            model = eval('o.%s' % '.'.join(item[0].split('.')[:-1]),
                         {'o': self[0]})
        field = model._fields[item[0].split('.')[-1]]
        if field.relational:
            if isinstance(item[2], basestring):
                item[2] = dict(self.env[field.comodel_name].name_search(
                    name=item[2], operator=item[1])).keys()
                item[1] = 'in'
            if field.type.endswith('2many'):
                item[0] += '.ids'
            else:
                item[0] += '.id'
        return item

    def compute(item):
        item = preformat(item)
        if isinstance(item, tuple):
            item = list(item)
        item[0] = 'o.%s' % item[0]
        item[1] = SQL2PYTHON_OPERATORS.get(item[1], item[1])
        reverse = True if item[1] in (
            'in', 'not in') and not isinstance(item[2],
                                               (tuple, list)) else False
        item[2] = repr(item[2])
        if reverse:
            item = item[::-1]
        expr_to_eval = ' '.join(map(str, item))
        try:
            return self.filtered(
                lambda rec: eval(expr_to_eval, dict(localdict, o=rec)))
        except:
            return self.browse()

    def parse():
        for item in domain[::-1]:
            if isinstance(item, (tuple, list)):
                stack.append(compute(item))
            else:
                a = stack.pop()
                if item == '!':
                    b = self
                else:
                    b = stack.pop()
                stack.append(SET_OPERATORS[item](b, a))
        return stack.pop()

    return parse()
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)
Exemple #43
0
def filtered_from_domain(self, domain):
    if not domain or not self:
        return self

    def get_records(item):
        records = self
        remote_field = item[0].split('.')[:-1]
        if remote_field:
            records = eval("rec.mapped('%s')" % '.'.join(remote_field),
                           {'rec': self})
        return records

    def get_field(item):
        return get_records(item)._fields[item[0].split('.')[-1]]

    def extend(domain):
        for index, item in enumerate(domain):
            if isinstance(item, list):
                field = get_field(item)
                if field.search and not field.related:
                    extension = field.search(get_records(item), *item[1:])
                    domain = domain[:index] + normalize_domain(
                        extension) + domain[index + 1:]
        return domain

    localdict = {
        'time': time,
        'datetime': datetime,
        'relativedelta': relativedelta,
        'context': self._context,
        'uid': self._uid,
        'user': self.env.user
    }
    try:
        if not isinstance(domain, basestring):
            domain = repr(domain)
        domain = extend(normalize_domain(eval(domain, localdict)))
    except Exception:
        raise Warning(
            _('Domain not supported for %s filtering: %s') %
            (self._name, domain))

    stack = []

    def preformat(item):
        if isinstance(item, tuple):
            item = list(item)
        reverse = False
        field = get_field(item)
        if field.relational:
            if isinstance(item[2], basestring):
                item[2] = dict(self.env[field.comodel_name].name_search(
                    name=item[2], operator=item[1], limit=0)).keys()
                item[1] = 'in'
            item[0] = 'rec.%s' % item[0]
            if field.type.endswith('2many'):
                item[0] += '.ids'
                py_operator = SQL2PYTHON_OPERATORS.get(item[1], item[1])
                if py_operator in ('in', 'not in'):
                    item[0] = '%sset(%s)' % (py_operator.startswith('not')
                                             and 'not ' or '', item[0])
                    item[1] = '&'
                    item[2] = set(item[2])
            else:
                item[0] += '.id'
        else:
            reverse = 'like' in item[1]
            item[0] = 'rec.%s' % item[0]
        item[1] = SQL2PYTHON_OPERATORS.get(item[1], item[1])
        item[2] = repr(item[2])
        if reverse:
            item = item[::-1]
        return ' '.join(map(str, item))

    def compute(item):
        try:
            expr = preformat(item)
            return self.filtered(
                lambda rec: eval(expr, dict(localdict, rec=rec)))
        except Exception:
            return self.browse()

    def parse():
        for item in domain[::-1]:
            if isinstance(item, (tuple, list)):
                stack.append(compute(item))
            else:
                a = stack.pop()
                if item == '!':
                    b = self
                else:
                    b = stack.pop()
                stack.append(SET_OPERATORS[item](b, a))
        return stack.pop()

    return parse()