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