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