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