1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2014 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 from openerp.report.report_sxw import report_sxw, report_rml
32 from openerp.tools.config import config
33 from openerp.tools.safe_eval import safe_eval as eval
34 from openerp.tools.translate import _
36 _logger = logging.getLogger(__name__)
38 class actions(osv.osv):
39 _name = 'ir.actions.actions'
43 'name': fields.char('Name', size=64, required=True),
44 'type': fields.char('Action Type', required=True, size=32),
45 'usage': fields.char('Action Usage', size=32),
46 'help': fields.text('Action description',
47 help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
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'])
103 elif r['report_xsl'] and r['report_xml']:
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 _inherit = 'ir.actions.actions'
110 _table = 'ir_act_report_xml'
111 _sequence = 'ir_actions_id_seq'
114 'name': fields.char('Name', size=64, required=True, translate=True),
115 'model': fields.char('Object', size=64, required=True),
116 'type': fields.char('Action Type', size=32, required=True),
117 'report_name': fields.char('Service Name', size=64, required=True),
118 'usage': fields.char('Action Usage', size=32),
119 'report_type': fields.char('Report Type', size=32, required=True, help="Report Type, e.g. pdf, html, raw, sxw, odt, html2html, mako2html, ..."),
120 'groups_id': fields.many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', 'Groups'),
121 '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."),
122 '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.'),
123 '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.'),
124 'auto': fields.boolean('Custom Python Parser'),
126 'header': fields.boolean('Add RML Header', help="Add or not the corporate RML header"),
128 'report_xsl': fields.char('XSL Path', size=256),
129 'report_xml': fields.char('XML Path', size=256, help=''),
131 # Pending deprecation... to be replaced by report_file as this object will become the default report object (not so specific to RML anymore)
132 '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"),
133 # temporary related field as report_rml is pending deprecation - this field will replace report_rml after v6.0
134 '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),
136 'report_sxw': fields.function(_report_sxw, type='char', string='SXW Path'),
137 'report_sxw_content_data': fields.binary('SXW Content'),
138 'report_rml_content_data': fields.binary('RML Content'),
139 'report_sxw_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='SXW Content',),
140 'report_rml_content': fields.function(_report_content, fnct_inv=_report_content_inv, type='binary', string='RML Content'),
144 'type': 'ir.actions.report.xml',
148 'report_sxw_content': False,
149 'report_type': 'pdf',
155 class act_window(osv.osv):
156 _name = 'ir.actions.act_window'
157 _table = 'ir_act_window'
158 _inherit = 'ir.actions.actions'
159 _sequence = 'ir_actions_id_seq'
162 def _check_model(self, cr, uid, ids, context=None):
163 for action in self.browse(cr, uid, ids, context):
164 if not self.pool.get(action.res_model):
166 if action.src_model and not self.pool.get(action.src_model):
170 def _invalid_model_msg(self, cr, uid, ids, context=None):
171 return _('Invalid model name in the action definition.')
174 (_check_model, _invalid_model_msg, ['res_model','src_model'])
177 def _views_get_fnc(self, cr, uid, ids, name, arg, context=None):
178 """Returns an ordered list of the specific view modes that should be
179 enabled when displaying the result of this action, along with the
180 ID of the specific view to use for each mode, if any were required.
182 This function hides the logic of determining the precedence between
183 the view_modes string, the view_ids o2m, and the view_id m2o that can
184 be set on the action.
186 :rtype: dict in the form { action_id: list of pairs (tuples) }
187 :return: { action_id: [(view_id, view_mode), ...], ... }, where view_mode
188 is one of the possible values for ir.ui.view.type and view_id
189 is the ID of a specific view to use for this mode, or False for
193 for act in self.browse(cr, uid, ids):
194 res[act.id] = [(view.view_id.id, view.view_mode) for view in act.view_ids]
195 view_ids_modes = [view.view_mode for view in act.view_ids]
196 modes = act.view_mode.split(',')
197 missing_modes = [mode for mode in modes if mode not in view_ids_modes]
199 if act.view_id and act.view_id.type in missing_modes:
200 # reorder missing modes to put view_id first if present
201 missing_modes.remove(act.view_id.type)
202 res[act.id].append((act.view_id.id, act.view_id.type))
203 res[act.id].extend([(False, mode) for mode in missing_modes])
206 def _search_view(self, cr, uid, ids, name, arg, context=None):
208 for act in self.browse(cr, uid, ids, context=context):
209 field_get = self.pool[act.res_model].fields_view_get(cr, uid,
210 act.search_view_id and act.search_view_id.id or False,
211 'search', context=context)
212 res[act.id] = str(field_get)
216 'name': fields.char('Action Name', size=64, translate=True),
217 'type': fields.char('Action Type', size=32, required=True),
218 'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
219 'domain': fields.char('Domain Value', size=250,
220 help="Optional domain filtering of the destination data, as a Python expression"),
221 'context': fields.char('Context Value', size=250, required=True,
222 help="Context dictionary as Python expression, empty by default (Default: {})"),
223 'res_id': fields.integer('Record ID', help="Database ID of record to open in form view, when ``view_mode`` is set to 'form' only"),
224 'res_model': fields.char('Destination Model', size=64, required=True,
225 help="Model name of the object to open in the view window"),
226 'src_model': fields.char('Source Model', size=64,
227 help="Optional model name of the objects on which this action should be visible"),
228 'target': fields.selection([('current','Current Window'),('new','New Window'),('inline','Inline Edit'),('inlineview','Inline View')], 'Target Window'),
229 'view_mode': fields.char('View Mode', size=250, required=True,
230 help="Comma-separated list of allowed view modes, such as 'form', 'tree', 'calendar', etc. (Default: tree,form)"),
231 'view_type': fields.selection((('tree','Tree'),('form','Form')), string='View Type', required=True,
232 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"),
233 'usage': fields.char('Action Usage', size=32,
234 help="Used to filter menu and home actions from the user form."),
235 'view_ids': fields.one2many('ir.actions.act_window.view', 'act_window_id', 'Views'),
236 'views': fields.function(_views_get_fnc, type='binary', string='Views',
237 help="This function field computes the ordered list of views that should be enabled " \
238 "when displaying the result of an action, federating view mode, views and " \
239 "reference view. The result is returned as an ordered list of pairs (view_id,view_mode)."),
240 'limit': fields.integer('Limit', help='Default limit for the list view'),
241 'auto_refresh': fields.integer('Auto-Refresh',
242 help='Add an auto-refresh on the view'),
243 'groups_id': fields.many2many('res.groups', 'ir_act_window_group_rel',
244 'act_id', 'gid', 'Groups'),
245 'search_view_id': fields.many2one('ir.ui.view', 'Search View Ref.'),
246 'filter': fields.boolean('Filter'),
247 'auto_search':fields.boolean('Auto Search'),
248 'search_view' : fields.function(_search_view, type='text', string='Search View'),
249 '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"),
253 'type': 'ir.actions.act_window',
255 'view_mode': 'tree,form',
264 def for_xml_id(self, cr, uid, module, xml_id, context=None):
265 """ Returns the act_window object created for the provided xml_id
267 :param module: the module the act_window originates in
268 :param xml_id: the namespace-less id of the action (the @id
269 attribute from the XML file)
270 :return: A read() view of the ir.actions.act_window
272 dataobj = self.pool.get('ir.model.data')
273 data_id = dataobj._get_id (cr, SUPERUSER_ID, module, xml_id)
274 res_id = dataobj.browse(cr, uid, data_id, context).res_id
275 return self.read(cr, uid, res_id, [], context)
283 ('calendar', 'Calendar'),
285 ('kanban', 'Kanban')]
286 class act_window_view(osv.osv):
287 _name = 'ir.actions.act_window.view'
288 _table = 'ir_act_window_view'
289 _rec_name = 'view_id'
292 'sequence': fields.integer('Sequence'),
293 'view_id': fields.many2one('ir.ui.view', 'View'),
294 'view_mode': fields.selection(VIEW_TYPES, string='View Type', required=True),
295 'act_window_id': fields.many2one('ir.actions.act_window', 'Action', ondelete='cascade'),
296 'multi': fields.boolean('On Multiple Doc.',
297 help="If set to true, the action will not be displayed on the right toolbar of a form view."),
302 def _auto_init(self, cr, context=None):
303 super(act_window_view, self)._auto_init(cr, context)
304 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'act_window_view_unique_mode_per_action\'')
305 if not cr.fetchone():
306 cr.execute('CREATE UNIQUE INDEX act_window_view_unique_mode_per_action ON ir_act_window_view (act_window_id, view_mode)')
309 class act_wizard(osv.osv):
310 _name = 'ir.actions.wizard'
311 _inherit = 'ir.actions.actions'
312 _table = 'ir_act_wizard'
313 _sequence = 'ir_actions_id_seq'
316 'name': fields.char('Wizard Info', size=64, required=True, translate=True),
317 'type': fields.char('Action Type', size=32, required=True),
318 'wiz_name': fields.char('Wizard Name', size=64, required=True),
319 '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."),
320 'groups_id': fields.many2many('res.groups', 'res_groups_wizard_rel', 'uid', 'gid', 'Groups'),
321 'model': fields.char('Object', size=64),
324 'type': 'ir.actions.wizard',
329 class act_url(osv.osv):
330 _name = 'ir.actions.act_url'
331 _table = 'ir_act_url'
332 _inherit = 'ir.actions.actions'
333 _sequence = 'ir_actions_id_seq'
336 'name': fields.char('Action Name', size=64, translate=True),
337 'type': fields.char('Action Type', size=32, required=True),
338 'url': fields.text('Action URL',required=True),
339 'target': fields.selection((
340 ('new', 'New Window'),
341 ('self', 'This Window')),
342 'Action Target', required=True
346 'type': 'ir.actions.act_url',
351 def model_get(self, cr, uid, context=None):
352 wkf_pool = self.pool.get('workflow')
353 ids = wkf_pool.search(cr, uid, [])
354 osvs = wkf_pool.read(cr, uid, ids, ['osv'])
357 mpool = self.pool.get('ir.model')
359 model = osv.get('osv')
360 id = mpool.search(cr, uid, [('model','=',model)])
361 name = mpool.read(cr, uid, id)[0]['name']
362 res.append((model, name))
366 class ir_model_fields(osv.osv):
367 _inherit = 'ir.model.fields'
368 _rec_name = 'field_description'
370 'complete_name': fields.char('Complete Name', size=64, select=1),
374 class server_object_lines(osv.osv):
375 _name = 'ir.server.object.lines'
376 _sequence = 'ir_actions_id_seq'
378 'server_id': fields.many2one('ir.actions.server', 'Object Mapping'),
379 'col1': fields.many2one('ir.model.fields', 'Destination', required=True),
380 'value': fields.text('Value', required=True, help="Expression containing a value specification. \n"
381 "When Formula type is selected, this field may be a Python expression "
382 " that can use the same values as for the condition field on the server action.\n"
383 "If Value type is selected, the value will be used directly without evaluation."),
384 'type': fields.selection([
386 ('equation','Formula')
387 ], 'Type', required=True, size=32, change_default=True),
392 server_object_lines()
395 # Actions that are run on the server side
397 class actions_server(osv.osv):
399 def _select_signals(self, cr, uid, context=None):
400 cr.execute("""SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t
401 WHERE w.id = a.wkf_id AND
402 (t.act_from = a.id OR t.act_to = a.id) AND
403 t.signal IS NOT NULL""")
404 result = cr.fetchall() or []
407 if rs[0] is not None and rs[1] is not None:
408 line = rs[1], "%s - (%s)" % (rs[1], rs[0])
412 def _select_objects(self, cr, uid, context=None):
413 model_pool = self.pool.get('ir.model')
414 ids = model_pool.search(cr, uid, [], limit=None)
415 res = model_pool.read(cr, uid, ids, ['model', 'name'])
416 return [(r['model'], r['name']) for r in res] + [('','')]
418 def change_object(self, cr, uid, ids, copy_object, state, context=None):
419 if state == 'object_copy' and copy_object:
422 model_pool = self.pool.get('ir.model')
423 model = copy_object.split(',')[0]
424 mid = model_pool.search(cr, uid, [('model','=',model)])
426 'value': {'srcmodel_id': mid[0]},
432 _name = 'ir.actions.server'
433 _table = 'ir_act_server'
434 _inherit = 'ir.actions.actions'
435 _sequence = 'ir_actions_id_seq'
436 _order = 'sequence,name'
438 'name': fields.char('Action Name', required=True, size=64, translate=True),
439 'condition' : fields.char('Condition', size=256, required=True,
440 help="Condition that is tested before the action is executed, "
441 "and prevent execution if it is not verified.\n"
442 "Example: object.list_price > 5000\n"
443 "It is a Python expression that can use the following values:\n"
444 " - self: ORM model of the record on which the action is triggered\n"
445 " - object or obj: browse_record of the record on which the action is triggered\n"
446 " - pool: ORM model pool (i.e. self.pool)\n"
447 " - time: Python time module\n"
448 " - cr: database cursor\n"
449 " - uid: current user id\n"
450 " - context: current context"),
451 'state': fields.selection([
452 ('client_action','Client Action'),
454 ('loop','Iteration'),
455 ('code','Python Code'),
456 ('trigger','Trigger'),
459 ('object_create','Create Object'),
460 ('object_copy','Copy Object'),
461 ('object_write','Write Object'),
462 ('other','Multi Actions'),
463 ], 'Action Type', required=True, size=32, help="Type of the Action that is to be executed"),
464 'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
465 "It is a Python block that can use the same values as for the condition field"),
466 '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."),
467 'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
468 'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
469 'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
470 'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),
471 '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)"),
472 '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"
473 "Example: object.invoice_address_id.email, or 'me@example.com'"),
474 '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 "
475 "available in the condition field, e.g. `Hello [[ object.partner_id.name ]]`"),
476 'message': fields.text('Message', translate=True, help="Email contents, may contain expressions enclosed in double brackets based on the same values as those "
477 "available in the condition field, e.g. `Dear [[ object.partner_id.name ]]`"),
478 '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"),
479 'sms': fields.char('SMS', size=160, translate=True),
480 'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', 'Other Actions'),
481 'usage': fields.char('Action Usage', size=32),
482 'type': fields.char('Action Type', size=32, required=True),
483 '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."),
484 'fields_lines': fields.one2many('ir.server.object.lines', 'server_id', 'Field Mappings.'),
485 '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."),
486 '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."),
487 '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."),
488 '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`."),
489 'copy_object': fields.reference('Copy Of', selection=_select_objects, size=256),
494 'type': 'ir.actions.server',
496 'code': """# You can use the following variables:
497 # - self: ORM model of the record on which the action is triggered
498 # - object: browse_record of the record on which the action is triggered if there is one, otherwise None
499 # - pool: ORM model pool (i.e. self.pool)
500 # - time: Python time module
501 # - cr: database cursor
502 # - uid: current user id
503 # - context: current context
504 # If you plan to return an action, assign: action = {...}
508 def get_email(self, cr, uid, action, context):
509 obj_pool = self.pool.get(action.model_id.model)
510 id = context.get('active_id')
511 obj = obj_pool.browse(cr, uid, id)
515 if '/' in action.email.complete_name:
516 fields = action.email.complete_name.split('/')
517 elif '.' in action.email.complete_name:
518 fields = action.email.complete_name.split('.')
522 obj = getattr(obj, field)
524 _logger.exception('Failed to parse: %s', field)
528 def get_mobile(self, cr, uid, action, context):
529 obj_pool = self.pool.get(action.model_id.model)
530 id = context.get('active_id')
531 obj = obj_pool.browse(cr, uid, id)
535 if '/' in action.mobile.complete_name:
536 fields = action.mobile.complete_name.split('/')
537 elif '.' in action.mobile.complete_name:
538 fields = action.mobile.complete_name.split('.')
542 obj = getattr(obj, field)
544 _logger.exception('Failed to parse: %s', field)
548 def merge_message(self, cr, uid, keystr, action, context=None):
553 obj_pool = self.pool.get(action.model_id.model)
554 id = context.get('active_id')
555 obj = obj_pool.browse(cr, uid, id)
556 exp = str(match.group()[2:-2]).strip()
560 'context': dict(context), # copy context to prevent side-effects of eval
563 if result in (None, False):
564 return str("--------")
565 return tools.ustr(result)
567 com = re.compile('(\[\[.+?\]\])')
568 message = com.sub(merge, keystr)
572 # Context should contains:
574 # id : current id of the object
576 # False : Finished correctly
577 # ACTION_ID : Action to launch
579 # FIXME: refactor all the eval() calls in run()!
580 def run(self, cr, uid, ids, context=None):
583 user = self.pool.get('res.users').browse(cr, uid, uid)
584 for action in self.browse(cr, uid, ids, context):
586 obj_pool = self.pool.get(action.model_id.model)
587 if context.get('active_model') == action.model_id.model and context.get('active_id'):
588 obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
596 'context': dict(context), # copy context to prevent side-effects of eval
600 expr = eval(str(action.condition), cxt)
604 if action.state=='client_action':
605 if not action.action_id:
606 raise osv.except_osv(_('Error'), _("Please specify an action to launch!"))
607 return self.pool.get(action.action_id.type)\
608 .read(cr, uid, action.action_id.id, context=context)
610 if action.state=='code':
611 eval(action.code.strip(), cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
615 if action.state == 'email':
616 email_from = config['email_from']
618 _logger.debug('--email-from command line option is not specified, using a fallback value instead.')
620 email_from = user.email
622 email_from = "%s@%s" % (user.login, gethostname())
625 address = eval(str(action.email), cxt)
627 address = str(action.email)
630 _logger.info('No partner email address specified, not sending any email.')
633 # handle single and multiple recipient addresses
634 addresses = address if isinstance(address, (tuple, list)) else [address]
635 subject = self.merge_message(cr, uid, action.subject, action, context)
636 body = self.merge_message(cr, uid, action.message, action, context)
638 ir_mail_server = self.pool.get('ir.mail_server')
639 msg = ir_mail_server.build_email(email_from, addresses, subject, body)
640 res_email = ir_mail_server.send_email(cr, uid, msg)
642 _logger.info('Email successfully sent to: %s', addresses)
644 _logger.warning('Failed to send email to: %s', addresses)
646 if action.state == 'trigger':
647 wf_service = netsvc.LocalService("workflow")
648 model = action.wkf_model_id.model
649 m2o_field_name = action.trigger_obj_id.name
650 target_id = obj_pool.read(cr, uid, context.get('active_id'), [m2o_field_name])[m2o_field_name]
651 target_id = target_id[0] if isinstance(target_id,tuple) else target_id
652 wf_service.trg_validate(uid, model, int(target_id), action.trigger_name, cr)
654 if action.state == 'sms':
655 #TODO: set the user and password from the system
656 # for the sms gateway user / password
657 # USE smsclient module from extra-addons
658 _logger.warning('SMS Facility has not been implemented yet. Use smsclient module!')
660 if action.state == 'other':
662 for act in action.child_ids:
663 context['active_id'] = context['active_ids'][0]
664 result = self.run(cr, uid, [act.id], context)
669 if action.state == 'loop':
670 expr = eval(str(action.expression), cxt)
671 context['object'] = obj
673 context['active_id'] = i.id
674 self.run(cr, uid, [action.loop_action.id], context)
676 if action.state == 'object_write':
678 for exp in action.fields_lines:
680 if exp.type == 'equation':
681 expr = eval(euq, cxt)
684 res[exp.col1.name] = expr
686 if not action.write_id:
687 if not action.srcmodel_id:
688 obj_pool = self.pool.get(action.model_id.model)
689 obj_pool.write(cr, uid, [context.get('active_id')], res)
691 write_id = context.get('active_id')
692 obj_pool = self.pool.get(action.srcmodel_id.model)
693 obj_pool.write(cr, uid, [write_id], res)
695 elif action.write_id:
696 obj_pool = self.pool.get(action.srcmodel_id.model)
697 rec = self.pool.get(action.model_id.model).browse(cr, uid, context.get('active_id'))
698 id = eval(action.write_id, {'object': rec})
702 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
704 if type(id) != type(1):
705 raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
707 obj_pool.write(cr, uid, [write_id], res)
709 if action.state == 'object_create':
711 for exp in action.fields_lines:
713 if exp.type == 'equation':
714 expr = eval(euq, cxt)
717 res[exp.col1.name] = expr
719 obj_pool = self.pool.get(action.srcmodel_id.model)
720 res_id = obj_pool.create(cr, uid, res)
722 self.pool.get(action.model_id.model).write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
724 if action.state == 'object_copy':
726 for exp in action.fields_lines:
728 if exp.type == 'equation':
729 expr = eval(euq, cxt)
732 res[exp.col1.name] = expr
734 model = action.copy_object.split(',')[0]
735 cid = action.copy_object.split(',')[1]
736 obj_pool = self.pool.get(model)
737 obj_pool.copy(cr, uid, int(cid), res)
743 class act_window_close(osv.osv):
744 _name = 'ir.actions.act_window_close'
745 _inherit = 'ir.actions.actions'
746 _table = 'ir_actions'
748 'type': 'ir.actions.act_window_close',
752 # This model use to register action services.
753 TODO_STATES = [('open', 'To Do'),
755 TODO_TYPES = [('manual', 'Launch Manually'),('once', 'Launch Manually Once'),
756 ('automatic', 'Launch Automatically')]
757 class ir_actions_todo(osv.osv):
759 Configuration Wizards
761 _name = 'ir.actions.todo'
762 _description = "Configuration Wizards"
764 'action_id': fields.many2one(
765 'ir.actions.actions', 'Action', select=True, required=True),
766 'sequence': fields.integer('Sequence'),
767 'state': fields.selection(TODO_STATES, string='Status', required=True),
768 'name': fields.char('Name', size=64),
769 'type': fields.selection(TODO_TYPES, 'Type', required=True,
770 help="""Manual: Launched manually.
771 Automatic: Runs whenever the system is reconfigured.
772 Launch Manually Once: after having been launched manually, it sets automatically to Done."""),
773 'groups_id': fields.many2many('res.groups', 'res_groups_action_rel', 'uid', 'gid', 'Groups'),
774 'note': fields.text('Text', translate=True),
783 def action_launch(self, cr, uid, ids, context=None):
784 """ Launch Action of Wizard"""
785 wizard_id = ids and ids[0] or False
786 wizard = self.browse(cr, uid, wizard_id, context=context)
787 if wizard.type in ('automatic', 'once'):
788 wizard.write({'state': 'done'})
791 act_type = self.pool.get('ir.actions.actions').read(cr, uid, wizard.action_id.id, ['type'], context=context)
793 res = self.pool.get(act_type['type']).read(cr, uid, wizard.action_id.id, [], context=context)
794 if act_type['type'] != 'ir.actions.act_window':
796 res.setdefault('context','{}')
797 res['nodestroy'] = True
799 # Open a specific record when res_id is provided in the context
800 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
801 ctx = eval(res['context'], {'user': user})
802 if ctx.get('res_id'):
803 res.update({'res_id': ctx.pop('res_id')})
805 # disable log for automatic wizards
806 if wizard.type == 'automatic':
807 ctx.update({'disable_log': True})
808 res.update({'context': ctx})
812 def action_open(self, cr, uid, ids, context=None):
813 """ Sets configuration wizard in TODO state"""
814 return self.write(cr, uid, ids, {'state': 'open'}, context=context)
816 def progress(self, cr, uid, context=None):
817 """ Returns a dict with 3 keys {todo, done, total}.
819 These keys all map to integers and provide the number of todos
820 marked as open, the total number of todos and the number of
821 todos not open (which is basically a shortcut to total-todo)
825 user_groups = set(map(
827 self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
828 def groups_match(todo):
829 """ Checks if the todo's groups match those of the current user
831 return not todo.groups_id \
832 or bool(user_groups.intersection((
833 group.id for group in todo.groups_id)))
838 self.search(cr, uid, [('state', '!=', 'open')], context=context),
844 self.search(cr, uid, [], context=context),
850 'todo': len(total) - len(done)
855 class act_client(osv.osv):
856 _name = 'ir.actions.client'
857 _inherit = 'ir.actions.actions'
858 _table = 'ir_act_client'
859 _sequence = 'ir_actions_id_seq'
862 def _get_params(self, cr, uid, ids, field_name, arg, context):
864 for record in self.browse(cr, uid, ids, context=context):
865 result[record.id] = record.params_store and eval(record.params_store, {'uid': uid}) or False
868 def _set_params(self, cr, uid, id, field_name, field_value, arg, context):
869 if isinstance(field_value, dict):
870 self.write(cr, uid, id, {'params_store': repr(field_value)}, context=context)
872 self.write(cr, uid, id, {'params_store': field_value}, context=context)
875 'name': fields.char('Action Name', required=True, size=64, translate=True),
876 'tag': fields.char('Client action tag', size=64, required=True,
877 help="An arbitrary string, interpreted by the client"
878 " according to its own needs and wishes. There "
879 "is no central tag repository across clients."),
880 'res_model': fields.char('Destination Model', size=64,
881 help="Optional model, mostly used for needactions."),
882 'context': fields.char('Context Value', size=250, required=True,
883 help="Context dictionary as Python expression, empty by default (Default: {})"),
884 'params': fields.function(_get_params, fnct_inv=_set_params,
886 string="Supplementary arguments",
887 help="Arguments sent to the client along with"
889 'params_store': fields.binary("Params storage", readonly=True)
892 'type': 'ir.actions.client',
898 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: