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 def render_report(self, cr, uid, ids, name, data, context=None):
111 Look up a report definition and render the report for the provided IDs.
118 # First lookup in the deprecated place, because if the report definition
119 # has not been updated, it is more likely the correct definition is there.
120 if 'report.' + name in openerp.report.interface.report_int._reports:
121 new_report = openerp.report.interface.report_int._reports['report.' + name]
123 cr.execute("SELECT * FROM ir_act_report_xml WHERE report_name=%s", (name,))
124 r = cr.dictfetchone()
126 if r['report_rml'] or r['report_rml_content_data']:
128 kwargs = { 'parser': operator.attrgetter(r['parser'])(openerp.addons) }
131 new_report = report_sxw('report.'+r['report_name'], r['model'],
132 opj('addons',r['report_rml'] or '/'), header=r['header'], register=False, **kwargs)
133 elif r['report_xsl']:
134 new_report = report_rml('report.'+r['report_name'], r['model'],
135 opj('addons',r['report_xml']),
136 r['report_xsl'] and opj('addons',r['report_xsl']), register=False)
138 raise Exception, "Unhandled report type: %s" % r
140 raise Exception, "Required report does not exist: %s" % r
142 return new_report.create(cr, uid, ids, data, context)
144 _name = 'ir.actions.report.xml'
145 _inherit = 'ir.actions.actions'
146 _table = 'ir_act_report_xml'
147 _sequence = 'ir_actions_id_seq'
150 'name': fields.char('Name', size=64, required=True, translate=True),
151 'model': fields.char('Object', size=64, required=True),
152 'type': fields.char('Action Type', size=32, required=True),
153 'report_name': fields.char('Service Name', size=64, required=True),
154 'usage': fields.char('Action Usage', size=32),
155 'report_type': fields.char('Report Type', size=32, required=True, help="Report Type, e.g. pdf, html, raw, sxw, odt, html2html, mako2html, ..."),
156 'groups_id': fields.many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', 'Groups'),
157 '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."),
158 '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.'),
159 '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.'),
160 'auto': fields.boolean('Custom Python Parser'),
162 'header': fields.boolean('Add RML Header', help="Add or not the corporate RML header"),
164 'report_xsl': fields.char('XSL Path', size=256),
165 'report_xml': fields.char('XML Path', size=256, help=''),
167 # Pending deprecation... to be replaced by report_file as this object will become the default report object (not so specific to RML anymore)
168 '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"),
169 # temporary related field as report_rml is pending deprecation - this field will replace report_rml after v6.0
170 '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),
172 'report_sxw': fields.function(_report_sxw, type='char', string='SXW Path'),
173 'report_sxw_content_data': fields.binary('SXW Content'),
174 'report_rml_content_data': fields.binary('RML Content'),
175 'report_sxw_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='SXW Content',),
176 'report_rml_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='RML Content'),
178 'parser': fields.char('Parser Class'),
181 'type': 'ir.actions.report.xml',
185 'report_sxw_content': False,
186 'report_type': 'pdf',
192 class act_window(osv.osv):
193 _name = 'ir.actions.act_window'
194 _table = 'ir_act_window'
195 _inherit = 'ir.actions.actions'
196 _sequence = 'ir_actions_id_seq'
199 def _check_model(self, cr, uid, ids, context=None):
200 for action in self.browse(cr, uid, ids, context):
201 if not self.pool.get(action.res_model):
203 if action.src_model and not self.pool.get(action.src_model):
207 def _invalid_model_msg(self, cr, uid, ids, context=None):
208 return _('Invalid model name in the action definition.')
211 (_check_model, _invalid_model_msg, ['res_model','src_model'])
214 def _views_get_fnc(self, cr, uid, ids, name, arg, context=None):
215 """Returns an ordered list of the specific view modes that should be
216 enabled when displaying the result of this action, along with the
217 ID of the specific view to use for each mode, if any were required.
219 This function hides the logic of determining the precedence between
220 the view_modes string, the view_ids o2m, and the view_id m2o that can
221 be set on the action.
223 :rtype: dict in the form { action_id: list of pairs (tuples) }
224 :return: { action_id: [(view_id, view_mode), ...], ... }, where view_mode
225 is one of the possible values for ir.ui.view.type and view_id
226 is the ID of a specific view to use for this mode, or False for
230 for act in self.browse(cr, uid, ids):
231 res[act.id] = [(view.view_id.id, view.view_mode) for view in act.view_ids]
232 view_ids_modes = [view.view_mode for view in act.view_ids]
233 modes = act.view_mode.split(',')
234 missing_modes = [mode for mode in modes if mode not in view_ids_modes]
236 if act.view_id and act.view_id.type in missing_modes:
237 # reorder missing modes to put view_id first if present
238 missing_modes.remove(act.view_id.type)
239 res[act.id].append((act.view_id.id, act.view_id.type))
240 res[act.id].extend([(False, mode) for mode in missing_modes])
243 def _search_view(self, cr, uid, ids, name, arg, context=None):
245 for act in self.browse(cr, uid, ids, context=context):
246 field_get = self.pool.get(act.res_model).fields_view_get(cr, uid,
247 act.search_view_id and act.search_view_id.id or False,
248 'search', context=context)
249 res[act.id] = str(field_get)
253 'name': fields.char('Action Name', size=64, translate=True),
254 'type': fields.char('Action Type', size=32, required=True),
255 'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
256 'domain': fields.char('Domain Value', size=250,
257 help="Optional domain filtering of the destination data, as a Python expression"),
258 'context': fields.char('Context Value', size=250, required=True,
259 help="Context dictionary as Python expression, empty by default (Default: {})"),
260 'res_id': fields.integer('Record ID', help="Database ID of record to open in form view, when ``view_mode`` is set to 'form' only"),
261 'res_model': fields.char('Destination Model', size=64, required=True,
262 help="Model name of the object to open in the view window"),
263 'src_model': fields.char('Source Model', size=64,
264 help="Optional model name of the objects on which this action should be visible"),
265 'target': fields.selection([('current','Current Window'),('new','New Window'),('inline','Inline Edit'),('inlineview','Inline View')], 'Target Window'),
266 'view_mode': fields.char('View Mode', size=250, required=True,
267 help="Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)"),
268 'view_type': fields.selection((('tree','Tree'),('form','Form')), string='View Type', required=True,
269 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"),
270 'usage': fields.char('Action Usage', size=32,
271 help="Used to filter menu and home actions from the user form."),
272 'view_ids': fields.one2many('ir.actions.act_window.view', 'act_window_id', 'Views'),
273 'views': fields.function(_views_get_fnc, type='binary', string='Views',
274 help="This function field computes the ordered list of views that should be enabled " \
275 "when displaying the result of an action, federating view mode, views and " \
276 "reference view. The result is returned as an ordered list of pairs (view_id,view_mode)."),
277 'limit': fields.integer('Limit', help='Default limit for the list view'),
278 'auto_refresh': fields.integer('Auto-Refresh',
279 help='Add an auto-refresh on the view'),
280 'groups_id': fields.many2many('res.groups', 'ir_act_window_group_rel',
281 'act_id', 'gid', 'Groups'),
282 'search_view_id': fields.many2one('ir.ui.view', 'Search View Ref.'),
283 'filter': fields.boolean('Filter'),
284 'auto_search':fields.boolean('Auto Search'),
285 'search_view' : fields.function(_search_view, type='text', string='Search View'),
286 '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"),
290 'type': 'ir.actions.act_window',
292 'view_mode': 'tree,form',
301 def for_xml_id(self, cr, uid, module, xml_id, context=None):
302 """ Returns the act_window object created for the provided xml_id
304 :param module: the module the act_window originates in
305 :param xml_id: the namespace-less id of the action (the @id
306 attribute from the XML file)
307 :return: A read() view of the ir.actions.act_window
309 dataobj = self.pool.get('ir.model.data')
310 data_id = dataobj._get_id (cr, SUPERUSER_ID, module, xml_id)
311 res_id = dataobj.browse(cr, uid, data_id, context).res_id
312 return self.read(cr, uid, res_id, [], context)
320 ('calendar', 'Calendar'),
322 ('kanban', 'Kanban')]
323 class act_window_view(osv.osv):
324 _name = 'ir.actions.act_window.view'
325 _table = 'ir_act_window_view'
326 _rec_name = 'view_id'
329 'sequence': fields.integer('Sequence'),
330 'view_id': fields.many2one('ir.ui.view', 'View'),
331 'view_mode': fields.selection(VIEW_TYPES, string='View Type', required=True),
332 'act_window_id': fields.many2one('ir.actions.act_window', 'Action', ondelete='cascade'),
333 'multi': fields.boolean('On Multiple Doc.',
334 help="If set to true, the action will not be displayed on the right toolbar of a form view."),
339 def _auto_init(self, cr, context=None):
340 super(act_window_view, self)._auto_init(cr, context)
341 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'act_window_view_unique_mode_per_action\'')
342 if not cr.fetchone():
343 cr.execute('CREATE UNIQUE INDEX act_window_view_unique_mode_per_action ON ir_act_window_view (act_window_id, view_mode)')
346 class act_wizard(osv.osv):
347 _name = 'ir.actions.wizard'
348 _inherit = 'ir.actions.actions'
349 _table = 'ir_act_wizard'
350 _sequence = 'ir_actions_id_seq'
353 'name': fields.char('Wizard Info', size=64, required=True, translate=True),
354 'type': fields.char('Action Type', size=32, required=True),
355 'wiz_name': fields.char('Wizard Name', size=64, required=True),
356 '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."),
357 'groups_id': fields.many2many('res.groups', 'res_groups_wizard_rel', 'uid', 'gid', 'Groups'),
358 'model': fields.char('Object', size=64),
361 'type': 'ir.actions.wizard',
366 class act_url(osv.osv):
367 _name = 'ir.actions.act_url'
368 _table = 'ir_act_url'
369 _inherit = 'ir.actions.actions'
370 _sequence = 'ir_actions_id_seq'
373 'name': fields.char('Action Name', size=64, translate=True),
374 'type': fields.char('Action Type', size=32, required=True),
375 'url': fields.text('Action URL',required=True),
376 'target': fields.selection((
377 ('new', 'New Window'),
378 ('self', 'This Window')),
379 'Action Target', required=True
383 'type': 'ir.actions.act_url',
388 def model_get(self, cr, uid, context=None):
389 wkf_pool = self.pool.get('workflow')
390 ids = wkf_pool.search(cr, uid, [])
391 osvs = wkf_pool.read(cr, uid, ids, ['osv'])
394 mpool = self.pool.get('ir.model')
396 model = osv.get('osv')
397 id = mpool.search(cr, uid, [('model','=',model)])
398 name = mpool.read(cr, uid, id)[0]['name']
399 res.append((model, name))
403 class ir_model_fields(osv.osv):
404 _inherit = 'ir.model.fields'
405 _rec_name = 'field_description'
407 'complete_name': fields.char('Complete Name', size=64, select=1),
411 class server_object_lines(osv.osv):
412 _name = 'ir.server.object.lines'
413 _sequence = 'ir_actions_id_seq'
415 'server_id': fields.many2one('ir.actions.server', 'Object Mapping'),
416 'col1': fields.many2one('ir.model.fields', 'Destination', required=True),
417 'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
418 "When Formula type is selected, this field may be a Python expression "
419 " that can use the same values as for the condition field on the server action.\n"
420 "If Value type is selected, the value will be used directly without evaluation."),
421 'type': fields.selection([
423 ('equation','Formula')
424 ], 'Type', required=True, size=32, change_default=True),
429 server_object_lines()
432 # Actions that are run on the server side
434 class actions_server(osv.osv):
436 def _select_signals(self, cr, uid, context=None):
437 cr.execute("""SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t
438 WHERE w.id = a.wkf_id AND
439 (t.act_from = a.id OR t.act_to = a.id) AND
440 t.signal IS NOT NULL""")
441 result = cr.fetchall() or []
444 if rs[0] is not None and rs[1] is not None:
445 line = rs[1], "%s - (%s)" % (rs[1], rs[0])
449 def _select_objects(self, cr, uid, context=None):
450 model_pool = self.pool.get('ir.model')
451 ids = model_pool.search(cr, uid, [('name','not ilike','.')])
452 res = model_pool.read(cr, uid, ids, ['model', 'name'])
453 return [(r['model'], r['name']) for r in res] + [('','')]
455 def change_object(self, cr, uid, ids, copy_object, state, context=None):
456 if state == 'object_copy' and copy_object:
459 model_pool = self.pool.get('ir.model')
460 model = copy_object.split(',')[0]
461 mid = model_pool.search(cr, uid, [('model','=',model)])
463 'value': {'srcmodel_id': mid[0]},
469 _name = 'ir.actions.server'
470 _table = 'ir_act_server'
471 _inherit = 'ir.actions.actions'
472 _sequence = 'ir_actions_id_seq'
473 _order = 'sequence,name'
475 'name': fields.char('Action Name', required=True, size=64, translate=True),
476 'condition' : fields.char('Condition', size=256, required=True,
477 help="Condition that is tested before the action is executed, "
478 "and prevent execution if it is not verified.\n"
479 "Example: object.list_price > 5000\n"
480 "It is a Python expression that can use the following values:\n"
481 " - self: ORM model of the record on which the action is triggered\n"
482 " - object or obj: browse_record of the record on which the action is triggered\n"
483 " - pool: ORM model pool (i.e. self.pool)\n"
484 " - time: Python time module\n"
485 " - cr: database cursor\n"
486 " - uid: current user id\n"
487 " - context: current context"),
488 'state': fields.selection([
489 ('client_action','Client Action'),
491 ('loop','Iteration'),
492 ('code','Python Code'),
493 ('trigger','Trigger'),
496 ('object_create','Create Object'),
497 ('object_copy','Copy Object'),
498 ('object_write','Write Object'),
499 ('other','Multi Actions'),
500 ], 'Action Type', required=True, size=32, help="Type of the Action that is to be executed"),
501 'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
502 "It is a Python block that can use the same values as for the condition field"),
503 '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."),
504 'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
505 'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
506 'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
507 'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),
508 '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)"),
509 '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"
510 "Example: object.invoice_address_id.email, or 'me@example.com'"),
511 '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 "
512 "available in the condition field, e.g. `Hello [[ object.partner_id.name ]]`"),
513 'message': fields.text('Message', translate=True, help="Email contents, may contain expressions enclosed in double brackets based on the same values as those "
514 "available in the condition field, e.g. `Dear [[ object.partner_id.name ]]`"),
515 '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"),
516 'sms': fields.char('SMS', size=160, translate=True),
517 'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', 'Other Actions'),
518 'usage': fields.char('Action Usage', size=32),
519 'type': fields.char('Action Type', size=32, required=True),
520 '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."),
521 'fields_lines': fields.one2many('ir.server.object.lines', 'server_id', 'Field Mappings.'),
522 '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."),
523 '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."),
524 '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."),
525 '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`."),
526 'copy_object': fields.reference('Copy Of', selection=_select_objects, size=256),
531 'type': 'ir.actions.server',
533 'code': """# You can use the following variables:
534 # - self: ORM model of the record on which the action is triggered
535 # - object: browse_record of the record on which the action is triggered if there is one, otherwise None
536 # - pool: ORM model pool (i.e. self.pool)
537 # - time: Python time module
538 # - cr: database cursor
539 # - uid: current user id
540 # - context: current context
541 # If you plan to return an action, assign: action = {...}
545 def get_email(self, cr, uid, action, context):
546 obj_pool = self.pool.get(action.model_id.model)
547 id = context.get('active_id')
548 obj = obj_pool.browse(cr, uid, id)
552 if '/' in action.email.complete_name:
553 fields = action.email.complete_name.split('/')
554 elif '.' in action.email.complete_name:
555 fields = action.email.complete_name.split('.')
559 obj = getattr(obj, field)
561 _logger.exception('Failed to parse: %s', field)
565 def get_mobile(self, cr, uid, action, context):
566 obj_pool = self.pool.get(action.model_id.model)
567 id = context.get('active_id')
568 obj = obj_pool.browse(cr, uid, id)
572 if '/' in action.mobile.complete_name:
573 fields = action.mobile.complete_name.split('/')
574 elif '.' in action.mobile.complete_name:
575 fields = action.mobile.complete_name.split('.')
579 obj = getattr(obj, field)
581 _logger.exception('Failed to parse: %s', field)
585 def merge_message(self, cr, uid, keystr, action, context=None):
590 obj_pool = self.pool.get(action.model_id.model)
591 id = context.get('active_id')
592 obj = obj_pool.browse(cr, uid, id)
593 exp = str(match.group()[2:-2]).strip()
597 'context': dict(context), # copy context to prevent side-effects of eval
600 if result in (None, False):
601 return str("--------")
602 return tools.ustr(result)
604 com = re.compile('(\[\[.+?\]\])')
605 message = com.sub(merge, keystr)
609 # Context should contains:
611 # id : current id of the object
613 # False : Finished correctly
614 # ACTION_ID : Action to launch
616 # FIXME: refactor all the eval() calls in run()!
617 def run(self, cr, uid, ids, context=None):
620 user = self.pool.get('res.users').browse(cr, uid, uid)
621 for action in self.browse(cr, uid, ids, context):
623 obj_pool = self.pool.get(action.model_id.model)
624 if context.get('active_model') == action.model_id.model and context.get('active_id'):
625 obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
633 'context': dict(context), # copy context to prevent side-effects of eval
637 expr = eval(str(action.condition), cxt)
641 if action.state=='client_action':
642 if not action.action_id:
643 raise osv.except_osv(_('Error'), _("Please specify an action to launch !"))
644 return self.pool.get(action.action_id.type)\
645 .read(cr, uid, action.action_id.id, context=context)
647 if action.state=='code':
648 eval(action.code, cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
652 if action.state == 'email':
653 email_from = config['email_from']
655 _logger.debug('--email-from command line option is not specified, using a fallback value instead.')
657 email_from = user.email
659 email_from = "%s@%s" % (user.login, gethostname())
662 address = eval(str(action.email), cxt)
664 address = str(action.email)
667 _logger.info('No partner email address specified, not sending any email.')
670 # handle single and multiple recipient addresses
671 addresses = address if isinstance(address, (tuple, list)) else [address]
672 subject = self.merge_message(cr, uid, action.subject, action, context)
673 body = self.merge_message(cr, uid, action.message, action, context)
675 ir_mail_server = self.pool.get('ir.mail_server')
676 msg = ir_mail_server.build_email(email_from, addresses, subject, body)
677 res_email = ir_mail_server.send_email(cr, uid, msg)
679 _logger.info('Email successfully sent to: %s', addresses)
681 _logger.warning('Failed to send email to: %s', addresses)
683 if action.state == 'trigger':
684 wf_service = netsvc.LocalService("workflow")
685 model = action.wkf_model_id.model
686 m2o_field_name = action.trigger_obj_id.name
687 target_id = obj_pool.read(cr, uid, context.get('active_id'), [m2o_field_name])[m2o_field_name]
688 target_id = target_id[0] if isinstance(target_id,tuple) else target_id
689 wf_service.trg_validate(uid, model, int(target_id), action.trigger_name, cr)
691 if action.state == 'sms':
692 #TODO: set the user and password from the system
693 # for the sms gateway user / password
694 # USE smsclient module from extra-addons
695 _logger.warning('SMS Facility has not been implemented yet. Use smsclient module!')
697 if action.state == 'other':
699 for act in action.child_ids:
700 context['active_id'] = context['active_ids'][0]
701 result = self.run(cr, uid, [act.id], context)
706 if action.state == 'loop':
707 expr = eval(str(action.expression), cxt)
708 context['object'] = obj
710 context['active_id'] = i.id
711 self.run(cr, uid, [action.loop_action.id], context)
713 if action.state == 'object_write':
715 for exp in action.fields_lines:
717 if exp.type == 'equation':
718 expr = eval(euq, cxt)
721 res[exp.col1.name] = expr
723 if not action.write_id:
724 if not action.srcmodel_id:
725 obj_pool = self.pool.get(action.model_id.model)
726 obj_pool.write(cr, uid, [context.get('active_id')], res)
728 write_id = context.get('active_id')
729 obj_pool = self.pool.get(action.srcmodel_id.model)
730 obj_pool.write(cr, uid, [write_id], res)
732 elif action.write_id:
733 obj_pool = self.pool.get(action.srcmodel_id.model)
734 rec = self.pool.get(action.model_id.model).browse(cr, uid, context.get('active_id'))
735 id = eval(action.write_id, {'object': rec})
739 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
741 if type(id) != type(1):
742 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
744 obj_pool.write(cr, uid, [write_id], res)
746 if action.state == 'object_create':
748 for exp in action.fields_lines:
750 if exp.type == 'equation':
751 expr = eval(euq, cxt)
754 res[exp.col1.name] = expr
756 obj_pool = self.pool.get(action.srcmodel_id.model)
757 res_id = obj_pool.create(cr, uid, res)
759 self.pool.get(action.model_id.model).write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
761 if action.state == 'object_copy':
763 for exp in action.fields_lines:
765 if exp.type == 'equation':
766 expr = eval(euq, cxt)
769 res[exp.col1.name] = expr
771 model = action.copy_object.split(',')[0]
772 cid = action.copy_object.split(',')[1]
773 obj_pool = self.pool.get(model)
774 obj_pool.copy(cr, uid, int(cid), res)
780 class act_window_close(osv.osv):
781 _name = 'ir.actions.act_window_close'
782 _inherit = 'ir.actions.actions'
783 _table = 'ir_actions'
785 'type': 'ir.actions.act_window_close',
789 # This model use to register action services.
790 TODO_STATES = [('open', 'To Do'),
792 TODO_TYPES = [('manual', 'Launch Manually'),('once', 'Launch Manually Once'),
793 ('automatic', 'Launch Automatically')]
794 class ir_actions_todo(osv.osv):
796 Configuration Wizards
798 _name = 'ir.actions.todo'
799 _description = "Configuration Wizards"
801 'action_id': fields.many2one(
802 'ir.actions.actions', 'Action', select=True, required=True),
803 'sequence': fields.integer('Sequence'),
804 'state': fields.selection(TODO_STATES, string='Status', required=True),
805 'name': fields.char('Name', size=64),
806 'type': fields.selection(TODO_TYPES, 'Type', required=True,
807 help="""Manual: Launched manually.
808 Automatic: Runs whenever the system is reconfigured.
809 Launch Manually Once: after having been launched manually, it sets automatically to Done."""),
810 'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
811 'note': fields.text('Text', translate=True),
820 def action_launch(self, cr, uid, ids, context=None):
821 """ Launch Action of Wizard"""
822 wizard_id = ids and ids[0] or False
823 wizard = self.browse(cr, uid, wizard_id, context=context)
824 if wizard.type in ('automatic', 'once'):
825 wizard.write({'state': 'done'})
828 act_type = self.pool.get('ir.actions.actions').read(cr, uid, wizard.action_id.id, ['type'], context=context)
830 res = self.pool.get(act_type['type']).read(cr, uid, wizard.action_id.id, [], context=context)
831 if act_type<>'ir.actions.act_window':
833 res.setdefault('context','{}')
834 res['nodestroy'] = True
836 # Open a specific record when res_id is provided in the context
837 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
838 ctx = eval(res['context'], {'user': user})
839 if ctx.get('res_id'):
840 res.update({'res_id': ctx.pop('res_id')})
842 # disable log for automatic wizards
843 if wizard.type == 'automatic':
844 ctx.update({'disable_log': True})
845 res.update({'context': ctx})
849 def action_open(self, cr, uid, ids, context=None):
850 """ Sets configuration wizard in TODO state"""
851 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
853 def progress(self, cr, uid, context=None):
854 """ Returns a dict with 3 keys {todo, done, total}.
856 These keys all map to integers and provide the number of todos
857 marked as open, the total number of todos and the number of
858 todos not open (which is basically a shortcut to total-todo)
862 user_groups = set(map(
864 self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
865 def groups_match(todo):
866 """ Checks if the todo's groups match those of the current user
868 return not todo.groups_id \
869 or bool(user_groups.intersection((
870 group.id for group in todo.groups_id)))
875 self.search(cr, uid, [('state', '!=', 'open')], context=context),
881 self.search(cr, uid, [], context=context),
887 'todo': len(total) - len(done)
892 class act_client(osv.osv):
893 _name = 'ir.actions.client'
894 _inherit = 'ir.actions.actions'
895 _table = 'ir_act_client'
896 _sequence = 'ir_actions_id_seq'
899 def _get_params(self, cr, uid, ids, field_name, arg, context):
901 for record in self.browse(cr, uid, ids, context=context):
902 result[record.id] = record.params_store and eval(record.params_store, {'uid': uid}) or False
905 def _set_params(self, cr, uid, id, field_name, field_value, arg, context):
906 if isinstance(field_value, dict):
907 self.write(cr, uid, id, {'params_store': repr(field_value)}, context=context)
909 self.write(cr, uid, id, {'params_store': field_value}, context=context)
912 'name': fields.char('Action Name', required=True, size=64, translate=True),
913 'tag': fields.char('Client action tag', size=64, required=True,
914 help="An arbitrary string, interpreted by the client"
915 " according to its own needs and wishes. There "
916 "is no central tag repository across clients."),
917 'res_model': fields.char('Destination Model', size=64,
918 help="Optional model, mostly used for needactions."),
919 'context': fields.char('Context Value', size=250, required=True,
920 help="Context dictionary as Python expression, empty by default (Default: {})"),
921 'params': fields.function(_get_params, fnct_inv=_set_params,
923 string="Supplementary arguments",
924 help="Arguments sent to the client along with"
926 'params_store': fields.binary("Params storage", readonly=True)
929 'type': 'ir.actions.client',
935 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: