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