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