4b996bc9c2f8ee9686fc44bf2c215635a518b3dc
[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
30 from datetime import datetime
31 from lxml import etree
32 import time
33 import ir
34 import misc
35 import netsvc
36 import osv
37 import pooler
38 from config import config
39 from yaml_import import convert_yaml_import
40
41 class ConvertError(Exception):
42     def __init__(self, doc, orig_excpt):
43         self.d = doc
44         self.orig = orig_excpt
45
46     def __str__(self):
47         return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
48
49 def _ref(self, cr):
50     return lambda x: self.id_get(cr, False, x)
51
52 def _obj(pool, cr, uid, model_str, context=None):
53     model = pool.get(model_str)
54     return lambda x: model.browse(cr, uid, x, context=context)
55
56 def _eval_xml(self,node, pool, cr, uid, idref, context=None):
57     if context is None:
58         context = {}
59     if node.tag in ('field','value'):
60             t = node.get('type','char')
61             f_model = node.get('model', '').encode('ascii')
62             if node.get('search'):
63                 f_search = node.get("search",'').encode('utf-8')
64                 f_use = node.get("use",'id').encode('ascii')
65                 f_name = node.get("name",'').encode('utf-8')
66                 q = eval(f_search, idref)
67                 ids = pool.get(f_model).search(cr, uid, q)
68                 if f_use != 'id':
69                     ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
70                 _cols = pool.get(f_model)._columns
71                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
72                     return ids
73                 f_val = False
74                 if len(ids):
75                     f_val = ids[0]
76                     if isinstance(f_val, tuple):
77                         f_val = f_val[0]
78                 return f_val
79             a_eval = node.get('eval','')
80             if a_eval:
81                 import time
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)
255
256     def _remove_ir_values(self, cr, name, value, model):
257         ir_value_ids = self.pool.get('ir.values').search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
258         if ir_value_ids:
259             self.pool.get('ir.values').unlink(cr, self.uid, ir_value_ids)
260             self.pool.get('ir.model.data')._unlink(cr, self.uid, 'ir.values', ir_value_ids)
261
262         return True
263
264     def _tag_report(self, cr, rec, data_node=None):
265         res = {}
266         for dest,f in (('name','string'),('model','model'),('report_name','name')):
267             res[dest] = rec.get(f,'').encode('utf8')
268             assert res[dest], "Attribute %s of report is empty !" % (f,)
269         for field,dest in (('rml','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
270             if rec.get(field):
271                 res[dest] = rec.get(field).encode('utf8')
272         if rec.get('auto'):
273             res['auto'] = eval(rec.get('auto','False'))
274         if rec.get('sxw'):
275             sxw_content = misc.file_open(rec.get('sxw')).read()
276             res['report_sxw_content'] = sxw_content
277         if rec.get('header'):
278             res['header'] = eval(rec.get('header','False'))
279         if rec.get('report_type'):
280             res['report_type'] = rec.get('report_type')
281
282         res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
283
284         xml_id = rec.get('id','').encode('utf8')
285         self._test_xml_id(xml_id)
286
287         if rec.get('groups'):
288             g_names = rec.get('groups','').split(',')
289             groups_value = []
290             groups_obj = self.pool.get('res.groups')
291             for group in g_names:
292                 if group.startswith('-'):
293                     group_id = self.id_get(cr, 'res.groups', group[1:])
294                     groups_value.append((3, group_id))
295                 else:
296                     group_id = self.id_get(cr, 'res.groups', group)
297                     groups_value.append((4, group_id))
298             res['groups_id'] = groups_value
299
300         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)
301         self.idref[xml_id] = int(id)
302
303         if not rec.get('menu') or eval(rec.get('menu','False')):
304             keyword = str(rec.get('keyword', 'client_print_multi'))
305             keys = [('action',keyword),('res_model',res['model'])]
306             value = 'ir.actions.report.xml,'+str(id)
307             replace = rec.get('replace', True)
308             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)
309         elif self.mode=='update' and eval(rec.get('menu','False'))==False:
310             # Special check for report having attribute menu=False on update
311             value = 'ir.actions.report.xml,'+str(id)
312             self._remove_ir_values(cr, res['name'], value, res['model'])
313         return False
314
315     def _tag_function(self, cr, rec, data_node=None):
316         if self.isnoupdate(data_node) and self.mode != 'init':
317             return
318         context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
319         uid = self.get_uid(cr, self.uid, data_node, rec)
320         _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
321         return
322
323     def _tag_wizard(self, cr, rec, data_node=None):
324         string = rec.get("string",'').encode('utf8')
325         model = rec.get("model",'').encode('utf8')
326         name = rec.get("name",'').encode('utf8')
327         xml_id = rec.get('id','').encode('utf8')
328         self._test_xml_id(xml_id)
329         multi = rec.get('multi','') and eval(rec.get('multi','False'))
330         res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
331
332         if rec.get('groups'):
333             g_names = rec.get('groups','').split(',')
334             groups_value = []
335             groups_obj = self.pool.get('res.groups')
336             for group in g_names:
337                 if group.startswith('-'):
338                     group_id = self.id_get(cr, 'res.groups', group[1:])
339                     groups_value.append((3, group_id))
340                 else:
341                     group_id = self.id_get(cr, 'res.groups', group)
342                     groups_value.append((4, group_id))
343             res['groups_id'] = groups_value
344
345         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)
346         self.idref[xml_id] = int(id)
347         # ir_set
348         if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
349             keyword = str(rec.get('keyword','') or 'client_action_multi')
350             keys = [('action',keyword),('res_model',model)]
351             value = 'ir.actions.wizard,'+str(id)
352             replace = rec.get("replace",'') or True
353             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
354         elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
355             # Special check for wizard having attribute menu=False on update
356             value = 'ir.actions.wizard,'+str(id)
357             self._remove_ir_values(cr, string, value, model)
358
359     def _tag_url(self, cr, rec, data_node=None):
360         url = rec.get("string",'').encode('utf8')
361         target = rec.get("target",'').encode('utf8')
362         name = rec.get("name",'').encode('utf8')
363         xml_id = rec.get('id','').encode('utf8')
364         self._test_xml_id(xml_id)
365
366         res = {'name': name, 'url': url, 'target':target}
367
368         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)
369         self.idref[xml_id] = int(id)
370         # ir_set
371         if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
372             keyword = str(rec.get('keyword','') or 'client_action_multi')
373             keys = [('action',keyword)]
374             value = 'ir.actions.url,'+str(id)
375             replace = rec.get("replace",'') or True
376             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, url, ["ir.actions.url"], value, replace=replace, isobject=True, xml_id=xml_id)
377         elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
378             # Special check for URL having attribute menu=False on update
379             value = 'ir.actions.url,'+str(id)
380             self._remove_ir_values(cr, url, value, "ir.actions.url")
381
382     def _tag_act_window(self, cr, rec, data_node=None):
383         name = rec.get('name','').encode('utf-8')
384         xml_id = rec.get('id','').encode('utf8')
385         self._test_xml_id(xml_id)
386         type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
387         view_id = False
388         if rec.get('view'):
389             view_id = self.id_get(cr, 'ir.actions.act_window', rec.get('view','').encode('utf-8'))
390         domain = rec.get('domain','').encode('utf-8') or '{}'
391         context = rec.get('context','').encode('utf-8') or '{}'
392         res_model = rec.get('res_model','').encode('utf-8')
393         src_model = rec.get('src_model','').encode('utf-8')
394         view_type = rec.get('view_type','').encode('utf-8') or 'form'
395         view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
396
397         usage = rec.get('usage','').encode('utf-8')
398         limit = rec.get('limit','').encode('utf-8')
399         auto_refresh = rec.get('auto_refresh','').encode('utf-8')
400         uid = self.uid
401         # def ref() added because , if context has ref('id') eval wil use this ref
402
403         active_id=str("active_id") # for further reference in client/bin/tools/__init__.py
404
405         def ref(str_id):
406             return self.id_get(cr, None, str_id)
407         context=eval(context)
408
409         res = {
410             'name': name,
411             'type': type,
412             'view_id': view_id,
413             'domain': domain,
414             'context': context,
415             'res_model': res_model,
416             'src_model': src_model,
417             'view_type': view_type,
418             'view_mode': view_mode,
419             'usage': usage,
420             'limit': limit,
421             'auto_refresh': auto_refresh,
422 #            'groups_id':groups_id,
423         }
424
425         if rec.get('groups'):
426             g_names = rec.get('groups','').split(',')
427             groups_value = []
428             groups_obj = self.pool.get('res.groups')
429             for group in g_names:
430                 if group.startswith('-'):
431                     group_id = self.id_get(cr, 'res.groups', group[1:])
432                     groups_value.append((3, group_id))
433                 else:
434                     group_id = self.id_get(cr, 'res.groups', group)
435                     groups_value.append((4, group_id))
436             res['groups_id'] = groups_value
437
438         if rec.get('target'):
439             res['target'] = rec.get('target','')
440         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)
441         self.idref[xml_id] = int(id)
442
443         if src_model:
444             #keyword = 'client_action_relate'
445             keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
446             keys = [('action', keyword), ('res_model', res_model)]
447             value = 'ir.actions.act_window,'+str(id)
448             replace = rec.get('replace','') or True
449             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)
450         # TODO add remove ir.model.data
451
452     def _tag_ir_set(self, cr, rec, data_node=None):
453         if self.mode != 'init':
454             return
455         res = {}
456         for field in rec.findall('./field'):
457             f_name = field.get("name",'').encode('utf-8')
458             f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
459             res[f_name] = f_val
460         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))
461
462     def _tag_workflow(self, cr, rec, data_node=None):
463         if self.isnoupdate(data_node) and self.mode != 'init':
464             return
465         model = str(rec.get('model',''))
466         w_ref = rec.get('ref','')
467         if w_ref:
468             id = self.id_get(cr, model, w_ref)
469         else:
470             number_children = len(rec)
471             assert number_children > 0,\
472                 'You must define a child node if you dont give a ref'
473             assert number_children == 1,\
474                 'Only one child node is accepted (%d given)' % number_children
475             id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
476
477         uid = self.get_uid(cr, self.uid, data_node, rec)
478         wf_service = netsvc.LocalService("workflow")
479         wf_service.trg_validate(uid, model,
480             id,
481             str(rec.get('action','')), cr)
482
483     #
484     # Support two types of notation:
485     #   name="Inventory Control/Sending Goods"
486     # or
487     #   action="action_id"
488     #   parent="parent_id"
489     #
490     def _tag_menuitem(self, cr, rec, data_node=None):
491         rec_id = rec.get("id",'').encode('ascii')
492         self._test_xml_id(rec_id)
493         m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
494
495         values = {'parent_id': False}
496         if not rec.get('parent'):
497             pid = False
498             for idx, menu_elem in enumerate(m_l):
499                 if pid:
500                     cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
501                 else:
502                     cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
503                 res = cr.fetchone()
504                 if idx==len(m_l)-1:
505                     values = {'parent_id': pid,'name':menu_elem}
506                 elif res:
507                     pid = res[0]
508                     xml_id = idx==len(m_l)-1 and rec.get('id','').encode('utf8')
509                     try:
510                         npid = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, 'ir.ui.menu', self.module, xml_id, idx==len(m_l)-1)
511                     except:
512                         self.logger.notifyChannel('init', netsvc.LOG_ERROR, "module: %s xml_id: %s" % (self.module, xml_id))
513                 else:
514                     # the menuitem does't exist but we are in branch (not a leaf)
515                     self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l)))
516                     pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
517         else:
518             menu_parent_id = self.id_get(cr, 'ir.ui.menu', rec.get('parent',''))
519             values = {'parent_id': menu_parent_id}
520             if rec.get('name'):
521                 values['name'] = rec.get('name')
522             try:
523                 res = [ self.id_get(cr, 'ir.ui.menu', rec.get('id','')) ]
524             except:
525                 res = None
526
527         if rec.get('action'):
528             a_action = rec.get('action','').encode('utf8')
529             a_type = rec.get('type','').encode('utf8') or 'act_window'
530             icons = {
531                 "act_window": 'STOCK_NEW',
532                 "report.xml": 'STOCK_PASTE',
533                 "wizard": 'STOCK_EXECUTE',
534                 "url": 'STOCK_JUMP_TO'
535             }
536             values['icon'] = icons.get(a_type,'STOCK_NEW')
537             if a_type=='act_window':
538                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
539                 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
540                 rrres = cr.fetchone()
541                 assert rrres, "No window action defined for this id %s !\n" \
542                     "Verify that this is a window action or add a type argument." % (a_action,)
543                 action_type,action_mode,action_name,view_id,target = rrres
544                 if view_id:
545                     cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
546                     action_mode, = cr.fetchone()
547                 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
548                 if cr.rowcount:
549                     action_mode, = cr.fetchone()
550                 if action_type=='tree':
551                     values['icon'] = 'STOCK_INDENT'
552                 elif action_mode and action_mode.startswith('tree'):
553                     values['icon'] = 'STOCK_JUSTIFY_FILL'
554                 elif action_mode and action_mode.startswith('graph'):
555                     values['icon'] = 'terp-graph'
556                 elif action_mode and action_mode.startswith('calendar'):
557                     values['icon'] = 'terp-calendar'
558                 if target=='new':
559                     values['icon'] = 'STOCK_EXECUTE'
560                 if not values.get('name', False):
561                     values['name'] = action_name
562             elif a_type=='wizard':
563                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
564                 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
565                 resw = cr.fetchone()
566                 if (not values.get('name', False)) and resw:
567                     values['name'] = resw[0]
568         if rec.get('sequence'):
569             values['sequence'] = int(rec.get('sequence'))
570         if rec.get('icon'):
571             values['icon'] = str(rec.get('icon'))
572
573         if rec.get('groups'):
574             g_names = rec.get('groups','').split(',')
575             groups_value = []
576             groups_obj = self.pool.get('res.groups')
577             for group in g_names:
578                 if group.startswith('-'):
579                     group_id = self.id_get(cr, 'res.groups', group[1:])
580                     groups_value.append((3, group_id))
581                 else:
582                     group_id = self.id_get(cr, 'res.groups', group)
583                     groups_value.append((4, group_id))
584             values['groups_id'] = groups_value
585
586         xml_id = rec.get('id','').encode('utf8')
587         self._test_xml_id(xml_id)
588         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)
589
590         if rec_id and pid:
591             self.idref[rec_id] = int(pid)
592
593         if rec.get('action') and pid:
594             a_action = rec.get('action').encode('utf8')
595             a_type = rec.get('type','').encode('utf8') or 'act_window'
596             a_id = self.id_get(cr, 'ir.actions.%s' % a_type, a_action)
597             action = "ir.actions.%s,%d" % (a_type, a_id)
598             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)
599         return ('ir.ui.menu', pid)
600
601     def _assert_equals(self, f1, f2, prec = 4):
602         return not round(f1 - f2, prec)
603
604     def _tag_assert(self, cr, rec, data_node=None):
605         if self.isnoupdate(data_node) and self.mode != 'init':
606             return
607
608         rec_model = rec.get("model",'').encode('ascii')
609         model = self.pool.get(rec_model)
610         assert model, "The model %s does not exist !" % (rec_model,)
611         rec_id = rec.get("id",'').encode('ascii')
612         self._test_xml_id(rec_id)
613         rec_src = rec.get("search",'').encode('utf8')
614         rec_src_count = rec.get("count")
615
616         severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
617         rec_string = rec.get("string",'').encode('utf8') or 'unknown'
618
619         ids = None
620         eval_dict = {'ref': _ref(self, cr)}
621         context = self.get_context(data_node, rec, eval_dict)
622         uid = self.get_uid(cr, self.uid, data_node, rec)
623         if rec_id:
624             ids = [self.id_get(cr, rec_model, rec_id)]
625         elif rec_src:
626             q = eval(rec_src, eval_dict)
627             ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
628             if rec_src_count:
629                 count = int(rec_src_count)
630                 if len(ids) != count:
631                     self.assert_report.record_assertion(False, severity)
632                     msg = 'assertion "%s" failed!\n'    \
633                           ' Incorrect search count:\n'  \
634                           ' expected count: %d\n'       \
635                           ' obtained count: %d\n'       \
636                           % (rec_string, count, len(ids))
637                     self.logger.notifyChannel('init', severity, msg)
638                     sevval = getattr(logging, severity.upper())
639                     if sevval >= config['assert_exit_level']:
640                         # TODO: define a dedicated exception
641                         raise Exception('Severe assertion failure')
642                     return
643
644         assert ids is not None,\
645             'You must give either an id or a search criteria'
646         ref = _ref(self, cr)
647         for id in ids:
648             brrec =  model.browse(cr, uid, id, context)
649             class d(dict):
650                 def __getitem__(self2, key):
651                     if key in brrec:
652                         return brrec[key]
653                     return dict.__getitem__(self2, key)
654             globals = d()
655             globals['floatEqual'] = self._assert_equals
656             globals['ref'] = ref
657             globals['_ref'] = ref
658             for test in rec.findall('./test'):
659                 f_expr = test.get("expr",'').encode('utf-8')
660                 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
661                 expression_value = eval(f_expr, globals)
662                 if expression_value != expected_value: # assertion failed
663                     self.assert_report.record_assertion(False, severity)
664                     msg = 'assertion "%s" failed!\n'    \
665                           ' xmltag: %s\n'               \
666                           ' expected value: %r\n'       \
667                           ' obtained value: %r\n'       \
668                           % (rec_string, etree.tostring(test), expected_value, expression_value)
669                     self.logger.notifyChannel('init', severity, msg)
670                     sevval = getattr(logging, severity.upper())
671                     if sevval >= config['assert_exit_level']:
672                         # TODO: define a dedicated exception
673                         raise Exception('Severe assertion failure')
674                     return
675         else: # all tests were successful for this assertion tag (no break)
676             self.assert_report.record_assertion(True, severity)
677
678     def _tag_record(self, cr, rec, data_node=None):
679         rec_model = rec.get("model").encode('ascii')
680         model = self.pool.get(rec_model)
681         assert model, "The model %s does not exist !" % (rec_model,)
682         rec_id = rec.get("id",'').encode('ascii')
683         rec_context = rec.get("context", None)
684         if rec_context:
685             rec_context = eval(rec_context)
686         self._test_xml_id(rec_id)
687         if self.isnoupdate(data_node) and self.mode != 'init':
688             # check if the xml record has an id string
689             if rec_id:
690                 if '.' in rec_id:
691                     module,rec_id2 = rec_id.split('.')
692                 else:
693                     module = self.module
694                     rec_id2 = rec_id
695                 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
696                 # check if the resource already existed at the last update
697                 if id:
698                     # if it existed, we don't update the data, but we need to
699                     # know the id of the existing record anyway
700                     self.idref[rec_id] = int(id)
701                     return None
702                 else:
703                     # if the resource didn't exist
704                     if not self.nodeattr2bool(rec, 'forcecreate', True):
705                         # we don't want to create it, so we skip it
706                         return None
707                     # else, we let the record to be created
708
709             else:
710                 # otherwise it is skipped
711                 return None
712         res = {}
713         for field in rec.findall('./field'):
714 #TODO: most of this code is duplicated above (in _eval_xml)...
715             f_name = field.get("name",'').encode('utf-8')
716             f_ref = field.get("ref",'').encode('ascii')
717             f_search = field.get("search",'').encode('utf-8')
718             f_model = field.get("model",'').encode('ascii')
719             if not f_model and model._columns.get(f_name,False):
720                 f_model = model._columns[f_name]._obj
721             f_use = field.get("use",'').encode('ascii') or 'id'
722             f_val = False
723
724             if f_search:
725                 q = eval(f_search, self.idref)
726                 field = []
727                 assert f_model, 'Define an attribute model="..." in your .XML file !'
728                 f_obj = self.pool.get(f_model)
729                 # browse the objects searched
730                 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
731                 # column definitions of the "local" object
732                 _cols = self.pool.get(rec_model)._columns
733                 # if the current field is many2many
734                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
735                     f_val = [(6, 0, map(lambda x: x[f_use], s))]
736                 elif len(s):
737                     # otherwise (we are probably in a many2one field),
738                     # take the first element of the search
739                     f_val = s[0][f_use]
740             elif f_ref:
741                 if f_ref=="null":
742                     f_val = False
743                 else:
744                     f_val = self.id_get(cr, f_model, f_ref)
745             else:
746                 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
747                 if model._columns.has_key(f_name):
748                     if isinstance(model._columns[f_name], osv.fields.integer):
749                         f_val = int(f_val)
750             res[f_name] = f_val
751
752         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 )
753         if rec_id:
754             self.idref[rec_id] = int(id)
755         if config.get('import_partial', False):
756             cr.commit()
757         return rec_model, id
758
759     def id_get(self, cr, model, id_str):
760         if id_str in self.idref:
761             return self.idref[id_str]
762         mod = self.module
763         if '.' in id_str:
764             mod,id_str = id_str.split('.')
765         result = self.pool.get('ir.model.data')._get_id(cr, self.uid, mod, id_str)
766         res = self.pool.get('ir.model.data').read(cr, self.uid, [result], ['res_id'])
767         if res and res[0] and res[0]['res_id']:
768             return int(res[0]['res_id'])
769         return False
770
771     def parse(self, de):
772         if not de.tag in ['terp', 'openerp']:
773             self.logger.notifyChannel("init", netsvc.LOG_ERROR, "Mismatch xml format" )
774             raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
775
776         if de.tag == 'terp':
777             self.logger.notifyChannel("init", netsvc.LOG_WARNING, "The tag <terp/> is deprecated, use <openerp/>")
778
779         for n in de.findall('./data'):
780             for rec in n:
781                     if rec.tag in self._tags:
782                         try:
783                             self._tags[rec.tag](self.cr, rec, n)
784                         except:
785                             self.logger.notifyChannel("init", netsvc.LOG_ERROR, '\n'+etree.tostring(rec))
786                             self.cr.rollback()
787                             raise
788         return True
789
790     def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
791
792         self.logger = netsvc.Logger()
793         self.mode = mode
794         self.module = module
795         self.cr = cr
796         self.idref = idref
797         self.pool = pooler.get_pool(cr.dbname)
798         self.uid = 1
799         if report is None:
800             report = assertion_report()
801         self.assert_report = report
802         self.noupdate = noupdate
803         self._tags = {
804             'menuitem': self._tag_menuitem,
805             'record': self._tag_record,
806             'assert': self._tag_assert,
807             'report': self._tag_report,
808             'wizard': self._tag_wizard,
809             'delete': self._tag_delete,
810             'ir_set': self._tag_ir_set,
811             'function': self._tag_function,
812             'workflow': self._tag_workflow,
813             'act_window': self._tag_act_window,
814             'url': self._tag_url
815         }
816
817 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
818         noupdate=False):
819     '''Import csv file :
820         quote: "
821         delimiter: ,
822         encoding: utf-8'''
823     if not idref:
824         idref={}
825     model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
826     #remove folder path from model
827     head, model = os.path.split(model)
828
829     pool = pooler.get_pool(cr.dbname)
830
831     input = cStringIO.StringIO(csvcontent)
832     reader = csv.reader(input, quotechar='"', delimiter=',')
833     fields = reader.next()
834     fname_partial = ""
835     if config.get('import_partial'):
836         fname_partial = module + '/'+ fname
837         if not os.path.isfile(config.get('import_partial')):
838             pickle.dump({}, file(config.get('import_partial'),'w+'))
839         else:
840             data = pickle.load(file(config.get('import_partial')))
841             if fname_partial in data:
842                 if not data[fname_partial]:
843                     return
844                 else:
845                     for i in range(data[fname_partial]):
846                         reader.next()
847
848     if not (mode == 'init' or 'id' in fields):
849         logger = netsvc.Logger()
850         logger.notifyChannel("init", netsvc.LOG_ERROR,
851             "Import specification does not contain 'id' and we are in init mode, Cannot continue.")
852         return
853
854     uid = 1
855     datas = []
856     for line in reader:
857         if (not line) or not reduce(lambda x,y: x or y, line) :
858             continue
859         try:
860             datas.append(map(lambda x: misc.ustr(x), line))
861         except:
862             logger = netsvc.Logger()
863             logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line)
864     pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
865     if config.get('import_partial'):
866         data = pickle.load(file(config.get('import_partial')))
867         data[fname_partial] = 0
868         pickle.dump(data, file(config.get('import_partial'),'wb'))
869         cr.commit()
870
871 #
872 # xml import/export
873 #
874 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
875     doc = etree.parse(xmlfile)
876     relaxng = etree.RelaxNG(
877         etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
878     try:
879         relaxng.assert_(doc)
880     except Exception, e:
881         logger = netsvc.Logger()
882         logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
883         logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
884         raise
885
886     if idref is None:
887         idref={}
888     obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
889     obj.parse(doc.getroot())
890     return True
891