1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2011 OpenERP S.A. <http://www.openerp.com>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
25 from socket import gethostname
28 from openerp import SUPERUSER_ID
29 from openerp import netsvc, tools
30 from openerp.osv import fields, osv
31 import openerp.report.interface
32 from openerp.report.report_sxw import report_sxw, report_rml
33 from openerp.tools.config import config
34 from openerp.tools.safe_eval import safe_eval as eval
35 from openerp.tools.translate import _
37 _logger = logging.getLogger(__name__)
39 class actions(osv.osv):
40 _name = 'ir.actions.actions'
44 'name': fields.char('Name', size=64, required=True),
45 'type': fields.char('Action Type', required=True, size=32),
46 'usage': fields.char('Action Usage', size=32),
47 'help': fields.text('Action description',
48 help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
52 'usage': lambda *a: False,
57 class report_xml(osv.osv):
59 def _report_content(self, cursor, user, ids, name, arg, context=None):
61 for report in self.browse(cursor, user, ids, context=context):
62 data = report[name + '_data']
63 if not data and report[name[:-8]]:
66 fp = tools.file_open(report[name[:-8]], mode='rb')
76 def _report_content_inv(self, cursor, user, id, name, value, arg, context=None):
77 self.write(cursor, user, id, {name+'_data': value}, context=context)
79 def _report_sxw(self, cursor, user, ids, name, arg, context=None):
81 for report in self.browse(cursor, user, ids, context=context):
83 res[report.id] = report.report_rml.replace('.rml', '.sxw')
85 res[report.id] = False
88 def register_all(self, cr):
89 """Report registration handler that may be overridden by subclasses to
90 add their own kinds of report services.
91 Loads all reports with no manual loaders (auto==True) and
92 registers the appropriate services to implement them.
95 cr.execute("SELECT * FROM ir_act_report_xml WHERE auto=%s ORDER BY id", (True,))
96 result = cr.dictfetchall()
97 reports = openerp.report.interface.report_int._reports
99 if reports.has_key('report.'+r['report_name']):
101 if r['report_rml'] or r['report_rml_content_data']:
102 report_sxw('report.'+r['report_name'], r['model'],
103 opj('addons',r['report_rml'] or '/'), header=r['header'])
105 report_rml('report.'+r['report_name'], r['model'],
106 opj('addons',r['report_xml']),
107 r['report_xsl'] and opj('addons',r['report_xsl']))
109 _name = 'ir.actions.report.xml'
110 _inherit = 'ir.actions.actions'
111 _table = 'ir_act_report_xml'
112 _sequence = 'ir_actions_id_seq'
115 'name': fields.char('Name', size=64, required=True, translate=True),
116 'model': fields.char('Object', size=64, required=True),
117 'type': fields.char('Action Type', size=32, required=True),
118 'report_name': fields.char('Service Name', size=64, required=True),
119 'usage': fields.char('Action Usage', size=32),
120 'report_type': fields.char('Report Type', size=32, required=True, help="Report Type, e.g. pdf, html, raw, sxw, odt, html2html, mako2html, ..."),
121 'groups_id': fields.many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', 'Groups'),
122 'multi': fields.boolean('On Multiple Doc.', help="If set to true, the action will not be displayed on the right toolbar of a form view."),
123 'attachment': fields.char('Save as Attachment Prefix', size=128, help='This is the filename of the attachment used to store the printing result. Keep empty to not save the printed reports. You can use a python expression with the object and time variables.'),
124 'attachment_use': fields.boolean('Reload from Attachment', help='If you check this, then the second time the user prints with same attachment name, it returns the previous report.'),
125 'auto': fields.boolean('Custom Python Parser'),
127 'header': fields.boolean('Add RML Header', help="Add or not the corporate RML header"),
129 'report_xsl': fields.char('XSL Path', size=256),
130 'report_xml': fields.char('XML Path', size=256, help=''),
132 # Pending deprecation... to be replaced by report_file as this object will become the default report object (not so specific to RML anymore)
133 'report_rml': fields.char('Main Report File Path', size=256, help="The path to the main report file (depending on Report Type) or NULL if the content is in another data field"),
134 # temporary related field as report_rml is pending deprecation - this field will replace report_rml after v6.0
135 'report_file': fields.related('report_rml', type="char", size=256, required=False, readonly=False, string='Report File', help="The path to the main report file (depending on Report Type) or NULL if the content is in another field", store=True),
137 'report_sxw': fields.function(_report_sxw, type='char', string='SXW Path'),
138 'report_sxw_content_data': fields.binary('SXW Content'),
139 'report_rml_content_data': fields.binary('RML Content'),
140 'report_sxw_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='SXW Content',),
141 'report_rml_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='RML Content'),
145 'type': 'ir.actions.report.xml',
149 'report_sxw_content': False,
150 'report_type': 'pdf',
156 class act_window(osv.osv):
157 _name = 'ir.actions.act_window'
158 _table = 'ir_act_window'
159 _inherit = 'ir.actions.actions'
160 _sequence = 'ir_actions_id_seq'
163 def _check_model(self, cr, uid, ids, context=None):
164 for action in self.browse(cr, uid, ids, context):
165 if not self.pool.get(action.res_model):
167 if action.src_model and not self.pool.get(action.src_model):
171 def _invalid_model_msg(self, cr, uid, ids, context=None):
172 return _('Invalid model name in the action definition.')
175 (_check_model, _invalid_model_msg, ['res_model','src_model'])
178 def _views_get_fnc(self, cr, uid, ids, name, arg, context=None):
179 """Returns an ordered list of the specific view modes that should be
180 enabled when displaying the result of this action, along with the
181 ID of the specific view to use for each mode, if any were required.
183 This function hides the logic of determining the precedence between
184 the view_modes string, the view_ids o2m, and the view_id m2o that can
185 be set on the action.
187 :rtype: dict in the form { action_id: list of pairs (tuples) }
188 :return: { action_id: [(view_id, view_mode), ...], ... }, where view_mode
189 is one of the possible values for ir.ui.view.type and view_id
190 is the ID of a specific view to use for this mode, or False for
194 for act in self.browse(cr, uid, ids):
195 res[act.id] = [(view.view_id.id, view.view_mode) for view in act.view_ids]
196 view_ids_modes = [view.view_mode for view in act.view_ids]
197 modes = act.view_mode.split(',')
198 missing_modes = [mode for mode in modes if mode not in view_ids_modes]
200 if act.view_id and act.view_id.type in missing_modes:
201 # reorder missing modes to put view_id first if present
202 missing_modes.remove(act.view_id.type)
203 res[act.id].append((act.view_id.id, act.view_id.type))
204 res[act.id].extend([(False, mode) for mode in missing_modes])
207 def _search_view(self, cr, uid, ids, name, arg, context=None):
209 for act in self.browse(cr, uid, ids, context=context):
210 field_get = self.pool.get(act.res_model).fields_view_get(cr, uid,
211 act.search_view_id and act.search_view_id.id or False,
212 'search', context=context)
213 res[act.id] = str(field_get)
217 'name': fields.char('Action Name', size=64, translate=True),
218 'type': fields.char('Action Type', size=32, required=True),
219 'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
220 'domain': fields.char('Domain Value',
221 help="Optional domain filtering of the destination data, as a Python expression"),
222 'context': fields.char('Context Value', required=True,
223 help="Context dictionary as Python expression, empty by default (Default: {})"),
224 'res_id': fields.integer('Record ID', help="Database ID of record to open in form view, when ``view_mode`` is set to 'form' only"),
225 'res_model': fields.char('Destination Model', size=64, required=True,
226 help="Model name of the object to open in the view window"),
227 'src_model': fields.char('Source Model', size=64,
228 help="Optional model name of the objects on which this action should be visible"),
229 'target': fields.selection([('current','Current Window'),('new','New Window'),('inline','Inline Edit'),('inlineview','Inline View')], 'Target Window'),
230 'view_mode': fields.char('View Mode', size=250, required=True,
231 help="Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)"),
232 'view_type': fields.selection((('tree','Tree'),('form','Form')), string='View Type', required=True,
233 help="View type: Tree type to use for the tree view, set to 'tree' for a hierarchical tree view, or 'form' for a regular list view"),
234 'usage': fields.char('Action Usage', size=32,
235 help="Used to filter menu and home actions from the user form."),
236 'view_ids': fields.one2many('ir.actions.act_window.view', 'act_window_id', 'Views'),
237 'views': fields.function(_views_get_fnc, type='binary', string='Views',
238 help="This function field computes the ordered list of views that should be enabled " \
239 "when displaying the result of an action, federating view mode, views and " \
240 "reference view. The result is returned as an ordered list of pairs (view_id,view_mode)."),
241 'limit': fields.integer('Limit', help='Default limit for the list view'),
242 'auto_refresh': fields.integer('Auto-Refresh',
243 help='Add an auto-refresh on the view'),
244 'groups_id': fields.many2many('res.groups', 'ir_act_window_group_rel',
245 'act_id', 'gid', 'Groups'),
246 'search_view_id': fields.many2one('ir.ui.view', 'Search View Ref.'),
247 'filter': fields.boolean('Filter'),
248 'auto_search':fields.boolean('Auto Search'),
249 'search_view' : fields.function(_search_view, type='text', string='Search View'),
250 'multi': fields.boolean('Action on Multiple Doc.', help="If set to true, the action will not be displayed on the right toolbar of a form view"),
254 'type': 'ir.actions.act_window',
256 'view_mode': 'tree,form',
265 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
266 u = isinstance(ids, (int, long))
269 results = super(act_window, self).read(cr, uid, ids, fields=fields, context=context, load=load)
271 if not fields or 'help' in fields:
272 context = dict(context or {})
274 'active_model' : context.get('active_model', None),
275 'active_id' : context.get('active_id', None),
276 'active_ids' : context.get('active_ids', None),
280 if res.get('res_model', False):
282 with tools.mute_logger("openerp.tools.safe_eval"):
283 eval_context = eval(res['context'] or "{}", dic) or {}
286 custom_context = dict(context, **eval_context)
287 res['help'] = self.pool.get(res.get('res_model')).get_empty_list_help(cr, uid, res.get('help', ""), context=custom_context)
293 def for_xml_id(self, cr, uid, module, xml_id, context=None):
294 """ Returns the act_window object created for the provided xml_id
296 :param module: the module the act_window originates in
297 :param xml_id: the namespace-less id of the action (the @id
298 attribute from the XML file)
299 :return: A read() view of the ir.actions.act_window
301 dataobj = self.pool.get('ir.model.data')
302 data_id = dataobj._get_id (cr, SUPERUSER_ID, module, xml_id)
303 res_id = dataobj.browse(cr, uid, data_id, context).res_id
304 return self.read(cr, uid, res_id, [], context)
312 ('calendar', 'Calendar'),
314 ('kanban', 'Kanban')]
315 class act_window_view(osv.osv):
316 _name = 'ir.actions.act_window.view'
317 _table = 'ir_act_window_view'
318 _rec_name = 'view_id'
321 'sequence': fields.integer('Sequence'),
322 'view_id': fields.many2one('ir.ui.view', 'View'),
323 'view_mode': fields.selection(VIEW_TYPES, string='View Type', required=True),
324 'act_window_id': fields.many2one('ir.actions.act_window', 'Action', ondelete='cascade'),
325 'multi': fields.boolean('On Multiple Doc.',
326 help="If set to true, the action will not be displayed on the right toolbar of a form view."),
331 def _auto_init(self, cr, context=None):
332 super(act_window_view, self)._auto_init(cr, context)
333 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'act_window_view_unique_mode_per_action\'')
334 if not cr.fetchone():
335 cr.execute('CREATE UNIQUE INDEX act_window_view_unique_mode_per_action ON ir_act_window_view (act_window_id, view_mode)')
338 class act_wizard(osv.osv):
339 _name = 'ir.actions.wizard'
340 _inherit = 'ir.actions.actions'
341 _table = 'ir_act_wizard'
342 _sequence = 'ir_actions_id_seq'
345 'name': fields.char('Wizard Info', size=64, required=True, translate=True),
346 'type': fields.char('Action Type', size=32, required=True),
347 'wiz_name': fields.char('Wizard Name', size=64, required=True),
348 'multi': fields.boolean('Action on Multiple Doc.', help="If set to true, the wizard will not be displayed on the right toolbar of a form view."),
349 'groups_id': fields.many2many('res.groups', 'res_groups_wizard_rel', 'uid', 'gid', 'Groups'),
350 'model': fields.char('Object', size=64),
353 'type': 'ir.actions.wizard',
358 class act_url(osv.osv):
359 _name = 'ir.actions.act_url'
360 _table = 'ir_act_url'
361 _inherit = 'ir.actions.actions'
362 _sequence = 'ir_actions_id_seq'
365 'name': fields.char('Action Name', size=64, translate=True),
366 'type': fields.char('Action Type', size=32, required=True),
367 'url': fields.text('Action URL',required=True),
368 'target': fields.selection((
369 ('new', 'New Window'),
370 ('self', 'This Window')),
371 'Action Target', required=True
375 'type': 'ir.actions.act_url',
380 def model_get(self, cr, uid, context=None):
381 wkf_pool = self.pool.get('workflow')
382 ids = wkf_pool.search(cr, uid, [])
383 osvs = wkf_pool.read(cr, uid, ids, ['osv'])
386 mpool = self.pool.get('ir.model')
388 model = osv.get('osv')
389 id = mpool.search(cr, uid, [('model','=',model)])
390 name = mpool.read(cr, uid, id)[0]['name']
391 res.append((model, name))
395 class ir_model_fields(osv.osv):
396 _inherit = 'ir.model.fields'
397 _rec_name = 'field_description'
399 'complete_name': fields.char('Complete Name', size=64, select=1),
403 class server_object_lines(osv.osv):
404 _name = 'ir.server.object.lines'
405 _sequence = 'ir_actions_id_seq'
407 'server_id': fields.many2one('ir.actions.server', 'Object Mapping'),
408 'col1': fields.many2one('ir.model.fields', 'Destination', required=True),
409 'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
410 "When Formula type is selected, this field may be a Python expression "
411 " that can use the same values as for the condition field on the server action.\n"
412 "If Value type is selected, the value will be used directly without evaluation."),
413 'type': fields.selection([
415 ('equation','Formula')
416 ], 'Type', required=True, size=32, change_default=True),
421 server_object_lines()
424 # Actions that are run on the server side
426 class actions_server(osv.osv):
428 def _select_signals(self, cr, uid, context=None):
429 cr.execute("""SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t
430 WHERE w.id = a.wkf_id AND
431 (t.act_from = a.id OR t.act_to = a.id) AND
432 t.signal IS NOT NULL""")
433 result = cr.fetchall() or []
436 if rs[0] is not None and rs[1] is not None:
437 line = rs[1], "%s - (%s)" % (rs[1], rs[0])
441 def _select_objects(self, cr, uid, context=None):
442 model_pool = self.pool.get('ir.model')
443 ids = model_pool.search(cr, uid, [('name','not ilike','.')])
444 res = model_pool.read(cr, uid, ids, ['model', 'name'])
445 return [(r['model'], r['name']) for r in res] + [('','')]
447 def change_object(self, cr, uid, ids, copy_object, state, context=None):
448 if state == 'object_copy' and copy_object:
451 model_pool = self.pool.get('ir.model')
452 model = copy_object.split(',')[0]
453 mid = model_pool.search(cr, uid, [('model','=',model)])
455 'value': {'srcmodel_id': mid[0]},
461 _name = 'ir.actions.server'
462 _table = 'ir_act_server'
463 _inherit = 'ir.actions.actions'
464 _sequence = 'ir_actions_id_seq'
465 _order = 'sequence,name'
467 'name': fields.char('Action Name', required=True, size=64, translate=True),
468 'condition' : fields.char('Condition', size=256, required=True,
469 help="Condition that is tested before the action is executed, "
470 "and prevent execution if it is not verified.\n"
471 "Example: object.list_price > 5000\n"
472 "It is a Python expression that can use the following values:\n"
473 " - self: ORM model of the record on which the action is triggered\n"
474 " - object or obj: browse_record of the record on which the action is triggered\n"
475 " - pool: ORM model pool (i.e. self.pool)\n"
476 " - time: Python time module\n"
477 " - cr: database cursor\n"
478 " - uid: current user id\n"
479 " - context: current context"),
480 'state': fields.selection([
481 ('client_action','Client Action'),
483 ('loop','Iteration'),
484 ('code','Python Code'),
485 ('trigger','Trigger'),
488 ('object_create','Create Object'),
489 ('object_copy','Copy Object'),
490 ('object_write','Write Object'),
491 ('other','Multi Actions'),
492 ], 'Action Type', required=True, size=32, help="Type of the Action that is to be executed"),
493 'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
494 "It is a Python block that can use the same values as for the condition field"),
495 'sequence': fields.integer('Sequence', help="Important when you deal with multiple actions, the execution order will be decided based on this, low number is higher priority."),
496 'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
497 'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
498 'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
499 'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),
500 'trigger_obj_id': fields.many2one('ir.model.fields','Relation Field', help="The field on the current object that links to the target object record (must be a many2one, or an integer field with the record ID)"),
501 'email': fields.char('Email Address', size=512, help="Expression that returns the email address to send to. Can be based on the same values as for the condition field.\n"
502 "Example: object.invoice_address_id.email, or 'me@example.com'"),
503 'subject': fields.char('Subject', size=1024, translate=True, help="Email subject, may contain expressions enclosed in double brackets based on the same values as those "
504 "available in the condition field, e.g. `Hello [[ object.partner_id.name ]]`"),
505 'message': fields.text('Message', translate=True, help="Email contents, may contain expressions enclosed in double brackets based on the same values as those "
506 "available in the condition field, e.g. `Dear [[ object.partner_id.name ]]`"),
507 'mobile': fields.char('Mobile No', size=512, help="Provides fields that be used to fetch the mobile number, e.g. you select the invoice, then `object.invoice_address_id.mobile` is the field which gives the correct mobile number"),
508 'sms': fields.char('SMS', size=160, translate=True),
509 'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', 'Other Actions'),
510 'usage': fields.char('Action Usage', size=32),
511 'type': fields.char('Action Type', size=32, required=True),
512 'srcmodel_id': fields.many2one('ir.model', 'Model', help="Object in which you want to create / write the object. If it is empty then refer to the Object field."),
513 'fields_lines': fields.one2many('ir.server.object.lines', 'server_id', 'Field Mappings.'),
514 'record_id':fields.many2one('ir.model.fields', 'Create Id', help="Provide the field name where the record id is stored after the create operations. If it is empty, you can not track the new record."),
515 'write_id':fields.char('Write Id', size=256, help="Provide the field name that the record id refers to for the write operation. If it is empty it will refer to the active id of the object."),
516 'loop_action':fields.many2one('ir.actions.server', 'Loop Action', help="Select the action that will be executed. Loop action will not be avaliable inside loop."),
517 'expression':fields.char('Loop Expression', size=512, help="Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`."),
518 'copy_object': fields.reference('Copy Of', selection=_select_objects, size=256),
523 'type': 'ir.actions.server',
525 'code': """# You can use the following variables:
526 # - self: ORM model of the record on which the action is triggered
527 # - object: browse_record of the record on which the action is triggered if there is one, otherwise None
528 # - pool: ORM model pool (i.e. self.pool)
529 # - time: Python time module
530 # - cr: database cursor
531 # - uid: current user id
532 # - context: current context
533 # If you plan to return an action, assign: action = {...}
537 def get_email(self, cr, uid, action, context):
538 obj_pool = self.pool.get(action.model_id.model)
539 id = context.get('active_id')
540 obj = obj_pool.browse(cr, uid, id)
544 if '/' in action.email.complete_name:
545 fields = action.email.complete_name.split('/')
546 elif '.' in action.email.complete_name:
547 fields = action.email.complete_name.split('.')
551 obj = getattr(obj, field)
553 _logger.exception('Failed to parse: %s', field)
557 def get_mobile(self, cr, uid, action, context):
558 obj_pool = self.pool.get(action.model_id.model)
559 id = context.get('active_id')
560 obj = obj_pool.browse(cr, uid, id)
564 if '/' in action.mobile.complete_name:
565 fields = action.mobile.complete_name.split('/')
566 elif '.' in action.mobile.complete_name:
567 fields = action.mobile.complete_name.split('.')
571 obj = getattr(obj, field)
573 _logger.exception('Failed to parse: %s', field)
577 def merge_message(self, cr, uid, keystr, action, context=None):
582 obj_pool = self.pool.get(action.model_id.model)
583 id = context.get('active_id')
584 obj = obj_pool.browse(cr, uid, id)
585 exp = str(match.group()[2:-2]).strip()
589 'context': dict(context), # copy context to prevent side-effects of eval
592 if result in (None, False):
593 return str("--------")
594 return tools.ustr(result)
596 com = re.compile('(\[\[.+?\]\])')
597 message = com.sub(merge, keystr)
601 # Context should contains:
603 # id : current id of the object
605 # False : Finished correctly
606 # ACTION_ID : Action to launch
608 # FIXME: refactor all the eval() calls in run()!
609 def run(self, cr, uid, ids, context=None):
612 user = self.pool.get('res.users').browse(cr, uid, uid)
613 for action in self.browse(cr, uid, ids, context):
615 obj_pool = self.pool.get(action.model_id.model)
616 if context.get('active_model') == action.model_id.model and context.get('active_id'):
617 obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
625 'context': dict(context), # copy context to prevent side-effects of eval
629 expr = eval(str(action.condition), cxt)
633 if action.state=='client_action':
634 if not action.action_id:
635 raise osv.except_osv(_('Error'), _("Please specify an action to launch !"))
636 return self.pool.get(action.action_id.type)\
637 .read(cr, uid, action.action_id.id, context=context)
639 if action.state=='code':
640 eval(action.code, cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
644 if action.state == 'email':
645 email_from = config['email_from']
647 _logger.debug('--email-from command line option is not specified, using a fallback value instead.')
649 email_from = user.email
651 email_from = "%s@%s" % (user.login, gethostname())
654 address = eval(str(action.email), cxt)
656 address = str(action.email)
659 _logger.info('No partner email address specified, not sending any email.')
662 # handle single and multiple recipient addresses
663 addresses = address if isinstance(address, (tuple, list)) else [address]
664 subject = self.merge_message(cr, uid, action.subject, action, context)
665 body = self.merge_message(cr, uid, action.message, action, context)
667 ir_mail_server = self.pool.get('ir.mail_server')
668 msg = ir_mail_server.build_email(email_from, addresses, subject, body)
669 res_email = ir_mail_server.send_email(cr, uid, msg)
671 _logger.info('Email successfully sent to: %s', addresses)
673 _logger.warning('Failed to send email to: %s', addresses)
675 if action.state == 'trigger':
676 wf_service = netsvc.LocalService("workflow")
677 model = action.wkf_model_id.model
678 m2o_field_name = action.trigger_obj_id.name
679 target_id = obj_pool.read(cr, uid, context.get('active_id'), [m2o_field_name])[m2o_field_name]
680 target_id = target_id[0] if isinstance(target_id,tuple) else target_id
681 wf_service.trg_validate(uid, model, int(target_id), action.trigger_name, cr)
683 if action.state == 'sms':
684 #TODO: set the user and password from the system
685 # for the sms gateway user / password
686 # USE smsclient module from extra-addons
687 _logger.warning('SMS Facility has not been implemented yet. Use smsclient module!')
689 if action.state == 'other':
691 for act in action.child_ids:
692 context['active_id'] = context['active_ids'][0]
693 result = self.run(cr, uid, [act.id], context)
698 if action.state == 'loop':
699 expr = eval(str(action.expression), cxt)
700 context['object'] = obj
702 context['active_id'] = i.id
703 self.run(cr, uid, [action.loop_action.id], context)
705 if action.state == 'object_write':
707 for exp in action.fields_lines:
709 if exp.type == 'equation':
710 expr = eval(euq, cxt)
713 res[exp.col1.name] = expr
715 if not action.write_id:
716 if not action.srcmodel_id:
717 obj_pool = self.pool.get(action.model_id.model)
718 obj_pool.write(cr, uid, [context.get('active_id')], res)
720 write_id = context.get('active_id')
721 obj_pool = self.pool.get(action.srcmodel_id.model)
722 obj_pool.write(cr, uid, [write_id], res)
724 elif action.write_id:
725 obj_pool = self.pool.get(action.srcmodel_id.model)
726 rec = self.pool.get(action.model_id.model).browse(cr, uid, context.get('active_id'))
727 id = eval(action.write_id, {'object': rec})
731 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
733 if type(id) != type(1):
734 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
736 obj_pool.write(cr, uid, [write_id], res)
738 if action.state == 'object_create':
740 for exp in action.fields_lines:
742 if exp.type == 'equation':
743 expr = eval(euq, cxt)
746 res[exp.col1.name] = expr
748 obj_pool = self.pool.get(action.srcmodel_id.model)
749 res_id = obj_pool.create(cr, uid, res)
751 self.pool.get(action.model_id.model).write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
753 if action.state == 'object_copy':
755 for exp in action.fields_lines:
757 if exp.type == 'equation':
758 expr = eval(euq, cxt)
761 res[exp.col1.name] = expr
763 model = action.copy_object.split(',')[0]
764 cid = action.copy_object.split(',')[1]
765 obj_pool = self.pool.get(model)
766 obj_pool.copy(cr, uid, int(cid), res)
772 class act_window_close(osv.osv):
773 _name = 'ir.actions.act_window_close'
774 _inherit = 'ir.actions.actions'
775 _table = 'ir_actions'
777 'type': 'ir.actions.act_window_close',
781 # This model use to register action services.
782 TODO_STATES = [('open', 'To Do'),
784 TODO_TYPES = [('manual', 'Launch Manually'),('once', 'Launch Manually Once'),
785 ('automatic', 'Launch Automatically')]
786 class ir_actions_todo(osv.osv):
788 Configuration Wizards
790 _name = 'ir.actions.todo'
791 _description = "Configuration Wizards"
793 'action_id': fields.many2one(
794 'ir.actions.actions', 'Action', select=True, required=True),
795 'sequence': fields.integer('Sequence'),
796 'state': fields.selection(TODO_STATES, string='Status', required=True),
797 'name': fields.char('Name', size=64),
798 'type': fields.selection(TODO_TYPES, 'Type', required=True,
799 help="""Manual: Launched manually.
800 Automatic: Runs whenever the system is reconfigured.
801 Launch Manually Once: after having been launched manually, it sets automatically to Done."""),
802 'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
803 'note': fields.text('Text', translate=True),
812 def action_launch(self, cr, uid, ids, context=None):
813 """ Launch Action of Wizard"""
814 wizard_id = ids and ids[0] or False
815 wizard = self.browse(cr, uid, wizard_id, context=context)
816 if wizard.type in ('automatic', 'once'):
817 wizard.write({'state': 'done'})
820 act_type = self.pool.get('ir.actions.actions').read(cr, uid, wizard.action_id.id, ['type'], context=context)
822 res = self.pool.get(act_type['type']).read(cr, uid, wizard.action_id.id, [], context=context)
823 if act_type<>'ir.actions.act_window':
825 res.setdefault('context','{}')
826 res['nodestroy'] = True
828 # Open a specific record when res_id is provided in the context
829 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
830 ctx = eval(res['context'], {'user': user})
831 if ctx.get('res_id'):
832 res.update({'res_id': ctx.pop('res_id')})
834 # disable log for automatic wizards
835 if wizard.type == 'automatic':
836 ctx.update({'disable_log': True})
837 res.update({'context': ctx})
841 def action_open(self, cr, uid, ids, context=None):
842 """ Sets configuration wizard in TODO state"""
843 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
845 def progress(self, cr, uid, context=None):
846 """ Returns a dict with 3 keys {todo, done, total}.
848 These keys all map to integers and provide the number of todos
849 marked as open, the total number of todos and the number of
850 todos not open (which is basically a shortcut to total-todo)
854 user_groups = set(map(
856 self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
857 def groups_match(todo):
858 """ Checks if the todo's groups match those of the current user
860 return not todo.groups_id \
861 or bool(user_groups.intersection((
862 group.id for group in todo.groups_id)))
867 self.search(cr, uid, [('state', '!=', 'open')], context=context),
873 self.search(cr, uid, [], context=context),
879 'todo': len(total) - len(done)
884 class act_client(osv.osv):
885 _name = 'ir.actions.client'
886 _inherit = 'ir.actions.actions'
887 _table = 'ir_act_client'
888 _sequence = 'ir_actions_id_seq'
891 def _get_params(self, cr, uid, ids, field_name, arg, context):
893 for record in self.browse(cr, uid, ids, context=context):
894 result[record.id] = record.params_store and eval(record.params_store, {'uid': uid}) or False
897 def _set_params(self, cr, uid, id, field_name, field_value, arg, context):
898 if isinstance(field_value, dict):
899 self.write(cr, uid, id, {'params_store': repr(field_value)}, context=context)
901 self.write(cr, uid, id, {'params_store': field_value}, context=context)
904 'name': fields.char('Action Name', required=True, size=64, translate=True),
905 'tag': fields.char('Client action tag', size=64, required=True,
906 help="An arbitrary string, interpreted by the client"
907 " according to its own needs and wishes. There "
908 "is no central tag repository across clients."),
909 'res_model': fields.char('Destination Model', size=64,
910 help="Optional model, mostly used for needactions."),
911 'context': fields.char('Context Value', size=250, required=True,
912 help="Context dictionary as Python expression, empty by default (Default: {})"),
913 'params': fields.function(_get_params, fnct_inv=_set_params,
915 string="Supplementary arguments",
916 help="Arguments sent to the client along with"
918 'params_store': fields.binary("Params storage", readonly=True)
921 'type': 'ir.actions.client',
927 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: