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