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