[FIX] convert: set uid variable which are used in view domain
[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         
376         usage = rec.get('usage','').encode('utf-8')
377         limit = rec.get('limit','').encode('utf-8')
378         auto_refresh = rec.get('auto_refresh','').encode('utf-8')
379         uid = self.uid
380         # def ref() added because , if context has ref('id') eval wil use this ref
381
382         active_id=str("active_id") # for further reference in client/bin/tools/__init__.py
383
384         def ref(str_id):
385             return self.id_get(cr, None, str_id)
386         context=eval(context)
387         domain=eval(domain)
388
389         res = {
390             'name': name,
391             'type': type,
392             'view_id': view_id,
393             'domain': domain,
394             'context': context,
395             'res_model': res_model,
396             'src_model': src_model,
397             'view_type': view_type,
398             'view_mode': view_mode,
399             'usage': usage,
400             'limit': limit,
401             'auto_refresh': auto_refresh,
402 #            'groups_id':groups_id,
403         }
404
405         if rec.get('groups'):
406             g_names = rec.get('groups','').split(',')
407             groups_value = []
408             groups_obj = self.pool.get('res.groups')
409             for group in g_names:
410                 if group.startswith('-'):
411                     group_id = self.id_get(cr, 'res.groups', group[1:])
412                     groups_value.append((3, group_id))
413                 else:
414                     group_id = self.id_get(cr, 'res.groups', group)
415                     groups_value.append((4, group_id))
416             res['groups_id'] = groups_value
417
418         if rec.get('target'):
419             res['target'] = rec.get('target','')
420         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)
421         self.idref[xml_id] = int(id)
422
423         if src_model:
424             #keyword = 'client_action_relate'
425             keyword = rec.get('key2','').encode('utf-8') or '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         res = self.pool.get('ir.model.data').read(cr, self.uid, [result], ['res_id'])
747         if res and res[0] and res[0]['res_id']:
748             return int(res[0]['res_id'])
749         return False
750
751     def parse(self, de):
752         if not de.tag in ['terp', 'openerp']:
753             self.logger.notifyChannel("init", netsvc.LOG_ERROR, "Mismatch xml format" )
754             raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
755
756         if de.tag == 'terp':
757             self.logger.notifyChannel("init", netsvc.LOG_WARNING, "The tag <terp/> is deprecated, use <openerp/>")
758
759         for n in de.findall('./data'):
760             for rec in n:
761                     if rec.tag in self._tags:
762                         try:
763                             self._tags[rec.tag](self.cr, rec, n)
764                         except:
765                             self.logger.notifyChannel("init", netsvc.LOG_ERROR, '\n'+etree.tostring(rec))
766                             self.cr.rollback()
767                             raise
768         return True
769
770     def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
771
772         self.logger = netsvc.Logger()
773         self.mode = mode
774         self.module = module
775         self.cr = cr
776         self.idref = idref
777         self.pool = pooler.get_pool(cr.dbname)
778         self.uid = 1
779         if report is None:
780             report = assertion_report()
781         self.assert_report = report
782         self.noupdate = noupdate
783         self._tags = {
784             'menuitem': self._tag_menuitem,
785             'record': self._tag_record,
786             'assert': self._tag_assert,
787             'report': self._tag_report,
788             'wizard': self._tag_wizard,
789             'delete': self._tag_delete,
790             'ir_set': self._tag_ir_set,
791             'function': self._tag_function,
792             'workflow': self._tag_workflow,
793             'act_window': self._tag_act_window,
794             'url': self._tag_url
795         }
796
797 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
798         noupdate=False):
799     '''Import csv file :
800         quote: "
801         delimiter: ,
802         encoding: utf-8'''
803     if not idref:
804         idref={}
805     model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
806     #remove folder path from model
807     head, model = os.path.split(model)
808
809     pool = pooler.get_pool(cr.dbname)
810
811     input = cStringIO.StringIO(csvcontent)
812     reader = csv.reader(input, quotechar='"', delimiter=',')
813     fields = reader.next()
814     fname_partial = ""
815     if config.get('import_partial'):
816         fname_partial = module + '/'+ fname
817         if not os.path.isfile(config.get('import_partial')):
818             pickle.dump({}, file(config.get('import_partial'),'w+'))
819         else:
820             data = pickle.load(file(config.get('import_partial')))
821             if fname_partial in data:
822                 if not data[fname_partial]:
823                     return
824                 else:
825                     for i in range(data[fname_partial]):
826                         reader.next()
827
828     if not (mode == 'init' or 'id' in fields):
829         logger = netsvc.Logger()
830         logger.notifyChannel("init", netsvc.LOG_ERROR,
831             "Import specification does not contain 'id' and we are in init mode, Cannot continue.")
832         return
833
834     uid = 1
835     datas = []
836     for line in reader:
837         if (not line) or not reduce(lambda x,y: x or y, line) :
838             continue
839         try:
840             datas.append(map(lambda x: misc.ustr(x), line))
841         except:
842             logger = netsvc.Logger()
843             logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line)
844     pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
845     if config.get('import_partial'):
846         data = pickle.load(file(config.get('import_partial')))
847         data[fname_partial] = 0
848         pickle.dump(data, file(config.get('import_partial'),'wb'))
849         cr.commit()
850
851 #
852 # xml import/export
853 #
854 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
855     doc = etree.parse(xmlfile)
856     relaxng = etree.RelaxNG(
857         etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
858     try:
859         relaxng.assert_(doc)
860     except Exception, e:
861         logger = netsvc.Logger()
862         logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
863         logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
864         raise
865
866     if idref is None:
867         idref={}
868     obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
869     obj.parse(doc.getroot())
870     return True
871