Merge commit 'origin/master' into mdv-gpl3-py26
[odoo/odoo.git] / bin / addons / base / ir / ir_actions.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution    
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 from osv import fields,osv
24 import tools
25 import time
26 from tools.config import config
27 from tools.translate import _
28 import netsvc
29 import re
30
31 class actions(osv.osv):
32     _name = 'ir.actions.actions'
33     _table = 'ir_actions'
34     _columns = {
35         'name': fields.char('Action Name', required=True, size=64),
36         'type': fields.char('Action Type', required=True, size=32),
37         'usage': fields.char('Action Usage', size=32),
38     }
39     _defaults = {
40         'usage': lambda *a: False,
41     }
42 actions()
43
44 class report_custom(osv.osv):
45     _name = 'ir.actions.report.custom'
46     _table = 'ir_act_report_custom'
47     _sequence = 'ir_actions_id_seq'
48     _columns = {
49         'name': fields.char('Report Name', size=64, required=True, translate=True),
50         'type': fields.char('Report Type', size=32, required=True),
51         'model':fields.char('Object', size=64, required=True),
52         'report_id': fields.integer('Report Ref.', required=True),
53         'usage': fields.char('Action Usage', size=32),
54         '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.")
55     }
56     _defaults = {
57         'multi': lambda *a: False,
58         'type': lambda *a: 'ir.actions.report.custom',
59     }
60 report_custom()
61
62 class report_xml(osv.osv):
63
64     def _report_content(self, cursor, user, ids, name, arg, context=None):
65         res = {}
66         for report in self.browse(cursor, user, ids, context=context):
67             data = report[name + '_data']
68             if not data and report[name[:-8]]:
69                 try:
70                     fp = tools.file_open(report[name[:-8]], mode='rb')
71                     data = fp.read()
72                 except:
73                     data = False
74             res[report.id] = data
75         return res
76
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)
79
80     def _report_sxw(self, cursor, user, ids, name, arg, context=None):
81         res = {}
82         for report in self.browse(cursor, user, ids, context=context):
83             if report.report_rml:
84                 res[report.id] = report.report_rml.replace('.rml', '.sxw')
85             else:
86                 res[report.id] = False
87         return res
88
89     _name = 'ir.actions.report.xml'
90     _table = 'ir_act_report_xml'
91     _sequence = 'ir_actions_id_seq'
92     _columns = {
93         'name': fields.char('Name', size=64, required=True, translate=True),
94         'type': fields.char('Report Type', size=32, required=True),
95         'model': fields.char('Object', size=64, required=True),
96         'report_name': fields.char('Internal Name', size=64, required=True),
97         'report_xsl': fields.char('XSL path', size=256),
98         'report_xml': fields.char('XML path', size=256),
99         'report_rml': fields.char('RML path', size=256,
100             help="The .rml path of the file or NULL if the content is in report_rml_content"),
101         'report_sxw': fields.function(_report_sxw, method=True, type='char',
102             string='SXW path'),
103         'report_sxw_content_data': fields.binary('SXW content'),
104         'report_rml_content_data': fields.binary('RML content'),
105         'report_sxw_content': fields.function(_report_content,
106             fnct_inv=_report_content_inv, method=True,
107             type='binary', string='SXW content',),
108         'report_rml_content': fields.function(_report_content,
109             fnct_inv=_report_content_inv, method=True,
110             type='binary', string='RML content'),
111         'auto': fields.boolean('Automatic XSL:RML', required=True),
112         'usage': fields.char('Action Usage', size=32),
113         'header': fields.boolean('Add RML header',
114             help="Add or not the coporate RML header"),
115         'multi': fields.boolean('On multiple doc.',
116             help="If set to true, the action will not be displayed on the right toolbar of a form view."),
117         'report_type': fields.selection([
118             ('pdf', 'pdf'),
119             ('html', 'html'),
120             ('raw', 'raw'),
121             ('sxw', 'sxw'),
122             ('txt', 'txt'),
123             ('odt', 'odt'),
124             ('html2html','Html from html'),
125             ], string='Type', required=True),
126         'groups_id': fields.many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', 'Groups'),
127         '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.'),
128         '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.')
129     }
130     _defaults = {
131         'type': lambda *a: 'ir.actions.report.xml',
132         'multi': lambda *a: False,
133         'auto': lambda *a: True,
134         'header': lambda *a: True,
135         'report_sxw_content': lambda *a: False,
136         'report_type': lambda *a: 'pdf',
137         'attachment': lambda *a: False,
138     }
139
140 report_xml()
141
142 class act_window(osv.osv):
143     _name = 'ir.actions.act_window'
144     _table = 'ir_act_window'
145     _sequence = 'ir_actions_id_seq'
146     def _check_model(self, cr, uid, ids, context={}):
147         for action in self.browse(cr, uid, ids, context):
148             if not self.pool.get(action.res_model):
149                 return False
150             if action.src_model and not self.pool.get(action.src_model):
151                 return False
152         return True
153     _constraints = [
154         (_check_model, 'Invalid model name in the action definition.', ['res_model','src_model'])
155     ]
156
157     def _views_get_fnc(self, cr, uid, ids, name, arg, context={}):
158         res={}
159         for act in self.browse(cr, uid, ids):
160             res[act.id]=[(view.view_id.id, view.view_mode) for view in act.view_ids]
161             modes = act.view_mode.split(',')
162             if len(modes)>len(act.view_ids):
163                 find = False
164                 if act.view_id:
165                     res[act.id].append((act.view_id.id, act.view_id.type))
166                 for t in modes[len(act.view_ids):]:
167                     if act.view_id and (t == act.view_id.type) and not find:
168                         find = True
169                         continue
170                     res[act.id].append((False, t))
171         return res
172
173     _columns = {
174         'name': fields.char('Action Name', size=64, translate=True),
175         'type': fields.char('Action Type', size=32, required=True),
176         'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
177         'domain': fields.char('Domain Value', size=250),
178         'context': fields.char('Context Value', size=250),
179         'res_model': fields.char('Object', size=64),
180         'src_model': fields.char('Source Object', size=64),
181         'target': fields.selection([('current','Current Window'),('new','New Window')], 'Target Window'),
182         'view_type': fields.selection((('tree','Tree'),('form','Form')),string='View Type'),
183         'view_mode': fields.char('View Mode', size=250),
184         'usage': fields.char('Action Usage', size=32),
185         'view_ids': fields.one2many('ir.actions.act_window.view', 'act_window_id', 'Views'),
186         'views': fields.function(_views_get_fnc, method=True, type='binary', string='Views'),
187         'limit': fields.integer('Limit', help='Default limit for the list view'),
188         'auto_refresh': fields.integer('Auto-Refresh',
189             help='Add an auto-refresh on the view'),
190         'groups_id': fields.many2many('res.groups', 'ir_act_window_group_rel',
191             'act_id', 'gid', 'Groups'),
192     }
193     _defaults = {
194         'type': lambda *a: 'ir.actions.act_window',
195         'view_type': lambda *a: 'form',
196         'view_mode': lambda *a: 'tree,form',
197         'context': lambda *a: '{}',
198         'limit': lambda *a: 80,
199         'target': lambda *a: 'current',
200         'auto_refresh': lambda *a: 0,
201     }
202 act_window()
203
204 class act_window_view(osv.osv):
205     _name = 'ir.actions.act_window.view'
206     _table = 'ir_act_window_view'
207     _rec_name = 'view_id'
208     _columns = {
209         'sequence': fields.integer('Sequence'),
210         'view_id': fields.many2one('ir.ui.view', 'View'),
211         'view_mode': fields.selection((
212             ('tree', 'Tree'),
213             ('form', 'Form'),
214             ('graph', 'Graph'),
215             ('calendar', 'Calendar'),
216             ('gantt', 'Gantt')), string='View Type', required=True),
217         'act_window_id': fields.many2one('ir.actions.act_window', 'Action', ondelete='cascade'),
218         'multi': fields.boolean('On Multiple Doc.',
219             help="If set to true, the action will not be displayed on the right toolbar of a form view."),
220     }
221     _defaults = {
222         'multi': lambda *a: False,
223     }
224     _order = 'sequence'
225 act_window_view()
226
227 class act_wizard(osv.osv):
228     _name = 'ir.actions.wizard'
229     _inherit = 'ir.actions.actions'
230     _table = 'ir_act_wizard'
231     _sequence = 'ir_actions_id_seq'
232     _columns = {
233         'name': fields.char('Wizard Info', size=64, required=True, translate=True),
234         'type': fields.char('Action Type', size=32, required=True),
235         'wiz_name': fields.char('Wizard Name', size=64, required=True),
236         '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."),
237         'groups_id': fields.many2many('res.groups', 'res_groups_wizard_rel', 'uid', 'gid', 'Groups'),
238         'model': fields.char('Object', size=64),
239     }
240     _defaults = {
241         'type': lambda *a: 'ir.actions.wizard',
242         'multi': lambda *a: False,
243     }
244 act_wizard()
245
246 class act_url(osv.osv):
247     _name = 'ir.actions.url'
248     _table = 'ir_act_url'
249     _sequence = 'ir_actions_id_seq'
250     _columns = {
251         'name': fields.char('Action Name', size=64, translate=True),
252         'type': fields.char('Action Type', size=32, required=True),
253         'url': fields.text('Action URL',required=True),
254         'target': fields.selection((
255             ('new', 'New Window'),
256             ('self', 'This Window')),
257             'Action Target', required=True
258         )
259     }
260     _defaults = {
261         'type': lambda *a: 'ir.actions.act_url',
262         'target': lambda *a: 'new'
263     }
264 act_url()
265
266 def model_get(self, cr, uid, context={}):
267     wkf_pool = self.pool.get('workflow')
268     ids = wkf_pool.search(cr, uid, [])
269     osvs = wkf_pool.read(cr, uid, ids, ['osv'])
270
271     res = []
272     mpool = self.pool.get('ir.model')
273     for osv in osvs:
274         model = osv.get('osv')
275         id = mpool.search(cr, uid, [('model','=',model)])
276         name = mpool.read(cr, uid, id)[0]['name']
277         res.append((model, name))
278
279     return res
280
281 class ir_model_fields(osv.osv):
282     _inherit = 'ir.model.fields'
283     _rec_name = 'field_description'
284     _columns = {
285         'complete_name': fields.char('Complete Name', size=64, select=1),
286     }
287
288     def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=800):
289         def get_fields(cr, uid, field, rel):
290             result = []
291             mobj = self.pool.get('ir.model')
292             id = mobj.search(cr, uid, [('model','=',rel)])
293
294             obj = self.pool.get('ir.model.fields')
295             ids = obj.search(cr, uid, [('model_id','in',id)])
296             records = obj.read(cr, uid, ids)
297             for record in records:
298                 id = record['id']
299                 fld = field + '/' + record['name']
300
301                 result.append((id, fld))
302             return result
303
304         if not args:
305             args=[]
306         if not context:
307             context={}
308             return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit)
309
310         if context.get('key') != 'server_action':
311             return super(ir_model_fields, self).name_search(cr, uid, name, args, operator, context, limit)
312
313         result = []
314         obj = self.pool.get('ir.model.fields')
315         ids = obj.search(cr, uid, args)
316         records = obj.read(cr, uid, ids)
317         for record in records:
318             id = record['id']
319             field = record['name']
320
321             if record['ttype'] == 'many2one':
322                 rel = record['relation']
323                 res = get_fields(cr, uid, field, record['relation'])
324                 for rs in res:
325                     result.append(rs)
326
327             result.append((id, field))
328
329         for rs in result:
330             obj.write(cr, uid, [rs[0]], {'complete_name':rs[1]})
331
332         iids = []
333         for rs in result:
334             iids.append(rs[0])
335
336         result = super(ir_model_fields, self).name_search(cr, uid, name, [('complete_name','ilike',name), ('id','in',iids)], operator, context, limit)
337
338         return result
339
340 ir_model_fields()
341
342 class server_object_lines(osv.osv):
343     _name = 'ir.server.object.lines'
344     _sequence = 'ir_actions_id_seq'
345     _columns = {
346         'server_id': fields.many2one('ir.actions.server', 'Object Mapping'),
347         'col1': fields.many2one('ir.model.fields', 'Destination', required=True),
348         'value': fields.text('Value', required=True),
349         'type': fields.selection([
350             ('value','Value'),
351             ('equation','Formula')
352         ], 'Type', required=True, size=32, change_default=True),
353     }
354     _defaults = {
355         'type': lambda *a: 'equation',
356     }
357 server_object_lines()
358
359 ##
360 # Actions that are run on the server side
361 #
362 class actions_server(osv.osv):
363
364     def _select_signals(self, cr, uid, context={}):
365         cr.execute("select distinct t.signal as key, t.signal || ' - [ ' || w.osv || ' ] ' as val from wkf w, wkf_activity a, wkf_transition t "\
366                         " where w.id = a.wkf_id " \
367                         " and t.act_from = a.wkf_id " \
368                         " or t.act_to = a.wkf_id and t.signal not in (null, NULL)")
369         result = cr.fetchall() or []
370         res = []
371         for rs in result:
372             if not rs[0] == None and not rs[1] == None:
373                 res.append(rs)
374         return res
375
376     _name = 'ir.actions.server'
377     _table = 'ir_act_server'
378     _sequence = 'ir_actions_id_seq'
379     _order = 'sequence'
380     _columns = {
381         'name': fields.char('Action Name', required=True, size=64, help="Easy to Refer action by name e.g. One Sales Order -> Many Invoices"),
382         'condition' : fields.char('Condition', size=256, required=True, help="Condition that is to be tested before action is executed, e.g. object.list_price > object.cost_price"),
383         'state': fields.selection([
384             ('client_action','Client Action'),
385             ('dummy','Dummy'),
386             ('loop','Iteration'),
387             ('code','Python Code'),
388             ('trigger','Trigger'),
389             ('email','Email'),
390             ('sms','SMS'),
391             ('object_create','Create Object'),
392             ('object_write','Write Object'),
393             ('other','Multi Actions'),
394         ], 'Action Type', required=True, size=32, help="Type of the Action that is to be executed"),
395         'code':fields.text('Python Code', help="Python code to be executed"),
396         '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."),
397         'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create)."),
398         'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
399         'trigger_name': fields.selection(_select_signals, string='Trigger Name', size=128, help="Select the Signal name that is to be used as the trigger."),
400         'wkf_model_id': fields.many2one('ir.model', 'Workflow On', help="Workflow to be executed on this model."),
401         'trigger_obj_id': fields.many2one('ir.model.fields','Trigger On', help="Select the object from the model on which the workflow will executed."),
402         'email': fields.char('Email Address', size=512, help="Provides the fields that will be used to fetch the email address, e.g. when you select the invoice, then `object.invoice_address_id.email` is the field which gives the correct address"),
403         'subject': fields.char('Subject', size=1024, translate=True, help="Specify the subject. You can use fields from the object, e.g. `Hello [[ object.partner_id.name ]]`"),
404         'message': fields.text('Message', translate=True, help="Specify the message. You can use the fields from the object. e.g. `Dear [[ object.partner_id.name ]]`"),
405         '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"),
406         'sms': fields.char('SMS', size=160, translate=True),
407         'child_ids': fields.many2many('ir.actions.server', 'rel_server_actions', 'server_id', 'action_id', 'Other Actions'),
408         'usage': fields.char('Action Usage', size=32),
409         'type': fields.char('Action Type', size=32, required=True),
410         '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."),
411         'fields_lines': fields.one2many('ir.server.object.lines', 'server_id', 'Field Mappings.'),
412         '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."),
413         '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."),
414         '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."),
415         '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`."),
416     }
417     _defaults = {
418         'state': lambda *a: 'dummy',
419         'condition': lambda *a: 'True',
420         'type': lambda *a: 'ir.actions.server',
421         'sequence': lambda *a: 5,
422         'code': lambda *a: """# You can use the following variables
423 #    - object
424 #    - object2
425 #    - time
426 #    - cr
427 #    - uid
428 #    - ids
429 # If you plan to return an action, assign: action = {...}
430 """,
431     }
432
433     def get_email(self, cr, uid, action, context):
434         logger = netsvc.Logger()
435         obj_pool = self.pool.get(action.model_id.model)
436         id = context.get('active_id')
437         obj = obj_pool.browse(cr, uid, id)
438
439         fields = None
440
441         if '/' in action.email.complete_name:
442             fields = action.email.complete_name.split('/')
443         elif '.' in action.email.complete_name:
444             fields = action.email.complete_name.split('.')
445
446         for field in fields:
447             try:
448                 obj = getattr(obj, field)
449             except Exception,e :
450                 logger.notifyChannel('Workflow', netsvc.LOG_ERROR, 'Failed to parse : %s' % (field))
451
452         return obj
453
454     def get_mobile(self, cr, uid, action, context):
455         logger = netsvc.Logger()
456         obj_pool = self.pool.get(action.model_id.model)
457         id = context.get('active_id')
458         obj = obj_pool.browse(cr, uid, id)
459
460         fields = None
461
462         if '/' in action.mobile.complete_name:
463             fields = action.mobile.complete_name.split('/')
464         elif '.' in action.mobile.complete_name:
465             fields = action.mobile.complete_name.split('.')
466
467         for field in fields:
468             try:
469                 obj = getattr(obj, field)
470             except Exception,e :
471                 logger.notifyChannel('Workflow', netsvc.LOG_ERROR, 'Failed to parse : %s' % (field))
472
473         return obj
474
475     def merge_message(self, cr, uid, keystr, action, context):
476         logger = netsvc.Logger()
477         def merge(match):
478             obj_pool = self.pool.get(action.model_id.model)
479             id = context.get('active_id')
480             obj = obj_pool.browse(cr, uid, id)
481             exp = str(match.group()[2:-2]).strip()
482             result = eval(exp, {'object':obj, 'context': context,'time':time})
483             if result in (None, False):
484                 return str("--------")
485             return str(result)
486         
487         com = re.compile('(\[\[.+?\]\])')
488         message = com.sub(merge, keystr)
489         
490         return message
491
492     # Context should contains:
493     #   ids : original ids
494     #   id  : current id of the object
495     # OUT:
496     #   False : Finnished correctly
497     #   ACTION_ID : Action to launch
498     
499     def run(self, cr, uid, ids, context={}):
500         logger = netsvc.Logger()
501         
502         for action in self.browse(cr, uid, ids, context):
503             obj_pool = self.pool.get(action.model_id.model)
504             obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
505             cxt = {
506                 'context':context, 
507                 'object': obj, 
508                 'time':time,
509                 'cr': cr,
510                 'pool' : self.pool,
511                 'uid' : uid
512             }
513             expr = eval(str(action.condition), cxt)
514             if not expr:
515                 continue
516             
517             if action.state=='client_action':
518                 if not action.action_id:
519                     raise osv.except_osv(_('Error'), _("Please specify an action to launch !")) 
520                 result = self.pool.get(action.action_id.type).read(cr, uid, action.action_id.id, context=context)
521                 return result
522
523             if action.state=='code':
524                 localdict = {
525                     'self': self.pool.get(action.model_id.model),
526                     'context': context,
527                     'time': time,
528                     'ids': ids,
529                     'cr': cr,
530                     'uid': uid,
531                     'obj':obj
532                 }
533                 exec action.code in localdict
534                 if 'action' in localdict:
535                     return localdict['action']
536
537             if action.state == 'email':
538                 user = config['email_from']
539                 address = str(action.email)
540                 try:
541                     address =  eval(str(action.email), cxt)
542                 except:
543                     pass
544                 
545                 if not address:
546                     raise osv.except_osv(_('Error'), _("Please specify the Partner Email address !"))
547                 if not user:
548                     raise osv.except_osv(_('Error'), _("Please specify server option --smtp-from !"))
549                 
550                 subject = self.merge_message(cr, uid, str(action.subject), action, context)
551                 body = self.merge_message(cr, uid, str(action.message), action, context)
552                 
553                 if tools.email_send(user, [address], subject, body, debug=False, subtype='html') == True:
554                     logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (address))
555                 else:
556                     logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (address))
557
558             if action.state == 'trigger':
559                 wf_service = netsvc.LocalService("workflow")
560                 model = action.wkf_model_id.model
561                 obj_pool = self.pool.get(action.model_id.model)
562                 res_id = self.pool.get(action.model_id.model).read(cr, uid, [context.get('active_id')], [action.trigger_obj_id.name])
563                 id = res_id [0][action.trigger_obj_id.name]
564                 wf_service.trg_validate(uid, model, int(id), action.trigger_name, cr)
565
566             if action.state == 'sms':
567                 #TODO: set the user and password from the system
568                 # for the sms gateway user / password
569                 api_id = ''
570                 text = action.sms
571                 to = self.get_mobile(cr, uid, action, context)
572                 #TODO: Apply message mearge with the field
573                 if tools.sms_send(user, password, api_id, text, to) == True:
574                     logger.notifyChannel('sms', netsvc.LOG_INFO, 'SMS successfully send to : %s' % (action.address))
575                 else:
576                     logger.notifyChannel('sms', netsvc.LOG_ERROR, 'Failed to send SMS to : %s' % (action.address))
577             
578             if action.state == 'other':
579                 res = []
580                 for act in action.child_ids:
581                     context['active_id'] = context['active_ids'][0]
582                     result = self.run(cr, uid, [act.id], context)
583                     if result:
584                         res.append(result)
585                     
586                 return res
587             
588             if action.state == 'loop':
589                 obj_pool = self.pool.get(action.model_id.model)
590                 obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
591                 cxt = {
592                     'context':context, 
593                     'object': obj, 
594                     'time':time,
595                     'cr': cr,
596                     'pool' : self.pool,
597                     'uid' : uid
598                 }
599                 expr = eval(str(action.expression), cxt)
600                 context['object'] = obj
601                 for i in expr:
602                     context['active_id'] = i.id
603                     result = self.run(cr, uid, [action.loop_action.id], context)
604             
605             if action.state == 'object_write':
606                 res = {}
607                 for exp in action.fields_lines:
608                     euq = exp.value
609                     if exp.type == 'equation':
610                         obj_pool = self.pool.get(action.model_id.model)
611                         obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
612                         cxt = {'context':context, 'object': obj, 'time':time}
613                         expr = eval(euq, cxt)
614                     else:
615                         expr = exp.value
616                     res[exp.col1.name] = expr
617
618                 if not action.write_id:
619                     if not action.srcmodel_id:
620                         obj_pool = self.pool.get(action.model_id.model)
621                         obj_pool.write(cr, uid, [context.get('active_id')], res)
622                     else:
623                         write_id = context.get('active_id')
624                         obj_pool = self.pool.get(action.srcmodel_id.model)
625                         obj_pool.write(cr, uid, [write_id], res)
626                         
627                 elif action.write_id:
628                     obj_pool = self.pool.get(action.srcmodel_id.model)
629                     rec = self.pool.get(action.model_id.model).browse(cr, uid, context.get('active_id'))
630                     id = eval(action.write_id, {'object': rec})
631                     try:
632                         id = int(id)
633                     except:
634                         raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
635                     
636                     if type(id) != type(1):
637                         raise osv.except_osv(_('Error'), _("Problem in configuration `Record Id` in Server Action!"))
638                     write_id = id
639                     obj_pool.write(cr, uid, [write_id], res)
640
641             if action.state == 'object_create':
642                 res = {}
643                 for exp in action.fields_lines:
644                     euq = exp.value
645                     if exp.type == 'equation':
646                         obj_pool = self.pool.get(action.model_id.model)
647                         obj = obj_pool.browse(cr, uid, context['active_id'], context=context)
648                         expr = eval(euq, {'context':context, 'object': obj, 'time':time})
649                     else:
650                         expr = exp.value
651                     res[exp.col1.name] = expr
652
653                 obj_pool = None
654                 res_id = False
655                 obj_pool = self.pool.get(action.srcmodel_id.model)
656                 res_id = obj_pool.create(cr, uid, res)
657                 cr.commit()
658                 if action.record_id:
659                     self.pool.get(action.model_id.model).write(cr, uid, [context.get('active_id')], {action.record_id.name:res_id})
660
661         return False
662
663 actions_server()
664
665 class act_window_close(osv.osv):
666     _name = 'ir.actions.act_window_close'
667     _inherit = 'ir.actions.actions'
668     _table = 'ir_actions'
669     _defaults = {
670         'type': lambda *a: 'ir.actions.act_window_close',
671     }
672 act_window_close()
673
674 # This model use to register action services.
675 # if action type is 'configure', it will be start on configuration wizard.
676 # if action type is 'service',
677 #                - if start_type= 'at once', it will be start at one time on start date
678 #                - if start_type='auto', it will be start on auto starting from start date, and stop on stop date
679 #                - if start_type="manual", it will start and stop on manually 
680 class ir_actions_todo(osv.osv):
681     _name = 'ir.actions.todo'    
682     _columns={
683         'name':fields.char('Name',size=64,required=True, select=True),
684         'note':fields.text('Text', translate=True),
685         'start_date': fields.datetime('Start Date'),
686         'end_date': fields.datetime('End Date'),
687         'action_id':fields.many2one('ir.actions.act_window', 'Action', select=True,required=True, ondelete='cascade'),
688         'sequence':fields.integer('Sequence'),
689         'active': fields.boolean('Active'),
690         'type':fields.selection([('configure', 'Configure'),('service', 'Service'),('other','Other')], string='Type', required=True),
691         'start_on':fields.selection([('at_once', 'At Once'),('auto', 'Auto'),('manual','Manual')], string='Start On'),
692         'groups_id': fields.many2many('res.groups', 'res_groups_act_todo_rel', 'act_todo_id', 'group_id', 'Groups'),
693         'users_id': fields.many2many('res.users', 'res_users_act_todo_rel', 'act_todo_id', 'user_id', 'Users'),
694         'state':fields.selection([('open', 'Not Started'),('done', 'Done'),('skip','Skipped'),('cancel','Cancel')], string='State', required=True)
695     }
696     _defaults={
697         'state': lambda *a: 'open',
698         'sequence': lambda *a: 10,
699         'active':lambda *a:True,
700         'type':lambda *a:'configure'
701     }
702     _order="sequence"
703 ir_actions_todo()
704
705 # This model to use run all configuration actions
706 class ir_actions_configuration_wizard(osv.osv_memory):
707     _name='ir.actions.configuration.wizard'
708     def next_configuration_action(self,cr,uid,context={}):
709         item_obj = self.pool.get('ir.actions.todo')
710         item_ids = item_obj.search(cr, uid, [('type','=','configure'),('state', '=', 'open'),('active','=',True)], limit=1, context=context)
711         if item_ids and len(item_ids):
712             item = item_obj.browse(cr, uid, item_ids[0], context=context)
713             return item
714         return False
715     def _get_action_name(self, cr, uid, context={}):
716         next_action=self.next_configuration_action(cr,uid,context=context)        
717         if next_action:
718             return next_action.note
719         else:
720             return "Your database is now fully configured.\n\nClick 'Continue' and enjoy your OpenERP experience..."
721         return False
722
723     def _get_action(self, cr, uid, context={}):
724         next_action=self.next_configuration_action(cr,uid,context=context)
725         if next_action:           
726             return next_action.id
727         return False
728
729     def _progress_get(self,cr,uid, context={}):
730         total = self.pool.get('ir.actions.todo').search_count(cr, uid, [], context)
731         todo = self.pool.get('ir.actions.todo').search_count(cr, uid, [('type','=','configure'),('active','=',True),('state','<>','open')], context)
732         return max(5.0,round(todo*100/total))
733
734     _columns = {
735         'name': fields.text('Next Wizard',readonly=True),
736         'progress': fields.float('Configuration Progress', readonly=True),
737         'item_id':fields.many2one('ir.actions.todo', 'Next Configuration Wizard',invisible=True, readonly=True),
738     }
739     _defaults={
740         'progress': _progress_get,
741         'item_id':_get_action,
742         'name':_get_action_name,
743     }
744     def button_next(self,cr,uid,ids,context=None):
745         user_action=self.pool.get('res.users').browse(cr,uid,uid)
746         act_obj=self.pool.get(user_action.menu_id.type)
747         action_ids=act_obj.search(cr,uid,[('name','=',user_action.menu_id.name)])
748         action_open=act_obj.browse(cr,uid,action_ids)[0]
749         if context.get('menu',False):
750             return{
751                 'view_type': action_open.view_type,
752                 'view_id':action_open.view_id and [action_open.view_id.id] or False,
753                 'res_model': action_open.res_model,
754                 'type': action_open.type,
755                 'domain':action_open.domain
756             }
757         return {'type':'ir.actions.act_window_close'}
758
759     def button_skip(self,cr,uid,ids,context=None):
760         item_obj = self.pool.get('ir.actions.todo')
761         item_id=self.read(cr,uid,ids)[0]['item_id']
762         if item_id:
763             item = item_obj.browse(cr, uid, item_id, context=context)
764             item_obj.write(cr, uid, item.id, {
765                 'state': 'skip',
766                 }, context=context)
767             return{
768                 'view_type': 'form',
769                 "view_mode": 'form',
770                 'res_model': 'ir.actions.configuration.wizard',
771                 'type': 'ir.actions.act_window',
772                 'target':'new',
773             }
774         return self.button_next(cr, uid, ids, context)
775
776     def button_continue(self, cr, uid, ids, context=None):
777         item_obj = self.pool.get('ir.actions.todo')
778         item_id=self.read(cr,uid,ids)[0]['item_id']
779         if item_id:
780             item = item_obj.browse(cr, uid, item_id, context=context)
781             item_obj.write(cr, uid, item.id, {
782                 'state': 'done',
783                 }, context=context)
784             return{
785                   'view_mode': item.action_id.view_mode,
786                   'view_type': item.action_id.view_type,
787                   'view_id':item.action_id.view_id and [item.action_id.view_id.id] or False,
788                   'res_model': item.action_id.res_model,
789                   'type': item.action_id.type,
790                   'target':item.action_id.target,
791             }
792         return self.button_next(cr, uid, ids, context)
793 ir_actions_configuration_wizard()
794
795 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
796