account.analytic.line: fix on_change_unit_amount()
[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 cStringIO
23 import csv
24 import logging
25 import os.path
26 import pickle
27 import re
28 import sys
29 import time
30
31 from datetime import datetime
32 from lxml import etree
33
34 import ir
35 import misc
36 import netsvc
37 import osv
38 import pooler
39 from config import config
40 from yaml_import import convert_yaml_import
41
42 class ConvertError(Exception):
43     def __init__(self, doc, orig_excpt):
44         self.d = doc
45         self.orig = orig_excpt
46
47     def __str__(self):
48         return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
49
50 def _ref(self, cr):
51     return lambda x: self.id_get(cr, False, x)
52
53 def _obj(pool, cr, uid, model_str, context=None):
54     model = pool.get(model_str)
55     return lambda x: model.browse(cr, uid, x, context=context)
56
57 def _eval_xml(self,node, pool, cr, uid, idref, context=None):
58     if context is None:
59         context = {}
60     if node.tag in ('field','value'):
61             t = node.get('type','char')
62             f_model = node.get('model', '').encode('ascii')
63             if node.get('search'):
64                 f_search = node.get("search",'').encode('utf-8')
65                 f_use = node.get("use",'id').encode('ascii')
66                 f_name = node.get("name",'').encode('utf-8')
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                 idref2 = idref.copy()
83                 idref2['time'] = time
84                 idref2['DateTime'] = datetime
85                 import release
86                 idref2['version'] = release.major_version
87                 idref2['ref'] = lambda x: self.id_get(cr, False, x)
88                 if len(f_model):
89                     idref2['obj'] = _obj(self.pool, cr, uid, f_model, context=context)
90                 try:
91                     import pytz
92                 except:
93                     logger = netsvc.Logger()
94                     logger.notifyChannel("init", netsvc.LOG_WARNING, 'could not find pytz library')
95                     class pytzclass(object):
96                         all_timezones=[]
97                     pytz=pytzclass()
98                 idref2['pytz'] = pytz
99                 try:
100                         return eval(a_eval, idref2)
101                 except:
102                         logger = netsvc.Logger()
103                         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))
104                         return ""
105             if t == 'xml':
106                 def _process(s, idref):
107                     m = re.findall('[^%]%\((.*?)\)[ds]', s)
108                     for id in m:
109                         if not id in idref:
110                             idref[id]=self.id_get(cr, False, id)
111                     return s % idref
112                 return '<?xml version="1.0"?>\n'\
113                     +_process("".join([etree.tostring(n, encoding='utf-8')
114                                        for n in node]),
115                               idref)
116             if t in ('char', 'int', 'float'):
117                 d = node.text
118                 if t == 'int':
119                     d = d.strip()
120                     if d == 'None':
121                         return None
122                     else:
123                         return int(d.strip())
124                 elif t == 'float':
125                     return float(d.strip())
126                 return d
127             elif t in ('list','tuple'):
128                 res=[]
129                 for n in node.findall('./value'):
130                     res.append(_eval_xml(self,n,pool,cr,uid,idref))
131                 if t=='tuple':
132                     return tuple(res)
133                 return res
134     elif node.tag == "getitem":
135         for n in node:
136             res=_eval_xml(self,n,pool,cr,uid,idref)
137         if not res:
138             raise LookupError
139         elif node.get('type') in ("int", "list"):
140             return res[int(node.get('index'))]
141         else:
142             return res[node.get('index','').encode("utf8")]
143     elif node.tag == "function":
144         args = []
145         a_eval = node.get('eval','')
146         if a_eval:
147             idref['ref'] = lambda x: self.id_get(cr, False, x)
148             args = eval(a_eval, idref)
149         for n in node:
150             return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
151             if return_val is not None:
152                 args.append(return_val)
153         model = pool.get(node.get('model',''))
154         method = node.get('name','')
155         res = getattr(model, method)(cr, uid, *args)
156         return res
157     elif node.tag == "test":
158         return node.text
159
160 escape_re = re.compile(r'(?<!\\)/')
161 def escape(x):
162     return x.replace('\\/', '/')
163
164 class assertion_report(object):
165     def __init__(self):
166         self._report = {}
167
168     def record_assertion(self, success, severity):
169         """
170             Records the result of an assertion for the failed/success count
171             returns success
172         """
173         if severity in self._report:
174             self._report[severity][success] += 1
175         else:
176             self._report[severity] = {success:1, not success: 0}
177         return success
178
179     def get_report(self):
180         return self._report
181
182     def __str__(self):
183         res = '\nAssertions report:\nLevel\tsuccess\tfailed\n'
184         success = failed = 0
185         for sev in self._report:
186             res += sev + '\t' + str(self._report[sev][True]) + '\t' + str(self._report[sev][False]) + '\n'
187             success += self._report[sev][True]
188             failed += self._report[sev][False]
189         res += 'total\t' + str(success) + '\t' + str(failed) + '\n'
190         res += 'end of report (' + str(success + failed) + ' assertion(s) checked)'
191         return res
192
193 class xml_import(object):
194     @staticmethod
195     def nodeattr2bool(node, attr, default=False):
196         if not node.get(attr):
197             return default
198         val = node.get(attr).strip()
199         if not val:
200             return default
201         return val.lower() not in ('0', 'false', 'off')
202
203     def isnoupdate(self, data_node=None):
204         return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
205
206     def get_context(self, data_node, node, eval_dict):
207         data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
208         if data_node_context:
209             context = eval(data_node_context, eval_dict)
210         else:
211             context = {}
212
213         node_context = node.get("context",'').encode('utf8')
214         if node_context:
215             context.update(eval(node_context, eval_dict))
216
217         return context
218
219     def get_uid(self, cr, uid, data_node, node):
220         node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
221         if node_uid:
222             return self.id_get(cr, None, node_uid)
223         return uid
224
225     def _test_xml_id(self, xml_id):
226         id = xml_id
227         if '.' in xml_id:
228             module, id = xml_id.split('.', 1)
229             assert '.' not in id, """The ID reference "%s" must contain
230 maximum one dot. They are used to refer to other modules ID, in the
231 form: module.record_id""" % (xml_id,)
232             if module != self.module:
233                 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
234                 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
235
236         if len(id) > 64:
237             self.logger.notifyChannel('init', netsvc.LOG_ERROR, 'id: %s is to long (max: 64)'% (id,))
238
239     def _tag_delete(self, cr, rec, data_node=None):
240         d_model = rec.get("model",'')
241         d_search = rec.get("search",'')
242         d_id = rec.get("id",'')
243         ids = []
244         if d_search:
245             ids = self.pool.get(d_model).search(cr,self.uid,eval(d_search))
246         if d_id:
247             try:
248                 ids.append(self.id_get(cr, d_model, d_id))
249             except:
250                 # d_id cannot be found. doesn't matter in this case
251                 pass
252         if ids:
253             self.pool.get(d_model).unlink(cr, self.uid, ids)
254             self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids, direct=True)
255
256     def _tag_report(self, cr, rec, data_node=None):
257         res = {}
258         for dest,f in (('name','string'),('model','model'),('report_name','name')):
259             res[dest] = rec.get(f,'').encode('utf8')
260             assert res[dest], "Attribute %s of report is empty !" % (f,)
261         for field,dest in (('rml','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
262             if rec.get(field):
263                 res[dest] = rec.get(field).encode('utf8')
264         if rec.get('auto'):
265             res['auto'] = eval(rec.get('auto'))
266         if rec.get('sxw'):
267             sxw_content = misc.file_open(rec.get('sxw')).read()
268             res['report_sxw_content'] = sxw_content
269         if rec.get('header'):
270             res['header'] = eval(rec.get('header'))
271         if rec.get('report_type'):
272             res['report_type'] = rec.get('report_type')
273
274         res['multi'] = rec.get('multi') and eval(rec.get('multi'))
275         xml_id = rec.get('id','').encode('utf8')
276         self._test_xml_id(xml_id)
277
278         if rec.get('groups'):
279             g_names = rec.get('groups','').split(',')
280             groups_value = []
281             groups_obj = self.pool.get('res.groups')
282             for group in g_names:
283                 if group.startswith('-'):
284                     group_id = self.id_get(cr, 'res.groups', group[1:])
285                     groups_value.append((3, group_id))
286                 else:
287                     group_id = self.id_get(cr, 'res.groups', group)
288                     groups_value.append((4, group_id))
289             res['groups_id'] = groups_value
290
291         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)
292         self.idref[xml_id] = int(id)
293
294         if not rec.get('menu') or eval(rec.get('menu','')):
295             keyword = str(rec.get('keyword', 'client_print_multi'))
296             keys = [('action',keyword),('res_model',res['model'])]
297             value = 'ir.actions.report.xml,'+str(id)
298             replace = rec.get('replace', True)
299             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)
300         return False
301
302     def _tag_function(self, cr, rec, data_node=None):
303         if self.isnoupdate(data_node) and self.mode != 'init':
304             return
305         context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
306         uid = self.get_uid(cr, self.uid, data_node, rec)
307         _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
308         return
309
310     def _tag_wizard(self, cr, rec, data_node=None):
311         string = rec.get("string",'').encode('utf8')
312         model = rec.get("model",'').encode('utf8')
313         name = rec.get("name",'').encode('utf8')
314         xml_id = rec.get('id','').encode('utf8')
315         self._test_xml_id(xml_id)
316         multi = rec.get('multi','') and eval(rec.get('multi',''))
317         res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
318
319         if rec.get('groups'):
320             g_names = rec.get('groups','').split(',')
321             groups_value = []
322             groups_obj = self.pool.get('res.groups')
323             for group in g_names:
324                 if group.startswith('-'):
325                     group_id = self.id_get(cr, 'res.groups', group[1:])
326                     groups_value.append((3, group_id))
327                 else:
328                     group_id = self.id_get(cr, 'res.groups', group)
329                     groups_value.append((4, group_id))
330             res['groups_id'] = groups_value
331
332         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)
333         self.idref[xml_id] = int(id)
334         # ir_set
335         if (not rec.get('menu') or eval(rec.get('menu',''))) and id:
336             keyword = str(rec.get('keyword','') or 'client_action_multi')
337             keys = [('action',keyword),('res_model',model)]
338             value = 'ir.actions.wizard,'+str(id)
339             replace = rec.get("replace",'') or True
340             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
341
342     def _tag_url(self, cr, rec, data_node=None):
343         url = rec.get("string",'').encode('utf8')
344         target = rec.get("target",'').encode('utf8')
345         name = rec.get("name",'').encode('utf8')
346         xml_id = rec.get('id','').encode('utf8')
347         self._test_xml_id(xml_id)
348
349         res = {'name': name, 'url': url, 'target':target}
350
351         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)
352         self.idref[xml_id] = int(id)
353         # ir_set
354         if (not rec.get('menu') or eval(rec.get('menu',''))) and id:
355             keyword = str(rec.get('keyword','') or 'client_action_multi')
356             keys = [('action',keyword)]
357             value = 'ir.actions.url,'+str(id)
358             replace = rec.get("replace",'') or True
359             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)
360
361     def _tag_act_window(self, cr, rec, data_node=None):
362         name = rec.get('name','').encode('utf-8')
363         xml_id = rec.get('id','').encode('utf8')
364         self._test_xml_id(xml_id)
365         type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
366         view_id = False
367         if rec.get('view'):
368             view_id = self.id_get(cr, 'ir.actions.act_window', rec.get('view','').encode('utf-8'))
369         domain = rec.get('domain','').encode('utf-8') or '{}'
370         context = rec.get('context','').encode('utf-8') or '{}'
371         res_model = rec.get('res_model','').encode('utf-8')
372         src_model = rec.get('src_model','').encode('utf-8')
373         view_type = rec.get('view_type','').encode('utf-8') or 'form'
374         view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
375         usage = rec.get('usage','').encode('utf-8')
376         limit = rec.get('limit','').encode('utf-8')
377         auto_refresh = rec.get('auto_refresh','').encode('utf-8')
378
379         # def ref() added because , if context has ref('id') eval wil use this ref
380
381         active_id=str("active_id") # for further reference in client/bin/tools/__init__.py
382
383         def ref(str_id):
384             return self.id_get(cr, None, str_id)
385         context=eval(context)
386
387         res = {
388             'name': name,
389             'type': type,
390             'view_id': view_id,
391             'domain': domain,
392             'context': context,
393             'res_model': res_model,
394             'src_model': src_model,
395             'view_type': view_type,
396             'view_mode': view_mode,
397             'usage': usage,
398             'limit': limit,
399             'auto_refresh': auto_refresh,
400 #            'groups_id':groups_id,
401         }
402
403         if rec.get('groups'):
404             g_names = rec.get('groups','').split(',')
405             groups_value = []
406             groups_obj = self.pool.get('res.groups')
407             for group in g_names:
408                 if group.startswith('-'):
409                     group_id = self.id_get(cr, 'res.groups', group[1:])
410                     groups_value.append((3, group_id))
411                 else:
412                     group_id = self.id_get(cr, 'res.groups', group)
413                     groups_value.append((4, group_id))
414             res['groups_id'] = groups_value
415
416         if rec.get('target'):
417             res['target'] = rec.get('target','')
418         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)
419         self.idref[xml_id] = int(id)
420
421         if src_model:
422             keyword = 'client_action_relate'
423             keys = [('action', keyword), ('res_model', res_model)]
424             value = 'ir.actions.act_window,'+str(id)
425             replace = rec.get('replace','') or True
426             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)
427         # TODO add remove ir.model.data
428
429     def _tag_ir_set(self, cr, rec, data_node=None):
430         if self.mode != 'init':
431             return
432         res = {}
433         for field in rec.findall('./field'):
434             f_name = field.get("name",'').encode('utf-8')
435             f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
436             res[f_name] = f_val
437         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))
438
439     def _tag_workflow(self, cr, rec, data_node=None):
440         if self.isnoupdate(data_node) and self.mode != 'init':
441             return
442         model = str(rec.get('model',''))
443         w_ref = rec.get('ref','')
444         if w_ref:
445             id = self.id_get(cr, model, w_ref)
446         else:
447             number_children = len(rec)
448             assert number_children > 0,\
449                 'You must define a child node if you dont give a ref'
450             assert number_children == 1,\
451                 'Only one child node is accepted (%d given)' % number_children
452             id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
453
454         uid = self.get_uid(cr, self.uid, data_node, rec)
455         wf_service = netsvc.LocalService("workflow")
456         wf_service.trg_validate(uid, model,
457             id,
458             str(rec.get('action','')), cr)
459
460     #
461     # Support two types of notation:
462     #   name="Inventory Control/Sending Goods"
463     # or
464     #   action="action_id"
465     #   parent="parent_id"
466     #
467     def _tag_menuitem(self, cr, rec, data_node=None):
468         rec_id = rec.get("id",'').encode('ascii')
469         self._test_xml_id(rec_id)
470         m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
471
472         values = {'parent_id': False}
473         if not rec.get('parent'):
474             pid = False
475             for idx, menu_elem in enumerate(m_l):
476                 if pid:
477                     cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
478                 else:
479                     cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
480                 res = cr.fetchone()
481                 if idx==len(m_l)-1:
482                     values = {'parent_id': pid,'name':menu_elem}
483                 elif res:
484                     pid = res[0]
485                     xml_id = idx==len(m_l)-1 and rec.get('id','').encode('utf8')
486                     try:
487                         npid = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, 'ir.ui.menu', self.module, xml_id, idx==len(m_l)-1)
488                     except:
489                         self.logger.notifyChannel('init', netsvc.LOG_ERROR, "module: %s xml_id: %s" % (self.module, xml_id))
490                 else:
491                     # the menuitem does't exist but we are in branch (not a leaf)
492                     self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l)))
493                     pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
494         else:
495             menu_parent_id = self.id_get(cr, 'ir.ui.menu', rec.get('parent',''))
496             values = {'parent_id': menu_parent_id}
497             if rec.get('name'):
498                 values['name'] = rec.get('name')
499             try:
500                 res = [ self.id_get(cr, 'ir.ui.menu', rec.get('id','')) ]
501             except:
502                 res = None
503
504         if rec.get('action'):
505             a_action = rec.get('action','').encode('utf8')
506             a_type = rec.get('type','').encode('utf8') or 'act_window'
507             icons = {
508                 "act_window": 'STOCK_NEW',
509                 "report.xml": 'STOCK_PASTE',
510                 "wizard": 'STOCK_EXECUTE',
511                 "url": 'STOCK_JUMP_TO'
512             }
513             values['icon'] = icons.get(a_type,'STOCK_NEW')
514             if a_type=='act_window':
515                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
516                 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
517                 rrres = cr.fetchone()
518                 assert rrres, "No window action defined for this id %s !\n" \
519                     "Verify that this is a window action or add a type argument." % (a_action,)
520                 action_type,action_mode,action_name,view_id,target = rrres
521                 if view_id:
522                     cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
523                     action_mode, = cr.fetchone()
524                 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
525                 if cr.rowcount:
526                     action_mode, = cr.fetchone()
527                 if action_type=='tree':
528                     values['icon'] = 'STOCK_INDENT'
529                 elif action_mode and action_mode.startswith('tree'):
530                     values['icon'] = 'STOCK_JUSTIFY_FILL'
531                 elif action_mode and action_mode.startswith('graph'):
532                     values['icon'] = 'terp-graph'
533                 elif action_mode and action_mode.startswith('calendar'):
534                     values['icon'] = 'terp-calendar'
535                 if target=='new':
536                     values['icon'] = 'STOCK_EXECUTE'
537                 if not values.get('name', False):
538                     values['name'] = action_name
539             elif a_type=='wizard':
540                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
541                 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
542                 resw = cr.fetchone()
543                 if (not values.get('name', False)) and resw:
544                     values['name'] = resw[0]
545         if rec.get('sequence'):
546             values['sequence'] = int(rec.get('sequence'))
547         if rec.get('icon'):
548             values['icon'] = str(rec.get('icon'))
549
550         if rec.get('groups'):
551             g_names = rec.get('groups','').split(',')
552             groups_value = []
553             groups_obj = self.pool.get('res.groups')
554             for group in g_names:
555                 if group.startswith('-'):
556                     group_id = self.id_get(cr, 'res.groups', group[1:])
557                     groups_value.append((3, group_id))
558                 else:
559                     group_id = self.id_get(cr, 'res.groups', group)
560                     groups_value.append((4, group_id))
561             values['groups_id'] = groups_value
562
563         xml_id = rec.get('id','').encode('utf8')
564         self._test_xml_id(xml_id)
565         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)
566
567         if rec_id and pid:
568             self.idref[rec_id] = int(pid)
569
570         if rec.get('action') and pid:
571             a_action = rec.get('action').encode('utf8')
572             a_type = rec.get('type','').encode('utf8') or 'act_window'
573             a_id = self.id_get(cr, 'ir.actions.%s' % a_type, a_action)
574             action = "ir.actions.%s,%d" % (a_type, a_id)
575             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)
576         return ('ir.ui.menu', pid)
577
578     def _assert_equals(self, f1, f2, prec = 4):
579         return not round(f1 - f2, prec)
580
581     def _tag_assert(self, cr, rec, data_node=None):
582         if self.isnoupdate(data_node) and self.mode != 'init':
583             return
584
585         rec_model = rec.get("model",'').encode('ascii')
586         model = self.pool.get(rec_model)
587         assert model, "The model %s does not exist !" % (rec_model,)
588         rec_id = rec.get("id",'').encode('ascii')
589         self._test_xml_id(rec_id)
590         rec_src = rec.get("search",'').encode('utf8')
591         rec_src_count = rec.get("count")
592
593         severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
594         rec_string = rec.get("string",'').encode('utf8') or 'unknown'
595
596         ids = None
597         eval_dict = {'ref': _ref(self, cr)}
598         context = self.get_context(data_node, rec, eval_dict)
599         uid = self.get_uid(cr, self.uid, data_node, rec)
600         if rec_id:
601             ids = [self.id_get(cr, rec_model, rec_id)]
602         elif rec_src:
603             q = eval(rec_src, eval_dict)
604             ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
605             if rec_src_count:
606                 count = int(rec_src_count)
607                 if len(ids) != count:
608                     self.assert_report.record_assertion(False, severity)
609                     msg = 'assertion "%s" failed!\n'    \
610                           ' Incorrect search count:\n'  \
611                           ' expected count: %d\n'       \
612                           ' obtained count: %d\n'       \
613                           % (rec_string, count, len(ids))
614                     self.logger.notifyChannel('init', severity, msg)
615                     sevval = getattr(logging, severity.upper())
616                     if sevval >= config['assert_exit_level']:
617                         # TODO: define a dedicated exception
618                         raise Exception('Severe assertion failure')
619                     return
620
621         assert ids is not None,\
622             'You must give either an id or a search criteria'
623         ref = _ref(self, cr)
624         for id in ids:
625             brrec =  model.browse(cr, uid, id, context)
626             class d(dict):
627                 def __getitem__(self2, key):
628                     if key in brrec:
629                         return brrec[key]
630                     return dict.__getitem__(self2, key)
631             globals = d()
632             globals['floatEqual'] = self._assert_equals
633             globals['ref'] = ref
634             globals['_ref'] = ref
635             for test in rec.findall('./test'):
636                 f_expr = test.get("expr",'').encode('utf-8')
637                 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
638                 expression_value = eval(f_expr, globals)
639                 if expression_value != expected_value: # assertion failed
640                     self.assert_report.record_assertion(False, severity)
641                     msg = 'assertion "%s" failed!\n'    \
642                           ' xmltag: %s\n'               \
643                           ' expected value: %r\n'       \
644                           ' obtained value: %r\n'       \
645                           % (rec_string, etree.tostring(test), expected_value, expression_value)
646                     self.logger.notifyChannel('init', severity, msg)
647                     sevval = getattr(logging, severity.upper())
648                     if sevval >= config['assert_exit_level']:
649                         # TODO: define a dedicated exception
650                         raise Exception('Severe assertion failure')
651                     return
652         else: # all tests were successful for this assertion tag (no break)
653             self.assert_report.record_assertion(True, severity)
654
655     def _tag_record(self, cr, rec, data_node=None):
656         rec_model = rec.get("model").encode('ascii')
657         model = self.pool.get(rec_model)
658         assert model, "The model %s does not exist !" % (rec_model,)
659         rec_id = rec.get("id",'').encode('ascii')
660         rec_context = rec.get("context", None)
661         if rec_context:
662             rec_context = eval(rec_context)
663         self._test_xml_id(rec_id)
664         if self.isnoupdate(data_node) and self.mode != 'init':
665             # check if the xml record has an id string
666             if rec_id:
667                 if '.' in rec_id:
668                     module,rec_id2 = rec_id.split('.')
669                 else:
670                     module = self.module
671                     rec_id2 = rec_id
672                 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
673                 # check if the resource already existed at the last update
674                 if id:
675                     # if it existed, we don't update the data, but we need to
676                     # know the id of the existing record anyway
677                     self.idref[rec_id] = int(id)
678                     return None
679                 else:
680                     # if the resource didn't exist
681                     if not self.nodeattr2bool(rec, 'forcecreate', True):
682                         # we don't want to create it, so we skip it
683                         return None
684                     # else, we let the record to be created
685
686             else:
687                 # otherwise it is skipped
688                 return None
689         res = {}
690         for field in rec.findall('./field'):
691 #TODO: most of this code is duplicated above (in _eval_xml)...
692             f_name = field.get("name",'').encode('utf-8')
693             f_ref = field.get("ref",'').encode('ascii')
694             f_search = field.get("search",'').encode('utf-8')
695             f_model = field.get("model",'').encode('ascii')
696             if not f_model and model._columns.get(f_name,False):
697                 f_model = model._columns[f_name]._obj
698             f_use = field.get("use",'').encode('ascii') or 'id'
699             f_val = False
700
701             if f_search:
702                 q = eval(f_search, self.idref)
703                 field = []
704                 assert f_model, 'Define an attribute model="..." in your .XML file !'
705                 f_obj = self.pool.get(f_model)
706                 # browse the objects searched
707                 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
708                 # column definitions of the "local" object
709                 _cols = self.pool.get(rec_model)._columns
710                 # if the current field is many2many
711                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
712                     f_val = [(6, 0, map(lambda x: x[f_use], s))]
713                 elif len(s):
714                     # otherwise (we are probably in a many2one field),
715                     # take the first element of the search
716                     f_val = s[0][f_use]
717             elif f_ref:
718                 if f_ref=="null":
719                     f_val = False
720                 else:
721                     f_val = self.id_get(cr, f_model, f_ref)
722             else:
723                 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
724                 if model._columns.has_key(f_name):
725                     if isinstance(model._columns[f_name], osv.fields.integer):
726                         f_val = int(f_val)
727             res[f_name] = f_val
728
729         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 )
730         if rec_id:
731             self.idref[rec_id] = int(id)
732         if config.get('import_partial', False):
733             cr.commit()
734         return rec_model, id
735
736     def id_get(self, cr, model, id_str):
737         if id_str in self.idref:
738             return self.idref[id_str]
739         mod = self.module
740         if '.' in id_str:
741             mod,id_str = id_str.split('.')
742         result = self.pool.get('ir.model.data')._get_id(cr, self.uid, mod, id_str)
743         res = self.pool.get('ir.model.data').read(cr, self.uid, [result], ['res_id'])
744         if res and res[0] and res[0]['res_id']:
745             return int(res[0]['res_id'])
746         return False
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