fix
[odoo/odoo.git] / addons / base_module_record / base_module_record.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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 from xml.dom import minidom
23 from osv.osv import osv_pool
24 from osv import fields,osv
25 import pooler
26 import string
27 import tools
28
29 class recording_objects_proxy(osv_pool):
30     def execute(self, *args, **argv):
31         if args[3] == 'create':
32             _old_args = args[4].copy()
33         elif args[3] == 'write':
34             _old_args = args[5].copy()
35         elif len(args) >= 5 and isinstance(args[4], dict):
36             _old_args = args[4].copy()
37         elif len(args) > 5 and args[3] != 'write' and isinstance(args[5], dict):
38             _old_args = args[5].copy()
39         else:
40             _old_args = None
41         res = super(recording_objects_proxy, self).execute(*args, **argv)
42         pool = pooler.get_pool(args[0])
43         mod = pool.get('ir.module.record')
44         
45         if mod and mod.recording:
46             if args[3] not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink'):
47                 if _old_args is not None:
48                     if args[3] != 'write' and args[3] != 'create' and len(args) > 5 and isinstance(args[5], dict):
49                        args=list(args) 
50                        args[5]=_old_args
51                        args=tuple(args)
52                        mod.recording_data.append(('osv_memory_action', args, argv ,None))
53                     else:
54                        if args[3] == 'create':
55                            args[4].update(_old_args)
56                        elif args[3] == 'write':
57                            args[5].update(_old_args)
58                        mod.recording_data.append(('query', args, argv,res))
59         return res
60
61     def exec_workflow(self, *args, **argv):
62         res = super(recording_objects_proxy, self).exec_workflow(*args, **argv)
63         pool = pooler.get_pool(args[0])
64         mod = pool.get('ir.module.record')
65         if mod and mod.recording:
66             mod.recording_data.append(('workflow', args, argv))
67         return res
68
69 recording_objects_proxy()
70       
71 class xElement(minidom.Element):
72     """dom.Element with compact print
73     The Element in minidom has a problem: if printed, adds whitespace
74     around the text nodes. The standard will not ignore that whitespace.
75     This class simply prints the contained nodes in their compact form, w/o
76     added spaces.
77     """
78     def writexml(self, writer, indent="", addindent="", newl=""):
79         writer.write(indent)
80         minidom.Element.writexml(self, writer, indent='', addindent='', newl='')
81         writer.write(newl)
82
83 def doc_createXElement(xdoc, tagName):
84         e = xElement(tagName)
85         e.ownerDocument = xdoc
86         return e
87
88 import yaml
89
90 class base_module_record(osv.osv):
91     _name = "ir.module.record"
92     _columns = {
93
94     }
95     def __init__(self, *args, **kwargs):
96         self.recording = 0
97         self.recording_data = []
98         self.depends = {}
99         super(base_module_record, self).__init__(*args, **kwargs)
100
101     # To Be Improved
102     def _create_id(self, cr, uid, model, data):
103         i = 0
104         while True:
105             try:
106                 name = filter(lambda x: x in string.letters, (data.get('name','') or '').lower())
107             except:
108                 name=''
109 #            name=data.get('name','') or ''.lower()
110             val = model.replace('.','_')+'_'+ name + str(i)
111             i+=1
112             if val not in self.ids.values():
113                 break
114         return val
115
116     def _get_id(self, cr, uid, model, id):
117         if type(id)==type(()):
118             id=id[0]
119         if (model,id) in self.ids:
120             res_id = self.ids[(model,id)]
121             return res_id, False
122         dt = self.pool.get('ir.model.data')
123         dtids = dt.search(cr, uid, [('model','=',model), ('res_id','=',id)])
124         if not dtids:
125             return False, None
126         obj = dt.browse(cr, uid, dtids[0])
127         self.depends[obj.module] = True
128         return obj.module+'.'+obj.name, obj.noupdate
129     
130     def _create_record(self, cr, uid, doc, model, data, record_id, noupdate=False):
131         data_pool = self.pool.get('ir.model.data')
132         model_pool = self.pool.get(model)
133         
134         record = doc.createElement('record')
135         record.setAttribute("id", record_id)
136         record.setAttribute("model", model)
137         record_list = [record]
138         
139         lids  = data_pool.search(cr, uid, [('model','=',model)])
140         res = data_pool.read(cr, uid, lids[:1], ['module'])
141         if res:
142             self.depends[res[0]['module']]=True
143         fields = model_pool.fields_get(cr, uid)
144         for key,val in data.items():
145             if not (val or (fields[key]['type']=='boolean')):
146                 continue
147             if fields[key]['type'] in ('integer','float'):
148                 field = doc.createElement('field')
149                 field.setAttribute("name", key)
150                 field.setAttribute("eval", val and str(val) or 'False' )
151                 record.appendChild(field)
152             elif fields[key]['type'] in ('boolean',):
153                 field = doc.createElement('field')
154                 field.setAttribute("name", key)
155                 field.setAttribute("eval", val and '1' or '0' )
156                 record.appendChild(field)
157             elif fields[key]['type'] in ('many2one',):
158                 field = doc.createElement('field')
159                 field.setAttribute("name", key)
160                 if type(val) in (type(''),type(u'')):
161                     id = val
162                 else:
163                     id,update = self._get_id(cr, uid, fields[key]['relation'], val)
164                     noupdate = noupdate or update
165                 if not id:
166                     relation_pool = self.pool.get(fields[key]['relation'])
167                     
168                     field.setAttribute("model", fields[key]['relation'])
169                     fld_nm = relation_pool._rec_name
170                     name = relation_pool.read(cr, uid, val,[fld_nm])[fld_nm] or False
171                     field.setAttribute("search", str([(str(fld_nm) ,'=', name)]))
172                 else:
173                     field.setAttribute("ref", id)
174                 record.appendChild(field)
175             elif fields[key]['type'] in ('one2many',):
176                 for valitem in (val or []):
177                     if valitem[0] in (0,1):
178                         if key in model_pool._columns:
179                             model_pool._columns[key]._fields_id
180                         else:
181                             model_pool._inherit_fields[key][2]._fields_id
182                         if valitem[0] == 0:
183                             newid = self._create_id(cr, uid, fields[key]['relation'], valitem[2])
184                             valitem[1]=newid
185                         else:
186                             newid,update = self._get_id(cr, uid, fields[key]['relation'], valitem[1])
187                             if not newid:
188                                 newid = self._create_id(cr, uid, fields[key]['relation'], valitem[2])
189                                 valitem[1]=newid
190                         self.ids[(fields[key]['relation'], valitem[1])] = newid
191                         
192                         childrecord, update = self._create_record(cr, uid, doc, fields[key]['relation'],valitem[2], newid)
193                         noupdate = noupdate or update
194                         record_list += childrecord
195                     else:
196                         pass
197             elif fields[key]['type'] in ('many2many',):
198                 res = []
199                 for valitem in (val or []):
200                     if valitem[0]==6:
201                         for id2 in valitem[2]:
202                             id,update = self._get_id(cr, uid, fields[key]['relation'], id2)
203                             self.ids[(fields[key]['relation'],id2)] = id
204                             noupdate = noupdate or update
205                             res.append(id)
206                         field = doc.createElement('field')
207                         field.setAttribute("name", key)
208                         field.setAttribute("eval", "[(6,0,["+','.join(map(lambda x: "ref('%s')" % (x,), res))+'])]')
209                         record.appendChild(field)
210             else:
211                 field = doc_createXElement(doc, 'field')
212                 field.setAttribute("name", key)
213                 field.appendChild(doc.createTextNode(val))
214                 record.appendChild(field)
215
216         return record_list, noupdate
217
218     def _create_yaml_record(self, cr, uid, model, data, record_id):
219         record={'model': model, 'id': str(record_id)}
220         
221         model_pool = self.pool.get(model)
222         data_pool = self.pool.get('ir.model.data')
223         lids  = data_pool.search(cr, uid, [('model','=',model)])
224         
225         res = data_pool.read(cr, uid, lids[:1], ['module'])
226         attrs={}
227         if res:
228             self.depends[res[0]['module']]=True
229         fields = model_pool.fields_get(cr, uid)
230         defaults={}
231         try:
232             defaults[model] = model_pool.default_get(cr, uid, data)
233         except:
234             defaults[model]={}
235         for key,val in data.items():  
236             if ((key in defaults[model]) and (val ==  defaults[model][key])) and not(fields[key].get('required',False)):
237                 continue
238             if fields[key]['type'] in ('integer','float'):
239                 if not val:
240                     val=0.0
241                 attrs[key] = val
242             elif not (val or (fields[key]['type']=='function')):
243                 continue
244             elif fields[key]['type'] in ('boolean',):
245                 if not val:
246                     continue
247                 attrs[key] = val
248             elif fields[key]['type'] in ('many2one',):
249                 if type(val) in (type(''), type(u'')):
250                     id = val
251                 else:
252                     id, update = self._get_id(cr, uid, fields[key]['relation'], val)
253                 attrs[key] = str(id)
254             elif fields[key]['type'] in ('one2many',):
255                 items=[[]]
256                 for valitem in (val or []):
257                     if valitem[0] in (0,1):
258                         if key in model_pool._columns:
259                             fname = model_pool._columns[key]._fields_id
260                         else:
261                             fname = model_pool._inherit_fields[key][2]._fields_id
262                         del valitem[2][fname] #delete parent_field from child's fields list
263                         
264                         childrecord = self._create_yaml_record(cr, uid, fields[key]['relation'],valitem[2], None)
265                         items[0].append(childrecord['attrs'])
266                 attrs[key] = items
267             elif fields[key]['type'] in ('many2many',):
268                 if (key in defaults[model]) and (val[0][2] ==  defaults[model][key]):
269                     continue
270                 res = []
271                 for valitem in (val or []):
272                     if valitem[0]==6:
273                         for id2 in valitem[2]:
274                             id,update = self._get_id(cr, uid, fields[key]['relation'], id2)
275                             self.ids[(fields[key]['relation'],id2)] = id
276                             res.append(str(id))
277                         m2m=[res]
278                 if m2m[0]:
279                     attrs[key] = m2m
280             else:
281                 try:
282                     attrs[key]=str(val)
283                 except:
284                     attrs[key]=tools.ustr(val)
285                 attrs[key]=attrs[key].replace('"','\'')
286         record['attrs'] = attrs
287         return record
288
289     def get_copy_data(self, cr, uid, model, id, result):
290         res = []
291         obj=self.pool.get(model)
292         data=obj.read(cr, uid,[id])
293         if type(data)==type([]):
294             del data[0]['id']
295             data=data[0]
296         else:
297             del data['id']
298
299         mod_fields = obj.fields_get(cr, uid)
300         for f in filter(lambda a: isinstance(obj._columns[a], fields.function)\
301                     and (not obj._columns[a].store),obj._columns):
302             del data[f]
303             
304         for key,val in data.items():
305             if result.has_key(key):
306                 continue
307             if mod_fields[key]['type'] == 'many2one':
308                 if type(data[key])==type(True) or type(data[key])==type(1):
309                     result[key]=data[key]
310                 elif not data[key]:
311                     result[key] = False
312                 else:
313                     result[key]=data[key][0]
314
315             elif mod_fields[key]['type'] in ('one2many',):
316 #                continue # due to this start stop recording will not record one2many field
317                 rel = mod_fields[key]['relation']
318                 if len(data[key]):
319                     res1=[]
320                     for rel_id in data[key]:
321                         res=[0,0]
322                         res.append(self.get_copy_data(cr, uid,rel,rel_id,{}))
323                         res1.append(res)
324                     result[key]=res1
325                 else:
326                     result[key]=data[key]
327
328             elif mod_fields[key]['type'] == 'many2many':
329                 result[key]=[(6,0,data[key])]
330
331             else:
332                 result[key]=data[key]
333         for k,v in obj._inherits.items():
334             del result[v]
335         return result
336
337     def _create_function(self, cr, uid, doc, model, name, record_id):
338         record = doc.createElement('function')
339         record.setAttribute("name", name)
340         record.setAttribute("model", model)
341         record_list = [record]
342
343         value = doc.createElement('value')
344         value.setAttribute('eval', '[ref(\'%s\')]' % (record_id, ))
345         value.setAttribute('model', model)
346
347         record.appendChild(value)
348         return record_list, False
349
350     def _generate_object_xml(self, cr, uid, rec, recv, doc, result=None):
351         record_list = []
352         noupdate = False
353         if rec[3]=='write':
354             for id in rec[4]:
355                 id,update = self._get_id(cr, uid, rec[2], id)
356                 noupdate = noupdate or update
357                 if not id:
358                     continue
359                 record,update = self._create_record(cr, uid, doc, rec[2], rec[5], id)
360                 noupdate = noupdate or update
361                 record_list += record
362                 
363         elif rec[4] in ('menu_create',):
364             for id in rec[5]:
365                 id,update = self._get_id(cr, uid, rec[3], id)
366                 noupdate = noupdate or update
367                 if not id:
368                     continue
369                 record,update = self._create_function(cr, uid, doc, rec[3], rec[4], id)
370                 noupdate = noupdate or update
371                 record_list += record
372
373         elif rec[3]=='create':
374             id = self._create_id(cr, uid, rec[2],rec[4])
375             record,noupdate = self._create_record(cr, uid, doc, rec[2], rec[4], id)
376             self.ids[(rec[2], result)] = id
377             record_list += record
378
379         elif rec[3]=='copy':
380             data=self.get_copy_data(cr,uid,rec[2],rec[4],rec[5])
381             copy_rec=(rec[0],rec[1],rec[2],rec[3],rec[4],data,rec[5])
382             rec=copy_rec
383             rec_data=[(self.recording_data[0][0],rec,self.recording_data[0][2],self.recording_data[0][3])]
384             self.recording_data=rec_data
385             id = self._create_id(cr, uid, rec[2],rec[5])
386             record,noupdate = self._create_record(cr, uid, doc, rec[2], rec[5], id)
387             self.ids[(rec[2], result)] = id
388             record_list += record
389
390         return record_list,noupdate
391
392     def _generate_object_yaml(self, cr, uid, rec, result=None):
393         if self.mode=="create":
394             yml_id = self._create_id(cr, uid, rec[2],rec[4])
395             self.ids[(rec[2], result)] = yml_id
396             record = self._create_yaml_record(cr, uid, rec[2], rec[4], yml_id)
397             return record
398         if self.mode=="workflow":
399             id,update = self._get_id(cr, uid, rec[2], rec[4])
400             data = {}
401             data['model'] = rec[2]
402             data['action'] = rec[3]
403             data['ref'] = id
404             return data
405         if self.mode=="write":
406             id,update = self._get_id(cr, uid, rec[2],rec[4][0])
407             record = self._create_yaml_record(cr, uid, rec[2], rec[5], id)
408             return record
409         data=self.get_copy_data(cr,uid,rec[2],rec[4],rec[5])
410         copy_rec=(rec[0],rec[1],rec[2],rec[3],rec[4],data,rec[5])
411         rec=copy_rec
412         rec_data=[(self.recording_data[0][0],rec,self.recording_data[0][2],self.recording_data[0][3])]
413         self.recording_data=rec_data
414         id = self._create_id(cr, uid, rec[2],rec[5])
415         record = self._create_yaml_record(cr, uid, str(rec[2]), rec[5], id)
416         self.ids[(rec[2], result)] = id
417         return record
418
419     def _generate_function_yaml(self, cr, uid, args):
420         db, uid, model, action, ids, context = args
421         temp_context = context.copy()
422         active_id = temp_context['active_id']
423         active_model = temp_context['active_model']
424         active_id, update = self._get_id(cr, uid, active_model, active_id)
425         if not active_id:
426             active_id = 1
427         rec_id, noupdate = self._get_id(cr, uid, model, ids[0])
428         temp_context['active_id'] = "ref('%s')"%unicode(active_id)
429         temp_context['active_ids'][0] = "ref('%s')"%str(active_id)
430         function={}
431         function['model'] = model
432         function['action'] = action
433         attrs = "self.%s(cr, uid, [ref('%s')], {" %(action, rec_id, )
434         for k, v in temp_context.iteritems():
435             if isinstance(v, str):
436                 f= "'"+k+"': "+"'%s'"%v + ", "
437             else:
438                 v=str(v).replace('"', '')
439                 f= "'"+k+"': "+"%s"%v + ", "
440             attrs = attrs + f
441         attrs=str(attrs)+'})'
442         function['attrs'] = attrs
443         return function
444             
445     def _generate_assert_xml(self, rec, doc):
446         pass
447
448     def generate_xml(self, cr, uid):
449         # Create the minidom document
450         if len(self.recording_data):
451             self.ids = {}
452             doc = minidom.Document()
453             terp = doc.createElement("openerp")
454             doc.appendChild(terp)
455             for rec in self.recording_data:
456                 if rec[0]=='workflow':
457                     rec_id,noupdate = self._get_id(cr, uid, rec[1][2], rec[1][4])
458                     if not rec_id:
459                         continue
460                     data = doc.createElement("data")
461                     terp.appendChild(data)
462                     wkf = doc.createElement('workflow')
463                     data.appendChild(wkf)
464                     wkf.setAttribute("model", rec[1][2])
465                     wkf.setAttribute("action", rec[1][3])
466                     if noupdate:
467                         data.setAttribute("noupdate", "1")
468                     wkf.setAttribute("ref", rec_id)
469                 if rec[0]=='query':
470                     res_list,noupdate = self._generate_object_xml(cr, uid, rec[1], rec[2], doc, rec[3])
471                     data = doc.createElement("data")
472                     if noupdate:
473                         data.setAttribute("noupdate", "1")
474                     if res_list:
475                         terp.appendChild(data)
476                     for res in res_list:
477                         data.appendChild(res)
478                 elif rec[0]=='assert':
479                         pass
480             return doc.toprettyxml(indent="\t").encode('utf-8')
481
482     def generate_yaml(self, cr, uid):
483         self.ids = {}
484         if len(self.recording_data):
485             yaml_file='''\n'''
486     
487             for rec in self.recording_data:
488                 if rec[1][3] == 'create':
489                     self.mode="create"
490                 elif rec[1][3] == 'write':
491                     self.mode="write"
492                 elif rec[1][3] == 'copy':
493                     self.mode="copy"
494                 elif rec[0] == 'workflow':
495                     self.mode="workflow"
496                 elif rec[0] == 'osv_memory_action':
497                     self.mode='osv_memory_action'
498                 else:
499                     continue
500                 if self.mode == "workflow":
501                     record = self._generate_object_yaml(cr, uid, rec[1],rec[0])
502                     yaml_file += "!comment Performing a workflow action %s on module %s"%(record['action'], record['model']) + '''\n'''
503                     object = yaml.load(unicode('''\n !workflow %s \n'''%record,'iso-8859-1'))
504                     yaml_file += str(object) + '''\n\n'''
505                 elif self.mode == 'osv_memory_action':
506                     osv_action = self._generate_function_yaml(cr, uid, rec[1])
507                     yaml_file += "!comment Performing an osv_memory action %s on module %s"%(osv_action['action'], osv_action['model']) + '''\n'''
508                     osv_action = yaml.load(unicode('''\n !python %s \n'''%osv_action,'iso-8859-1'))
509                     yaml_file += str(osv_action) + '''\n'''
510                     attrs = yaml.dump(osv_action.attrs, default_flow_style=False)
511                     attrs = attrs.replace("''", '"')
512                     attrs = attrs.replace("'", '')
513                     yaml_file += attrs + '''\n\n'''
514                 else:
515                     record = self._generate_object_yaml(cr, uid, rec[1], rec[3])
516                     if self.mode == "create" or self.mode == "copy":
517                         yaml_file += "!comment Creating a %s record"%(record['model']) + '''\n'''
518                     else:
519                         yaml_file += "!comment Modifying a %s record"%(record['model']) + '''\n'''
520                     object = yaml.load(unicode('''\n !record %s \n'''%record,'iso-8859-1'))
521                     yaml_file += str(object) + '''\n'''
522                     attrs = yaml.dump(object.attrs, default_flow_style=False)
523                     yaml_file += attrs + '''\n\n'''
524                     
525         yaml_result=''''''
526         for line in yaml_file.split('\n'):
527             line=line.replace("''","'")
528             if (line.find('!record') == 0) or (line.find('!workflow') == 0) or (line.find('!python') == 0):
529                 line = "- \n" + "  " + line
530             elif line.find('!comment') == 0:
531                 line=line.replace('!comment','- \n ')   
532             elif line.find('- -') != -1:
533                 line=line.replace('- -','  -')
534                 line = "    " + line
535             else:
536                 line = "    " + line
537             yaml_result += line + '''\n'''
538             
539         return yaml_result
540
541 base_module_record()
542 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
543