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