~ubuntu-branches/ubuntu/quantal/openerp6.1/quantal

« back to all changes in this revision

Viewing changes to openerp/addons/base/ir/ir_actions.py

  • Committer: Package Import Robot
  • Author(s): Yolanda Robla
  • Date: 2012-09-20 15:29:00 UTC
  • Revision ID: package-import@ubuntu.com-20120920152900-woyy3yww8z6acmsk
Tags: upstream-6.1-1+dfsg
ImportĀ upstreamĀ versionĀ 6.1-1+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    OpenERP, Open Source Management Solution
 
5
#    Copyright (C) 2004-2011 OpenERP S.A. <http://www.openerp.com>
 
6
#
 
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.
 
11
#
 
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.
 
16
#
 
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/>.
 
19
#
 
20
##############################################################################
 
21
 
 
22
import ast
 
23
import copy
 
24
import logging
 
25
import os
 
26
import re
 
27
import time
 
28
import tools
 
29
from xml import dom
 
30
 
 
31
import netsvc
 
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
 
38
 
 
39
_logger = logging.getLogger(__name__)
 
40
 
 
41
class actions(osv.osv):
 
42
    _name = 'ir.actions.actions'
 
43
    _table = 'ir_actions'
 
44
    _order = 'name'
 
45
    _columns = {
 
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),
 
49
    }
 
50
    _defaults = {
 
51
        'usage': lambda *a: False,
 
52
    }
 
53
actions()
 
54
 
 
55
 
 
56
class report_xml(osv.osv):
 
57
 
 
58
    def _report_content(self, cursor, user, ids, name, arg, context=None):
 
59
        res = {}
 
60
        for report in self.browse(cursor, user, ids, context=context):
 
61
            data = report[name + '_data']
 
62
            if not data and report[name[:-8]]:
 
63
                fp = None
 
64
                try:
 
65
                    fp = tools.file_open(report[name[:-8]], mode='rb')
 
66
                    data = fp.read()
 
67
                except:
 
68
                    data = False
 
69
                finally:
 
70
                    if fp:
 
71
                        fp.close()
 
72
            res[report.id] = data
 
73
        return res
 
74
 
 
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)
 
77
 
 
78
    def _report_sxw(self, cursor, user, ids, name, arg, context=None):
 
79
        res = {}
 
80
        for report in self.browse(cursor, user, ids, context=context):
 
81
            if report.report_rml:
 
82
                res[report.id] = report.report_rml.replace('.rml', '.sxw')
 
83
            else:
 
84
                res[report.id] = False
 
85
        return res
 
86
 
 
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.
 
92
        """
 
93
        opj = os.path.join
 
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
 
97
        for r in result:
 
98
            if svcs.has_key('report.'+r['report_name']):
 
99
                continue
 
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
            if r['report_xsl']:
 
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']))
 
107
 
 
108
    _name = 'ir.actions.report.xml'
 
109
    _table = 'ir_act_report_xml'
 
110
    _sequence = 'ir_actions_id_seq'
 
111
    _order = 'name'
 
112
    _columns = {
 
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'),
 
124
 
 
125
        'header': fields.boolean('Add RML header', help="Add or not the corporate RML header"),
 
126
 
 
127
        'report_xsl': fields.char('XSL path', size=256),
 
128
        'report_xml': fields.char('XML path', size=256, help=''),
 
129
 
 
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),
 
134
 
 
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'),
 
140
 
 
141
    }
 
142
    _defaults = {
 
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,
 
150
    }
 
151
 
 
152
report_xml()
 
153
 
 
154
class act_window(osv.osv):
 
155
    _name = 'ir.actions.act_window'
 
156
    _table = 'ir_act_window'
 
157
    _sequence = 'ir_actions_id_seq'
 
158
    _order = 'name'
 
159
 
 
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):
 
163
                return False
 
164
            if action.src_model and not self.pool.get(action.src_model):
 
165
                return False
 
166
        return True
 
167
 
 
168
    def _invalid_model_msg(self, cr, uid, ids, context=None):
 
169
        return _('Invalid model name in the action definition.')
 
170
 
 
171
    _constraints = [
 
172
        (_check_model, _invalid_model_msg, ['res_model','src_model'])
 
173
    ]
 
174
 
 
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.
 
179
 
 
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.
 
183
 
 
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
 
188
                    the default one.
 
189
        """
 
190
        res = {}
 
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]
 
196
            if missing_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])
 
202
        return res
 
203
 
 
204
    def _search_view(self, cr, uid, ids, name, arg, context=None):
 
205
        res = {}
 
206
        def encode(s):
 
207
            if isinstance(s, unicode):
 
208
                return s.encode('utf8')
 
209
            return s
 
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
 
215
            else:
 
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)
 
219
                if res_view:
 
220
                    search_view_id = res_view[0]
 
221
            if search_view_id:
 
222
                field_get = self.pool.get(act.res_model).fields_view_get(cr, uid, search_view_id,
 
223
                            'search', context)
 
224
                fields_from_fields_get.update(field_get['fields'])
 
225
                field_get['fields'] = fields_from_fields_get
 
226
                res[act.id] = str(field_get)
 
227
            else:
 
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':
 
232
                            if child.childNodes:
 
233
                                fld = doc.createElement('field')
 
234
                                for attr in child.attributes.keys():
 
235
                                    fld.setAttribute(attr, child.getAttribute(attr))
 
236
                                new_node.appendChild(fld)
 
237
                            else:
 
238
                                new_node.appendChild(child)
 
239
                        elif child.localName in ('page','group','notebook'):
 
240
                            process_child(child, new_node, doc)
 
241
 
 
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)
 
249
 
 
250
                form_arch['arch'] = new_node.toxml()
 
251
                form_arch['fields'].update(fields_from_fields_get)
 
252
                res[act.id] = str(form_arch)
 
253
        return res
 
254
 
 
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])
 
258
 
 
259
    _columns = {
 
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.',
 
294
            translate=True),
 
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"),
 
298
    }
 
299
 
 
300
    _defaults = {
 
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,
 
309
        'multi': False,
 
310
    }
 
311
 
 
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
 
314
 
 
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
 
319
        """
 
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)
 
324
 
 
325
act_window()
 
326
 
 
327
VIEW_TYPES = [
 
328
    ('tree', 'Tree'),
 
329
    ('form', 'Form'),
 
330
    ('graph', 'Graph'),
 
331
    ('calendar', 'Calendar'),
 
332
    ('gantt', 'Gantt'),
 
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'
 
338
    _order = 'sequence'
 
339
    _columns = {
 
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."),
 
346
    }
 
347
    _defaults = {
 
348
        'multi': lambda *a: False,
 
349
    }
 
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)')
 
355
act_window_view()
 
356
 
 
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'
 
362
    _order = 'name'
 
363
    _columns = {
 
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),
 
370
    }
 
371
    _defaults = {
 
372
        'type': lambda *a: 'ir.actions.wizard',
 
373
        'multi': lambda *a: False,
 
374
    }
 
375
act_wizard()
 
376
 
 
377
class act_url(osv.osv):
 
378
    _name = 'ir.actions.url'
 
379
    _table = 'ir_act_url'
 
380
    _sequence = 'ir_actions_id_seq'
 
381
    _order = 'name'
 
382
    _columns = {
 
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
 
390
        )
 
391
    }
 
392
    _defaults = {
 
393
        'type': lambda *a: 'ir.actions.act_url',
 
394
        'target': lambda *a: 'new'
 
395
    }
 
396
act_url()
 
397
 
 
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'])
 
402
 
 
403
    res = []
 
404
    mpool = self.pool.get('ir.model')
 
405
    for osv in osvs:
 
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))
 
410
 
 
411
    return res
 
412
 
 
413
class ir_model_fields(osv.osv):
 
414
    _inherit = 'ir.model.fields'
 
415
    _rec_name = 'field_description'
 
416
    _columns = {
 
417
        'complete_name': fields.char('Complete Name', size=64, select=1),
 
418
    }
 
419
ir_model_fields()
 
420
 
 
421
class server_object_lines(osv.osv):
 
422
    _name = 'ir.server.object.lines'
 
423
    _sequence = 'ir_actions_id_seq'
 
424
    _columns = {
 
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([
 
432
            ('value','Value'),
 
433
            ('equation','Formula')
 
434
        ], 'Type', required=True, size=32, change_default=True),
 
435
    }
 
436
    _defaults = {
 
437
        'type': lambda *a: 'equation',
 
438
    }
 
439
server_object_lines()
 
440
 
 
441
##
 
442
# Actions that are run on the server side
 
443
#
 
444
class actions_server(osv.osv):
 
445
 
 
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 []
 
452
        res = []
 
453
        for rs in result:
 
454
            if rs[0] is not None and rs[1] is not None:
 
455
                line = rs[1], "%s - (%s)" % (rs[1], rs[0])
 
456
                res.append(line)
 
457
        return res
 
458
 
 
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] +  [('','')]
 
464
 
 
465
    def change_object(self, cr, uid, ids, copy_object, state, context=None):
 
466
        if state == 'object_copy' and copy_object:
 
467
            if context is None:
 
468
                context = {}
 
469
            model_pool = self.pool.get('ir.model')
 
470
            model = copy_object.split(',')[0]
 
471
            mid = model_pool.search(cr, uid, [('model','=',model)])
 
472
            return {
 
473
                'value': {'srcmodel_id': mid[0]},
 
474
                'context': context
 
475
            }
 
476
        else:
 
477
            return {}
 
478
 
 
479
    _name = 'ir.actions.server'
 
480
    _table = 'ir_act_server'
 
481
    _sequence = 'ir_actions_id_seq'
 
482
    _order = 'sequence,name'
 
483
    _columns = {
 
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'),
 
499
            ('dummy','Dummy'),
 
500
            ('loop','Iteration'),
 
501
            ('code','Python Code'),
 
502
            ('trigger','Trigger'),
 
503
            ('email','Email'),
 
504
            ('sms','SMS'),
 
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),
 
536
    }
 
537
    _defaults = {
 
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 = {...}
 
551
""",
 
552
    }
 
553
 
 
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)
 
558
 
 
559
        fields = None
 
560
 
 
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('.')
 
565
 
 
566
        for field in fields:
 
567
            try:
 
568
                obj = getattr(obj, field)
 
569
            except Exception:
 
570
                _logger.exception('Failed to parse: %s', field)
 
571
 
 
572
        return obj
 
573
 
 
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)
 
578
 
 
579
        fields = None
 
580
 
 
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('.')
 
585
 
 
586
        for field in fields:
 
587
            try:
 
588
                obj = getattr(obj, field)
 
589
            except Exception:
 
590
                _logger.exception('Failed to parse: %s', field)
 
591
 
 
592
        return obj
 
593
 
 
594
    def merge_message(self, cr, uid, keystr, action, context=None):
 
595
        if context is None:
 
596
            context = {}
 
597
 
 
598
        def merge(match):
 
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()
 
603
            result = eval(exp,
 
604
                          {
 
605
                            'object': obj,
 
606
                            'context': dict(context), # copy context to prevent side-effects of eval
 
607
                            'time': time,
 
608
                          })
 
609
            if result in (None, False):
 
610
                return str("--------")
 
611
            return tools.ustr(result)
 
612
 
 
613
        com = re.compile('(\[\[.+?\]\])')
 
614
        message = com.sub(merge, keystr)
 
615
 
 
616
        return message
 
617
 
 
618
    # Context should contains:
 
619
    #   ids : original ids
 
620
    #   id  : current id of the object
 
621
    # OUT:
 
622
    #   False : Finnished correctly
 
623
    #   ACTION_ID : Action to launch
 
624
 
 
625
    # FIXME: refactor all the eval() calls in run()!
 
626
    def run(self, cr, uid, ids, context=None):
 
627
        if context is None:
 
628
            context = {}
 
629
        user = self.pool.get('res.users').browse(cr, uid, uid)
 
630
        for action in self.browse(cr, uid, ids, context):
 
631
            obj = None
 
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)
 
635
            cxt = {
 
636
                'self': obj_pool,
 
637
                'object': obj,
 
638
                'obj': obj,
 
639
                'pool': self.pool,
 
640
                'time': time,
 
641
                'cr': cr,
 
642
                'context': dict(context), # copy context to prevent side-effects of eval
 
643
                'uid': uid,
 
644
                'user': user
 
645
            }
 
646
            expr = eval(str(action.condition), cxt)
 
647
            if not expr:
 
648
                continue
 
649
 
 
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)
 
655
 
 
656
            if action.state=='code':
 
657
                eval(action.code, cxt, mode="exec", nocopy=True) # nocopy allows to return 'action'
 
658
                if 'action' in cxt:
 
659
                    return cxt['action']
 
660
 
 
661
            if action.state == 'email':
 
662
                email_from = config['email_from']
 
663
                address = str(action.email)
 
664
                try:
 
665
                    address =  eval(str(action.email), cxt)
 
666
                except:
 
667
                    pass
 
668
 
 
669
                if not address:
 
670
                    _logger.info('No partner email address specified, not sending any email.')
 
671
                    continue
 
672
 
 
673
                if not email_from:
 
674
                    _logger.debug('--email-from command line option is not specified, using a fallback value instead.')
 
675
                    if user.user_email:
 
676
                        email_from = user.user_email
 
677
                    else:
 
678
                        email_from = "%s@%s" % (user.login, gethostname())
 
679
 
 
680
                subject = self.merge_message(cr, uid, action.subject, action, context)
 
681
                body = self.merge_message(cr, uid, action.message, action, context)
 
682
 
 
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)
 
686
                if res_email:
 
687
                    _logger.info('Email successfully sent to: %s', address)
 
688
                else:
 
689
                    _logger.warning('Failed to send email to: %s', address)
 
690
 
 
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)
 
698
 
 
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!')
 
704
 
 
705
            if action.state == 'other':
 
706
                res = []
 
707
                for act in action.child_ids:
 
708
                    context['active_id'] = context['active_ids'][0]
 
709
                    result = self.run(cr, uid, [act.id], context)
 
710
                    if result:
 
711
                        res.append(result)
 
712
                return res
 
713
 
 
714
            if action.state == 'loop':
 
715
                expr = eval(str(action.expression), cxt)
 
716
                context['object'] = obj
 
717
                for i in expr:
 
718
                    context['active_id'] = i.id
 
719
                    result = self.run(cr, uid, [action.loop_action.id], context)
 
720
 
 
721
            if action.state == 'object_write':
 
722
                res = {}
 
723
                for exp in action.fields_lines:
 
724
                    euq = exp.value
 
725
                    if exp.type == 'equation':
 
726
                        expr = eval(euq, cxt)
 
727
                    else:
 
728
                        expr = exp.value
 
729
                    res[exp.col1.name] = expr
 
730
 
 
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)
 
735
                    else:
 
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)
 
739
 
 
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})
 
744
                    try:
 
745
                        id = int(id)
 
746
                    except:
 
747
                        raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
 
748
 
 
749
                    if type(id) != type(1):
 
750
                        raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
 
751
                    write_id = id
 
752
                    obj_pool.write(cr, uid, [write_id], res)
 
753
 
 
754
            if action.state == 'object_create':
 
755
                res = {}
 
756
                for exp in action.fields_lines:
 
757
                    euq = exp.value
 
758
                    if exp.type == 'equation':
 
759
                        expr = eval(euq, cxt)
 
760
                    else:
 
761
                        expr = exp.value
 
762
                    res[exp.col1.name] = expr
 
763
 
 
764
                obj_pool = None
 
765
                res_id = False
 
766
                obj_pool = self.pool.get(action.srcmodel_id.model)
 
767
                res_id = obj_pool.create(cr, uid, res)
 
768
                if action.record_id:
 
769
                    self.pool.get(action.model_id.model).write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
 
770
 
 
771
            if action.state == 'object_copy':
 
772
                res = {}
 
773
                for exp in action.fields_lines:
 
774
                    euq = exp.value
 
775
                    if exp.type == 'equation':
 
776
                        expr = eval(euq, cxt)
 
777
                    else:
 
778
                        expr = exp.value
 
779
                    res[exp.col1.name] = expr
 
780
 
 
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)
 
785
 
 
786
        return False
 
787
 
 
788
actions_server()
 
789
 
 
790
class act_window_close(osv.osv):
 
791
    _name = 'ir.actions.act_window_close'
 
792
    _inherit = 'ir.actions.actions'
 
793
    _table = 'ir_actions'
 
794
    _defaults = {
 
795
        'type': lambda *a: 'ir.actions.act_window_close',
 
796
    }
 
797
act_window_close()
 
798
 
 
799
class ir_actions_todo_category(osv.osv):
 
800
    """
 
801
    Category of Configuration Wizards
 
802
    """
 
803
 
 
804
    _name = 'ir.actions.todo.category'
 
805
    _description = "Configuration Wizard Category"
 
806
    _columns = {
 
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'),
 
810
    }
 
811
ir_actions_todo_category()
 
812
 
 
813
# This model use to register action services.
 
814
TODO_STATES = [('open', 'To Do'),
 
815
               ('done', 'Done')]
 
816
TODO_TYPES = [('manual', 'Launch Manually'),('once', 'Launch Manually Once'),
 
817
              ('automatic', 'Launch Automatically')]
 
818
class ir_actions_todo(osv.osv):
 
819
    """
 
820
    Configuration Wizards
 
821
    """
 
822
    _name = 'ir.actions.todo'
 
823
    _description = "Configuration Wizards"
 
824
    _columns={
 
825
        'action_id': fields.many2one(
 
826
            'ir.actions.act_window', 'Action', select=True, required=True,
 
827
            ondelete='cascade'),
 
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'),
 
838
    }
 
839
    _defaults={
 
840
        'state': 'open',
 
841
        'sequence': 10,
 
842
        'type': 'manual',
 
843
    }
 
844
    _order="sequence,name,id"
 
845
 
 
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'})
 
852
 
 
853
        # Load action
 
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
 
857
 
 
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')})
 
863
 
 
864
        # disable log for automatic wizards
 
865
        if wizard.type == 'automatic':
 
866
            ctx.update({'disable_log': True})
 
867
        res.update({'context': ctx})
 
868
 
 
869
        return res
 
870
 
 
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)
 
874
 
 
875
    def progress(self, cr, uid, context=None):
 
876
        """ Returns a dict with 3 keys {todo, done, total}.
 
877
 
 
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)
 
881
 
 
882
        :rtype: dict
 
883
        """
 
884
        user_groups = set(map(
 
885
            lambda x: x.id,
 
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
 
889
            """
 
890
            return not todo.groups_id \
 
891
                   or bool(user_groups.intersection((
 
892
                        group.id for group in todo.groups_id)))
 
893
 
 
894
        done = filter(
 
895
            groups_match,
 
896
            self.browse(cr, uid,
 
897
                self.search(cr, uid, [('state', '!=', 'open')], context=context),
 
898
                        context=context))
 
899
 
 
900
        total = filter(
 
901
            groups_match,
 
902
            self.browse(cr, uid,
 
903
                self.search(cr, uid, [], context=context),
 
904
                        context=context))
 
905
 
 
906
        return {
 
907
            'done': len(done),
 
908
            'total': len(total),
 
909
            'todo': len(total) - len(done)
 
910
        }
 
911
 
 
912
ir_actions_todo()
 
913
 
 
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'
 
919
    _order = 'name'
 
920
 
 
921
    def _get_params(self, cr, uid, ids, field_name, arg, context):
 
922
        return dict([
 
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)
 
926
        ])
 
927
 
 
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)
 
931
 
 
932
    _columns = {
 
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,
 
938
                                  type='binary', 
 
939
                                  string="Supplementary arguments",
 
940
                                  help="Arguments sent to the client along with"
 
941
                                       "the view tag"),
 
942
        'params_store': fields.binary("Params storage", readonly=True)
 
943
    }
 
944
    _defaults = {
 
945
        'type': 'ir.actions.client',
 
946
 
 
947
    }
 
948
act_client()
 
949
 
 
950
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: