def compute_all(self, price_unit, currency=None, quantity=1.0, product=None, partner=None, is_refund=False, handle_price_include=True): taxes = self.filtered(lambda r: r.amount_type != 'code') company = self.env.company if product and product._name == 'product.template': product = product.product_variant_id for tax in self.filtered(lambda r: r.amount_type == 'code'): localdict = self._context.get('tax_computation_context', {}) localdict.update({ 'price_unit': price_unit, 'quantity': quantity, 'product': product, 'partner': partner, 'company': company }) safe_eval(tax.python_applicable, localdict, mode="exec", nocopy=True) if localdict.get('result', False): taxes += tax return super(AccountTaxPython, taxes).compute_all( price_unit, currency, quantity, product, partner, is_refund=is_refund, handle_price_include=handle_price_include)
def execute_code(self, code_exec): def reconciled_inv(): """ returns the list of invoices that are set as reconciled = True """ return self.env['account.move'].search([('reconciled', '=', True) ]).ids def order_columns(item, cols=None): """ This function is used to display a dictionary as a string, with its columns in the order chosen. :param item: dict :param cols: list of field names :returns: a list of tuples (fieldname: value) in a similar way that would dict.items() do except that the returned values are following the order given by cols :rtype: [(key, value)] """ if cols is None: cols = list(item) return [(col, item.get(col)) for col in cols if col in item] localdict = { 'cr': self.env.cr, 'uid': self.env.uid, 'reconciled_inv': reconciled_inv, # specific function used in different tests 'result': None, # used to store the result of the test 'column_order': None, # used to choose the display order of columns (in case you are returning a list of dict) '_': _, } safe_eval(code_exec, localdict, mode="exec", nocopy=True) result = localdict['result'] column_order = localdict.get('column_order', None) if not isinstance(result, (tuple, list, set)): result = [result] if not result: result = [_('The test was passed successfully')] else: def _format(item): if isinstance(item, dict): return ', '.join([ "%s: %s" % (tup[0], tup[1]) for tup in order_columns(item, column_order) ]) else: return item result = [_format(rec) for rec in result] return result
def postprocess_pdf_report(self, record, buffer): '''Hook to handle post processing during the pdf report generation. The basic behavior consists to create a new attachment containing the pdf base64 encoded. :param record_id: The record that will own the attachment. :param pdf_content: The optional name content of the file to avoid reading both times. :return: A modified buffer if the previous one has been modified, None otherwise. ''' attachment_name = safe_eval(self.attachment, { 'object': record, 'time': time }) if not attachment_name: return None attachment_vals = { 'name': attachment_name, 'datas': base64.encodestring(buffer.getvalue()), 'res_model': self.model, 'res_id': record.id, 'type': 'binary', } try: self.env['ir.attachment'].create(attachment_vals) except AccessError: _logger.info("Cannot save PDF report %r as attachment", attachment_vals['name']) else: _logger.info('The PDF document %s is now saved in the database', attachment_vals['name']) return buffer
def _get_failing(self, for_records, mode='read'): """ Returns the rules for the mode for the current user which fail on the specified records. Can return any global rule and/or all local rules (since local rules are OR-ed together, the entire group succeeds or fails, while global rules get AND-ed and can each fail) """ Model = for_records.browse(()).sudo() eval_context = self._eval_context() all_rules = self._get_rules(Model._name, mode=mode).sudo() # first check if the group rules fail for any record (aka if # searching on (records, group_rules) filters out some of the records) group_rules = all_rules.filtered(lambda r: r.groups and r.groups & self.env.user.groups_id) group_domains = expression.OR([ safe_eval(r.domain_force, eval_context) if r.domain_force else [] for r in group_rules ]) # if all records get returned, the group rules are not failing if Model.search_count(expression.AND([[('id', 'in', for_records.ids)], group_domains])) == len(for_records): group_rules = self.browse(()) # failing rules are previously selected group rules or any failing global rule def is_failing(r, ids=for_records.ids): dom = safe_eval(r.domain_force, eval_context) if r.domain_force else [] return Model.search_count(expression.AND([ [('id', 'in', ids)], expression.normalize_domain(dom) ])) < len(ids) return all_rules.filtered(lambda r: r in group_rules or (not r.groups and is_failing(r))).with_user(self.env.user)
def generate_coupon(self): """Generates the number of coupons entered in wizard field nbr_coupons """ program = self.env['sale.coupon.program'].browse( self.env.context.get('active_id')) vals = {'program_id': program.id} if self.generation_type == 'nbr_coupon' and self.nbr_coupons > 0: for count in range(0, self.nbr_coupons): self.env['sale.coupon'].create(vals) if self.generation_type == 'nbr_customer' and self.partners_domain: for partner in self.env['res.partner'].search( safe_eval(self.partners_domain)): vals.update({'partner_id': partner.id}) coupon = self.env['sale.coupon'].create(vals) subject = '%s, a coupon has been generated for you' % ( partner.name) template = self.env.ref( 'sale_coupon.mail_template_sale_coupon', raise_if_not_found=False) if template: template.send_mail(coupon.id, email_values={ 'email_to': partner.email, 'email_from': self.env.user.email or '', 'subject': subject, })
def format(self, percent, value, grouping=False, monetary=False): """ Format() will return the language-specific output for float values""" self.ensure_one() if percent[0] != '%': raise ValueError( _("format() must be given exactly one %char format specifier")) formatted = percent % value # floats and decimal ints need special action! if grouping: lang_grouping, thousands_sep, decimal_point = self._data_get( monetary) eval_lang_grouping = safe_eval(lang_grouping) if percent[-1] in 'eEfFgG': parts = formatted.split('.') parts[0] = intersperse(parts[0], eval_lang_grouping, thousands_sep)[0] formatted = decimal_point.join(parts) elif percent[-1] in 'diu': formatted = intersperse(formatted, eval_lang_grouping, thousands_sep)[0] return formatted
def test_01_safe_eval(self): """ Try a few common expressions to verify they work with safe_eval """ expected = (1, {"a": 9 * 2}, (True, False, None)) actual = safe_eval('(1, {"a": 9 * 2}, (True, False, None))') self.assertEqual( actual, expected, "Simple python expressions are not working with safe_eval")
def action_view_task(self): self.ensure_one() list_view_id = self.env.ref('project.view_task_tree2').id form_view_id = self.env.ref('project.view_task_form2').id action = {'type': 'ir.actions.act_window_close'} task_projects = self.tasks_ids.mapped('project_id') if len(task_projects) == 1 and len( self.tasks_ids ) > 1: # redirect to task of the project (with kanban stage, ...) action = self.with_context(active_id=task_projects.id).env.ref( 'project.act_project_project_2_project_task_all').read()[0] if action.get('context'): eval_context = self.env[ 'ir.actions.actions']._get_eval_context() eval_context.update({'active_id': task_projects.id}) action['context'] = safe_eval(action['context'], eval_context) else: action = self.env.ref('project.action_view_task').read()[0] action['context'] = { } # erase default context to avoid default filter if len(self.tasks_ids) > 1: # cross project kanban task action['views'] = [[False, 'kanban'], [list_view_id, 'tree'], [form_view_id, 'form'], [False, 'graph'], [False, 'calendar'], [False, 'pivot']] elif len(self.tasks_ids) == 1: # single task -> form view action['views'] = [(form_view_id, 'form')] action['res_id'] = self.tasks_ids.id # filter on the task of the current SO action.setdefault('context', {}) action['context'].update({'search_default_sale_order_id': self.id}) return action
def _is_valid_partner(self, partner): if self.rule_partners_domain: domain = safe_eval( self.rule_partners_domain) + [('id', '=', partner.id)] return bool(self.env['res.partner'].search_count(domain)) else: return True
def _is_valid_product(self, product): # NOTE: if you override this method, think of also overriding _get_valid_products if self.rule_products_domain: domain = safe_eval( self.rule_products_domain) + [('id', '=', product.id)] return bool(self.env['product.product'].search_count(domain)) else: return True
def _filter_post_export_domain(self, records): """ Filter the records that satisfy the postcondition of action ``self``. """ if self.filter_domain and records: domain = [('id', 'in', records.ids)] + safe_eval( self.filter_domain, self._get_eval_context()) return records.search(domain), domain else: return records, None
def _compute_recipients_count(self): self.res_ids_count = len(literal_eval( self.res_ids)) if self.res_ids else 0 if self.res_model: self.active_domain_count = self.env[self.res_model].search_count( safe_eval(self.active_domain or '[]')) else: self.active_domain_count = 0
def get_alias_values(self): has_group_use_lead = self.env.user.has_group('crm.group_use_lead') values = super(Team, self).get_alias_values() values['alias_defaults'] = defaults = safe_eval(self.alias_defaults or "{}") defaults[ 'type'] = 'lead' if has_group_use_lead and self.use_leads else 'opportunity' defaults['team_id'] = self.id return values
def _fetch_attachment(self): """ This method will check if we have any existent attachement matching the model and res_ids and create them if not found. """ self.ensure_one() obj = self.env[self.model].browse(self.res_id) if not self.attachment_id: report = self.report_template if not report: report_name = self.env.context.get('report_name') report = self.env['ir.actions.report']._get_report_from_name( report_name) if not report: return False else: self.write({'report_template': report.id}) # report = self.env.ref('account.account_invoices') if report.print_report_name: report_name = safe_eval(report.print_report_name, {'object': obj}) elif report.attachment: report_name = safe_eval(report.attachment, {'object': obj}) else: report_name = 'Document' filename = "%s.%s" % (report_name, "pdf") pdf_bin, _ = report.with_context( snailmail_layout=not self.cover).render_qweb_pdf(self.res_id) attachment = self.env['ir.attachment'].create({ 'name': filename, 'datas': base64.b64encode(pdf_bin), 'res_model': 'snailmail.letter', 'res_id': self.id, 'type': 'binary', # override default_type from context, possibly meant for another model! }) self.write({'attachment_id': attachment.id}) return self.attachment_id
def get_google_drive_config(self, res_model, res_id): ''' Function called by the js, when no google doc are yet associated with a record, with the aim to create one. It will first seek for a google.docs.config associated with the model `res_model` to find out what's the template of google doc to copy (this is usefull if you want to start with a non-empty document, a type or a name different than the default values). If no config is associated with the `res_model`, then a blank text document with a default name is created. :param res_model: the object for which the google doc is created :param ids: the list of ids of the objects for which the google doc is created. This list is supposed to have a length of 1 element only (batch processing is not supported in the code, though nothing really prevent it) :return: the config id and config name ''' # TO DO in master: fix my signature and my model if isinstance(res_model, str): res_model = self.env['ir.model'].search([('model', '=', res_model) ]).id if not res_id: raise UserError( _("Creating google drive may only be done by one at a time.")) # check if a model is configured with a template configs = self.search([('model_id', '=', res_model)]) config_values = [] for config in configs.sudo(): if config.filter_id: if config.filter_id.user_id and config.filter_id.user_id.id != self.env.user.id: #Private continue domain = [('id', 'in', [res_id])] + safe_eval( config.filter_id.domain) additionnal_context = safe_eval(config.filter_id.context) google_doc_configs = self.env[ config.filter_id.model_id].with_context( **additionnal_context).search(domain) if google_doc_configs: config_values.append({ 'id': config.id, 'name': config.name }) else: config_values.append({'id': config.id, 'name': config.name}) return config_values
def _get_records(self): if not self.res_model: return None if self.use_active_domain: active_domain = safe_eval(self.active_domain or '[]') records = self.env[self.res_model].search(active_domain) elif self.res_id: records = self.env[self.res_model].browse(self.res_id) else: records = self.env[self.res_model].browse( literal_eval(self.res_ids or '[]')) return records
def _get_price_from_picking(self, total, weight, volume, quantity): price = 0.0 criteria_found = False price_dict = {'price': total, 'volume': volume, 'weight': weight, 'wv': volume * weight, 'quantity': quantity} for line in self.price_rule_ids: test = safe_eval(line.variable + line.operator + str(line.max_value), price_dict) if test: price = line.list_base_price + line.list_price * price_dict[line.variable_factor] criteria_found = True break if not criteria_found: raise UserError(_("No price rule matching this order; delivery cost cannot be computed.")) return price
def _compute_amount(self, base_amount, price_unit, quantity=1.0, product=None, partner=None): self.ensure_one() if product and product._name == 'product.template': product = product.product_variant_id if self.amount_type == 'code': company = self.env.company localdict = { 'base_amount': base_amount, 'price_unit': price_unit, 'quantity': quantity, 'product': product, 'partner': partner, 'company': company } safe_eval(self.python_compute, localdict, mode="exec", nocopy=True) return localdict['result'] return super(AccountTaxPython, self)._compute_amount(base_amount, price_unit, quantity, product, partner)
def action_view_all_rating(self): """ return the action to see all the rating of the project, and activate default filters """ if self.portal_show_rating: return { 'type': 'ir.actions.act_url', 'name': "Redirect to the Website Projcet Rating Page", 'target': 'self', 'url': "/project/rating/%s" % (self.id,) } action = self.env['ir.actions.act_window'].for_xml_id('project', 'rating_rating_action_view_project_rating') action['name'] = _('Ratings of %s') % (self.name,) action_context = safe_eval(action['context']) if action['context'] else {} action_context.update(self._context) action_context['search_default_parent_res_name'] = self.name action_context.pop('group_by', None) return dict(action, context=action_context)
def _check(self, automatic=False, use_new_cursor=False): """ This Function is called by scheduler. """ if '__action_done' not in self._context: self = self.with_context(__action_done={}) # retrieve all the action rules to run based on a timed condition eval_context = self._get_eval_context() for action in self.with_context(active_test=True).search([ ('trigger', '=', 'on_time') ]): last_run = fields.Datetime.from_string( action.last_run) or datetime.datetime.utcfromtimestamp(0) # retrieve all the records that satisfy the action's condition domain = [] context = dict(self._context) if action.filter_domain: domain = safe_eval(action.filter_domain, eval_context) records = self.env[action.model_name].with_context(context).search( domain) # determine when action should occur for the records if action.trg_date_id.name == 'date_action_last' and 'create_date' in records._fields: get_record_dt = lambda record: record[action.trg_date_id.name ] or record.create_date else: get_record_dt = lambda record: record[action.trg_date_id.name] # process action on the records that should be executed now = datetime.datetime.now() for record in records: record_dt = get_record_dt(record) if not record_dt: continue action_dt = self._check_delay(action, record, record_dt) if last_run <= action_dt < now: try: action._process(record) except Exception: _logger.error(traceback.format_exc()) action.write( {'last_run': now.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}) if automatic: # auto-commit for batch processing self._cr.commit()
def retrieve_attachment(self, record): '''Retrieve an attachment for a specific record. :param record: The record owning of the attachment. :param attachment_name: The optional name of the attachment. :return: A recordset of length <=1 or None ''' attachment_name = safe_eval(self.attachment, { 'object': record, 'time': time }) if self.attachment else '' if not attachment_name: return None return self.env['ir.attachment'].search( [('name', '=', attachment_name), ('res_model', '=', self.model), ('res_id', '=', record.id)], limit=1)
def test_05_safe_eval_forbiddon(self): """ Try forbidden expressions in safe_eval to verify they are not allowed""" # no forbidden builtin expression with self.assertRaises(ValueError): safe_eval('open("/etc/passwd","r")') # no forbidden opcodes with self.assertRaises(ValueError): safe_eval("import coffice", mode="exec") # no dunder with self.assertRaises(NameError): safe_eval("self.__name__", {'self': self}, mode="exec")
def action_your_pipeline(self): action = self.env.ref('crm.crm_lead_action_pipeline').read()[0] user_team_id = self.env.user.sale_team_id.id if not user_team_id: user_team_id = self.search([], limit=1).id action['help'] = _( """<p class='o_view_nocontent_smiling_face'>Add new opportunities</p><p> Looks like you are not a member of a Sales Team. You should add yourself as a member of one of the Sales Team. </p>""") if user_team_id: action[ 'help'] += "<p>As you don't belong to any Sales Team, COffice opens the first one by default.</p>" action_context = safe_eval(action['context'], {'uid': self.env.uid}) if user_team_id: action_context['default_team_id'] = user_team_id action['context'] = action_context return action
def _check_domain_validity(self): # take admin as should always be present for definition in self: if definition.computation_mode not in ('count', 'sum'): continue Obj = self.env[definition.model_id.model] try: domain = safe_eval( definition.domain, {'user': self.env.user.with_user(self.env.user)}) # dummy search to make sure the domain is valid Obj.search_count(domain) except (ValueError, SyntaxError) as e: msg = e if isinstance(e, SyntaxError): msg = (e.msg + '\n' + e.text) raise exceptions.UserError( _("The domain for the definition %s seems incorrect, please check it.\n\n%s" ) % (definition.name, msg)) return True
def get_action(self): """Get the ir.action related to update the goal In case of a manual goal, should return a wizard to update the value :return: action description in a dictionary """ if self.definition_id.action_id: # open a the action linked to the goal action = self.definition_id.action_id.read()[0] if self.definition_id.res_id_field: current_user = self.env.user.with_user(self.env.user) action['res_id'] = safe_eval(self.definition_id.res_id_field, {'user': current_user}) # if one element to display, should see it in form mode if possible action['views'] = [(view_id, mode) for (view_id, mode) in action['views'] if mode == 'form'] or action['views'] return action if self.computation_mode == 'manually': # open a wizard window to update the value manually action = { 'name': _("Update %s") % self.definition_id.name, 'id': self.id, 'type': 'ir.actions.act_window', 'views': [[False, 'form']], 'target': 'new', 'context': { 'default_goal_id': self.id, 'default_current': self.current }, 'res_model': 'gamification.goal.wizard' } return action return False
def _compute_domain(self, model_name, mode="read"): rules = self._get_rules(model_name, mode=mode) if not rules: return # browse user and rules as SUPERUSER_ID to avoid access errors! eval_context = self._eval_context() user_groups = self.env.user.groups_id global_domains = [] # list of domains group_domains = [] # list of domains for rule in rules.sudo(): # evaluate the domain for the current user dom = safe_eval(rule.domain_force, eval_context) if rule.domain_force else [] dom = expression.normalize_domain(dom) if not rule.groups: global_domains.append(dom) elif rule.groups & user_groups: group_domains.append(dom) # combine global domains and group domains if not group_domains: return expression.AND(global_domains) return expression.AND(global_domains + [expression.OR(group_domains)])
def _get_recipients(self): if self.mailing_domain: domain = safe_eval(self.mailing_domain) res_ids = self.env[self.mailing_model_real].search(domain).ids else: res_ids = [] domain = [('id', 'in', res_ids)] # randomly choose a fragment if self.contact_ab_pc < 100: contact_nbr = self.env[self.mailing_model_real].search_count( domain) topick = int(contact_nbr / 100.0 * self.contact_ab_pc) if self.campaign_id and self.unique_ab_testing: already_mailed = self.campaign_id._get_mailing_recipients()[ self.campaign_id.id] else: already_mailed = set([]) remaining = set(res_ids).difference(already_mailed) if topick > len(remaining): topick = len(remaining) res_ids = random.sample(remaining, topick) return res_ids
def _get_eval_domain(self): self.ensure_one() return safe_eval(self.domain, { 'datetime': datetime, 'context_today': datetime.datetime.now, })
def send_mail(self, auto_commit=False): """ Process the wizard content and proceed with sending the related email(s), rendering any template patterns on the fly if needed. """ notif_layout = self._context.get('custom_layout') # Several custom layouts make use of the model description at rendering, e.g. in the # 'View <document>' button. Some models are used for different business concepts, such as # 'purchase.order' which is used for a RFQ and and PO. To avoid confusion, we must use a # different wording depending on the state of the object. # Therefore, we can set the description in the context from the beginning to avoid falling # back on the regular display_name retrieved in '_notify_prepare_template_context'. model_description = self._context.get('model_description') for wizard in self: # Duplicate attachments linked to the email.template. # Indeed, basic mail.compose.message wizard duplicates attachments in mass # mailing mode. But in 'single post' mode, attachments of an email template # also have to be duplicated to avoid changing their ownership. if wizard.attachment_ids and wizard.composition_mode != 'mass_mail' and wizard.template_id: new_attachment_ids = [] for attachment in wizard.attachment_ids: if attachment in wizard.template_id.attachment_ids: new_attachment_ids.append(attachment.copy({'res_model': 'mail.compose.message', 'res_id': wizard.id}).id) else: new_attachment_ids.append(attachment.id) new_attachment_ids.reverse() wizard.write({'attachment_ids': [(6, 0, new_attachment_ids)]}) # Mass Mailing mass_mode = wizard.composition_mode in ('mass_mail', 'mass_post') Mail = self.env['mail.mail'] ActiveModel = self.env[wizard.model] if wizard.model and hasattr(self.env[wizard.model], 'message_post') else self.env['mail.thread'] if wizard.composition_mode == 'mass_post': # do not send emails directly but use the queue instead # add context key to avoid subscribing the author ActiveModel = ActiveModel.with_context(mail_notify_force_send=False, mail_create_nosubscribe=True) # wizard works in batch mode: [res_id] or active_ids or active_domain if mass_mode and wizard.use_active_domain and wizard.model: res_ids = self.env[wizard.model].search(safe_eval(wizard.active_domain)).ids elif mass_mode and wizard.model and self._context.get('active_ids'): res_ids = self._context['active_ids'] else: res_ids = [wizard.res_id] batch_size = int(self.env['ir.config_parameter'].sudo().get_param('mail.batch_size')) or self._batch_size sliced_res_ids = [res_ids[i:i + batch_size] for i in range(0, len(res_ids), batch_size)] if wizard.composition_mode == 'mass_mail' or wizard.is_log or (wizard.composition_mode == 'mass_post' and not wizard.notify): # log a note: subtype is False subtype_id = False elif wizard.subtype_id: subtype_id = wizard.subtype_id.id else: subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment') for res_ids in sliced_res_ids: batch_mails = Mail all_mail_values = wizard.get_mail_values(res_ids) for res_id, mail_values in all_mail_values.items(): if wizard.composition_mode == 'mass_mail': batch_mails |= Mail.create(mail_values) else: post_params = dict( message_type=wizard.message_type, subtype_id=subtype_id, email_layout_xmlid=notif_layout, add_sign=not bool(wizard.template_id), mail_auto_delete=wizard.template_id.auto_delete if wizard.template_id else False, model_description=model_description) post_params.update(mail_values) if ActiveModel._name == 'mail.thread': if wizard.model: post_params['model'] = wizard.model post_params['res_id'] = res_id if not ActiveModel.message_notify(**post_params): # if message_notify returns an empty record set, no recipients where found. raise UserError(_("No recipient found.")) else: ActiveModel.browse(res_id).message_post(**post_params) if wizard.composition_mode == 'mass_mail': batch_mails.send(auto_commit=auto_commit)
def _check_alias_defaults(self): for alias in self: try: dict(safe_eval(alias.alias_defaults)) except Exception: raise ValidationError(_('Invalid expression, it must be a literal python dictionary definition e.g. "{\'field\': \'value\'}"'))