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