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