def _search_group(self, cr, uid, obj, name, args, context=None): operand = args[0][2] operator = args[0][1] lst = True if isinstance(operand, bool): domains = [[('name', operator, operand)], [('category_id.name', operator, operand)]] if operator in expression.NEGATIVE_TERM_OPERATORS == (not operand): return expression.AND(domains) else: return expression.OR(domains) if isinstance(operand, basestring): lst = False operand = [operand] where = [] for group in operand: values = filter(bool, group.split('/')) group_name = values.pop().strip() category_name = values and '/'.join(values).strip() or group_name group_domain = [('name', operator, lst and [group_name] or group_name)] category_domain = [('category_id.name', operator, lst and [category_name] or category_name)] if operator in expression.NEGATIVE_TERM_OPERATORS and not values: category_domain = expression.OR([category_domain, [('category_id', '=', False)]]) if (operator in expression.NEGATIVE_TERM_OPERATORS) == (not values): sub_where = expression.AND([group_domain, category_domain]) else: sub_where = expression.OR([group_domain, category_domain]) if operator in expression.NEGATIVE_TERM_OPERATORS: where = expression.AND([where, sub_where]) else: where = expression.OR([where, sub_where]) return where
def code_search(self, name, args=None, operator='ilike', limit=100): if not args: args = [] if name: category_names = name.split(' / ') parents = list(category_names) child = parents.pop() domain = [('name', operator, child)] if parents: names_ids = self.name_search(' / '.join(parents), args=args, operator='ilike', limit=limit) category_ids = [name_id[0] for name_id in names_ids] if operator in expression.NEGATIVE_TERM_OPERATORS: categories = self.search([('id', 'not in', category_ids)]) domain = expression.OR([[('parent_id', 'in', categories.ids)], domain]) else: domain = expression.AND([[('parent_id', 'in', category_ids) ], domain]) for i in range(1, len(category_names)): domain = [[('name', operator, ' / '.join(category_names[-1 - i:]))], domain] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = expression.AND(domain) else: domain = expression.OR(domain) categories = self.search(expression.AND([domain, args]), limit=limit) else: categories = self.search(args, limit=limit) return categories.name_get()
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 name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100): if not args: args = [] if not context: context = {} if name: # Be sure name_search is symetric to name_get categories = name.split('/') parents = list(categories) child = parents.pop() domain = [('name', operator, child)] if parents: names_ids = self.name_search( cr, uid, '/'.join(parents), args=args, operator='ilike', context=context, limit=limit) category_ids = [name_id[0] for name_id in names_ids] if operator in expression.NEGATIVE_TERM_OPERATORS: category_ids = self.search( cr, uid, [('id', 'not in', category_ids)]) domain = expression.OR( [[('parent_id', 'in', category_ids)], domain]) else: domain = expression.AND( [[('parent_id', 'in', category_ids)], domain]) for i in range(1, len(categories)): domain = [ [('name', operator, '/'.join(categories[-1 - i:]))], domain] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = expression.AND(domain) else: domain = expression.OR(domain) ids = self.search( cr, uid, expression.AND([domain, args]), limit=limit, context=context) else: ids = self.search(cr, uid, args, limit=limit, context=context) return self.name_get(cr, uid, ids, context)
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 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 _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 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 test_40_negating_long_expression(self): source = ['!','&',('user_id','=',4),('partner_id','in',[1,2])] expect = ['|',('user_id','!=',4),('partner_id','not in',[1,2])] self.assertEqual(expression.distribute_not(source), expect, "distribute_not on expression applied wrongly") pos_leaves = [[('a', 'in', [])], [('d', '!=', 3)]] neg_leaves = [[('a', 'not in', [])], [('d', '=', 3)]] source = expression.OR([expression.AND(pos_leaves)] * 1000) expect = source self.assertEqual(expression.distribute_not(source), expect, "distribute_not on long expression without negation operator should not alter it") source = ['!'] + source expect = expression.AND([expression.OR(neg_leaves)] * 1000) self.assertEqual(expression.distribute_not(source), expect, "distribute_not on long expression applied wrongly")
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 _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 get_mention_suggestions(self, search, limit=8): """ Return 'limit'-first channels' id, name and public fields such that the name matches a 'search' string. Exclude channels of type chat (DM), and private channels the current user isn't registered to. """ domain = expression.AND([[('name', 'ilike', search)], [('channel_type', '=', 'channel')], expression.OR( [[('public', '!=', 'private')], [('channel_partner_ids', 'in', [self.env.user.partner_id.id])]])]) return self.search_read(domain, ['id', 'name', 'public'], limit=limit)
def domain_move_lines_for_reconciliation(self, excluded_ids=None, str=False): """ Add move display name in search of move lines""" _super = super(AccountMoveLine, self) _get_domain = _super.domain_move_lines_for_reconciliation domain = _get_domain(excluded_ids=excluded_ids, str=str) if not str and str != '/': return domain domain_trans_ref = [('move_id.display_name', 'ilike', str)] return expression.OR([domain, domain_trans_ref])
def get_mention_suggestions(self, search, channel, exclude=None, limit=8): """ Return 'limit'-first partners' id, name and email such that the name or email matches a 'search' string. Prioritize partners registered to channel 'channel[channel_id]' if given, or partners that are followers of a document identified by 'channel[res_model]' and 'channel[res_id]' otherwise, then users, and finally extend the research to all partners. Exclude partners whose id is in 'exclude'. """ if exclude is None: exclude = [] members = [] users = [] partners = [] search_dom = expression.AND([ expression.OR([[('name', 'ilike', search)], [('email', 'ilike', search)]]), [('id', 'not in', exclude)] ]) fields = ['id', 'name', 'email'] def search_partners(domain, fields, limit, exclude): partners = self.search_read(domain, fields, limit=limit) limit -= len(partners) exclude += [partner['id'] for partner in partners] return partners, limit, exclude # Search users registered to the channel if 'channel_id' in channel: domain = expression.AND([[('channel_ids', 'in', [channel['channel_id']])], search_dom]) members, limit, exclude = search_partners(domain, fields, limit, exclude) else: domain = expression.AND([[('res_model', '=', channel['res_model']) ], [('res_id', '=', channel['res_id'])]]) followers = self.env['mail.followers'].search(domain) domain = expression.AND([[('id', 'in', followers.mapped('partner_id').ids)], search_dom]) members, limit, exclude = search_partners(domain, fields, limit, exclude) if limit > 0: # Search users domain = expression.AND([[('user_ids.id', '!=', False)], search_dom]) users, limit, exclude = search_partners(domain, fields, limit, exclude) if limit > 0: # Search partners partners = self.search_read(search_dom, fields, limit=limit) return [members, users, partners]
def get_mention_suggestions(self, search, limit=8): """ Return 'limit'-first partners' id, name and email such that the name or email matches a 'search' string. Prioritize users, and then extend the research to all partners. """ search_dom = expression.OR([[('name', 'ilike', search)], [('email', 'ilike', search)]]) fields = ['id', 'name', 'email'] # Search users domain = expression.AND([[('user_ids.id', '!=', False)], search_dom]) users = self.search_read(domain, fields, limit=limit) # Search partners if less than 'limit' users found partners = [] if len(users) < limit: partners = self.search_read(search_dom, fields, limit=limit) # Remove duplicates partners = [p for p in partners if not len([u for u in users if u['id'] == p['id']])] return [users, partners]
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 _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_stuck_jobs_domain(self, queue_dl, started_dl): domain = [] now = fields.datetime.now() if queue_dl: queue_dl = now - timedelta(minutes=queue_dl) domain.append([ '&', ('date_enqueued', '<=', fields.Datetime.to_string(queue_dl)), ('state', '=', 'enqueued'), ]) if started_dl: started_dl = now - timedelta(minutes=started_dl) domain.append([ '&', ('date_started', '<=', fields.Datetime.to_string(started_dl)), ('state', '=', 'started'), ]) if not domain: raise exceptions.ValidationError( _("If both parameters are 0, ALL jobs will be requeued!") ) return expression.OR(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 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