def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False): if node.get('attrs'): modifiers.update(safe_eval(node.get('attrs'))) if node.get('states'): if 'invisible' in modifiers and isinstance(modifiers['invisible'], list): # TODO combine with AND or OR, use implicit AND for now. modifiers['invisible'].append( ('state', 'not in', node.get('states').split(','))) else: modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))] for a in ('invisible', 'readonly', 'required'): if node.get(a): v = bool(safe_eval(node.get(a), {'context': context or {}})) if in_tree_view and a == 'invisible': # Invisible in a tree view has a specific meaning, make it a # new key in the modifiers attribute. modifiers['column_invisible'] = v elif v or (a not in modifiers or not isinstance(modifiers[a], list)): # Don't set the attribute to False if a dynamic value was # provided (i.e. a domain from attrs or states). modifiers[a] = v
def compute_all(self, price_unit, currency=None, quantity=1.0, product=None, partner=None): taxes = self.filtered(lambda r: r.amount_type != 'code') company = self.env.user.company_id for tax in self.filtered(lambda r: r.amount_type == 'code'): localdict = { '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)
def run_action_code_multi(self, action, eval_context=None): safe_eval(action.sudo().code.strip(), eval_context, mode="exec", nocopy=True) # nocopy allows to return 'action' if 'action' in eval_context: return eval_context['action']
def _satisfy_condition(self, localdict): """ @param contract_id: id of hr.contract to be tested @return: returns True if the given rule match the condition for the given contract. Return False otherwise. """ self.ensure_one() if self.condition_select == 'none': return True elif self.condition_select == 'range': try: result = safe_eval(self.condition_range, localdict) return self.condition_range_min <= result and result <= self.condition_range_max or False except: raise UserError( _('Wrong range condition defined for salary rule %s (%s).') % (self.name, self.code)) else: # python code try: safe_eval(self.condition_python, localdict, mode='exec', nocopy=True) return 'result' in localdict and localdict['result'] or False except: raise UserError( _('Wrong python condition defined for salary rule %s (%s).' ) % (self.name, self.code))
def execute_code(self, code_exec): def reconciled_inv(): """ returns the list of invoices that are set as reconciled = True """ return self.env['account.invoice'].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 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 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: The newly generated attachment if no AccessError, else None. ''' 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()), 'datas_fname': attachment_name, 'res_model': self.model, 'res_id': record.id, } attachment = None try: attachment = 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 attachment
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 action_launch(self, context=None): """ Launch Action of Wizard""" self.ensure_one() self.write({'state': 'done'}) # Load action action = self.env[self.action_id.type].browse(self.action_id.id) result = action.read()[0] if action._name != 'ir.actions.act_window': return result result.setdefault('context', '{}') # Open a specific record when res_id is provided in the context ctx = safe_eval(result['context'], {'user': self.env.user}) if ctx.get('res_id'): result['res_id'] = ctx.pop('res_id') # disable log for automatic wizards ctx['disable_log'] = True result['context'] = ctx return result
def action_your_pipeline(self): action = self.env.ref('crm.crm_lead_opportunities_tree_view').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='oe_view_nocontent_create'>Click here to add new opportunities</p><p> Looks like you are not a member of a sales channel. You should add yourself as a member of one of the sales channel. </p>""" if user_team_id: action[ 'help'] += "<p>As you don't belong to any sales channel, izi 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 tree_view_id = self.env.ref('crm.crm_case_tree_view_oppor').id form_view_id = self.env.ref('crm.crm_case_form_view_oppor').id kanb_view_id = self.env.ref('crm.crm_case_kanban_view_leads').id action['views'] = [[kanb_view_id, 'kanban'], [tree_view_id, 'tree'], [form_view_id, 'form'], [False, 'graph'], [False, 'calendar'], [False, 'pivot']] action['context'] = action_context return action
def action_view_all_rating(self): """ return the action to see all the rating of the project, and activate default filters """ action = self.env['ir.actions.act_window'].for_xml_id('rating_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_rating_tasks'] = 1 return dict(action, context=action_context)
def _filter_post(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) else: return records
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 generate(self, uid, dom=None, args=None): Model = request.env[self.model].sudo(uid) domain = safe_eval(self.domain, (args or {}).copy()) if dom: domain += dom for record in Model.search_read(domain=domain, fields=['write_date', Model._rec_name]): if record.get(Model._rec_name, False): yield {'loc': (record['id'], record[Model._rec_name])}
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, pycompat.string_types): 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 _compute_rule(self, localdict): """ :param localdict: dictionary containing the environement in which to compute the rule :return: returns a tuple build as the base/amount computed, the quantity and the rate :rtype: (float, float, float) """ self.ensure_one() if self.amount_select == 'fix': try: return self.amount_fix, float( safe_eval(self.quantity, localdict)), 100.0 except: raise UserError( _('Wrong quantity defined for salary rule %s (%s).') % (self.name, self.code)) elif self.amount_select == 'percentage': try: return (float(safe_eval(self.amount_percentage_base, localdict)), float(safe_eval(self.quantity, localdict)), self.amount_percentage) except: raise UserError( _('Wrong percentage base or quantity defined for salary rule %s (%s).' ) % (self.name, self.code)) else: try: safe_eval(self.amount_python_compute, localdict, mode='exec', nocopy=True) return float( localdict['result'] ), 'result_qty' in localdict and localdict[ 'result_qty'] or 1.0, 'result_rate' in localdict and localdict[ 'result_rate'] or 100.0 except: raise UserError( _('Wrong python code defined for salary rule %s (%s).') % (self.name, self.code))
def _compute_amount(self, base_amount, price_unit, quantity=1.0, product=None, partner=None): self.ensure_one() if self.amount_type == 'code': company = self.env.user.company_id 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 eval_value(self, eval_context=None): result = dict.fromkeys(self.ids, False) for line in self: expr = line.value if line.type == 'equation': expr = safe_eval(line.value, eval_context) elif line.col1.ttype in ['many2one', 'integer']: try: expr = int(line.value) except Exception: pass result[line.id] = expr return result
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 not attachment_name: return None return self.env['ir.attachment'].search([ ('datas_fname', '=', attachment_name), ('res_model', '=', self.model), ('res_id', '=', record.id) ], limit=1)
def message_get_email_values(self, notif_mail=None): res = super(Task, self).message_get_email_values(notif_mail=notif_mail) headers = {} if res.get('headers'): try: headers.update(safe_eval(res['headers'])) except Exception: pass if self.project_id: current_objects = [h for h in headers.get('X-izi-Objects', '').split(',') if h] current_objects.insert(0, 'project.project-%s, ' % self.project_id.id) headers['X-izi-Objects'] = ','.join(current_objects) if self.tag_ids: headers['X-izi-Tags'] = ','.join(self.tag_ids.mapped('name')) res['headers'] = repr(headers) return res
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 action_view_project_ids(self): self.ensure_one() if len(self.project_ids) == 1: if self.env.user.has_group("hr_timesheet.group_hr_timesheet_user"): action = self.project_ids.action_view_timesheet_plan() else: action = self.env.ref("project.act_project_project_2_project_task_all").read()[0] action['context'] = safe_eval(action.get('context', '{}'), {'active_id': self.project_ids.id, 'active_ids': self.project_ids.ids}) else: view_form_id = self.env.ref('project.edit_project').id view_kanban_id = self.env.ref('project.view_project_kanban').id action = { 'type': 'ir.actions.act_window', 'domain': [('id', 'in', self.project_ids.ids)], 'views': [(view_kanban_id, 'kanban'), (view_form_id, 'form')], 'view_mode': 'kanban,form', 'name': _('Projects'), 'res_model': 'project.project', } 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.sudo(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 message_get_email_values(self, notif_mail=None): self.ensure_one() res = super(MailGroup, self).message_get_email_values(notif_mail=notif_mail) base_url = self.env['ir.config_parameter'].sudo().get_param( 'web.base.url') headers = {} if res.get('headers'): try: headers = safe_eval(res['headers']) except Exception: pass headers.update({ 'List-Archive': '<%s/groups/%s>' % (base_url, slug(self)), 'List-Subscribe': '<%s/groups>' % (base_url), 'List-Unsubscribe': '<%s/groups?unsubscribe>' % (base_url, ), }) res['headers'] = repr(headers) return res
def message_get_email_values(self, notif_mail=None): self.ensure_one() res = super(Channel, self).message_get_email_values(notif_mail=notif_mail) headers = {} if res.get('headers'): try: headers.update(safe_eval(res['headers'])) except Exception: pass headers['Precedence'] = 'list' # avoid out-of-office replies from MS Exchange # http://blogs.technet.com/b/exchange/archive/2006/10/06/3395024.aspx headers['X-Auto-Response-Suppress'] = 'OOF' if self.alias_domain and self.alias_name: headers['List-Id'] = '<%s.%s>' % (self.alias_name, self.alias_domain) headers['List-Post'] = '<mailto:%s@%s>' % (self.alias_name, self.alias_domain) # Avoid users thinking it was a personal message # X-Forge-To: will replace To: after SMTP envelope is determined by ir.mail.server list_to = '"%s" <%s@%s>' % (self.name, self.alias_name, self.alias_domain) headers['X-Forge-To'] = list_to res['headers'] = repr(headers) return res
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.sudo(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"): if mode not in self._MODES: raise ValueError('Invalid mode: %r' % (mode, )) if self._uid == SUPERUSER_ID: return None query = """ SELECT r.id FROM ir_rule r JOIN ir_model m ON (r.model_id=m.id) WHERE m.model=%s AND r.active AND r.perm_{mode} AND (r.id IN (SELECT rule_group_id FROM rule_group_rel rg JOIN res_groups_users_rel gu ON (rg.group_id=gu.gid) WHERE gu.uid=%s) OR r.global) """.format(mode=mode) self._cr.execute(query, (model_name, self._uid)) rule_ids = [row[0] for row in self._cr.fetchall()] if not rule_ids: 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 self.browse(rule_ids).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 return expression.AND(global_domains + [expression.OR(group_domains)])
def _send(self, auto_commit=False, raise_exception=False, smtp_session=None): IrMailServer = self.env['ir.mail_server'] for mail_id in self.ids: try: mail = self.browse(mail_id) if mail.state != 'outgoing': if mail.state != 'exception' and mail.auto_delete: mail.sudo().unlink() continue # TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method if mail.model: model = self.env['ir.model']._get(mail.model)[0] else: model = None if model: mail = mail.with_context(model_name=model.name) # load attachment binary data with a separate read(), as prefetching all # `datas` (binary field) could bloat the browse cache, triggerring # soft/hard mem limits with temporary data. attachments = [(a['datas_fname'], base64.b64decode(a['datas']), a['mimetype']) for a in mail.attachment_ids.sudo().read(['datas_fname', 'datas', 'mimetype'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_list.append(mail.send_get_email_dict(partner=partner)) # headers headers = {} ICP = self.env['ir.config_parameter'].sudo() bounce_alias = ICP.get_param("mail.bounce.alias") catchall_domain = ICP.get_param("mail.catchall.domain") if bounce_alias and catchall_domain: if mail.model and mail.res_id: headers['Return-Path'] = '%s+%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain) else: headers['Return-Path'] = '%s+%d@%s' % (bounce_alias, mail.id, catchall_domain) if mail.headers: try: headers.update(safe_eval(mail.headers)) except Exception: pass # Writing on the mail object may fail (e.g. lock on user) which # would trigger a rollback *after* actually sending the email. # To avoid sending twice the same email, provoke the failure earlier mail.write({ 'state': 'exception', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.'), }) mail_sent = False # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.subject, body=email.get('body'), body_alternative=email.get('body_alternative'), email_cc=tools.email_split(mail.email_cc), reply_to=mail.reply_to, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id, smtp_session=smtp_session) except AssertionError as error: if str(error) == IrMailServer.NO_VALID_RECIPIENT: # No valid recipient found for this particular # mail item -> ignore error to avoid blocking # delivery to next recipients, if any. If this is # the only recipient, the mail will show as failed. _logger.info("Ignoring invalid recipients for mail.mail %s: %s", mail.message_id, email.get('email_to')) else: raise if res: mail.write({'state': 'sent', 'message_id': res, 'failure_reason': False}) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 if mail_sent: _logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) mail._postprocess_sent_message(mail_sent=mail_sent) except MemoryError: # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job # instead of marking the mail as failed _logger.exception( 'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option', mail.id, mail.message_id) raise except psycopg2.Error: # If an error with the database occurs, chances are that the cursor is unusable. # This will lead to an `psycopg2.InternalError` being raised when trying to write # `state`, shadowing the original exception and forbid a retry on concurrent # update. Let's bubble it. raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({'state': 'exception', 'failure_reason': failure_reason}) mail._postprocess_sent_message(mail_sent=False) if raise_exception: if isinstance(e, AssertionError): # get the args of the original error, wrap into a value and throw a MailDeliveryException # that is an except_orm, with name and value as arguments value = '. '.join(e.args) raise MailDeliveryException(_("Mail Delivery Failed"), value) raise if auto_commit is True: self._cr.commit() return True
def get_diagram_info(self, id, model, node, connector, src_node, des_node, label, **kw): visible_node_fields = kw.get('visible_node_fields', []) invisible_node_fields = kw.get('invisible_node_fields', []) node_fields_string = kw.get('node_fields_string', []) connector_fields = kw.get('connector_fields', []) connector_fields_string = kw.get('connector_fields_string', []) bgcolors = {} shapes = {} bgcolor = kw.get('bgcolor', '') shape = kw.get('shape', '') if bgcolor: for color_spec in bgcolor.split(';'): if color_spec: colour, color_state = color_spec.split(':') bgcolors[colour] = color_state if shape: for shape_spec in shape.split(';'): if shape_spec: shape_colour, shape_color_state = shape_spec.split(':') shapes[shape_colour] = shape_color_state ir_view = http.request.env['ir.ui.view'] graphs = ir_view.graph_get(int(id), model, node, connector, src_node, des_node, label, (140, 180)) nodes = graphs['nodes'] transitions = graphs['transitions'] isolate_nodes = {} for blnk_node in graphs['blank_nodes']: isolate_nodes[blnk_node['id']] = blnk_node y = [t['y'] for t in nodes.values() if t['x'] == 20 if t['y']] y_max = (y and max(y)) or 120 connectors = {} list_tr = [] for tr in transitions: list_tr.append(tr) connectors.setdefault( tr, { 'id': int(tr), 's_id': transitions[tr][0], 'd_id': transitions[tr][1] }) connector_model = http.request.env[connector] data_connectors = connector_model.search([('id', 'in', list_tr) ]).read(connector_fields) for tr in data_connectors: transition_id = str(tr['id']) _sourceid, label = graphs['label'][transition_id] t = connectors[transition_id] t.update(source=tr[src_node][1], destination=tr[des_node][1], options={}, signal=label) for i, fld in enumerate(connector_fields): t['options'][connector_fields_string[i]] = tr[fld] fields = http.request.env['ir.model.fields'] field = fields.search([('model', '=', model), ('relation', '=', node)]) node_act = http.request.env[node] search_acts = node_act.search([(field.relation_field, '=', id)]) data_acts = search_acts.read(invisible_node_fields + visible_node_fields) for act in data_acts: n = nodes.get(str(act['id'])) if not n: n = isolate_nodes.get(act['id'], {}) y_max += 140 n.update(x=20, y=y_max) nodes[act['id']] = n n.update(id=act['id'], color='white', options={}) for color, expr in bgcolors.items(): if safe_eval(expr, act): n['color'] = color for shape, expr in shapes.items(): if safe_eval(expr, act): n['shape'] = shape for i, fld in enumerate(visible_node_fields): n['options'][node_fields_string[i]] = act[fld] _id, name = http.request.env[model].browse([id]).name_get()[0] return dict(nodes=nodes, conn=connectors, display_name=name, parent_field=graphs['node_parent_field'])