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