[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
388         res = {
389             'name': name,
390             'type': type,
391             'view_id': view_id,
392             'domain': domain,
393             'context': context,
394             'res_model': res_model,
395             'src_model': src_model,
396             'view_type': view_type,
397             'view_mode': view_mode,
398             'usage': usage,
399             'limit': limit,
400             'auto_refresh': auto_refresh,
401 #            'groups_id':groups_id,
402         }
403
404         if rec.get('groups'):
405             g_names = rec.get('groups','').split(',')
406             groups_value = []
407             groups_obj = self.pool.get('res.groups')
408             for group in g_names:
409                 if group.startswith('-'):
410                     group_id = self.id_get(cr, 'res.groups', group[1:])
411                     groups_value.append((3, group_id))
412                 else:
413                     group_id = self.id_get(cr, 'res.groups', group)
414                     groups_value.append((4, group_id))
415             res['groups_id'] = groups_value
416
417         if rec.get('target'):
418             res['target'] = rec.get('target','')
419         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)
420         self.idref[xml_id] = int(id)
421
422         if src_model:
423             #keyword = 'client_action_relate'
424             keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
425             keys = [('action', keyword), ('res_model', res_model)]
426             value = 'ir.actions.act_window,'+str(id)
427             replace = rec.get('replace','') or True
428             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)
429         # TODO add remove ir.model.data
430
431     def _tag_ir_set(self, cr, rec, data_node=None):
432         if self.mode != 'init':
433             return
434         res = {}
435         for field in rec.findall('./field'):
436             f_name = field.get("name",'').encode('utf-8')
437             f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
438             res[f_name] = f_val
439         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))
440
441     def _tag_workflow(self, cr, rec, data_node=None):
442         if self.isnoupdate(data_node) and self.mode != 'init':
443             return
444         model = str(rec.get('model',''))
445         w_ref = rec.get('ref','')
446         if w_ref:
447             id = self.id_get(cr, model, w_ref)
448         else:
449             number_children = len(rec)
450             assert number_children > 0,\
451                 'You must define a child node if you dont give a ref'
452             assert number_children == 1,\
453                 'Only one child node is accepted (%d given)' % number_children
454             id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
455
456         uid = self.get_uid(cr, self.uid, data_node, rec)
457         wf_service = netsvc.LocalService("workflow")
458         wf_service.trg_validate(uid, model,
459             id,
460             str(rec.get('action','')), cr)
461
462     #
463     # Support two types of notation:
464     #   name="Inventory Control/Sending Goods"
465     # or
466     #   action="action_id"
467     #   parent="parent_id"
468     #
469     def _tag_menuitem(self, cr, rec, data_node=None):
470         rec_id = rec.get("id",'').encode('ascii')
471         self._test_xml_id(rec_id)
472         m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
473
474         values = {'parent_id': False}
475         if not rec.get('parent'):
476             pid = False
477             for idx, menu_elem in enumerate(m_l):
478                 if pid:
479                     cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
480                 else:
481                     cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
482                 res = cr.fetchone()
483                 if idx==len(m_l)-1:
484                     values = {'parent_id': pid,'name':menu_elem}
485                 elif res:
486                     pid = res[0]
487                     xml_id = idx==len(m_l)-1 and rec.get('id','').encode('utf8')
488                     try:
489                         npid = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, 'ir.ui.menu', self.module, xml_id, idx==len(m_l)-1)
490                     except:
491                         self.logger.notifyChannel('init', netsvc.LOG_ERROR, "module: %s xml_id: %s" % (self.module, xml_id))
492                 else:
493                     # the menuitem does't exist but we are in branch (not a leaf)
494                     self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l)))
495                     pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
496         else:
497             menu_parent_id = self.id_get(cr, 'ir.ui.menu', rec.get('parent',''))
498             values = {'parent_id': menu_parent_id}
499             if rec.get('name'):
500                 values['name'] = rec.get('name')
501             try:
502                 res = [ self.id_get(cr, 'ir.ui.menu', rec.get('id','')) ]
503             except:
504                 res = None
505
506         if rec.get('action'):
507             a_action = rec.get('action','').encode('utf8')
508             a_type = rec.get('type','').encode('utf8') or 'act_window'
509             icons = {
510                 "act_window": 'STOCK_NEW',
511                 "report.xml": 'STOCK_PASTE',
512                 "wizard": 'STOCK_EXECUTE',
513                 "url": 'STOCK_JUMP_TO'
514             }
515             values['icon'] = icons.get(a_type,'STOCK_NEW')
516             if a_type=='act_window':
517                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
518                 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
519                 rrres = cr.fetchone()
520                 assert rrres, "No window action defined for this id %s !\n" \
521                     "Verify that this is a window action or add a type argument." % (a_action,)
522                 action_type,action_mode,action_name,view_id,target = rrres
523                 if view_id:
524                     cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
525                     action_mode, = cr.fetchone()
526                 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
527                 if cr.rowcount:
528                     action_mode, = cr.fetchone()
529                 if action_type=='tree':
530                     values['icon'] = 'STOCK_INDENT'
531                 elif action_mode and action_mode.startswith('tree'):
532                     values['icon'] = 'STOCK_JUSTIFY_FILL'
533                 elif action_mode and action_mode.startswith('graph'):
534                     values['icon'] = 'terp-graph'
535                 elif action_mode and action_mode.startswith('calendar'):
536                     values['icon'] = 'terp-calendar'
537                 if target=='new':
538                     values['icon'] = 'STOCK_EXECUTE'
539                 if not values.get('name', False):
540                     values['name'] = action_name
541             elif a_type=='wizard':
542                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
543                 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
544                 resw = cr.fetchone()
545                 if (not values.get('name', False)) and resw:
546                     values['name'] = resw[0]
547         if rec.get('sequence'):
548             values['sequence'] = int(rec.get('sequence'))
549         if rec.get('icon'):
550             values['icon'] = str(rec.get('icon'))
551
552         if rec.get('groups'):
553             g_names = rec.get('groups','').split(',')
554             groups_value = []
555             groups_obj = self.pool.get('res.groups')
556             for group in g_names:
557                 if group.startswith('-'):
558                     group_id = self.id_get(cr, 'res.groups', group[1:])
559                     groups_value.append((3, group_id))
560                 else:
561                     group_id = self.id_get(cr, 'res.groups', group)
562                     groups_value.append((4, group_id))
563             values['groups_id'] = groups_value
564
565         xml_id = rec.get('id','').encode('utf8')
566         self._test_xml_id(xml_id)
567         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)
568
569         if rec_id and pid:
570             self.idref[rec_id] = int(pid)
571
572         if rec.get('action') and pid:
573             a_action = rec.get('action').encode('utf8')
574             a_type = rec.get('type','').encode('utf8') or 'act_window'
575             a_id = self.id_get(cr, 'ir.actions.%s' % a_type, a_action)
576             action = "ir.actions.%s,%d" % (a_type, a_id)
577             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)
578         return ('ir.ui.menu', pid)
579
580     def _assert_equals(self, f1, f2, prec = 4):
581         return not round(f1 - f2, prec)
582
583     def _tag_assert(self, cr, rec, data_node=None):
584         if self.isnoupdate(data_node) and self.mode != 'init':
585             return
586
587         rec_model = rec.get("model",'').encode('ascii')
588         model = self.pool.get(rec_model)
589         assert model, "The model %s does not exist !" % (rec_model,)
590         rec_id = rec.get("id",'').encode('ascii')
591         self._test_xml_id(rec_id)
592         rec_src = rec.get("search",'').encode('utf8')
593         rec_src_count = rec.get("count")
594
595         severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
596         rec_string = rec.get("string",'').encode('utf8') or 'unknown'
597
598         ids = None
599         eval_dict = {'ref': _ref(self, cr)}
600         context = self.get_context(data_node, rec, eval_dict)
601         uid = self.get_uid(cr, self.uid, data_node, rec)
602         if rec_id:
603             ids = [self.id_get(cr, rec_model, rec_id)]
604         elif rec_src:
605             q = eval(rec_src, eval_dict)
606             ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
607             if rec_src_count:
608                 count = int(rec_src_count)
609                 if len(ids) != count:
610                     self.assert_report.record_assertion(False, severity)
611                     msg = 'assertion "%s" failed!\n'    \
612                           ' Incorrect search count:\n'  \
613                           ' expected count: %d\n'       \
614                           ' obtained count: %d\n'       \
615                           % (rec_string, count, len(ids))
616                     self.logger.notifyChannel('init', severity, msg)
617                     sevval = getattr(logging, severity.upper())
618                     if sevval >= config['assert_exit_level']:
619                         # TODO: define a dedicated exception
620                         raise Exception('Severe assertion failure')
621                     return
622
623         assert ids is not None,\
624             'You must give either an id or a search criteria'
625         ref = _ref(self, cr)
626         for id in ids:
627             brrec =  model.browse(cr, uid, id, context)
628             class d(dict):
629                 def __getitem__(self2, key):
630                     if key in brrec:
631                         return brrec[key]
632                     return dict.__getitem__(self2, key)
633             globals = d()
634             globals['floatEqual'] = self._assert_equals
635             globals['ref'] = ref
636             globals['_ref'] = ref
637             for test in rec.findall('./test'):
638                 f_expr = test.get("expr",'').encode('utf-8')
639                 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
640                 expression_value = eval(f_expr, globals)
641                 if expression_value != expected_value: # assertion failed
642                     self.assert_report.record_assertion(False, severity)
643                     msg = 'assertion "%s" failed!\n'    \
644                           ' xmltag: %s\n'               \
645                           ' expected value: %r\n'       \
646                           ' obtained value: %r\n'       \
647                           % (rec_string, etree.tostring(test), expected_value, expression_value)
648                     self.logger.notifyChannel('init', severity, msg)
649                     sevval = getattr(logging, severity.upper())
650                     if sevval >= config['assert_exit_level']:
651                         # TODO: define a dedicated exception
652                         raise Exception('Severe assertion failure')
653                     return
654         else: # all tests were successful for this assertion tag (no break)
655             self.assert_report.record_assertion(True, severity)
656
657     def _tag_record(self, cr, rec, data_node=None):
658         rec_model = rec.get("model").encode('ascii')
659         model = self.pool.get(rec_model)
660         assert model, "The model %s does not exist !" % (rec_model,)
661         rec_id = rec.get("id",'').encode('ascii')
662         rec_context = rec.get("context", None)
663         if rec_context:
664             rec_context = eval(rec_context)
665         self._test_xml_id(rec_id)
666         if self.isnoupdate(data_node) and self.mode != 'init':
667             # check if the xml record has an id string
668             if rec_id:
669                 if '.' in rec_id:
670                     module,rec_id2 = rec_id.split('.')
671                 else:
672                     module = self.module
673                     rec_id2 = rec_id
674                 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
675                 # check if the resource already existed at the last update
676                 if id:
677                     # if it existed, we don't update the data, but we need to
678                     # know the id of the existing record anyway
679                     self.idref[rec_id] = int(id)
680                     return None
681                 else:
682                     # if the resource didn't exist
683                     if not self.nodeattr2bool(rec, 'forcecreate', True):
684                         # we don't want to create it, so we skip it
685                         return None
686                     # else, we let the record to be created
687
688             else:
689                 # otherwise it is skipped
690                 return None
691         res = {}
692         for field in rec.findall('./field'):
693 #TODO: most of this code is duplicated above (in _eval_xml)...
694             f_name = field.get("name",'').encode('utf-8')
695             f_ref = field.get("ref",'').encode('ascii')
696             f_search = field.get("search",'').encode('utf-8')
697             f_model = field.get("model",'').encode('ascii')
698             if not f_model and model._columns.get(f_name,False):
699                 f_model = model._columns[f_name]._obj
700             f_use = field.get("use",'').encode('ascii') or 'id'
701             f_val = False
702
703             if f_search:
704                 q = eval(f_search, self.idref)
705                 field = []
706                 assert f_model, 'Define an attribute model="..." in your .XML file !'
707                 f_obj = self.pool.get(f_model)
708                 # browse the objects searched
709                 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
710                 # column definitions of the "local" object
711                 _cols = self.pool.get(rec_model)._columns
712                 # if the current field is many2many
713                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
714                     f_val = [(6, 0, map(lambda x: x[f_use], s))]
715                 elif len(s):
716                     # otherwise (we are probably in a many2one field),
717                     # take the first element of the search
718                     f_val = s[0][f_use]
719             elif f_ref:
720                 if f_ref=="null":
721                     f_val = False
722                 else:
723                     f_val = self.id_get(cr, f_model, f_ref)
724             else:
725                 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
726                 if model._columns.has_key(f_name):
727                     if isinstance(model._columns[f_name], osv.fields.integer):
728                         f_val = int(f_val)
729             res[f_name] = f_val
730
731         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 )
732         if rec_id:
733             self.idref[rec_id] = int(id)
734         if config.get('import_partial', False):
735             cr.commit()
736         return rec_model, id
737
738     def id_get(self, cr, model, id_str):
739         if id_str in self.idref:
740             return self.idref[id_str]
741         mod = self.module
742         if '.' in id_str:
743             mod,id_str = id_str.split('.')
744         result = self.pool.get('ir.model.data')._get_id(cr, self.uid, mod, id_str)
745         res = self.pool.get('ir.model.data').read(cr, self.uid, [result], ['res_id'])
746         if res and res[0] and res[0]['res_id']:
747             return int(res[0]['res_id'])
748         return False
749
750     def parse(self, de):
751         if not de.tag in ['terp', 'openerp']:
752             self.logger.notifyChannel("init", netsvc.LOG_ERROR, "Mismatch xml format" )
753             raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
754
755         if de.tag == 'terp':
756             self.logger.notifyChannel("init", netsvc.LOG_WARNING, "The tag <terp/> is deprecated, use <openerp/>")
757
758         for n in de.findall('./data'):
759             for rec in n:
760                     if rec.tag in self._tags:
761                         try:
762                             self._tags[rec.tag](self.cr, rec, n)
763                         except:
764                             self.logger.notifyChannel("init", netsvc.LOG_ERROR, '\n'+etree.tostring(rec))
765                             self.cr.rollback()
766                             raise
767         return True
768
769     def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
770
771         self.logger = netsvc.Logger()
772         self.mode = mode
773         self.module = module
774         self.cr = cr
775         self.idref = idref
776         self.pool = pooler.get_pool(cr.dbname)
777         self.uid = 1
778         if report is None:
779             report = assertion_report()
780         self.assert_report = report
781         self.noupdate = noupdate
782         self._tags = {
783             'menuitem': self._tag_menuitem,
784             'record': self._tag_record,
785             'assert': self._tag_assert,
786             'report': self._tag_report,
787             'wizard': self._tag_wizard,
788             'delete': self._tag_delete,
789             'ir_set': self._tag_ir_set,
790             'function': self._tag_function,
791             'workflow': self._tag_workflow,
792             'act_window': self._tag_act_window,
793             'url': self._tag_url
794         }
795
796 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
797         noupdate=False):
798     '''Import csv file :
799         quote: "
800         delimiter: ,
801         encoding: utf-8'''
802     if not idref:
803         idref={}
804     model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
805     #remove folder path from model
806     head, model = os.path.split(model)
807
808     pool = pooler.get_pool(cr.dbname)
809
810     input = cStringIO.StringIO(csvcontent)
811     reader = csv.reader(input, quotechar='"', delimiter=',')
812     fields = reader.next()
813     fname_partial = ""
814     if config.get('import_partial'):
815         fname_partial = module + '/'+ fname
816         if not os.path.isfile(config.get('import_partial')):
817             pickle.dump({}, file(config.get('import_partial'),'w+'))
818         else:
819             data = pickle.load(file(config.get('import_partial')))
820             if fname_partial in data:
821                 if not data[fname_partial]:
822                     return
823                 else:
824                     for i in range(data[fname_partial]):
825                         reader.next()
826
827     if not (mode == 'init' or 'id' in fields):
828         logger = netsvc.Logger()
829         logger.notifyChannel("init", netsvc.LOG_ERROR,
830             "Import specification does not contain 'id' and we are in init mode, Cannot continue.")
831         return
832
833     uid = 1
834     datas = []
835     for line in reader:
836         if (not line) or not reduce(lambda x,y: x or y, line) :
837             continue
838         try:
839             datas.append(map(lambda x: misc.ustr(x), line))
840         except:
841             logger = netsvc.Logger()
842             logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line)
843     pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
844     if config.get('import_partial'):
845         data = pickle.load(file(config.get('import_partial')))
846         data[fname_partial] = 0
847         pickle.dump(data, file(config.get('import_partial'),'wb'))
848         cr.commit()
849
850 #
851 # xml import/export
852 #
853 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
854     doc = etree.parse(xmlfile)
855     relaxng = etree.RelaxNG(
856         etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
857     try:
858         relaxng.assert_(doc)
859     except Exception, e:
860         logger = netsvc.Logger()
861         logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
862         logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
863         raise
864
865     if idref is None:
866         idref={}
867     obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
868     obj.parse(doc.getroot())
869     return True
870