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 _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 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 )
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)
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 _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 _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 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 = []
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])
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
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
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 []
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)
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
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)
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 []
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)
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 _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
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
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)
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)] ])
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 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()