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