[merge] mx.DateTime-removal branch by rvalyi
[odoo/odoo.git] / bin / tools / convert.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 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 import cStringIO
22 import csv
23 import logging
24 import os.path
25 import pickle
26 import re
27 import sys
28 import time
29
30 from datetime import datetime
31 from lxml import etree
32
33 import ir
34 import misc
35 import netsvc
36 import osv
37 import pooler
38 from config import config
39
40 class ConvertError(Exception):
41     def __init__(self, doc, orig_excpt):
42         self.d = doc
43         self.orig = orig_excpt
44
45     def __str__(self):
46         return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
47
48 def _ref(self, cr):
49     return lambda x: self.id_get(cr, False, x)
50
51 def _obj(pool, cr, uid, model_str, context=None):
52     model = pool.get(model_str)
53     return lambda x: model.browse(cr, uid, x, context=context)
54
55 def _eval_xml(self,node, pool, cr, uid, idref, context=None):
56     if context is None:
57         context = {}
58     if node.tag in ('field','value'):
59             t = node.get('type','char')
60             f_model = node.get('model', '').encode('ascii')
61             if node.get('search'):
62                 f_search = node.get("search",'').encode('utf-8')
63                 f_use = node.get("use",'id').encode('ascii')
64                 f_name = node.get("name",'').encode('utf-8')
65
66                 q = eval(f_search, idref)
67                 ids = pool.get(f_model).search(cr, uid, q)
68                 if f_use != 'id':
69                     ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
70                 _cols = pool.get(f_model)._columns
71                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
72                     return ids
73                 f_val = False
74                 if len(ids):
75                     f_val = ids[0]
76                     if isinstance(f_val, tuple):
77                         f_val = f_val[0]
78                 return f_val
79             a_eval = node.get('eval','')
80             if a_eval:
81                 idref2 = idref.copy()
82                 idref2['time'] = time
83                 idref2['DateTime'] = datetime
84                 import release
85                 idref2['version'] = release.major_version
86                 idref2['ref'] = lambda x: self.id_get(cr, False, x)
87                 if len(f_model):
88                     idref2['obj'] = _obj(self.pool, cr, uid, f_model, context=context)
89                 try:
90                     import pytz
91                 except:
92                     logger = netsvc.Logger()
93                     logger.notifyChannel("init", netsvc.LOG_WARNING, 'could not find pytz library')
94                     class pytzclass(object):
95                         all_timezones=[]
96                     pytz=pytzclass()
97                 idref2['pytz'] = pytz
98                 try:
99                         return eval(a_eval, idref2)
100                 except:
101                         logger = netsvc.Logger()
102                         logger.notifyChannel("init", netsvc.LOG_WARNING, 'could eval(%s) for %s in %s, please get back and fix it!' % (a_eval,node.get('name'),context))
103                         return ""
104             if t == 'xml':
105                 def _process(s, idref):
106                     m = re.findall('[^%]%\((.*?)\)[ds]', s)
107                     for id in m:
108                         if not id in idref:
109                             idref[id]=self.id_get(cr, False, id)
110                     return s % idref
111                 return '<?xml version="1.0"?>\n'\
112                     +_process("".join([etree.tostring(n, encoding='utf-8')
113                                        for n in node]),
114                               idref)
115             if t in ('char', 'int', 'float'):
116                 d = node.text
117                 if t == 'int':
118                     d = d.strip()
119                     if d == 'None':
120                         return None
121                     else:
122                         return int(d.strip())
123                 elif t == 'float':
124                     return float(d.strip())
125                 return d
126             elif t in ('list','tuple'):
127                 res=[]
128                 for n in node.findall('./value'):
129                     res.append(_eval_xml(self,n,pool,cr,uid,idref))
130                 if t=='tuple':
131                     return tuple(res)
132                 return res
133     elif node.tag == "getitem":
134         for n in node:
135             res=_eval_xml(self,n,pool,cr,uid,idref)
136         if not res:
137             raise LookupError
138         elif node.get('type') in ("int", "list"):
139             return res[int(node.get('index'))]
140         else:
141             return res[node.get('index','').encode("utf8")]
142     elif node.tag == "function":
143         args = []
144         a_eval = node.get('eval','')
145         if a_eval:
146             idref['ref'] = lambda x: self.id_get(cr, False, x)
147             args = eval(a_eval, idref)
148         for n in node:
149             return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
150             if return_val is not None:
151                 args.append(return_val)
152         model = pool.get(node.get('model',''))
153         method = node.get('name','')
154         res = getattr(model, method)(cr, uid, *args)
155         return res
156     elif node.tag == "test":
157         return node.text
158
159 escape_re = re.compile(r'(?<!\\)/')
160 def escape(x):
161     return x.replace('\\/', '/')
162
163 class assertion_report(object):
164     def __init__(self):
165         self._report = {}
166
167     def record_assertion(self, success, severity):
168         """
169             Records the result of an assertion for the failed/success count
170             returns success
171         """
172         if severity in self._report:
173             self._report[severity][success] += 1
174         else:
175             self._report[severity] = {success:1, not success: 0}
176         return success
177
178     def get_report(self):
179         return self._report
180
181     def __str__(self):
182         res = '\nAssertions report:\nLevel\tsuccess\tfailed\n'
183         success = failed = 0
184         for sev in self._report:
185             res += sev + '\t' + str(self._report[sev][True]) + '\t' + str(self._report[sev][False]) + '\n'
186             success += self._report[sev][True]
187             failed += self._report[sev][False]
188         res += 'total\t' + str(success) + '\t' + str(failed) + '\n'
189         res += 'end of report (' + str(success + failed) + ' assertion(s) checked)'
190         return res
191
192 class xml_import(object):
193
194     @staticmethod
195     def nodeattr2bool(node, attr, default=False):
196         if not node.get(attr):
197             return default
198         val = node.get(attr).strip()
199         if not val:
200             return default
201         return val.lower() not in ('0', 'false', 'off')
202
203     def isnoupdate(self, data_node=None):
204         return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
205
206     def get_context(self, data_node, node, eval_dict):
207         data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
208         if data_node_context:
209             context = eval(data_node_context, eval_dict)
210         else:
211             context = {}
212
213         node_context = node.get("context",'').encode('utf8')
214         if node_context:
215             context.update(eval(node_context, eval_dict))
216
217         return context
218
219     def get_uid(self, cr, uid, data_node, node):
220         node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
221         if node_uid:
222             return self.id_get(cr, None, node_uid)
223         return uid
224
225     def _test_xml_id(self, xml_id):
226         id = xml_id
227         if '.' in xml_id:
228             module, id = xml_id.split('.', 1)
229             assert '.' not in id, """The ID reference "%s" must contain
230 maximum one dot. They are used to refer to other modules ID, in the
231 form: module.record_id""" % (xml_id,)
232             if module != self.module:
233                 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
234                 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
235
236         if len(id) > 64:
237             self.logger.notifyChannel('init', netsvc.LOG_ERROR, 'id: %s is to long (max: 64)'% (id,))
238
239     def _tag_delete(self, cr, rec, data_node=None):
240         d_model = rec.get("model",'')
241         d_search = rec.get("search",'')
242         d_id = rec.get("id",'')
243         ids = []
244         if d_search:
245             ids = self.pool.get(d_model).search(cr,self.uid,eval(d_search))
246         if d_id:
247             try:
248                 ids.append(self.id_get(cr, d_model, d_id))
249             except:
250                 # d_id cannot be found. doesn't matter in this case
251                 pass
252         if ids:
253             self.pool.get(d_model).unlink(cr, self.uid, ids)
254             self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids, direct=True)
255
256     def _tag_report(self, cr, rec, data_node=None):
257         res = {}
258         for dest,f in (('name','string'),('model','model'),('report_name','name')):
259             res[dest] = rec.get(f,'').encode('utf8')
260             assert res[dest], "Attribute %s of report is empty !" % (f,)
261         for field,dest in (('rml','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
262             if rec.get(field):
263                 res[dest] = rec.get(field).encode('utf8')
264         if rec.get('auto'):
265             res['auto'] = eval(rec.get('auto'))
266         if rec.get('sxw'):
267             sxw_content = misc.file_open(rec.get('sxw')).read()
268             res['report_sxw_content'] = sxw_content
269         if rec.get('header'):
270             res['header'] = eval(rec.get('header'))
271         if rec.get('report_type'):
272             res['report_type'] = rec.get('report_type')
273
274         res['multi'] = rec.get('multi') and eval(rec.get('multi'))
275         xml_id = rec.get('id','').encode('utf8')
276         self._test_xml_id(xml_id)
277
278         if rec.get('groups'):
279             g_names = rec.get('groups','').split(',')
280             groups_value = []
281             groups_obj = self.pool.get('res.groups')
282             for group in g_names:
283                 if group.startswith('-'):
284                     group_id = self.id_get(cr, 'res.groups', group[1:])
285                     groups_value.append((3, group_id))
286                 else:
287                     group_id = self.id_get(cr, 'res.groups', group)
288                     groups_value.append((4, group_id))
289             res['groups_id'] = groups_value
290
291         id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.report.xml", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
292         self.idref[xml_id] = int(id)
293
294         if not rec.get('menu') or eval(rec.get('menu','')):
295             keyword = str(rec.get('keyword', 'client_print_multi'))
296             keys = [('action',keyword),('res_model',res['model'])]
297             value = 'ir.actions.report.xml,'+str(id)
298             replace = rec.get('replace', True)
299             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, res['name'], [res['model']], value, replace=replace, isobject=True, xml_id=xml_id)
300         return False
301
302     def _tag_function(self, cr, rec, data_node=None):
303         if self.isnoupdate(data_node) and self.mode != 'init':
304             return
305         context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
306         uid = self.get_uid(cr, self.uid, data_node, rec)
307         _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
308         return
309
310     def _tag_wizard(self, cr, rec, data_node=None):
311         string = rec.get("string",'').encode('utf8')
312         model = rec.get("model",'').encode('utf8')
313         name = rec.get("name",'').encode('utf8')
314         xml_id = rec.get('id','').encode('utf8')
315         self._test_xml_id(xml_id)
316         multi = rec.get('multi','') and eval(rec.get('multi',''))
317         res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
318
319         if rec.get('groups'):
320             g_names = rec.get('groups','').split(',')
321             groups_value = []
322             groups_obj = self.pool.get('res.groups')
323             for group in g_names:
324                 if group.startswith('-'):
325                     group_id = self.id_get(cr, 'res.groups', group[1:])
326                     groups_value.append((3, group_id))
327                 else:
328                     group_id = self.id_get(cr, 'res.groups', group)
329                     groups_value.append((4, group_id))
330             res['groups_id'] = groups_value
331
332         id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.wizard", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
333         self.idref[xml_id] = int(id)
334         # ir_set
335         if (not rec.get('menu') or eval(rec.get('menu',''))) and id:
336             keyword = str(rec.get('keyword','') or 'client_action_multi')
337             keys = [('action',keyword),('res_model',model)]
338             value = 'ir.actions.wizard,'+str(id)
339             replace = rec.get("replace",'') or True
340             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
341
342     def _tag_url(self, cr, rec, data_node=None):
343         url = rec.get("string",'').encode('utf8')
344         target = rec.get("target",'').encode('utf8')
345         name = rec.get("name",'').encode('utf8')
346         xml_id = rec.get('id','').encode('utf8')
347         self._test_xml_id(xml_id)
348
349         res = {'name': name, 'url': url, 'target':target}
350
351         id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
352         self.idref[xml_id] = int(id)
353         # ir_set
354         if (not rec.get('menu') or eval(rec.get('menu',''))) and id:
355             keyword = str(rec.get('keyword','') or 'client_action_multi')
356             keys = [('action',keyword)]
357             value = 'ir.actions.url,'+str(id)
358             replace = rec.get("replace",'') or True
359             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, url, ["ir.actions.url"], value, replace=replace, isobject=True, xml_id=xml_id)
360
361     def _tag_act_window(self, cr, rec, data_node=None):
362         name = rec.get('name','').encode('utf-8')
363         xml_id = rec.get('id','').encode('utf8')
364         self._test_xml_id(xml_id)
365         type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
366         view_id = False
367         if rec.get('view'):
368             view_id = self.id_get(cr, 'ir.actions.act_window', rec.get('view','').encode('utf-8'))
369         domain = rec.get('domain','').encode('utf-8') or '{}'
370         context = rec.get('context','').encode('utf-8') or '{}'
371         res_model = rec.get('res_model','').encode('utf-8')
372         src_model = rec.get('src_model','').encode('utf-8')
373         view_type = rec.get('view_type','').encode('utf-8') or 'form'
374         view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
375         usage = rec.get('usage','').encode('utf-8')
376         limit = rec.get('limit','').encode('utf-8')
377         auto_refresh = rec.get('auto_refresh','').encode('utf-8')
378
379         # def ref() added because , if context has ref('id') eval wil use this ref
380
381         active_id=str("active_id") # for further reference in client/bin/tools/__init__.py
382
383         def ref(str_id):
384             return self.id_get(cr, None, str_id)
385         context=eval(context)
386
387         res = {
388             'name': name,
389             'type': type,
390             'view_id': view_id,
391             'domain': domain,
392             'context': context,
393             'res_model': res_model,
394             'src_model': src_model,
395             'view_type': view_type,
396             'view_mode': view_mode,
397             'usage': usage,
398             'limit': limit,
399             'auto_refresh': auto_refresh,
400 #            'groups_id':groups_id,
401         }
402
403         if rec.get('groups'):
404             g_names = rec.get('groups','').split(',')
405             groups_value = []
406             groups_obj = self.pool.get('res.groups')
407             for group in g_names:
408                 if group.startswith('-'):
409                     group_id = self.id_get(cr, 'res.groups', group[1:])
410                     groups_value.append((3, group_id))
411                 else:
412                     group_id = self.id_get(cr, 'res.groups', group)
413                     groups_value.append((4, group_id))
414             res['groups_id'] = groups_value
415
416         if rec.get('target'):
417             res['target'] = rec.get('target','')
418         id = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.actions.act_window', self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
419         self.idref[xml_id] = int(id)
420
421         if src_model:
422             keyword = 'client_action_relate'
423             keys = [('action', keyword), ('res_model', res_model)]
424             value = 'ir.actions.act_window,'+str(id)
425             replace = rec.get('replace','') or True
426             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, xml_id, [src_model], value, replace=replace, isobject=True, xml_id=xml_id)
427         # TODO add remove ir.model.data
428
429     def _tag_ir_set(self, cr, rec, data_node=None):
430         if self.mode != 'init':
431             return
432         res = {}
433         for field in rec.findall('./field'):
434             f_name = field.get("name",'').encode('utf-8')
435             f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
436             res[f_name] = f_val
437         self.pool.get('ir.model.data').ir_set(cr, self.uid, res['key'], res['key2'], res['name'], res['models'], res['value'], replace=res.get('replace',True), isobject=res.get('isobject', False), meta=res.get('meta',None))
438
439     def _tag_workflow(self, cr, rec, data_node=None):
440         if self.isnoupdate(data_node) and self.mode != 'init':
441             return
442         model = str(rec.get('model',''))
443         w_ref = rec.get('ref','')
444         if w_ref:
445             id = self.id_get(cr, model, w_ref)
446         else:
447             number_children = len(rec)
448             assert number_children > 0,\
449                 'You must define a child node if you dont give a ref'
450             assert number_children == 1,\
451                 'Only one child node is accepted (%d given)' % number_children
452             id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
453
454         uid = self.get_uid(cr, self.uid, data_node, rec)
455         wf_service = netsvc.LocalService("workflow")
456         wf_service.trg_validate(uid, model,
457             id,
458             str(rec.get('action','')), cr)
459
460     #
461     # Support two types of notation:
462     #   name="Inventory Control/Sending Goods"
463     # or
464     #   action="action_id"
465     #   parent="parent_id"
466     #
467     def _tag_menuitem(self, cr, rec, data_node=None):
468         rec_id = rec.get("id",'').encode('ascii')
469         self._test_xml_id(rec_id)
470         m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
471
472         values = {'parent_id': False}
473         if not rec.get('parent'):
474             pid = False
475             for idx, menu_elem in enumerate(m_l):
476                 if pid:
477                     cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
478                 else:
479                     cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
480                 res = cr.fetchone()
481                 if idx==len(m_l)-1:
482                     values = {'parent_id': pid,'name':menu_elem}
483                 elif res:
484                     pid = res[0]
485                     xml_id = idx==len(m_l)-1 and rec.get('id','').encode('utf8')
486                     try:
487                         npid = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, 'ir.ui.menu', self.module, xml_id, idx==len(m_l)-1)
488                     except:
489                         self.logger.notifyChannel('init', netsvc.LOG_ERROR, "module: %s xml_id: %s" % (self.module, xml_id))
490                 else:
491                     # the menuitem does't exist but we are in branch (not a leaf)
492                     self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l)))
493                     pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
494         else:
495             menu_parent_id = self.id_get(cr, 'ir.ui.menu', rec.get('parent',''))
496             values = {'parent_id': menu_parent_id}
497             if rec.get('name'):
498                 values['name'] = rec.get('name')
499             try:
500                 res = [ self.id_get(cr, 'ir.ui.menu', rec.get('id','')) ]
501             except:
502                 res = None
503
504         if rec.get('action'):
505             a_action = rec.get('action','').encode('utf8')
506             a_type = rec.get('type','').encode('utf8') or 'act_window'
507             icons = {
508                 "act_window": 'STOCK_NEW',
509                 "report.xml": 'STOCK_PASTE',
510                 "wizard": 'STOCK_EXECUTE',
511                 "url": 'STOCK_JUMP_TO'
512             }
513             values['icon'] = icons.get(a_type,'STOCK_NEW')
514             if a_type=='act_window':
515                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
516                 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
517                 rrres = cr.fetchone()
518                 assert rrres, "No window action defined for this id %s !\n" \
519                     "Verify that this is a window action or add a type argument." % (a_action,)
520                 action_type,action_mode,action_name,view_id,target = rrres
521                 if view_id:
522                     cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
523                     action_mode, = cr.fetchone()
524                 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
525                 if cr.rowcount:
526                     action_mode, = cr.fetchone()
527                 if action_type=='tree':
528                     values['icon'] = 'STOCK_INDENT'
529                 elif action_mode and action_mode.startswith('tree'):
530                     values['icon'] = 'STOCK_JUSTIFY_FILL'
531                 elif action_mode and action_mode.startswith('graph'):
532                     values['icon'] = 'terp-graph'
533                 elif action_mode and action_mode.startswith('calendar'):
534                     values['icon'] = 'terp-calendar'
535                 if target=='new':
536                     values['icon'] = 'STOCK_EXECUTE'
537                 if not values.get('name', False):
538                     values['name'] = action_name
539             elif a_type=='wizard':
540                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
541                 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
542                 resw = cr.fetchone()
543                 if (not values.get('name', False)) and resw:
544                     values['name'] = resw[0]
545         if rec.get('sequence'):
546             values['sequence'] = int(rec.get('sequence'))
547         if rec.get('icon'):
548             values['icon'] = str(rec.get('icon'))
549
550         if rec.get('groups'):
551             g_names = rec.get('groups','').split(',')
552             groups_value = []
553             groups_obj = self.pool.get('res.groups')
554             for group in g_names:
555                 if group.startswith('-'):
556                     group_id = self.id_get(cr, 'res.groups', group[1:])
557                     groups_value.append((3, group_id))
558                 else:
559                     group_id = self.id_get(cr, 'res.groups', group)
560                     groups_value.append((4, group_id))
561             values['groups_id'] = groups_value
562
563         xml_id = rec.get('id','').encode('utf8')
564         self._test_xml_id(xml_id)
565         pid = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.ui.menu', self.module, values, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode, res_id=res and res[0] or False)
566
567         if rec_id and pid:
568             self.idref[rec_id] = int(pid)
569
570         if rec.get('action') and pid:
571             a_action = rec.get('action').encode('utf8')
572             a_type = rec.get('type','').encode('utf8') or 'act_window'
573             a_id = self.id_get(cr, 'ir.actions.%s' % a_type, a_action)
574             action = "ir.actions.%s,%d" % (a_type, a_id)
575             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(pid))], action, True, True, xml_id=rec_id)
576         return ('ir.ui.menu', pid)
577
578     def _assert_equals(self, f1, f2, prec = 4):
579         return not round(f1 - f2, prec)
580
581     def _tag_assert(self, cr, rec, data_node=None):
582         if self.isnoupdate(data_node) and self.mode != 'init':
583             return
584
585         rec_model = rec.get("model",'').encode('ascii')
586         model = self.pool.get(rec_model)
587         assert model, "The model %s does not exist !" % (rec_model,)
588         rec_id = rec.get("id",'').encode('ascii')
589         self._test_xml_id(rec_id)
590         rec_src = rec.get("search",'').encode('utf8')
591         rec_src_count = rec.get("count")
592
593         severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
594         rec_string = rec.get("string",'').encode('utf8') or 'unknown'
595
596         ids = None
597         eval_dict = {'ref': _ref(self, cr)}
598         context = self.get_context(data_node, rec, eval_dict)
599         uid = self.get_uid(cr, self.uid, data_node, rec)
600         if rec_id:
601             ids = [self.id_get(cr, rec_model, rec_id)]
602         elif rec_src:
603             q = eval(rec_src, eval_dict)
604             ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
605             if rec_src_count:
606                 count = int(rec_src_count)
607                 if len(ids) != count:
608                     self.assert_report.record_assertion(False, severity)
609                     msg = 'assertion "%s" failed!\n'    \
610                           ' Incorrect search count:\n'  \
611                           ' expected count: %d\n'       \
612                           ' obtained count: %d\n'       \
613                           % (rec_string, count, len(ids))
614                     self.logger.notifyChannel('init', severity, msg)
615                     sevval = getattr(logging, severity.upper())
616                     if sevval >= config['assert_exit_level']:
617                         # TODO: define a dedicated exception
618                         raise Exception('Severe assertion failure')
619                     return
620
621         assert ids is not None,\
622             'You must give either an id or a search criteria'
623         ref = _ref(self, cr)
624         for id in ids:
625             brrec =  model.browse(cr, uid, id, context)
626             class d(dict):
627                 def __getitem__(self2, key):
628                     if key in brrec:
629                         return brrec[key]
630                     return dict.__getitem__(self2, key)
631             globals = d()
632             globals['floatEqual'] = self._assert_equals
633             globals['ref'] = ref
634             globals['_ref'] = ref
635             for test in rec.findall('./test'):
636                 f_expr = test.get("expr",'').encode('utf-8')
637                 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
638                 expression_value = eval(f_expr, globals)
639                 if expression_value != expected_value: # assertion failed
640                     self.assert_report.record_assertion(False, severity)
641                     msg = 'assertion "%s" failed!\n'    \
642                           ' xmltag: %s\n'               \
643                           ' expected value: %r\n'       \
644                           ' obtained value: %r\n'       \
645                           % (rec_string, etree.tostring(test), expected_value, expression_value)
646                     self.logger.notifyChannel('init', severity, msg)
647                     sevval = getattr(logging, severity.upper())
648                     if sevval >= config['assert_exit_level']:
649                         # TODO: define a dedicated exception
650                         raise Exception('Severe assertion failure')
651                     return
652         else: # all tests were successful for this assertion tag (no break)
653             self.assert_report.record_assertion(True, severity)
654
655     def _tag_record(self, cr, rec, data_node=None):
656         rec_model = rec.get("model").encode('ascii')
657         model = self.pool.get(rec_model)
658         assert model, "The model %s does not exist !" % (rec_model,)
659         rec_id = rec.get("id",'').encode('ascii')
660         rec_context = rec.get("context", None)
661         if rec_context:
662             rec_context = eval(rec_context)
663         self._test_xml_id(rec_id)
664         if self.isnoupdate(data_node) and self.mode != 'init':
665             # check if the xml record has an id string
666             if rec_id:
667                 if '.' in rec_id:
668                     module,rec_id2 = rec_id.split('.')
669                 else:
670                     module = self.module
671                     rec_id2 = rec_id
672                 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
673                 # check if the resource already existed at the last update
674                 if id:
675                     # if it existed, we don't update the data, but we need to
676                     # know the id of the existing record anyway
677                     self.idref[rec_id] = int(id)
678                     return None
679                 else:
680                     # if the resource didn't exist
681                     if not self.nodeattr2bool(rec, 'forcecreate', True):
682                         # we don't want to create it, so we skip it
683                         return None
684                     # else, we let the record to be created
685
686             else:
687                 # otherwise it is skipped
688                 return None
689         res = {}
690         for field in rec.findall('./field'):
691 #TODO: most of this code is duplicated above (in _eval_xml)...
692             f_name = field.get("name",'').encode('utf-8')
693             f_ref = field.get("ref",'').encode('ascii')
694             f_search = field.get("search",'').encode('utf-8')
695             f_model = field.get("model",'').encode('ascii')
696             if not f_model and model._columns.get(f_name,False):
697                 f_model = model._columns[f_name]._obj
698             f_use = field.get("use",'').encode('ascii') or 'id'
699             f_val = False
700
701             if f_search:
702                 q = eval(f_search, self.idref)
703                 field = []
704                 assert f_model, 'Define an attribute model="..." in your .XML file !'
705                 f_obj = self.pool.get(f_model)
706                 # browse the objects searched
707                 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
708                 # column definitions of the "local" object
709                 _cols = self.pool.get(rec_model)._columns
710                 # if the current field is many2many
711                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
712                     f_val = [(6, 0, map(lambda x: x[f_use], s))]
713                 elif len(s):
714                     # otherwise (we are probably in a many2one field),
715                     # take the first element of the search
716                     f_val = s[0][f_use]
717             elif f_ref:
718                 if f_ref=="null":
719                     f_val = False
720                 else:
721                     f_val = self.id_get(cr, f_model, f_ref)
722             else:
723                 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
724                 if model._columns.has_key(f_name):
725                     if isinstance(model._columns[f_name], osv.fields.integer):
726                         f_val = int(f_val)
727             res[f_name] = f_val
728
729         id = self.pool.get('ir.model.data')._update(cr, self.uid, rec_model, self.module, res, rec_id or False, not self.isnoupdate(data_node), noupdate=self.isnoupdate(data_node), mode=self.mode, context=rec_context )
730         if rec_id:
731             self.idref[rec_id] = int(id)
732         if config.get('import_partial', False):
733             cr.commit()
734         return rec_model, id
735
736     def id_get(self, cr, model, id_str):
737         if id_str in self.idref:
738             return self.idref[id_str]
739         mod = self.module
740         if '.' in id_str:
741             mod,id_str = id_str.split('.')
742         result = self.pool.get('ir.model.data')._get_id(cr, self.uid, mod, id_str)
743         return int(self.pool.get('ir.model.data').read(cr, self.uid, [result], ['res_id'])[0]['res_id'])
744
745     def parse(self, de):
746         if not de.tag in ['terp', 'openerp']:
747             self.logger.notifyChannel("init", netsvc.LOG_ERROR, "Mismatch xml format" )
748             raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
749
750         if de.tag == 'terp':
751             self.logger.notifyChannel("init", netsvc.LOG_WARNING, "The tag <terp/> is deprecated, use <openerp/>")
752
753         for n in de.findall('./data'):
754             for rec in n:
755                     if rec.tag in self._tags:
756                         try:
757                             self._tags[rec.tag](self.cr, rec, n)
758                         except:
759                             self.logger.notifyChannel("init", netsvc.LOG_ERROR, '\n'+etree.tostring(rec))
760                             self.cr.rollback()
761                             raise
762         return True
763
764     def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
765
766         self.logger = netsvc.Logger()
767         self.mode = mode
768         self.module = module
769         self.cr = cr
770         self.idref = idref
771         self.pool = pooler.get_pool(cr.dbname)
772         self.uid = 1
773         if report is None:
774             report = assertion_report()
775         self.assert_report = report
776         self.noupdate = noupdate
777         self._tags = {
778             'menuitem': self._tag_menuitem,
779             'record': self._tag_record,
780             'assert': self._tag_assert,
781             'report': self._tag_report,
782             'wizard': self._tag_wizard,
783             'delete': self._tag_delete,
784             'ir_set': self._tag_ir_set,
785             'function': self._tag_function,
786             'workflow': self._tag_workflow,
787             'act_window': self._tag_act_window,
788             'url': self._tag_url
789         }
790
791 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
792         noupdate=False):
793     '''Import csv file :
794         quote: "
795         delimiter: ,
796         encoding: utf-8'''
797     if not idref:
798         idref={}
799     model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
800     #remove folder path from model
801     head, model = os.path.split(model)
802
803     pool = pooler.get_pool(cr.dbname)
804
805     input = cStringIO.StringIO(csvcontent)
806     reader = csv.reader(input, quotechar='"', delimiter=',')
807     fields = reader.next()
808     fname_partial = ""
809     if config.get('import_partial'):
810         fname_partial = module + '/'+ fname
811         if not os.path.isfile(config.get('import_partial')):
812             pickle.dump({}, file(config.get('import_partial'),'w+'))
813         else:
814             data = pickle.load(file(config.get('import_partial')))
815             if fname_partial in data:
816                 if not data[fname_partial]:
817                     return
818                 else:
819                     for i in range(data[fname_partial]):
820                         reader.next()
821
822     if not (mode == 'init' or 'id' in fields):
823         logger = netsvc.Logger()
824         logger.notifyChannel("init", netsvc.LOG_ERROR,
825             "Import specification does not contain 'id' and we are in init mode, Cannot continue.")
826         return
827
828     uid = 1
829     datas = []
830     for line in reader:
831         if (not line) or not reduce(lambda x,y: x or y, line) :
832             continue
833         try:
834             datas.append(map(lambda x: misc.ustr(x), line))
835         except:
836             logger = netsvc.Logger()
837             logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line)
838     pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate,filename=fname_partial)
839     if config.get('import_partial'):
840         data = pickle.load(file(config.get('import_partial')))
841         data[fname_partial] = 0
842         pickle.dump(data, file(config.get('import_partial'),'wb'))
843         cr.commit()
844
845 #
846 # xml import/export
847 #
848 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
849     doc = etree.parse(xmlfile)
850     relaxng = etree.RelaxNG(
851         etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
852     try:
853         relaxng.assert_(doc)
854     except Exception, e:
855         logger = netsvc.Logger()
856         logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
857         logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
858         raise
859
860     if idref is None:
861         idref={}
862     obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
863     obj.parse(doc.getroot())
864     return True
865
866 def convert_xml_export(res):
867     uid=1
868     pool=pooler.get_pool(cr.dbname)
869     cr=pooler.db.cursor()
870     idref = {}
871     page = etree.Element ( 'terp' )
872     doc = etree.ElementTree ( page )
873     data = etree.SubElement ( page, 'data' )
874     text_node = etree.SubElement ( page, 'text' )
875     text_node.text = 'Some textual content.'
876     cr.commit()
877     cr.close()
878
879
880 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
881