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
##############################################################################
32
from osv import fields,osv
33
from report.report_sxw import report_sxw, report_rml
34
from tools.config import config
35
from tools.safe_eval import safe_eval as eval
36
from tools.translate import _
37
from socket import gethostname
39
_logger = logging.getLogger(__name__)
41
class actions(osv.osv):
42
_name = 'ir.actions.actions'
46
'name': fields.char('Action Name', required=True, size=64),
47
'type': fields.char('Action Type', required=True, size=32,readonly=True),
48
'usage': fields.char('Action Usage', size=32),
51
'usage': lambda *a: False,
56
class report_xml(osv.osv):
58
def _report_content(self, cursor, user, ids, name, arg, context=None):
60
for report in self.browse(cursor, user, ids, context=context):
61
data = report[name + '_data']
62
if not data and report[name[:-8]]:
65
fp = tools.file_open(report[name[:-8]], mode='rb')
75
def _report_content_inv(self, cursor, user, id, name, value, arg, context=None):
76
self.write(cursor, user, id, {name+'_data': value}, context=context)
78
def _report_sxw(self, cursor, user, ids, name, arg, context=None):
80
for report in self.browse(cursor, user, ids, context=context):
82
res[report.id] = report.report_rml.replace('.rml', '.sxw')
84
res[report.id] = False
87
def register_all(self, cr):
88
"""Report registration handler that may be overridden by subclasses to
89
add their own kinds of report services.
90
Loads all reports with no manual loaders (auto==True) and
91
registers the appropriate services to implement them.
94
cr.execute("SELECT * FROM ir_act_report_xml WHERE auto=%s ORDER BY id", (True,))
95
result = cr.dictfetchall()
96
svcs = netsvc.Service._services
98
if svcs.has_key('report.'+r['report_name']):
100
if r['report_rml'] or r['report_rml_content_data']:
101
report_sxw('report.'+r['report_name'], r['model'],
102
opj('addons',r['report_rml'] or '/'), header=r['header'])
104
report_rml('report.'+r['report_name'], r['model'],
105
opj('addons',r['report_xml']),
106
r['report_xsl'] and opj('addons',r['report_xsl']))
108
_name = 'ir.actions.report.xml'
109
_table = 'ir_act_report_xml'
110
_sequence = 'ir_actions_id_seq'
113
'name': fields.char('Name', size=64, required=True, translate=True),
114
'model': fields.char('Object', size=64, required=True),
115
'type': fields.char('Action Type', size=32, required=True),
116
'report_name': fields.char('Service Name', size=64, required=True),
117
'usage': fields.char('Action Usage', size=32),
118
'report_type': fields.char('Report Type', size=32, required=True, help="Report Type, e.g. pdf, html, raw, sxw, odt, html2html, mako2html, ..."),
119
'groups_id': fields.many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', 'Groups'),
120
'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."),
121
'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.'),
122
'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.'),
123
'auto': fields.boolean('Custom python parser'),
125
'header': fields.boolean('Add RML header', help="Add or not the corporate RML header"),
127
'report_xsl': fields.char('XSL path', size=256),
128
'report_xml': fields.char('XML path', size=256, help=''),
130
# Pending deprecation... to be replaced by report_file as this object will become the default report object (not so specific to RML anymore)
131
'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"),
132
# temporary related field as report_rml is pending deprecation - this field will replace report_rml after v6.0
133
'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),
135
'report_sxw': fields.function(_report_sxw, type='char', string='SXW path'),
136
'report_sxw_content_data': fields.binary('SXW content'),
137
'report_rml_content_data': fields.binary('RML content'),
138
'report_sxw_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='SXW content',),
139
'report_rml_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='RML content'),
143
'type': lambda *a: 'ir.actions.report.xml',
144
'multi': lambda *a: False,
145
'auto': lambda *a: True,
146
'header': lambda *a: True,
147
'report_sxw_content': lambda *a: False,
148
'report_type': lambda *a: 'pdf',
149
'attachment': lambda *a: False,
154
class act_window(osv.osv):
155
_name = 'ir.actions.act_window'
156
_table = 'ir_act_window'
157
_sequence = 'ir_actions_id_seq'
160
def _check_model(self, cr, uid, ids, context=None):
161
for action in self.browse(cr, uid, ids, context):
162
if not self.pool.get(action.res_model):
164
if action.src_model and not self.pool.get(action.src_model):
168
def _invalid_model_msg(self, cr, uid, ids, context=None):
169
return _('Invalid model name in the action definition.')
172
(_check_model, _invalid_model_msg, ['res_model','src_model'])
175
def _views_get_fnc(self, cr, uid, ids, name, arg, context=None):
176
"""Returns an ordered list of the specific view modes that should be
177
enabled when displaying the result of this action, along with the
178
ID of the specific view to use for each mode, if any were required.
180
This function hides the logic of determining the precedence between
181
the view_modes string, the view_ids o2m, and the view_id m2o that can
182
be set on the action.
184
:rtype: dict in the form { action_id: list of pairs (tuples) }
185
:return: { action_id: [(view_id, view_mode), ...], ... }, where view_mode
186
is one of the possible values for ir.ui.view.type and view_id
187
is the ID of a specific view to use for this mode, or False for
191
for act in self.browse(cr, uid, ids):
192
res[act.id] = [(view.view_id.id, view.view_mode) for view in act.view_ids]
193
view_ids_modes = [view.view_mode for view in act.view_ids]
194
modes = act.view_mode.split(',')
195
missing_modes = [mode for mode in modes if mode not in view_ids_modes]
197
if act.view_id and act.view_id.type in missing_modes:
198
# reorder missing modes to put view_id first if present
199
missing_modes.remove(act.view_id.type)
200
res[act.id].append((act.view_id.id, act.view_id.type))
201
res[act.id].extend([(False, mode) for mode in missing_modes])
204
def _search_view(self, cr, uid, ids, name, arg, context=None):
207
if isinstance(s, unicode):
208
return s.encode('utf8')
210
for act in self.browse(cr, uid, ids, context=context):
211
fields_from_fields_get = self.pool.get(act.res_model).fields_get(cr, uid, context=context)
212
search_view_id = False
213
if act.search_view_id:
214
search_view_id = act.search_view_id.id
216
res_view = self.pool.get('ir.ui.view').search(cr, uid,
217
[('model','=',act.res_model),('type','=','search'),
218
('inherit_id','=',False)], context=context)
220
search_view_id = res_view[0]
222
field_get = self.pool.get(act.res_model).fields_view_get(cr, uid, search_view_id,
224
fields_from_fields_get.update(field_get['fields'])
225
field_get['fields'] = fields_from_fields_get
226
res[act.id] = str(field_get)
228
def process_child(node, new_node, doc):
229
for child in node.childNodes:
230
if child.localName=='field' and child.hasAttribute('select') \
231
and child.getAttribute('select')=='1':
233
fld = doc.createElement('field')
234
for attr in child.attributes.keys():
235
fld.setAttribute(attr, child.getAttribute(attr))
236
new_node.appendChild(fld)
238
new_node.appendChild(child)
239
elif child.localName in ('page','group','notebook'):
240
process_child(child, new_node, doc)
242
form_arch = self.pool.get(act.res_model).fields_view_get(cr, uid, False, 'form', context)
243
dom_arc = dom.minidom.parseString(encode(form_arch['arch']))
244
new_node = copy.deepcopy(dom_arc)
245
for child_node in new_node.childNodes[0].childNodes:
246
if child_node.nodeType == child_node.ELEMENT_NODE:
247
new_node.childNodes[0].removeChild(child_node)
248
process_child(dom_arc.childNodes[0],new_node.childNodes[0],dom_arc)
250
form_arch['arch'] = new_node.toxml()
251
form_arch['fields'].update(fields_from_fields_get)
252
res[act.id] = str(form_arch)
255
def _get_help_status(self, cr, uid, ids, name, arg, context=None):
256
activate_tips = self.pool.get('res.users').browse(cr, uid, uid).menu_tips
257
return dict([(id, activate_tips) for id in ids])
260
'name': fields.char('Action Name', size=64, translate=True),
261
'type': fields.char('Action Type', size=32, required=True),
262
'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
263
'domain': fields.char('Domain Value', size=250,
264
help="Optional domain filtering of the destination data, as a Python expression"),
265
'context': fields.char('Context Value', size=250, required=True,
266
help="Context dictionary as Python expression, empty by default (Default: {})"),
267
'res_model': fields.char('Object', size=64, required=True,
268
help="Model name of the object to open in the view window"),
269
'src_model': fields.char('Source Object', size=64,
270
help="Optional model name of the objects on which this action should be visible"),
271
'target': fields.selection([('current','Current Window'),('new','New Window'),('inline','Inline')], 'Target Window'),
272
'view_type': fields.selection((('tree','Tree'),('form','Form')), string='View Type', required=True,
273
help="View type: set to 'tree' for a hierarchical tree view, or 'form' for other views"),
274
'view_mode': fields.char('View Mode', size=250, required=True,
275
help="Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)"),
276
'usage': fields.char('Action Usage', size=32,
277
help="Used to filter menu and home actions from the user form."),
278
'view_ids': fields.one2many('ir.actions.act_window.view', 'act_window_id', 'Views'),
279
'views': fields.function(_views_get_fnc, type='binary', string='Views',
280
help="This function field computes the ordered list of views that should be enabled " \
281
"when displaying the result of an action, federating view mode, views and " \
282
"reference view. The result is returned as an ordered list of pairs (view_id,view_mode)."),
283
'limit': fields.integer('Limit', help='Default limit for the list view'),
284
'auto_refresh': fields.integer('Auto-Refresh',
285
help='Add an auto-refresh on the view'),
286
'groups_id': fields.many2many('res.groups', 'ir_act_window_group_rel',
287
'act_id', 'gid', 'Groups'),
288
'search_view_id': fields.many2one('ir.ui.view', 'Search View Ref.'),
289
'filter': fields.boolean('Filter'),
290
'auto_search':fields.boolean('Auto Search'),
291
'search_view' : fields.function(_search_view, type='text', string='Search View'),
292
'help': fields.text('Action description',
293
help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
295
'display_menu_tip':fields.function(_get_help_status, type='boolean', string='Display Menu Tips',
296
help='It gives the status if the tip has to be displayed or not when a user executes an action'),
297
'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"),
301
'type': lambda *a: 'ir.actions.act_window',
302
'view_type': lambda *a: 'form',
303
'view_mode': lambda *a: 'tree,form',
304
'context': lambda *a: '{}',
305
'limit': lambda *a: 80,
306
'target': lambda *a: 'current',
307
'auto_refresh': lambda *a: 0,
308
'auto_search':lambda *a: True,
312
def for_xml_id(self, cr, uid, module, xml_id, context=None):
313
""" Returns the act_window object created for the provided xml_id
315
:param module: the module the act_window originates in
316
:param xml_id: the namespace-less id of the action (the @id
317
attribute from the XML file)
318
:return: A read() view of the ir.actions.act_window
320
dataobj = self.pool.get('ir.model.data')
321
data_id = dataobj._get_id (cr, 1, module, xml_id)
322
res_id = dataobj.browse(cr, uid, data_id, context).res_id
323
return self.read(cr, uid, res_id, [], context)
331
('calendar', 'Calendar'),
333
('kanban', 'Kanban')]
334
class act_window_view(osv.osv):
335
_name = 'ir.actions.act_window.view'
336
_table = 'ir_act_window_view'
337
_rec_name = 'view_id'
340
'sequence': fields.integer('Sequence'),
341
'view_id': fields.many2one('ir.ui.view', 'View'),
342
'view_mode': fields.selection(VIEW_TYPES, string='View Type', required=True),
343
'act_window_id': fields.many2one('ir.actions.act_window', 'Action', ondelete='cascade'),
344
'multi': fields.boolean('On Multiple Doc.',
345
help="If set to true, the action will not be displayed on the right toolbar of a form view."),
348
'multi': lambda *a: False,
350
def _auto_init(self, cr, context=None):
351
super(act_window_view, self)._auto_init(cr, context)
352
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'act_window_view_unique_mode_per_action\'')
353
if not cr.fetchone():
354
cr.execute('CREATE UNIQUE INDEX act_window_view_unique_mode_per_action ON ir_act_window_view (act_window_id, view_mode)')
357
class act_wizard(osv.osv):
358
_name = 'ir.actions.wizard'
359
_inherit = 'ir.actions.actions'
360
_table = 'ir_act_wizard'
361
_sequence = 'ir_actions_id_seq'
364
'name': fields.char('Wizard Info', size=64, required=True, translate=True),
365
'type': fields.char('Action Type', size=32, required=True),
366
'wiz_name': fields.char('Wizard Name', size=64, required=True),
367
'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."),
368
'groups_id': fields.many2many('res.groups', 'res_groups_wizard_rel', 'uid', 'gid', 'Groups'),
369
'model': fields.char('Object', size=64),
372
'type': lambda *a: 'ir.actions.wizard',
373
'multi': lambda *a: False,
377
class act_url(osv.osv):
378
_name = 'ir.actions.url'
379
_table = 'ir_act_url'
380
_sequence = 'ir_actions_id_seq'
383
'name': fields.char('Action Name', size=64, translate=True),
384
'type': fields.char('Action Type', size=32, required=True),
385
'url': fields.text('Action URL',required=True),
386
'target': fields.selection((
387
('new', 'New Window'),
388
('self', 'This Window')),
389
'Action Target', required=True
393
'type': lambda *a: 'ir.actions.act_url',
394
'target': lambda *a: 'new'
398
def model_get(self, cr, uid, context=None):
399
wkf_pool = self.pool.get('workflow')
400
ids = wkf_pool.search(cr, uid, [])
401
osvs = wkf_pool.read(cr, uid, ids, ['osv'])
404
mpool = self.pool.get('ir.model')
406
model = osv.get('osv')
407
id = mpool.search(cr, uid, [('model','=',model)])
408
name = mpool.read(cr, uid, id)[0]['name']
409
res.append((model, name))
413
class ir_model_fields(osv.osv):
414
_inherit = 'ir.model.fields'
415
_rec_name = 'field_description'
417
'complete_name': fields.char('Complete Name', size=64, select=1),
421
class server_object_lines(osv.osv):
422
_name = 'ir.server.object.lines'
423
_sequence = 'ir_actions_id_seq'
425
'server_id': fields.many2one('ir.actions.server', 'Object Mapping'),
426
'col1': fields.many2one('ir.model.fields', 'Destination', required=True),
427
'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
428
"When Formula type is selected, this field may be a Python expression "
429
" that can use the same values as for the condition field on the server action.\n"
430
"If Value type is selected, the value will be used directly without evaluation."),
431
'type': fields.selection([
433
('equation','Formula')
434
], 'Type', required=True, size=32, change_default=True),
437
'type': lambda *a: 'equation',
439
server_object_lines()
442
# Actions that are run on the server side
444
class actions_server(osv.osv):
446
def _select_signals(self, cr, uid, context=None):
447
cr.execute("""SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t
448
WHERE w.id = a.wkf_id AND
449
(t.act_from = a.id OR t.act_to = a.id) AND
450
t.signal IS NOT NULL""")
451
result = cr.fetchall() or []
454
if rs[0] is not None and rs[1] is not None:
455
line = rs[1], "%s - (%s)" % (rs[1], rs[0])
459
def _select_objects(self, cr, uid, context=None):
460
model_pool = self.pool.get('ir.model')
461
ids = model_pool.search(cr, uid, [('name','not ilike','.')])
462
res = model_pool.read(cr, uid, ids, ['model', 'name'])
463
return [(r['model'], r['name']) for r in res] + [('','')]
465
def change_object(self, cr, uid, ids, copy_object, state, context=None):
466
if state == 'object_copy' and copy_object:
469
model_pool = self.pool.get('ir.model')
470
model = copy_object.split(',')[0]
471
mid = model_pool.search(cr, uid, [('model','=',model)])
473
'value': {'srcmodel_id': mid[0]},
479
_name = 'ir.actions.server'
480
_table = 'ir_act_server'
481
_sequence = 'ir_actions_id_seq'
482
_order = 'sequence,name'
484
'name': fields.char('Action Name', required=True, size=64, translate=True),
485
'condition' : fields.char('Condition', size=256, required=True,
486
help="Condition that is tested before the action is executed, "
487
"and prevent execution if it is not verified.\n"
488
"Example: object.list_price > 5000\n"
489
"It is a Python expression that can use the following values:\n"
490
" - self: ORM model of the record on which the action is triggered\n"
491
" - object or obj: browse_record of the record on which the action is triggered\n"
492
" - pool: ORM model pool (i.e. self.pool)\n"
493
" - time: Python time module\n"
494
" - cr: database cursor\n"
495
" - uid: current user id\n"
496
" - context: current context"),
497
'state': fields.selection([
498
('client_action','Client Action'),
500
('loop','Iteration'),
501
('code','Python Code'),
502
('trigger','Trigger'),
505
('object_create','Create Object'),
506
('object_copy','Copy Object'),
507
('object_write','Write Object'),
508
('other','Multi Actions'),
509
], 'Action Type', required=True, size=32, help="Type of the Action that is to be executed"),
510
'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
511
"It is a Python block that can use the same values as for the condition field"),
512
'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."),
513
'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create)."),
514
'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
515
'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
516
'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),
517
'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)"),
518
'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"
519
"Example: object.invoice_address_id.email, or 'me@example.com'"),
520
'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 "
521
"available in the condition field, e.g. `Hello [[ object.partner_id.name ]]`"),
522
'message': fields.text('Message', translate=True, help="Email contents, may contain expressions enclosed in double brackets based on the same values as those "
523
"available in the condition field, e.g. `Dear [[ object.partner_id.name ]]`"),
524
'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"),
525
'sms': fields.char('SMS', size=160, translate=True),
526
'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', 'Other Actions'),
527
'usage': fields.char('Action Usage', size=32),
528
'type': fields.char('Action Type', size=32, required=True),
529
'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."),
530
'fields_lines': fields.one2many('ir.server.object.lines', 'server_id', 'Field Mappings.'),
531
'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."),
532
'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."),
533
'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."),
534
'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`."),
535
'copy_object': fields.reference('Copy Of', selection=_select_objects, size=256),
538
'state': lambda *a: 'dummy',
539
'condition': lambda *a: 'True',
540
'type': lambda *a: 'ir.actions.server',
541
'sequence': lambda *a: 5,
542
'code': lambda *a: """# You can use the following variables:
543
# - self: ORM model of the record on which the action is triggered
544
# - object: browse_record of the record on which the action is triggered if there is one, otherwise None
545
# - pool: ORM model pool (i.e. self.pool)
546
# - time: Python time module
547
# - cr: database cursor
548
# - uid: current user id
549
# - context: current context
550
# If you plan to return an action, assign: action = {...}
554
def get_email(self, cr, uid, action, context):
555
obj_pool = self.pool.get(action.model_id.model)
556
id = context.get('active_id')
557
obj = obj_pool.browse(cr, uid, id)
561
if '/' in action.email.complete_name:
562
fields = action.email.complete_name.split('/')
563
elif '.' in action.email.complete_name:
564
fields = action.email.complete_name.split('.')
568
obj = getattr(obj, field)
570
_logger.exception('Failed to parse: %s', field)
574
def get_mobile(self, cr, uid, action, context):
575
obj_pool = self.pool.get(action.model_id.model)
576
id = context.get('active_id')
577
obj = obj_pool.browse(cr, uid, id)
581
if '/' in action.mobile.complete_name:
582
fields = action.mobile.complete_name.split('/')
583
elif '.' in action.mobile.complete_name:
584
fields = action.mobile.complete_name.split('.')
588
obj = getattr(obj, field)
590
_logger.exception('Failed to parse: %s', field)
594
def merge_message(self, cr, uid, keystr, action, context=None):
599
obj_pool = self.pool.get(action.model_id.model)
600
id = context.get('active_id')
601
obj = obj_pool.browse(cr, uid, id)
602
exp = str(match.group()[2:-2]).strip()
606
'context': dict(context), # copy context to prevent side-effects of eval
609
if result in (None, False):
610
return str("--------")
611
return tools.ustr(result)
613
com = re.compile('(\[\[.+?\]\])')
614
message = com.sub(merge, keystr)
618
# Context should contains:
620
# id : current id of the object
622
# False : Finnished correctly
623
# ACTION_ID : Action to launch
625
# FIXME: refactor all the eval() calls in run()!
626
def run(self, cr, uid, ids, context=None):
629
user = self.pool.get('res.users').browse(cr, uid, uid)
630
for action in self.browse(cr, uid, ids, context):
632
obj_pool = self.pool.get(action.model_id.model)
633
if context.get('active_model') == action.model_id.model and context.get('active_id'):
634
obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
642
'context': dict(context), # copy context to prevent side-effects of eval
646
expr = eval(str(action.condition), cxt)
650
if action.state=='client_action':
651
if not action.action_id:
652
raise osv.except_osv(_('Error'), _("Please specify an action to launch !"))
653
return self.pool.get(action.action_id.type)\
654
.read(cr, uid, action.action_id.id, context=context)
656
if action.state=='code':
657
eval(action.code, cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
661
if action.state == 'email':
662
email_from = config['email_from']
663
address = str(action.email)
665
address = eval(str(action.email), cxt)
670
_logger.info('No partner email address specified, not sending any email.')
674
_logger.debug('--email-from command line option is not specified, using a fallback value instead.')
676
email_from = user.user_email
678
email_from = "%s@%s" % (user.login, gethostname())
680
subject = self.merge_message(cr, uid, action.subject, action, context)
681
body = self.merge_message(cr, uid, action.message, action, context)
683
ir_mail_server = self.pool.get('ir.mail_server')
684
msg = ir_mail_server.build_email(email_from, [address], subject, body)
685
res_email = ir_mail_server.send_email(cr, uid, msg)
687
_logger.info('Email successfully sent to: %s', address)
689
_logger.warning('Failed to send email to: %s', address)
691
if action.state == 'trigger':
692
wf_service = netsvc.LocalService("workflow")
693
model = action.wkf_model_id.model
694
m2o_field_name = action.trigger_obj_id.name
695
target_id = obj_pool.read(cr, uid, context.get('active_id'), [m2o_field_name])[m2o_field_name]
696
target_id = target_id[0] if isinstance(target_id,tuple) else target_id
697
wf_service.trg_validate(uid, model, int(target_id), action.trigger_name, cr)
699
if action.state == 'sms':
700
#TODO: set the user and password from the system
701
# for the sms gateway user / password
702
# USE smsclient module from extra-addons
703
_logger.warning('SMS Facility has not been implemented yet. Use smsclient module!')
705
if action.state == 'other':
707
for act in action.child_ids:
708
context['active_id'] = context['active_ids'][0]
709
result = self.run(cr, uid, [act.id], context)
714
if action.state == 'loop':
715
expr = eval(str(action.expression), cxt)
716
context['object'] = obj
718
context['active_id'] = i.id
719
result = self.run(cr, uid, [action.loop_action.id], context)
721
if action.state == 'object_write':
723
for exp in action.fields_lines:
725
if exp.type == 'equation':
726
expr = eval(euq, cxt)
729
res[exp.col1.name] = expr
731
if not action.write_id:
732
if not action.srcmodel_id:
733
obj_pool = self.pool.get(action.model_id.model)
734
obj_pool.write(cr, uid, [context.get('active_id')], res)
736
write_id = context.get('active_id')
737
obj_pool = self.pool.get(action.srcmodel_id.model)
738
obj_pool.write(cr, uid, [write_id], res)
740
elif action.write_id:
741
obj_pool = self.pool.get(action.srcmodel_id.model)
742
rec = self.pool.get(action.model_id.model).browse(cr, uid, context.get('active_id'))
743
id = eval(action.write_id, {'object': rec})
747
raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
749
if type(id) != type(1):
750
raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
752
obj_pool.write(cr, uid, [write_id], res)
754
if action.state == 'object_create':
756
for exp in action.fields_lines:
758
if exp.type == 'equation':
759
expr = eval(euq, cxt)
762
res[exp.col1.name] = expr
766
obj_pool = self.pool.get(action.srcmodel_id.model)
767
res_id = obj_pool.create(cr, uid, res)
769
self.pool.get(action.model_id.model).write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
771
if action.state == 'object_copy':
773
for exp in action.fields_lines:
775
if exp.type == 'equation':
776
expr = eval(euq, cxt)
779
res[exp.col1.name] = expr
781
model = action.copy_object.split(',')[0]
782
cid = action.copy_object.split(',')[1]
783
obj_pool = self.pool.get(model)
784
res_id = obj_pool.copy(cr, uid, int(cid), res)
790
class act_window_close(osv.osv):
791
_name = 'ir.actions.act_window_close'
792
_inherit = 'ir.actions.actions'
793
_table = 'ir_actions'
795
'type': lambda *a: 'ir.actions.act_window_close',
799
class ir_actions_todo_category(osv.osv):
801
Category of Configuration Wizards
804
_name = 'ir.actions.todo.category'
805
_description = "Configuration Wizard Category"
807
'name':fields.char('Name', size=64, translate=True, required=True),
808
'sequence': fields.integer('Sequence'),
809
'wizards_ids': fields.one2many('ir.actions.todo', 'category_id', 'Configuration Wizards'),
811
ir_actions_todo_category()
813
# This model use to register action services.
814
TODO_STATES = [('open', 'To Do'),
816
TODO_TYPES = [('manual', 'Launch Manually'),('once', 'Launch Manually Once'),
817
('automatic', 'Launch Automatically')]
818
class ir_actions_todo(osv.osv):
820
Configuration Wizards
822
_name = 'ir.actions.todo'
823
_description = "Configuration Wizards"
825
'action_id': fields.many2one(
826
'ir.actions.act_window', 'Action', select=True, required=True,
828
'sequence': fields.integer('Sequence'),
829
'state': fields.selection(TODO_STATES, string='State', required=True),
830
'name': fields.char('Name', size=64),
831
'type': fields.selection(TODO_TYPES, 'Type', required=True,
832
help="""Manual: Launched manually.
833
Automatic: Runs whenever the system is reconfigured.
834
Launch Manually Once: after hacing been launched manually, it sets automatically to Done."""),
835
'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
836
'note': fields.text('Text', translate=True),
837
'category_id': fields.many2one('ir.actions.todo.category','Category'),
844
_order="sequence,name,id"
846
def action_launch(self, cr, uid, ids, context=None):
847
""" Launch Action of Wizard"""
848
wizard_id = ids and ids[0] or False
849
wizard = self.browse(cr, uid, wizard_id, context=context)
850
if wizard.type in ('automatic', 'once'):
851
wizard.write({'state': 'done'})
854
res = self.pool.get('ir.actions.act_window').read(cr, uid, wizard.action_id.id, [], context=context)
855
res.setdefault('context','{}')
856
res['nodestroy'] = True
858
# Open a specific record when res_id is provided in the context
859
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
860
ctx = eval(res['context'], {'user': user})
861
if ctx.get('res_id'):
862
res.update({'res_id': ctx.pop('res_id')})
864
# disable log for automatic wizards
865
if wizard.type == 'automatic':
866
ctx.update({'disable_log': True})
867
res.update({'context': ctx})
871
def action_open(self, cr, uid, ids, context=None):
872
""" Sets configuration wizard in TODO state"""
873
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
875
def progress(self, cr, uid, context=None):
876
""" Returns a dict with 3 keys {todo, done, total}.
878
These keys all map to integers and provide the number of todos
879
marked as open, the total number of todos and the number of
880
todos not open (which is basically a shortcut to total-todo)
884
user_groups = set(map(
886
self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
887
def groups_match(todo):
888
""" Checks if the todo's groups match those of the current user
890
return not todo.groups_id \
891
or bool(user_groups.intersection((
892
group.id for group in todo.groups_id)))
897
self.search(cr, uid, [('state', '!=', 'open')], context=context),
903
self.search(cr, uid, [], context=context),
909
'todo': len(total) - len(done)
914
class act_client(osv.osv):
915
_name = 'ir.actions.client'
916
_inherit = 'ir.actions.actions'
917
_table = 'ir_act_client'
918
_sequence = 'ir_actions_id_seq'
921
def _get_params(self, cr, uid, ids, field_name, arg, context):
923
((record.id, ast.literal_eval(record.params_store))
924
if record.params_store else (record.id, False))
925
for record in self.browse(cr, uid, ids, context=context)
928
def _set_params(self, cr, uid, id, field_name, field_value, arg, context):
929
assert isinstance(field_value, dict), "params can only be dictionaries"
930
self.write(cr, uid, id, {'params_store': repr(field_value)}, context=context)
933
'tag': fields.char('Client action tag', size=64, required=True,
934
help="An arbitrary string, interpreted by the client"
935
" according to its own needs and wishes. There "
936
"is no central tag repository across clients."),
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',
950
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: