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 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 _
36 import openerp.workflow
38 _logger = logging.getLogger(__name__)
40 class actions(osv.osv):
41 _name = 'ir.actions.actions'
45 'name': fields.char('Name', size=64, required=True),
46 'type': fields.char('Action Type', required=True, size=32),
47 'usage': fields.char('Action Usage', size=32),
48 'help': fields.text('Action description',
49 help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
53 'usage': lambda *a: False,
58 class report_xml(osv.osv):
60 def _report_content(self, cursor, user, ids, name, arg, context=None):
62 for report in self.browse(cursor, user, ids, context=context):
63 data = report[name + '_data']
64 if not data and report[name[:-8]]:
67 fp = tools.file_open(report[name[:-8]], mode='rb')
77 def _report_content_inv(self, cursor, user, id, name, value, arg, context=None):
78 self.write(cursor, user, id, {name+'_data': value}, context=context)
80 def _report_sxw(self, cursor, user, ids, name, arg, context=None):
82 for report in self.browse(cursor, user, ids, context=context):
84 res[report.id] = report.report_rml.replace('.rml', '.sxw')
86 res[report.id] = False
89 def register_all(self, cr):
90 """Report registration handler that may be overridden by subclasses to
91 add their own kinds of report services.
92 Loads all reports with no manual loaders (auto==True) and
93 registers the appropriate services to implement them.
96 cr.execute("SELECT * FROM ir_act_report_xml WHERE auto=%s ORDER BY id", (True,))
97 result = cr.dictfetchall()
98 reports = openerp.report.interface.report_int._reports
100 if reports.has_key('report.'+r['report_name']):
102 if r['report_rml'] or r['report_rml_content_data']:
103 report_sxw('report.'+r['report_name'], r['model'],
104 opj('addons',r['report_rml'] or '/'), header=r['header'])
106 report_rml('report.'+r['report_name'], r['model'],
107 opj('addons',r['report_xml']),
108 r['report_xsl'] and opj('addons',r['report_xsl']))
110 _name = 'ir.actions.report.xml'
111 _inherit = 'ir.actions.actions'
112 _table = 'ir_act_report_xml'
113 _sequence = 'ir_actions_id_seq'
116 'name': fields.char('Name', size=64, required=True, translate=True),
117 'model': fields.char('Object', size=64, required=True),
118 'type': fields.char('Action Type', size=32, required=True),
119 'report_name': fields.char('Service Name', size=64, required=True),
120 'usage': fields.char('Action Usage', size=32),
121 'report_type': fields.char('Report Type', size=32, required=True, help="Report Type, e.g. pdf, html, raw, sxw, odt, html2html, mako2html, ..."),
122 'groups_id': fields.many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', 'Groups'),
123 '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."),
124 '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.'),
125 '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.'),
126 'auto': fields.boolean('Custom Python Parser'),
128 'header': fields.boolean('Add RML Header', help="Add or not the corporate RML header"),
130 'report_xsl': fields.char('XSL Path', size=256),
131 'report_xml': fields.char('XML Path', size=256, help=''),
133 # Pending deprecation... to be replaced by report_file as this object will become the default report object (not so specific to RML anymore)
134 '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"),
135 # temporary related field as report_rml is pending deprecation - this field will replace report_rml after v6.0
136 '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),
138 'report_sxw': fields.function(_report_sxw, type='char', string='SXW Path'),
139 'report_sxw_content_data': fields.binary('SXW Content'),
140 'report_rml_content_data': fields.binary('RML Content'),
141 'report_sxw_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='SXW Content',),
142 'report_rml_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='RML Content'),
146 'type': 'ir.actions.report.xml',
150 'report_sxw_content': False,
151 'report_type': 'pdf',
157 class act_window(osv.osv):
158 _name = 'ir.actions.act_window'
159 _table = 'ir_act_window'
160 _inherit = 'ir.actions.actions'
161 _sequence = 'ir_actions_id_seq'
164 def _check_model(self, cr, uid, ids, context=None):
165 for action in self.browse(cr, uid, ids, context):
166 if not self.pool.get(action.res_model):
168 if action.src_model and not self.pool.get(action.src_model):
172 def _invalid_model_msg(self, cr, uid, ids, context=None):
173 return _('Invalid model name in the action definition.')
176 (_check_model, _invalid_model_msg, ['res_model','src_model'])
179 def _views_get_fnc(self, cr, uid, ids, name, arg, context=None):
180 """Returns an ordered list of the specific view modes that should be
181 enabled when displaying the result of this action, along with the
182 ID of the specific view to use for each mode, if any were required.
184 This function hides the logic of determining the precedence between
185 the view_modes string, the view_ids o2m, and the view_id m2o that can
186 be set on the action.
188 :rtype: dict in the form { action_id: list of pairs (tuples) }
189 :return: { action_id: [(view_id, view_mode), ...], ... }, where view_mode
190 is one of the possible values for ir.ui.view.type and view_id
191 is the ID of a specific view to use for this mode, or False for
195 for act in self.browse(cr, uid, ids):
196 res[act.id] = [(view.view_id.id, view.view_mode) for view in act.view_ids]
197 view_ids_modes = [view.view_mode for view in act.view_ids]
198 modes = act.view_mode.split(',')
199 missing_modes = [mode for mode in modes if mode not in view_ids_modes]
201 if act.view_id and act.view_id.type in missing_modes:
202 # reorder missing modes to put view_id first if present
203 missing_modes.remove(act.view_id.type)
204 res[act.id].append((act.view_id.id, act.view_id.type))
205 res[act.id].extend([(False, mode) for mode in missing_modes])
208 def _search_view(self, cr, uid, ids, name, arg, context=None):
210 for act in self.browse(cr, uid, ids, context=context):
211 field_get = self.pool[act.res_model].fields_view_get(cr, uid,
212 act.search_view_id and act.search_view_id.id or False,
213 'search', context=context)
214 res[act.id] = str(field_get)
218 'name': fields.char('Action Name', size=64, translate=True),
219 'type': fields.char('Action Type', size=32, required=True),
220 'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
221 'domain': fields.char('Domain Value', size=250,
222 help="Optional domain filtering of the destination data, as a Python expression"),
223 'context': fields.char('Context Value', size=250, required=True,
224 help="Context dictionary as Python expression, empty by default (Default: {})"),
225 'res_id': fields.integer('Record ID', help="Database ID of record to open in form view, when ``view_mode`` is set to 'form' only"),
226 'res_model': fields.char('Destination Model', size=64, required=True,
227 help="Model name of the object to open in the view window"),
228 'src_model': fields.char('Source Model', size=64,
229 help="Optional model name of the objects on which this action should be visible"),
230 'target': fields.selection([('current','Current Window'),('new','New Window'),('inline','Inline Edit'),('inlineview','Inline View')], 'Target Window'),
231 'view_mode': fields.char('View Mode', size=250, required=True,
232 help="Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)"),
233 'view_type': fields.selection((('tree','Tree'),('form','Form')), string='View Type', required=True,
234 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"),
235 'usage': fields.char('Action Usage', size=32,
236 help="Used to filter menu and home actions from the user form."),
237 'view_ids': fields.one2many('ir.actions.act_window.view', 'act_window_id', 'Views'),
238 'views': fields.function(_views_get_fnc, type='binary', string='Views',
239 help="This function field computes the ordered list of views that should be enabled " \
240 "when displaying the result of an action, federating view mode, views and " \
241 "reference view. The result is returned as an ordered list of pairs (view_id,view_mode)."),
242 'limit': fields.integer('Limit', help='Default limit for the list view'),
243 'auto_refresh': fields.integer('Auto-Refresh',
244 help='Add an auto-refresh on the view'),
245 'groups_id': fields.many2many('res.groups', 'ir_act_window_group_rel',
246 'act_id', 'gid', 'Groups'),
247 'search_view_id': fields.many2one('ir.ui.view', 'Search View Ref.'),
248 'filter': fields.boolean('Filter'),
249 'auto_search':fields.boolean('Auto Search'),
250 'search_view' : fields.function(_search_view, type='text', string='Search View'),
251 '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"),
255 'type': 'ir.actions.act_window',
257 'view_mode': 'tree,form',
266 def for_xml_id(self, cr, uid, module, xml_id, context=None):
267 """ Returns the act_window object created for the provided xml_id
269 :param module: the module the act_window originates in
270 :param xml_id: the namespace-less id of the action (the @id
271 attribute from the XML file)
272 :return: A read() view of the ir.actions.act_window
274 dataobj = self.pool.get('ir.model.data')
275 data_id = dataobj._get_id (cr, SUPERUSER_ID, module, xml_id)
276 res_id = dataobj.browse(cr, uid, data_id, context).res_id
277 return self.read(cr, uid, res_id, [], context)
285 ('calendar', 'Calendar'),
287 ('kanban', 'Kanban')]
288 class act_window_view(osv.osv):
289 _name = 'ir.actions.act_window.view'
290 _table = 'ir_act_window_view'
291 _rec_name = 'view_id'
294 'sequence': fields.integer('Sequence'),
295 'view_id': fields.many2one('ir.ui.view', 'View'),
296 'view_mode': fields.selection(VIEW_TYPES, string='View Type', required=True),
297 'act_window_id': fields.many2one('ir.actions.act_window', 'Action', ondelete='cascade'),
298 'multi': fields.boolean('On Multiple Doc.',
299 help="If set to true, the action will not be displayed on the right toolbar of a form view."),
304 def _auto_init(self, cr, context=None):
305 super(act_window_view, self)._auto_init(cr, context)
306 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'act_window_view_unique_mode_per_action\'')
307 if not cr.fetchone():
308 cr.execute('CREATE UNIQUE INDEX act_window_view_unique_mode_per_action ON ir_act_window_view (act_window_id, view_mode)')
311 class act_wizard(osv.osv):
312 _name = 'ir.actions.wizard'
313 _inherit = 'ir.actions.actions'
314 _table = 'ir_act_wizard'
315 _sequence = 'ir_actions_id_seq'
318 'name': fields.char('Wizard Info', size=64, required=True, translate=True),
319 'type': fields.char('Action Type', size=32, required=True),
320 'wiz_name': fields.char('Wizard Name', size=64, required=True),
321 '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."),
322 'groups_id': fields.many2many('res.groups', 'res_groups_wizard_rel', 'uid', 'gid', 'Groups'),
323 'model': fields.char('Object', size=64),
326 'type': 'ir.actions.wizard',
331 class act_url(osv.osv):
332 _name = 'ir.actions.act_url'
333 _table = 'ir_act_url'
334 _inherit = 'ir.actions.actions'
335 _sequence = 'ir_actions_id_seq'
338 'name': fields.char('Action Name', size=64, translate=True),
339 'type': fields.char('Action Type', size=32, required=True),
340 'url': fields.text('Action URL',required=True),
341 'target': fields.selection((
342 ('new', 'New Window'),
343 ('self', 'This Window')),
344 'Action Target', required=True
348 'type': 'ir.actions.act_url',
353 def model_get(self, cr, uid, context=None):
354 wkf_pool = self.pool.get('workflow')
355 ids = wkf_pool.search(cr, uid, [])
356 osvs = wkf_pool.read(cr, uid, ids, ['osv'])
359 mpool = self.pool.get('ir.model')
361 model = osv.get('osv')
362 id = mpool.search(cr, uid, [('model','=',model)])
363 name = mpool.read(cr, uid, id)[0]['name']
364 res.append((model, name))
368 class ir_model_fields(osv.osv):
369 _inherit = 'ir.model.fields'
370 _rec_name = 'field_description'
372 'complete_name': fields.char('Complete Name', size=64, select=1),
376 class server_object_lines(osv.osv):
377 _name = 'ir.server.object.lines'
378 _sequence = 'ir_actions_id_seq'
380 'server_id': fields.many2one('ir.actions.server', 'Object Mapping'),
381 'col1': fields.many2one('ir.model.fields', 'Destination', required=True),
382 'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
383 "When Formula type is selected, this field may be a Python expression "
384 " that can use the same values as for the condition field on the server action.\n"
385 "If Value type is selected, the value will be used directly without evaluation."),
386 'type': fields.selection([
388 ('equation','Formula')
389 ], 'Type', required=True, size=32, change_default=True),
394 server_object_lines()
397 # Actions that are run on the server side
399 class actions_server(osv.osv):
401 def _select_signals(self, cr, uid, context=None):
402 cr.execute("""SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t
403 WHERE w.id = a.wkf_id AND
404 (t.act_from = a.id OR t.act_to = a.id) AND
405 t.signal IS NOT NULL""")
406 result = cr.fetchall() or []
409 if rs[0] is not None and rs[1] is not None:
410 line = rs[1], "%s - (%s)" % (rs[1], rs[0])
414 def _select_objects(self, cr, uid, context=None):
415 model_pool = self.pool.get('ir.model')
416 ids = model_pool.search(cr, uid, [('name','not ilike','.')])
417 res = model_pool.read(cr, uid, ids, ['model', 'name'])
418 return [(r['model'], r['name']) for r in res] + [('','')]
420 def change_object(self, cr, uid, ids, copy_object, state, context=None):
421 if state == 'object_copy' and copy_object:
424 model_pool = self.pool.get('ir.model')
425 model = copy_object.split(',')[0]
426 mid = model_pool.search(cr, uid, [('model','=',model)])
428 'value': {'srcmodel_id': mid[0]},
434 _name = 'ir.actions.server'
435 _table = 'ir_act_server'
436 _inherit = 'ir.actions.actions'
437 _sequence = 'ir_actions_id_seq'
438 _order = 'sequence,name'
440 'name': fields.char('Action Name', required=True, size=64, translate=True),
441 'condition' : fields.char('Condition', size=256, required=True,
442 help="Condition that is tested before the action is executed, "
443 "and prevent execution if it is not verified.\n"
444 "Example: object.list_price > 5000\n"
445 "It is a Python expression that can use the following values:\n"
446 " - self: ORM model of the record on which the action is triggered\n"
447 " - object or obj: browse_record of the record on which the action is triggered\n"
448 " - pool: ORM model pool (i.e. self.pool)\n"
449 " - time: Python time module\n"
450 " - cr: database cursor\n"
451 " - uid: current user id\n"
452 " - context: current context"),
453 'state': fields.selection([
454 ('client_action','Client Action'),
456 ('loop','Iteration'),
457 ('code','Python Code'),
458 ('trigger','Trigger'),
461 ('object_create','Create Object'),
462 ('object_copy','Copy Object'),
463 ('object_write','Write Object'),
464 ('other','Multi Actions'),
465 ], 'Action Type', required=True, size=32, help="Type of the Action that is to be executed"),
466 'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
467 "It is a Python block that can use the same values as for the condition field"),
468 '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."),
469 'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
470 'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
471 'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
472 'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),
473 '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)"),
474 '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"
475 "Example: object.invoice_address_id.email, or 'me@example.com'"),
476 '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 "
477 "available in the condition field, e.g. `Hello [[ object.partner_id.name ]]`"),
478 'message': fields.text('Message', translate=True, help="Email contents, may contain expressions enclosed in double brackets based on the same values as those "
479 "available in the condition field, e.g. `Dear [[ object.partner_id.name ]]`"),
480 '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"),
481 'sms': fields.char('SMS', size=160, translate=True),
482 'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', 'Other Actions'),
483 'usage': fields.char('Action Usage', size=32),
484 'type': fields.char('Action Type', size=32, required=True),
485 '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."),
486 'fields_lines': fields.one2many('ir.server.object.lines', 'server_id', 'Field Mappings.'),
487 '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."),
488 '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."),
489 '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."),
490 '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`."),
491 'copy_object': fields.reference('Copy Of', selection=_select_objects, size=256),
496 'type': 'ir.actions.server',
498 'code': """# You can use the following variables:
499 # - self: ORM model of the record on which the action is triggered
500 # - object: browse_record of the record on which the action is triggered if there is one, otherwise None
501 # - pool: ORM model pool (i.e. self.pool)
502 # - time: Python time module
503 # - cr: database cursor
504 # - uid: current user id
505 # - context: current context
506 # If you plan to return an action, assign: action = {...}
510 def get_email(self, cr, uid, action, context):
511 obj_pool = self.pool.get(action.model_id.model)
512 id = context.get('active_id')
513 obj = obj_pool.browse(cr, uid, id)
517 if '/' in action.email.complete_name:
518 fields = action.email.complete_name.split('/')
519 elif '.' in action.email.complete_name:
520 fields = action.email.complete_name.split('.')
524 obj = getattr(obj, field)
526 _logger.exception('Failed to parse: %s', field)
530 def get_mobile(self, cr, uid, action, context):
531 obj_pool = self.pool.get(action.model_id.model)
532 id = context.get('active_id')
533 obj = obj_pool.browse(cr, uid, id)
537 if '/' in action.mobile.complete_name:
538 fields = action.mobile.complete_name.split('/')
539 elif '.' in action.mobile.complete_name:
540 fields = action.mobile.complete_name.split('.')
544 obj = getattr(obj, field)
546 _logger.exception('Failed to parse: %s', field)
550 def merge_message(self, cr, uid, keystr, action, context=None):
555 obj_pool = self.pool.get(action.model_id.model)
556 id = context.get('active_id')
557 obj = obj_pool.browse(cr, uid, id)
558 exp = str(match.group()[2:-2]).strip()
562 'context': dict(context), # copy context to prevent side-effects of eval
565 if result in (None, False):
566 return str("--------")
567 return tools.ustr(result)
569 com = re.compile('(\[\[.+?\]\])')
570 message = com.sub(merge, keystr)
574 # Context should contains:
576 # id : current id of the object
578 # False : Finished correctly
579 # ACTION_ID : Action to launch
581 # FIXME: refactor all the eval() calls in run()!
582 def run(self, cr, uid, ids, context=None):
585 user = self.pool.get('res.users').browse(cr, uid, uid)
586 for action in self.browse(cr, uid, ids, context):
588 obj_pool = self.pool.get(action.model_id.model)
589 if context.get('active_model') == action.model_id.model and context.get('active_id'):
590 obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
598 'context': dict(context), # copy context to prevent side-effects of eval
602 expr = eval(str(action.condition), cxt)
606 if action.state=='client_action':
607 if not action.action_id:
608 raise osv.except_osv(_('Error'), _("Please specify an action to launch !"))
609 return self.pool.get(action.action_id.type)\
610 .read(cr, uid, action.action_id.id, context=context)
612 if action.state=='code':
613 eval(action.code, cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
617 if action.state == 'email':
618 email_from = config['email_from']
620 _logger.debug('--email-from command line option is not specified, using a fallback value instead.')
622 email_from = user.email
624 email_from = "%s@%s" % (user.login, gethostname())
627 address = eval(str(action.email), cxt)
629 address = str(action.email)
632 _logger.info('No partner email address specified, not sending any email.')
635 # handle single and multiple recipient addresses
636 addresses = address if isinstance(address, (tuple, list)) else [address]
637 subject = self.merge_message(cr, uid, action.subject, action, context)
638 body = self.merge_message(cr, uid, action.message, action, context)
640 ir_mail_server = self.pool.get('ir.mail_server')
641 msg = ir_mail_server.build_email(email_from, addresses, subject, body)
642 res_email = ir_mail_server.send_email(cr, uid, msg)
644 _logger.info('Email successfully sent to: %s', addresses)
646 _logger.warning('Failed to send email to: %s', addresses)
648 if action.state == 'trigger':
649 model = action.wkf_model_id.model
650 m2o_field_name = action.trigger_obj_id.name
651 target_id = obj_pool.read(cr, uid, context.get('active_id'), [m2o_field_name])[m2o_field_name]
652 target_id = target_id[0] if isinstance(target_id,tuple) else target_id
653 openerp.workflow.trg_validate(uid, model, int(target_id), action.trigger_name, cr)
655 if action.state == 'sms':
656 #TODO: set the user and password from the system
657 # for the sms gateway user / password
658 # USE smsclient module from extra-addons
659 _logger.warning('SMS Facility has not been implemented yet. Use smsclient module!')
661 if action.state == 'other':
663 for act in action.child_ids:
664 context['active_id'] = context['active_ids'][0]
665 result = self.run(cr, uid, [act.id], context)
670 if action.state == 'loop':
671 expr = eval(str(action.expression), cxt)
672 context['object'] = obj
674 context['active_id'] = i.id
675 self.run(cr, uid, [action.loop_action.id], context)
677 if action.state == 'object_write':
679 for exp in action.fields_lines:
681 if exp.type == 'equation':
682 expr = eval(euq, cxt)
685 res[exp.col1.name] = expr
687 if not action.write_id:
688 if not action.srcmodel_id:
689 obj_pool = self.pool.get(action.model_id.model)
690 obj_pool.write(cr, uid, [context.get('active_id')], res)
692 write_id = context.get('active_id')
693 obj_pool = self.pool.get(action.srcmodel_id.model)
694 obj_pool.write(cr, uid, [write_id], res)
696 elif action.write_id:
697 obj_pool = self.pool.get(action.srcmodel_id.model)
698 rec = self.pool.get(action.model_id.model).browse(cr, uid, context.get('active_id'))
699 id = eval(action.write_id, {'object': rec})
703 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
705 if type(id) != type(1):
706 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
708 obj_pool.write(cr, uid, [write_id], res)
710 if action.state == 'object_create':
712 for exp in action.fields_lines:
714 if exp.type == 'equation':
715 expr = eval(euq, cxt)
718 res[exp.col1.name] = expr
720 obj_pool = self.pool.get(action.srcmodel_id.model)
721 res_id = obj_pool.create(cr, uid, res)
723 self.pool.get(action.model_id.model).write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
725 if action.state == 'object_copy':
727 for exp in action.fields_lines:
729 if exp.type == 'equation':
730 expr = eval(euq, cxt)
733 res[exp.col1.name] = expr
735 model = action.copy_object.split(',')[0]
736 cid = action.copy_object.split(',')[1]
737 obj_pool = self.pool.get(model)
738 obj_pool.copy(cr, uid, int(cid), res)
744 class act_window_close(osv.osv):
745 _name = 'ir.actions.act_window_close'
746 _inherit = 'ir.actions.actions'
747 _table = 'ir_actions'
749 'type': 'ir.actions.act_window_close',
753 # This model use to register action services.
754 TODO_STATES = [('open', 'To Do'),
756 TODO_TYPES = [('manual', 'Launch Manually'),('once', 'Launch Manually Once'),
757 ('automatic', 'Launch Automatically')]
758 class ir_actions_todo(osv.osv):
760 Configuration Wizards
762 _name = 'ir.actions.todo'
763 _description = "Configuration Wizards"
765 'action_id': fields.many2one(
766 'ir.actions.actions', 'Action', select=True, required=True),
767 'sequence': fields.integer('Sequence'),
768 'state': fields.selection(TODO_STATES, string='Status', required=True),
769 'name': fields.char('Name', size=64),
770 'type': fields.selection(TODO_TYPES, 'Type', required=True,
771 help="""Manual: Launched manually.
772 Automatic: Runs whenever the system is reconfigured.
773 Launch Manually Once: after having been launched manually, it sets automatically to Done."""),
774 'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
775 'note': fields.text('Text', translate=True),
784 def action_launch(self, cr, uid, ids, context=None):
785 """ Launch Action of Wizard"""
786 wizard_id = ids and ids[0] or False
787 wizard = self.browse(cr, uid, wizard_id, context=context)
788 if wizard.type in ('automatic', 'once'):
789 wizard.write({'state': 'done'})
792 act_type = self.pool.get('ir.actions.actions').read(cr, uid, wizard.action_id.id, ['type'], context=context)
794 res = self.pool.get(act_type['type']).read(cr, uid, wizard.action_id.id, [], context=context)
795 if act_type['type'] != 'ir.actions.act_window':
797 res.setdefault('context','{}')
798 res['nodestroy'] = True
800 # Open a specific record when res_id is provided in the context
801 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
802 ctx = eval(res['context'], {'user': user})
803 if ctx.get('res_id'):
804 res.update({'res_id': ctx.pop('res_id')})
806 # disable log for automatic wizards
807 if wizard.type == 'automatic':
808 ctx.update({'disable_log': True})
809 res.update({'context': ctx})
813 def action_open(self, cr, uid, ids, context=None):
814 """ Sets configuration wizard in TODO state"""
815 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
817 def progress(self, cr, uid, context=None):
818 """ Returns a dict with 3 keys {todo, done, total}.
820 These keys all map to integers and provide the number of todos
821 marked as open, the total number of todos and the number of
822 todos not open (which is basically a shortcut to total-todo)
826 user_groups = set(map(
828 self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
829 def groups_match(todo):
830 """ Checks if the todo's groups match those of the current user
832 return not todo.groups_id \
833 or bool(user_groups.intersection((
834 group.id for group in todo.groups_id)))
839 self.search(cr, uid, [('state', '!=', 'open')], context=context),
845 self.search(cr, uid, [], context=context),
851 'todo': len(total) - len(done)
856 class act_client(osv.osv):
857 _name = 'ir.actions.client'
858 _inherit = 'ir.actions.actions'
859 _table = 'ir_act_client'
860 _sequence = 'ir_actions_id_seq'
863 def _get_params(self, cr, uid, ids, field_name, arg, context):
865 for record in self.browse(cr, uid, ids, context=context):
866 result[record.id] = record.params_store and eval(record.params_store, {'uid': uid}) or False
869 def _set_params(self, cr, uid, id, field_name, field_value, arg, context):
870 if isinstance(field_value, dict):
871 self.write(cr, uid, id, {'params_store': repr(field_value)}, context=context)
873 self.write(cr, uid, id, {'params_store': field_value}, context=context)
876 'name': fields.char('Action Name', required=True, size=64, translate=True),
877 'tag': fields.char('Client action tag', size=64, required=True,
878 help="An arbitrary string, interpreted by the client"
879 " according to its own needs and wishes. There "
880 "is no central tag repository across clients."),
881 'res_model': fields.char('Destination Model', size=64,
882 help="Optional model, mostly used for needactions."),
883 'context': fields.char('Context Value', size=250, required=True,
884 help="Context dictionary as Python expression, empty by default (Default: {})"),
885 'params': fields.function(_get_params, fnct_inv=_set_params,
887 string="Supplementary arguments",
888 help="Arguments sent to the client along with"
890 'params_store': fields.binary("Params storage", readonly=True)
893 'type': 'ir.actions.client',
899 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: