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 ##############################################################################
26 from socket import gethostname
30 from openerp import SUPERUSER_ID
31 from openerp import tools
32 from openerp.osv import fields, osv
33 import openerp.report.interface
34 from openerp.report.report_sxw import report_sxw, report_rml
35 from openerp.tools.config import config
36 from openerp.tools.safe_eval import safe_eval as eval
37 from openerp.tools.translate import _
38 import openerp.workflow
40 _logger = logging.getLogger(__name__)
42 class actions(osv.osv):
43 _name = 'ir.actions.actions'
47 'name': fields.char('Name', size=64, required=True),
48 'type': fields.char('Action Type', required=True, size=32),
49 'usage': fields.char('Action Usage', size=32),
50 'help': fields.text('Action description',
51 help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
55 'usage': lambda *a: False,
60 class report_xml(osv.osv):
62 def _report_content(self, cursor, user, ids, name, arg, context=None):
64 for report in self.browse(cursor, user, ids, context=context):
65 data = report[name + '_data']
66 if not data and report[name[:-8]]:
69 fp = tools.file_open(report[name[:-8]], mode='rb')
79 def _report_content_inv(self, cursor, user, id, name, value, arg, context=None):
80 self.write(cursor, user, id, {name+'_data': value}, context=context)
82 def _report_sxw(self, cursor, user, ids, name, arg, context=None):
84 for report in self.browse(cursor, user, ids, context=context):
86 res[report.id] = report.report_rml.replace('.rml', '.sxw')
88 res[report.id] = False
91 def _lookup_report(self, cr, name):
93 Look up a report definition.
97 # First lookup in the deprecated place, because if the report definition
98 # has not been updated, it is more likely the correct definition is there.
99 # Only reports with custom parser sepcified in Python are still there.
100 if 'report.' + name in openerp.report.interface.report_int._reports:
101 new_report = openerp.report.interface.report_int._reports['report.' + name]
103 cr.execute("SELECT * FROM ir_act_report_xml WHERE report_name=%s", (name,))
104 r = cr.dictfetchone()
106 if r['report_rml'] or r['report_rml_content_data']:
108 kwargs = { 'parser': operator.attrgetter(r['parser'])(openerp.addons) }
111 new_report = report_sxw('report.'+r['report_name'], r['model'],
112 opj('addons',r['report_rml'] or '/'), header=r['header'], register=False, **kwargs)
113 elif r['report_xsl']:
114 new_report = report_rml('report.'+r['report_name'], r['model'],
115 opj('addons',r['report_xml']),
116 r['report_xsl'] and opj('addons',r['report_xsl']), register=False)
118 raise Exception, "Unhandled report type: %s" % r
120 raise Exception, "Required report does not exist: %s" % r
124 def render_report(self, cr, uid, res_ids, name, data, context=None):
126 Look up a report definition and render the report for the provided IDs.
128 new_report = self._lookup_report(cr, name)
129 return new_report.create(cr, uid, res_ids, data, context)
131 _name = 'ir.actions.report.xml'
132 _inherit = 'ir.actions.actions'
133 _table = 'ir_act_report_xml'
134 _sequence = 'ir_actions_id_seq'
137 'name': fields.char('Name', size=64, required=True, translate=True),
138 'model': fields.char('Object', size=64, required=True),
139 'type': fields.char('Action Type', size=32, required=True),
140 'report_name': fields.char('Service Name', size=64, required=True),
141 'usage': fields.char('Action Usage', size=32),
142 'report_type': fields.char('Report Type', size=32, required=True, help="Report Type, e.g. pdf, html, raw, sxw, odt, html2html, mako2html, ..."),
143 'groups_id': fields.many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', 'Groups'),
144 '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."),
145 '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.'),
146 '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.'),
147 'auto': fields.boolean('Custom Python Parser'),
149 'header': fields.boolean('Add RML Header', help="Add or not the corporate RML header"),
151 'report_xsl': fields.char('XSL Path', size=256),
152 'report_xml': fields.char('XML Path', size=256, help=''),
154 # Pending deprecation... to be replaced by report_file as this object will become the default report object (not so specific to RML anymore)
155 '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"),
156 # temporary related field as report_rml is pending deprecation - this field will replace report_rml after v6.0
157 '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),
159 'report_sxw': fields.function(_report_sxw, type='char', string='SXW Path'),
160 'report_sxw_content_data': fields.binary('SXW Content'),
161 'report_rml_content_data': fields.binary('RML Content'),
162 'report_sxw_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='SXW Content',),
163 'report_rml_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='RML Content'),
165 'parser': fields.char('Parser Class'),
168 'type': 'ir.actions.report.xml',
172 'report_sxw_content': False,
173 'report_type': 'pdf',
179 class act_window(osv.osv):
180 _name = 'ir.actions.act_window'
181 _table = 'ir_act_window'
182 _inherit = 'ir.actions.actions'
183 _sequence = 'ir_actions_id_seq'
186 def _check_model(self, cr, uid, ids, context=None):
187 for action in self.browse(cr, uid, ids, context):
188 if not self.pool.get(action.res_model):
190 if action.src_model and not self.pool.get(action.src_model):
194 def _invalid_model_msg(self, cr, uid, ids, context=None):
195 return _('Invalid model name in the action definition.')
198 (_check_model, _invalid_model_msg, ['res_model','src_model'])
201 def _views_get_fnc(self, cr, uid, ids, name, arg, context=None):
202 """Returns an ordered list of the specific view modes that should be
203 enabled when displaying the result of this action, along with the
204 ID of the specific view to use for each mode, if any were required.
206 This function hides the logic of determining the precedence between
207 the view_modes string, the view_ids o2m, and the view_id m2o that can
208 be set on the action.
210 :rtype: dict in the form { action_id: list of pairs (tuples) }
211 :return: { action_id: [(view_id, view_mode), ...], ... }, where view_mode
212 is one of the possible values for ir.ui.view.type and view_id
213 is the ID of a specific view to use for this mode, or False for
217 for act in self.browse(cr, uid, ids):
218 res[act.id] = [(view.view_id.id, view.view_mode) for view in act.view_ids]
219 view_ids_modes = [view.view_mode for view in act.view_ids]
220 modes = act.view_mode.split(',')
221 missing_modes = [mode for mode in modes if mode not in view_ids_modes]
223 if act.view_id and act.view_id.type in missing_modes:
224 # reorder missing modes to put view_id first if present
225 missing_modes.remove(act.view_id.type)
226 res[act.id].append((act.view_id.id, act.view_id.type))
227 res[act.id].extend([(False, mode) for mode in missing_modes])
230 def _search_view(self, cr, uid, ids, name, arg, context=None):
232 for act in self.browse(cr, uid, ids, context=context):
233 field_get = self.pool[act.res_model].fields_view_get(cr, uid,
234 act.search_view_id and act.search_view_id.id or False,
235 'search', context=context)
236 res[act.id] = str(field_get)
240 'name': fields.char('Action Name', size=64, translate=True),
241 'type': fields.char('Action Type', size=32, required=True),
242 'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
243 'domain': fields.char('Domain Value',
244 help="Optional domain filtering of the destination data, as a Python expression"),
245 'context': fields.char('Context Value', required=True,
246 help="Context dictionary as Python expression, empty by default (Default: {})"),
247 'res_id': fields.integer('Record ID', help="Database ID of record to open in form view, when ``view_mode`` is set to 'form' only"),
248 'res_model': fields.char('Destination Model', size=64, required=True,
249 help="Model name of the object to open in the view window"),
250 'src_model': fields.char('Source Model', size=64,
251 help="Optional model name of the objects on which this action should be visible"),
252 'target': fields.selection([('current','Current Window'),('new','New Window'),('inline','Inline Edit'),('inlineview','Inline View')], 'Target Window'),
253 'view_mode': fields.char('View Mode', size=250, required=True,
254 help="Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)"),
255 'view_type': fields.selection((('tree','Tree'),('form','Form')), string='View Type', required=True,
256 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"),
257 'usage': fields.char('Action Usage', size=32,
258 help="Used to filter menu and home actions from the user form."),
259 'view_ids': fields.one2many('ir.actions.act_window.view', 'act_window_id', 'Views'),
260 'views': fields.function(_views_get_fnc, type='binary', string='Views',
261 help="This function field computes the ordered list of views that should be enabled " \
262 "when displaying the result of an action, federating view mode, views and " \
263 "reference view. The result is returned as an ordered list of pairs (view_id,view_mode)."),
264 'limit': fields.integer('Limit', help='Default limit for the list view'),
265 'auto_refresh': fields.integer('Auto-Refresh',
266 help='Add an auto-refresh on the view'),
267 'groups_id': fields.many2many('res.groups', 'ir_act_window_group_rel',
268 'act_id', 'gid', 'Groups'),
269 'search_view_id': fields.many2one('ir.ui.view', 'Search View Ref.'),
270 'filter': fields.boolean('Filter'),
271 'auto_search':fields.boolean('Auto Search'),
272 'search_view' : fields.function(_search_view, type='text', string='Search View'),
273 '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"),
277 'type': 'ir.actions.act_window',
279 'view_mode': 'tree,form',
288 def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
289 """ call the method get_empty_list_help of the model and set the window action help message
291 ids_int = isinstance(ids, (int, long))
294 results = super(act_window, self).read(cr, uid, ids, fields=fields, context=context, load=load)
296 if not fields or 'help' in fields:
297 context = dict(context or {})
299 'active_model': context.get('active_model'),
300 'active_id': context.get('active_id'),
301 'active_ids': context.get('active_ids'),
305 model = res.get('res_model')
306 if model and self.pool.get(model):
308 with tools.mute_logger("openerp.tools.safe_eval"):
309 eval_context = eval(res['context'] or "{}", eval_dict) or {}
312 custom_context = dict(context, **eval_context)
313 res['help'] = self.pool.get(model).get_empty_list_help(cr, uid, res.get('help', ""), context=custom_context)
318 def for_xml_id(self, cr, uid, module, xml_id, context=None):
319 """ Returns the act_window object created for the provided xml_id
321 :param module: the module the act_window originates in
322 :param xml_id: the namespace-less id of the action (the @id
323 attribute from the XML file)
324 :return: A read() view of the ir.actions.act_window
326 dataobj = self.pool.get('ir.model.data')
327 data_id = dataobj._get_id (cr, SUPERUSER_ID, module, xml_id)
328 res_id = dataobj.browse(cr, uid, data_id, context).res_id
329 return self.read(cr, uid, res_id, [], context)
337 ('calendar', 'Calendar'),
339 ('kanban', 'Kanban')]
340 class act_window_view(osv.osv):
341 _name = 'ir.actions.act_window.view'
342 _table = 'ir_act_window_view'
343 _rec_name = 'view_id'
346 'sequence': fields.integer('Sequence'),
347 'view_id': fields.many2one('ir.ui.view', 'View'),
348 'view_mode': fields.selection(VIEW_TYPES, string='View Type', required=True),
349 'act_window_id': fields.many2one('ir.actions.act_window', 'Action', ondelete='cascade'),
350 'multi': fields.boolean('On Multiple Doc.',
351 help="If set to true, the action will not be displayed on the right toolbar of a form view."),
356 def _auto_init(self, cr, context=None):
357 super(act_window_view, self)._auto_init(cr, context)
358 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'act_window_view_unique_mode_per_action\'')
359 if not cr.fetchone():
360 cr.execute('CREATE UNIQUE INDEX act_window_view_unique_mode_per_action ON ir_act_window_view (act_window_id, view_mode)')
363 class act_wizard(osv.osv):
364 _name = 'ir.actions.wizard'
365 _inherit = 'ir.actions.actions'
366 _table = 'ir_act_wizard'
367 _sequence = 'ir_actions_id_seq'
370 'name': fields.char('Wizard Info', size=64, required=True, translate=True),
371 'type': fields.char('Action Type', size=32, required=True),
372 'wiz_name': fields.char('Wizard Name', size=64, required=True),
373 '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."),
374 'groups_id': fields.many2many('res.groups', 'res_groups_wizard_rel', 'uid', 'gid', 'Groups'),
375 'model': fields.char('Object', size=64),
378 'type': 'ir.actions.wizard',
383 class act_url(osv.osv):
384 _name = 'ir.actions.act_url'
385 _table = 'ir_act_url'
386 _inherit = 'ir.actions.actions'
387 _sequence = 'ir_actions_id_seq'
390 'name': fields.char('Action Name', size=64, translate=True),
391 'type': fields.char('Action Type', size=32, required=True),
392 'url': fields.text('Action URL',required=True),
393 'target': fields.selection((
394 ('new', 'New Window'),
395 ('self', 'This Window')),
396 'Action Target', required=True
400 'type': 'ir.actions.act_url',
405 def model_get(self, cr, uid, context=None):
406 wkf_pool = self.pool.get('workflow')
407 ids = wkf_pool.search(cr, uid, [])
408 osvs = wkf_pool.read(cr, uid, ids, ['osv'])
411 mpool = self.pool.get('ir.model')
413 model = osv.get('osv')
414 id = mpool.search(cr, uid, [('model','=',model)])
415 name = mpool.read(cr, uid, id)[0]['name']
416 res.append((model, name))
420 class ir_model_fields(osv.osv):
421 _inherit = 'ir.model.fields'
422 _rec_name = 'field_description'
424 'complete_name': fields.char('Complete Name', size=64, select=1),
428 class server_object_lines(osv.osv):
429 _name = 'ir.server.object.lines'
430 _sequence = 'ir_actions_id_seq'
432 'server_id': fields.many2one('ir.actions.server', 'Object Mapping'),
433 'col1': fields.many2one('ir.model.fields', 'Destination', required=True),
434 'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
435 "When Formula type is selected, this field may be a Python expression "
436 " that can use the same values as for the condition field on the server action.\n"
437 "If Value type is selected, the value will be used directly without evaluation."),
438 'type': fields.selection([
440 ('equation','Formula')
441 ], 'Type', required=True, size=32, change_default=True),
446 server_object_lines()
449 # Actions that are run on the server side
451 class actions_server(osv.osv):
453 def _select_signals(self, cr, uid, context=None):
454 cr.execute("""SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t
455 WHERE w.id = a.wkf_id AND
456 (t.act_from = a.id OR t.act_to = a.id) AND
457 t.signal IS NOT NULL""")
458 result = cr.fetchall() or []
461 if rs[0] is not None and rs[1] is not None:
462 line = rs[1], "%s - (%s)" % (rs[1], rs[0])
466 def _select_objects(self, cr, uid, context=None):
467 model_pool = self.pool.get('ir.model')
468 ids = model_pool.search(cr, uid, [('name','not ilike','.')])
469 res = model_pool.read(cr, uid, ids, ['model', 'name'])
470 return [(r['model'], r['name']) for r in res] + [('','')]
472 def change_object(self, cr, uid, ids, copy_object, state, context=None):
473 if state == 'object_copy' and copy_object:
476 model_pool = self.pool.get('ir.model')
477 model = copy_object.split(',')[0]
478 mid = model_pool.search(cr, uid, [('model','=',model)])
480 'value': {'srcmodel_id': mid[0]},
486 _name = 'ir.actions.server'
487 _table = 'ir_act_server'
488 _inherit = 'ir.actions.actions'
489 _sequence = 'ir_actions_id_seq'
490 _order = 'sequence,name'
492 'name': fields.char('Action Name', required=True, size=64, translate=True),
493 'condition' : fields.char('Condition', size=256, required=True,
494 help="Condition that is tested before the action is executed, "
495 "and prevent execution if it is not verified.\n"
496 "Example: object.list_price > 5000\n"
497 "It is a Python expression that can use the following values:\n"
498 " - self: ORM model of the record on which the action is triggered\n"
499 " - object or obj: browse_record of the record on which the action is triggered\n"
500 " - pool: ORM model pool (i.e. self.pool)\n"
501 " - time: Python time module\n"
502 " - cr: database cursor\n"
503 " - uid: current user id\n"
504 " - context: current context"),
505 'state': fields.selection([
506 ('client_action','Client Action'),
508 ('loop','Iteration'),
509 ('code','Python Code'),
510 ('trigger','Trigger'),
513 ('object_create','Create Object'),
514 ('object_copy','Copy Object'),
515 ('object_write','Write Object'),
516 ('other','Multi Actions'),
517 ], 'Action Type', required=True, size=32, help="Type of the Action that is to be executed"),
518 'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
519 "It is a Python block that can use the same values as for the condition field"),
520 '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."),
521 'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
522 'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
523 'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
524 'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),
525 '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)"),
526 '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"
527 "Example: object.invoice_address_id.email, or 'me@example.com'"),
528 '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 "
529 "available in the condition field, e.g. `Hello [[ object.partner_id.name ]]`"),
530 'message': fields.text('Message', translate=True, help="Email contents, may contain expressions enclosed in double brackets based on the same values as those "
531 "available in the condition field, e.g. `Dear [[ object.partner_id.name ]]`"),
532 '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"),
533 'sms': fields.char('SMS', size=160, translate=True),
534 'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', 'Other Actions'),
535 'usage': fields.char('Action Usage', size=32),
536 'type': fields.char('Action Type', size=32, required=True),
537 '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."),
538 'fields_lines': fields.one2many('ir.server.object.lines', 'server_id', 'Field Mappings.'),
539 '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."),
540 '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."),
541 '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."),
542 '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`."),
543 'copy_object': fields.reference('Copy Of', selection=_select_objects, size=256),
548 'type': 'ir.actions.server',
550 'code': """# You can use the following variables:
551 # - self: ORM model of the record on which the action is triggered
552 # - object: browse_record of the record on which the action is triggered if there is one, otherwise None
553 # - pool: ORM model pool (i.e. self.pool)
554 # - time: Python time module
555 # - cr: database cursor
556 # - uid: current user id
557 # - context: current context
558 # If you plan to return an action, assign: action = {...}
562 def get_email(self, cr, uid, action, context):
563 obj_pool = self.pool.get(action.model_id.model)
564 id = context.get('active_id')
565 obj = obj_pool.browse(cr, uid, id)
569 if '/' in action.email.complete_name:
570 fields = action.email.complete_name.split('/')
571 elif '.' in action.email.complete_name:
572 fields = action.email.complete_name.split('.')
576 obj = getattr(obj, field)
578 _logger.exception('Failed to parse: %s', field)
582 def get_mobile(self, cr, uid, action, context):
583 obj_pool = self.pool.get(action.model_id.model)
584 id = context.get('active_id')
585 obj = obj_pool.browse(cr, uid, id)
589 if '/' in action.mobile.complete_name:
590 fields = action.mobile.complete_name.split('/')
591 elif '.' in action.mobile.complete_name:
592 fields = action.mobile.complete_name.split('.')
596 obj = getattr(obj, field)
598 _logger.exception('Failed to parse: %s', field)
602 def merge_message(self, cr, uid, keystr, action, context=None):
607 obj_pool = self.pool.get(action.model_id.model)
608 id = context.get('active_id')
609 obj = obj_pool.browse(cr, uid, id)
610 exp = str(match.group()[2:-2]).strip()
614 'context': dict(context), # copy context to prevent side-effects of eval
617 if result in (None, False):
618 return str("--------")
619 return tools.ustr(result)
621 com = re.compile('(\[\[.+?\]\])')
622 message = com.sub(merge, keystr)
626 # Context should contains:
628 # id : current id of the object
630 # False : Finished correctly
631 # ACTION_ID : Action to launch
633 # FIXME: refactor all the eval() calls in run()!
634 def run(self, cr, uid, ids, context=None):
637 user = self.pool.get('res.users').browse(cr, uid, uid)
638 for action in self.browse(cr, uid, ids, context):
640 obj_pool = self.pool.get(action.model_id.model)
641 if context.get('active_model') == action.model_id.model and context.get('active_id'):
642 obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
650 'context': dict(context), # copy context to prevent side-effects of eval
654 expr = eval(str(action.condition), cxt)
658 if action.state=='client_action':
659 if not action.action_id:
660 raise osv.except_osv(_('Error'), _("Please specify an action to launch !"))
661 return self.pool.get(action.action_id.type)\
662 .read(cr, uid, action.action_id.id, context=context)
664 if action.state=='code':
665 eval(action.code.strip(), cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
669 if action.state == 'email':
670 email_from = config['email_from']
672 _logger.debug('--email-from command line option is not specified, using a fallback value instead.')
674 email_from = user.email
676 email_from = "%s@%s" % (user.login, gethostname())
679 address = eval(str(action.email), cxt)
681 address = str(action.email)
684 _logger.info('No partner email address specified, not sending any email.')
687 # handle single and multiple recipient addresses
688 addresses = address if isinstance(address, (tuple, list)) else [address]
689 subject = self.merge_message(cr, uid, action.subject, action, context)
690 body = self.merge_message(cr, uid, action.message, action, context)
692 ir_mail_server = self.pool.get('ir.mail_server')
693 msg = ir_mail_server.build_email(email_from, addresses, subject, body)
694 res_email = ir_mail_server.send_email(cr, uid, msg)
696 _logger.info('Email successfully sent to: %s', addresses)
698 _logger.warning('Failed to send email to: %s', addresses)
700 if action.state == 'trigger':
701 model = action.wkf_model_id.model
702 m2o_field_name = action.trigger_obj_id.name
703 target_id = obj_pool.read(cr, uid, context.get('active_id'), [m2o_field_name])[m2o_field_name]
704 target_id = target_id[0] if isinstance(target_id,tuple) else target_id
705 openerp.workflow.trg_validate(uid, model, int(target_id), action.trigger_name, cr)
707 if action.state == 'sms':
708 #TODO: set the user and password from the system
709 # for the sms gateway user / password
710 # USE smsclient module from extra-addons
711 _logger.warning('SMS Facility has not been implemented yet. Use smsclient module!')
713 if action.state == 'other':
715 for act in action.child_ids:
716 context['active_id'] = context['active_ids'][0]
717 result = self.run(cr, uid, [act.id], context)
722 if action.state == 'loop':
723 expr = eval(str(action.expression), cxt)
724 context['object'] = obj
726 context['active_id'] = i.id
727 self.run(cr, uid, [action.loop_action.id], context)
729 if action.state == 'object_write':
731 for exp in action.fields_lines:
733 if exp.type == 'equation':
734 expr = eval(euq, cxt)
737 res[exp.col1.name] = expr
739 if not action.write_id:
740 if not action.srcmodel_id:
741 obj_pool = self.pool.get(action.model_id.model)
742 obj_pool.write(cr, uid, [context.get('active_id')], res)
744 write_id = context.get('active_id')
745 obj_pool = self.pool.get(action.srcmodel_id.model)
746 obj_pool.write(cr, uid, [write_id], res)
748 elif action.write_id:
749 obj_pool = self.pool.get(action.srcmodel_id.model)
750 rec = self.pool.get(action.model_id.model).browse(cr, uid, context.get('active_id'))
751 id = eval(action.write_id, {'object': rec})
755 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
757 if type(id) != type(1):
758 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
760 obj_pool.write(cr, uid, [write_id], res)
762 if action.state == 'object_create':
764 for exp in action.fields_lines:
766 if exp.type == 'equation':
767 expr = eval(euq, cxt)
770 res[exp.col1.name] = expr
772 obj_pool = self.pool.get(action.srcmodel_id.model)
773 res_id = obj_pool.create(cr, uid, res)
775 self.pool.get(action.model_id.model).write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
777 if action.state == 'object_copy':
779 for exp in action.fields_lines:
781 if exp.type == 'equation':
782 expr = eval(euq, cxt)
785 res[exp.col1.name] = expr
787 model = action.copy_object.split(',')[0]
788 cid = action.copy_object.split(',')[1]
789 obj_pool = self.pool.get(model)
790 obj_pool.copy(cr, uid, int(cid), res)
796 class act_window_close(osv.osv):
797 _name = 'ir.actions.act_window_close'
798 _inherit = 'ir.actions.actions'
799 _table = 'ir_actions'
801 'type': 'ir.actions.act_window_close',
805 # This model use to register action services.
806 TODO_STATES = [('open', 'To Do'),
808 TODO_TYPES = [('manual', 'Launch Manually'),('once', 'Launch Manually Once'),
809 ('automatic', 'Launch Automatically')]
810 class ir_actions_todo(osv.osv):
812 Configuration Wizards
814 _name = 'ir.actions.todo'
815 _description = "Configuration Wizards"
817 'action_id': fields.many2one(
818 'ir.actions.actions', 'Action', select=True, required=True),
819 'sequence': fields.integer('Sequence'),
820 'state': fields.selection(TODO_STATES, string='Status', required=True),
821 'name': fields.char('Name', size=64),
822 'type': fields.selection(TODO_TYPES, 'Type', required=True,
823 help="""Manual: Launched manually.
824 Automatic: Runs whenever the system is reconfigured.
825 Launch Manually Once: after having been launched manually, it sets automatically to Done."""),
826 'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
827 'note': fields.text('Text', translate=True),
836 def action_launch(self, cr, uid, ids, context=None):
837 """ Launch Action of Wizard"""
838 wizard_id = ids and ids[0] or False
839 wizard = self.browse(cr, uid, wizard_id, context=context)
840 if wizard.type in ('automatic', 'once'):
841 wizard.write({'state': 'done'})
844 act_type = self.pool.get('ir.actions.actions').read(cr, uid, wizard.action_id.id, ['type'], context=context)
846 res = self.pool.get(act_type['type']).read(cr, uid, wizard.action_id.id, [], context=context)
847 if act_type['type'] != 'ir.actions.act_window':
849 res.setdefault('context','{}')
850 res['nodestroy'] = True
852 # Open a specific record when res_id is provided in the context
853 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
854 ctx = eval(res['context'], {'user': user})
855 if ctx.get('res_id'):
856 res.update({'res_id': ctx.pop('res_id')})
858 # disable log for automatic wizards
859 if wizard.type == 'automatic':
860 ctx.update({'disable_log': True})
861 res.update({'context': ctx})
865 def action_open(self, cr, uid, ids, context=None):
866 """ Sets configuration wizard in TODO state"""
867 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
869 def progress(self, cr, uid, context=None):
870 """ Returns a dict with 3 keys {todo, done, total}.
872 These keys all map to integers and provide the number of todos
873 marked as open, the total number of todos and the number of
874 todos not open (which is basically a shortcut to total-todo)
878 user_groups = set(map(
880 self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
881 def groups_match(todo):
882 """ Checks if the todo's groups match those of the current user
884 return not todo.groups_id \
885 or bool(user_groups.intersection((
886 group.id for group in todo.groups_id)))
891 self.search(cr, uid, [('state', '!=', 'open')], context=context),
897 self.search(cr, uid, [], context=context),
903 'todo': len(total) - len(done)
908 class act_client(osv.osv):
909 _name = 'ir.actions.client'
910 _inherit = 'ir.actions.actions'
911 _table = 'ir_act_client'
912 _sequence = 'ir_actions_id_seq'
915 def _get_params(self, cr, uid, ids, field_name, arg, context):
917 for record in self.browse(cr, uid, ids, context=context):
918 result[record.id] = record.params_store and eval(record.params_store, {'uid': uid}) or False
921 def _set_params(self, cr, uid, id, field_name, field_value, arg, context):
922 if isinstance(field_value, dict):
923 self.write(cr, uid, id, {'params_store': repr(field_value)}, context=context)
925 self.write(cr, uid, id, {'params_store': field_value}, context=context)
928 'name': fields.char('Action Name', required=True, size=64, translate=True),
929 'tag': fields.char('Client action tag', size=64, required=True,
930 help="An arbitrary string, interpreted by the client"
931 " according to its own needs and wishes. There "
932 "is no central tag repository across clients."),
933 'res_model': fields.char('Destination Model', size=64,
934 help="Optional model, mostly used for needactions."),
935 'context': fields.char('Context Value', size=250, required=True,
936 help="Context dictionary as Python expression, empty by default (Default: {})"),
937 'params': fields.function(_get_params, fnct_inv=_set_params,
939 string="Supplementary arguments",
940 help="Arguments sent to the client along with"
942 'params_store': fields.binary("Params storage", readonly=True)
945 'type': 'ir.actions.client',
951 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: