[REF] get rid of the mx.Datetime C dependency, meaning better portability, especially...
[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 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
37 import sys
38 try:
39     from lxml import etree
40 except:
41     sys.stderr.write("ERROR: pythonic binding for the libxml2 and libxslt libraries is missing\n")
42     sys.stderr.write("ERROR: Try to install python-lxml package\n")
43     sys.exit(2)
44 import pickle
45
46
47 class ConvertError(Exception):
48     def __init__(self, doc, orig_excpt):
49         self.d = doc
50         self.orig = orig_excpt
51
52     def __str__(self):
53         return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
54
55 def _ref(self, cr):
56     return lambda x: self.id_get(cr, False, x)
57
58 def _obj(pool, cr, uid, model_str, context=None):
59     model = pool.get(model_str)
60     return lambda x: model.browse(cr, uid, x, context=context)
61
62 def _eval_xml(self,node, pool, cr, uid, idref, context=None):
63     if context is None:
64         context = {}
65     if node.tag in ('field','value'):
66             t = node.get('type','') or 'char'
67             f_model = node.get("model", '').encode('ascii')
68             if len(node.get('search','')):
69                 f_search = node.get("search",'').encode('utf-8')
70                 f_use = node.get("use",'').encode('ascii')
71                 f_name = node.get("name",'').encode('utf-8')
72                 if len(f_use)==0:
73                     f_use = "id"
74                 q = eval(f_search, idref)
75                 ids = pool.get(f_model).search(cr, uid, q)
76                 if f_use<>'id':
77                     ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
78                 _cols = pool.get(f_model)._columns
79                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
80                     return ids
81                 f_val = False
82                 if len(ids):
83                     f_val = ids[0]
84                     if isinstance(f_val, tuple):
85                         f_val = f_val[0]
86                 return f_val
87             a_eval = node.get('eval','')
88             if len(a_eval):
89                 import time
90                 import datetime
91                 idref2 = idref.copy()
92                 idref2['time'] = time
93                 idref2['DateTime'] = datetime
94                 import release
95                 idref2['version'] = release.major_version
96                 idref2['ref'] = lambda x: self.id_get(cr, False, x)
97                 if len(f_model):
98                     idref2['obj'] = _obj(self.pool, cr, uid, f_model, context=context)
99                 try:
100                     import pytz
101                 except:
102                     logger = netsvc.Logger()
103                     logger.notifyChannel("init", netsvc.LOG_WARNING, 'could not find pytz library')
104                     class pytzclass(object):
105                         all_timezones=[]
106                     pytz=pytzclass()
107                 idref2['pytz'] = pytz
108                 try:
109                         return eval(a_eval, idref2)
110                 except:
111                         logger = netsvc.Logger()
112                         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))
113                         return ""
114             if t == 'xml':
115                 def _process(s, idref):
116                     m = re.findall('[^%]%\((.*?)\)[ds]', s)
117                     for id in m:
118                         if not id in idref:
119                             idref[id]=self.id_get(cr, False, id)
120                     return s % idref
121                 txt = '<?xml version="1.0"?>\n'+_process("".join([etree.tostring(i).encode("utf8") for i in node.getchildren()]), idref)
122                 return txt
123             if t in ('char', 'int', 'float'):
124                 d = node.text
125                 if t == 'int':
126                     d = d.strip()
127                     if d=='None':
128                         return None
129                     else:
130                         d=int(d.strip())
131                 elif t=='float':
132                     d=float(d.strip())
133                 return d
134             elif t in ('list','tuple'):
135                 res=[]
136                 for n in [i for i in node.getchildren() if (i.tag=='value')]:
137                     res.append(_eval_xml(self,n,pool,cr,uid,idref))
138                 if t=='tuple':
139                     return tuple(res)
140                 return res
141     elif node.tag == "getitem":
142         for n in [i for i in node.getchildren()]:
143             res=_eval_xml(self,n,pool,cr,uid,idref)
144         if not res:
145             raise LookupError
146         elif node.get('type','') in ("int", "list"):
147             return res[int(node.get('index',''))]
148         else:
149             return res[node.get('index','').encode("utf8")]
150     elif node.tag == "function":
151         args = []
152         a_eval = node.get('eval','')
153         if len(a_eval):
154             idref['ref'] = lambda x: self.id_get(cr, False, x)
155             args = eval(a_eval, idref)
156         for n in [i for i in node.getchildren()]:
157             return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
158             if return_val != None:
159                 args.append(return_val)
160         model = pool.get(node.get('model',''))
161         method = node.get('name','')
162         res = getattr(model, method)(cr, uid, *args)
163         return res
164     elif node.tag == "test":
165         d = node.text
166         return d
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 = eval(data_node_context, eval_dict)
219         else:
220             context = {}
221
222         node_context = node.get("context",'').encode('utf8')
223         if len(node_context):
224             context.update(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 len(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 contain
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" refers 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",'')
251         d_id = rec.get("id",'')
252         ids = []
253         if len(d_search):
254             ids = self.pool.get(d_model).search(cr,self.uid,eval(d_search))
255         if len(d_id):
256             try:
257                 ids.append(self.id_get(cr, d_model, d_id))
258             except:
259                 # d_id cannot be found. doesn't matter in this case
260                 pass
261         if len(ids):
262             self.pool.get(d_model).unlink(cr, self.uid, ids)
263             self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids, direct=True)
264         return False
265
266     def _tag_report(self, cr, rec, data_node=None):
267         res = {}
268         for dest,f in (('name','string'),('model','model'),('report_name','name')):
269             res[dest] = rec.get(f,'').encode('utf8')
270             assert res[dest], "Attribute %s of report is empty !" % (f,)
271         for field,dest in (('rml','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
272             if rec.get(field):
273                 res[dest] = rec.get(field,'').encode('utf8')
274         if rec.get('auto'):
275             res['auto'] = eval(rec.get('auto',''))
276         if rec.get('sxw'):
277             sxw_content = misc.file_open(rec.get('sxw','')).read()
278             res['report_sxw_content'] = sxw_content
279         if rec.get('header'):
280             res['header'] = eval(rec.get('header',''))
281         if rec.get('report_type'):
282             res['report_type'] = rec.get('report_type','')
283
284         res['multi'] = rec.get('multi','') and eval(rec.get('multi',''))
285         xml_id = rec.get('id','').encode('utf8')               
286
287         self._test_xml_id(xml_id)
288
289         if rec.get('groups'):
290             g_names = rec.get('groups','').split(',')
291             groups_value = []
292             groups_obj = self.pool.get('res.groups')
293             for group in g_names:
294                 if group.startswith('-'):
295                     group_id = self.id_get(cr, 'res.groups', group[1:])
296                     groups_value.append((3, group_id))
297                 else:
298                     group_id = self.id_get(cr, 'res.groups', group)
299                     groups_value.append((4, group_id))
300             res['groups_id'] = groups_value
301
302         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)
303         self.idref[xml_id] = int(id)
304
305         if not rec.get('menu') or eval(rec.get('menu','')):
306             keyword = str(rec.get('keyword','') or 'client_print_multi')
307             keys = [('action',keyword),('res_model',res['model'])]
308             value = 'ir.actions.report.xml,'+str(id)
309             replace = rec.get("replace",'') or True
310             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)
311         return False
312
313     def _tag_function(self, cr, rec, data_node=None):
314         if self.isnoupdate(data_node) and self.mode != 'init':
315             return
316         context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
317         uid = self.get_uid(cr, self.uid, data_node, rec)
318         _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
319         return False
320
321     def _tag_wizard(self, cr, rec, data_node=None):
322         string = rec.get("string",'').encode('utf8')
323         model = rec.get("model",'').encode('utf8')
324         name = rec.get("name",'').encode('utf8')
325         xml_id = rec.get('id','').encode('utf8')
326         self._test_xml_id(xml_id)
327         multi = rec.get('multi','') and eval(rec.get('multi',''))
328         res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
329
330         if rec.get('groups'):
331             g_names = rec.get('groups','').split(',')
332             groups_value = []
333             groups_obj = self.pool.get('res.groups')
334             for group in g_names:
335                 if group.startswith('-'):
336                     group_id = self.id_get(cr, 'res.groups', group[1:])
337                     groups_value.append((3, group_id))
338                 else:
339                     group_id = self.id_get(cr, 'res.groups', group)
340                     groups_value.append((4, group_id))
341             res['groups_id'] = groups_value
342
343         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)
344         self.idref[xml_id] = int(id)
345         # ir_set
346         if (not rec.get('menu') or eval(rec.get('menu',''))) and id:
347             keyword = str(rec.get('keyword','') or 'client_action_multi')
348             keys = [('action',keyword),('res_model',model)]
349             value = 'ir.actions.wizard,'+str(id)
350             replace = rec.get("replace",'') or True
351             self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
352         return False
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',''))) 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         return False
373
374     def _tag_act_window(self, cr, rec, data_node=None):
375         name = rec.get('name','').encode('utf-8')
376         xml_id = rec.get('id','').encode('utf8')
377         self._test_xml_id(xml_id)
378         type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
379         view_id = False
380         if rec.get('view'):
381             view_id = self.id_get(cr, 'ir.actions.act_window', rec.get('view','').encode('utf-8'))
382         domain = rec.get('domain','').encode('utf-8') or '{}'
383         context = rec.get('context','').encode('utf-8') or '{}'
384         res_model = rec.get('res_model','').encode('utf-8')
385         src_model = rec.get('src_model','').encode('utf-8')
386         view_type = rec.get('view_type','').encode('utf-8') or 'form'
387         view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
388         usage = rec.get('usage','').encode('utf-8')
389         limit = rec.get('limit','').encode('utf-8')
390         auto_refresh = rec.get('auto_refresh','').encode('utf-8')
391
392         # def ref() added because , if context has ref('id') eval wil use this ref
393
394         active_id=str("active_id") # for further reference in client/bin/tools/__init__.py
395
396         def ref(str_id):
397             return self.id_get(cr, None, str_id)
398         context=eval(context)
399
400         res = {
401             'name': name,
402             'type': type,
403             'view_id': view_id,
404             'domain': domain,
405             'context': context,
406             'res_model': res_model,
407             'src_model': src_model,
408             'view_type': view_type,
409             'view_mode': view_mode,
410             'usage': usage,
411             'limit': limit,
412             'auto_refresh': auto_refresh,
413 #            'groups_id':groups_id,
414         }
415
416         if rec.get('groups'):
417             g_names = rec.get('groups','').split(',')
418             groups_value = []
419             groups_obj = self.pool.get('res.groups')
420             for group in g_names:
421                 if group.startswith('-'):
422                     group_id = self.id_get(cr, 'res.groups', group[1:])
423                     groups_value.append((3, group_id))
424                 else:
425                     group_id = self.id_get(cr, 'res.groups', group)
426                     groups_value.append((4, group_id))
427             res['groups_id'] = groups_value
428
429         if rec.get('target'):
430             res['target'] = rec.get('target','')
431         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)
432         self.idref[xml_id] = int(id)
433
434         if src_model:
435             keyword = 'client_action_relate'
436             keys = [('action', keyword), ('res_model', res_model)]
437             value = 'ir.actions.act_window,'+str(id)
438             replace = rec.get('replace','') or True
439             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)
440         # TODO add remove ir.model.data
441         return False
442
443     def _tag_ir_set(self, cr, rec, data_node=None):
444         if not self.mode=='init':
445             return False
446         res = {}
447         for field in [i for i in rec.getchildren() if (i.tag=="field")]:
448             f_name = field.get("name",'').encode('utf-8')
449             f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
450             res[f_name] = f_val
451         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))
452         return False
453
454     def _tag_workflow(self, cr, rec, data_node=None):
455         if self.isnoupdate(data_node) and self.mode != 'init':
456             return
457         model = str(rec.get('model',''))
458         w_ref = rec.get('ref','')
459         if len(w_ref):
460             id = self.id_get(cr, model, w_ref)
461         else:
462             assert rec.getchildren(), 'You must define a child node if you dont give a ref'
463             element_childs = [i for i in rec.getchildren()]
464             assert len(element_childs) == 1, 'Only one child node is accepted (%d given)' % len(rec.getchildren())
465             id = _eval_xml(self, element_childs[0], self.pool, cr, self.uid, self.idref)
466
467         uid = self.get_uid(cr, self.uid, data_node, rec)
468         wf_service = netsvc.LocalService("workflow")
469         wf_service.trg_validate(uid, model,
470             id,
471             str(rec.get('action','')), cr)
472         return False
473
474     #
475     # Support two types of notation:
476     #   name="Inventory Control/Sending Goods"
477     # or
478     #   action="action_id"
479     #   parent="parent_id"
480     #
481     def _tag_menuitem(self, cr, rec, data_node=None):
482         rec_id = rec.get("id",'').encode('ascii')
483         self._test_xml_id(rec_id)
484         m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
485
486         values = {'parent_id': False}
487         if not rec.get('parent'):
488             pid = False
489             for idx, menu_elem in enumerate(m_l):
490                 if pid:
491                     cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
492                 else:
493                     cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
494                 res = cr.fetchone()
495                 if idx==len(m_l)-1:
496                     values = {'parent_id': pid,'name':menu_elem}
497                 elif res:
498                     pid = res[0]
499                     xml_id = idx==len(m_l)-1 and rec.get('id','').encode('utf8')
500                     try:
501                         npid = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, 'ir.ui.menu', self.module, xml_id, idx==len(m_l)-1)
502                     except:
503                         self.logger.notifyChannel('init', netsvc.LOG_ERROR, "module: %s xml_id: %s" % (self.module, xml_id))
504                 else:
505                     # the menuitem does't exist but we are in branch (not a leaf)
506                     self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l)))
507                     pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
508         else:
509             menu_parent_id = self.id_get(cr, 'ir.ui.menu', rec.get('parent',''))
510             values = {'parent_id': menu_parent_id}
511             if rec.get('name'):
512                 values['name'] = rec.get('name','')
513             try:
514                 res = [ self.id_get(cr, 'ir.ui.menu', rec.get('id','')) ]
515             except:
516                 res = None
517
518         if rec.get('action'):
519             a_action = rec.get('action','').encode('utf8')
520             a_type = rec.get('type','').encode('utf8') or 'act_window'
521             icons = {
522                 "act_window": 'STOCK_NEW',
523                 "report.xml": 'STOCK_PASTE',
524                 "wizard": 'STOCK_EXECUTE',
525                 "url": 'STOCK_JUMP_TO'
526             }
527             values['icon'] = icons.get(a_type,'STOCK_NEW')
528             if a_type=='act_window':
529                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
530                 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
531                 rrres = cr.fetchone()
532                 assert rrres, "No window action defined for this id %s !\n" \
533                     "Verify that this is a window action or add a type argument." % (a_action,)
534                 action_type,action_mode,action_name,view_id,target = rrres
535                 if view_id:
536                     cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
537                     action_mode, = cr.fetchone()
538                 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
539                 if cr.rowcount:
540                     action_mode, = cr.fetchone()
541                 if action_type=='tree':
542                     values['icon'] = 'STOCK_INDENT'
543                 elif action_mode and action_mode.startswith('tree'):
544                     values['icon'] = 'STOCK_JUSTIFY_FILL'
545                 elif action_mode and action_mode.startswith('graph'):
546                     values['icon'] = 'terp-graph'
547                 elif action_mode and action_mode.startswith('calendar'):
548                     values['icon'] = 'terp-calendar'
549                 if target=='new':
550                     values['icon'] = 'STOCK_EXECUTE'
551                 if not values.get('name', False):
552                     values['name'] = action_name
553             elif a_type=='wizard':
554                 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
555                 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
556                 resw = cr.fetchone()
557                 if (not values.get('name', False)) and resw:
558                     values['name'] = resw[0]
559         if rec.get('sequence'):
560             values['sequence'] = int(rec.get('sequence',''))
561         if rec.get('icon'):
562             values['icon'] = str(rec.get('icon',''))
563         if rec.get('groups'):
564             g_names = rec.get('groups','').split(',')
565             groups_value = []
566             groups_obj = self.pool.get('res.groups')
567             for group in g_names:
568                 if group.startswith('-'):
569                     group_id = self.id_get(cr, 'res.groups', group[1:])
570                     groups_value.append((3, group_id))
571                 else:
572                     group_id = self.id_get(cr, 'res.groups', group)
573                     groups_value.append((4, group_id))
574             values['groups_id'] = groups_value
575
576         xml_id = rec.get('id','').encode('utf8')
577         self._test_xml_id(xml_id)
578         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)
579
580         if rec_id and pid:
581             self.idref[rec_id] = int(pid)
582
583         if rec.get('action') and pid:
584             a_action = rec.get('action','').encode('utf8')
585             a_type = rec.get('type','').encode('utf8') or 'act_window'
586             a_id = self.id_get(cr, 'ir.actions.%s' % a_type, a_action)
587             action = "ir.actions.%s,%d" % (a_type, a_id)
588             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)
589         return ('ir.ui.menu', pid)
590
591     def _assert_equals(self, f1, f2, prec = 4):
592         return not round(f1 - f2, prec)
593
594     def _tag_assert(self, cr, rec, data_node=None):
595         if self.isnoupdate(data_node) and self.mode != 'init':
596             return
597
598         rec_model = rec.get("model",'').encode('ascii')
599         model = self.pool.get(rec_model)
600         assert model, "The model %s does not exist !" % (rec_model,)
601         rec_id = rec.get("id",'').encode('ascii')
602         self._test_xml_id(rec_id)
603         rec_src = rec.get("search",'').encode('utf8')
604         rec_src_count = rec.get("count",'')
605
606         severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
607         rec_string = rec.get("string",'').encode('utf8') or 'unknown'
608
609         ids = None
610         eval_dict = {'ref': _ref(self, cr)}
611         context = self.get_context(data_node, rec, eval_dict)
612         uid = self.get_uid(cr, self.uid, data_node, rec)
613         if len(rec_id):
614             ids = [self.id_get(cr, rec_model, rec_id)]
615         elif len(rec_src):
616             q = eval(rec_src, eval_dict)
617             ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
618             if len(rec_src_count):
619                 count = int(rec_src_count)
620                 if len(ids) != count:
621                     self.assert_report.record_assertion(False, severity)
622                     msg = 'assertion "%s" failed!\n'    \
623                           ' Incorrect search count:\n'  \
624                           ' expected count: %d\n'       \
625                           ' obtained count: %d\n'       \
626                           % (rec_string, count, len(ids))
627                     self.logger.notifyChannel('init', severity, msg)
628                     sevval = getattr(logging, severity.upper())
629                     if sevval >= config['assert_exit_level']:
630                         # TODO: define a dedicated exception
631                         raise Exception('Severe assertion failure')
632                     return
633
634         assert ids != None, 'You must give either an id or a search criteria'
635
636         ref = _ref(self, cr)
637         for id in ids:
638             brrec =  model.browse(cr, uid, id, context)
639             class d(dict):
640                 def __getitem__(self2, key):
641                     if key in brrec:
642                         return brrec[key]
643                     return dict.__getitem__(self2, key)
644             globals = d()
645             globals['floatEqual'] = self._assert_equals
646             globals['ref'] = ref
647             globals['_ref'] = ref
648             for test in [i for i in rec.getchildren() if (i.tag=="test")]:
649                 f_expr = test.get("expr",'').encode('utf-8')
650                 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
651                 expression_value = eval(f_expr, globals)
652                 if expression_value != expected_value: # assertion failed
653                     self.assert_report.record_assertion(False, severity)
654                     msg = 'assertion "%s" failed!\n'    \
655                           ' xmltag: %s\n'               \
656                           ' expected value: %r\n'       \
657                           ' obtained value: %r\n'       \
658                           % (rec_string, etree.tostring(test), expected_value, expression_value)
659                     self.logger.notifyChannel('init', severity, msg)
660                     sevval = getattr(logging, severity.upper())
661                     if sevval >= config['assert_exit_level']:
662                         # TODO: define a dedicated exception
663                         raise Exception('Severe assertion failure')
664                     return
665         else: # all tests were successful for this assertion tag (no break)
666             self.assert_report.record_assertion(True, severity)
667
668     def _tag_record(self, cr, rec, data_node=None):
669         rec_model = rec.get("model").encode('ascii')
670         model = self.pool.get(rec_model)
671         assert model, "The model %s does not exist !" % (rec_model,)
672         rec_id = rec.get("id",'').encode('ascii')
673         rec_context = rec.get("context", None)
674         if rec_context:
675             rec_context = eval(rec_context)
676         self._test_xml_id(rec_id)
677         if self.isnoupdate(data_node) and self.mode != 'init':
678             # check if the xml record has an id string
679             if rec_id:
680                 if '.' in rec_id:
681                     module,rec_id2 = rec_id.split('.')
682                 else:
683                     module = self.module
684                     rec_id2 = rec_id
685                 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
686                 # check if the resource already existed at the last update
687                 if id:
688                     # if it existed, we don't update the data, but we need to
689                     # know the id of the existing record anyway
690                     self.idref[rec_id] = int(id)
691                     return None
692                 else:
693                     # if the resource didn't exist
694                     if not self.nodeattr2bool(rec, 'forcecreate', True):
695                         # we don't want to create it, so we skip it
696                         return None
697                     # else, we let the record to be created
698
699             else:
700                 # otherwise it is skipped
701                 return None
702         res = {}
703         for field in [i for i in rec.getchildren() if (i.tag == "field")]:
704 #TODO: most of this code is duplicated above (in _eval_xml)...
705             f_name = field.get("name",'').encode('utf-8')
706             f_ref = field.get("ref",'').encode('ascii')
707             f_search = field.get("search",'').encode('utf-8')
708             f_model = field.get("model",'').encode('ascii')
709             if not f_model and model._columns.get(f_name,False):
710                 f_model = model._columns[f_name]._obj
711             f_use = field.get("use",'').encode('ascii') or 'id'
712             f_val = False
713
714             if len(f_search):
715                 q = eval(f_search, self.idref)
716                 field = []
717                 assert f_model, 'Define an attribute model="..." in your .XML file !'
718                 f_obj = self.pool.get(f_model)
719                 # browse the objects searched
720                 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
721                 # column definitions of the "local" object
722                 _cols = self.pool.get(rec_model)._columns
723                 # if the current field is many2many
724                 if (f_name in _cols) and _cols[f_name]._type=='many2many':
725                     f_val = [(6, 0, map(lambda x: x[f_use], s))]
726                 elif len(s):
727                     # otherwise (we are probably in a many2one field),
728                     # take the first element of the search
729                     f_val = s[0][f_use]
730             elif len(f_ref):
731                 if f_ref=="null":
732                     f_val = False
733                 else:
734                     f_val = self.id_get(cr, f_model, f_ref)
735             else:
736                 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
737                 if model._columns.has_key(f_name):
738                     if isinstance(model._columns[f_name], osv.fields.integer):
739                         f_val = int(f_val)
740             res[f_name] = f_val
741
742         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 )
743         if rec_id:
744             self.idref[rec_id] = int(id)
745         if config.get('import_partial', False):
746             cr.commit()
747         return rec_model, id
748
749     def id_get(self, cr, model, id_str):
750         if id_str in self.idref:
751             return self.idref[id_str]
752         mod = self.module
753         if '.' in id_str:
754             mod,id_str = id_str.split('.')
755         result = self.pool.get('ir.model.data')._get_id(cr, self.uid, mod, id_str)
756         return int(self.pool.get('ir.model.data').read(cr, self.uid, [result], ['res_id'])[0]['res_id'])
757
758     def parse(self, xmlstr):
759         de = etree.XML(xmlstr)
760
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 [i for i in de.getchildren() if (i.tag=="data")]:
769             for rec in n.getchildren():
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         logger = netsvc.Logger()
839         logger.notifyChannel("init", netsvc.LOG_ERROR,
840             "Import specification does not contain 'id' and we are in init mode, Cannot continue.")
841         return
842
843     uid = 1
844     datas = []
845     for line in reader:
846         if (not line) or not reduce(lambda x,y: x or y, line) :
847             continue
848         try:
849             datas.append(map(lambda x: misc.ustr(x), line))
850         except:
851             logger = netsvc.Logger()
852             logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line)
853     pool.get(model).import_data(cr, uid, fields, datas,mode, module,noupdate,filename=fname_partial)
854     if config.get('import_partial'):
855         data = pickle.load(file(config.get('import_partial')))
856         data[fname_partial] = 0
857         pickle.dump(data, file(config.get('import_partial'),'wb'))
858         cr.commit()
859
860 #
861 # xml import/export
862 #
863 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
864     xmlstr = xmlfile.read()
865     xmlfile.seek(0)
866     relaxng_doc = etree.parse(file(os.path.join( config['root_path'], 'import_xml.rng' )))
867     relaxng = etree.RelaxNG(relaxng_doc)
868
869     doc = etree.parse(xmlfile)
870     try:
871         relaxng.assert_(doc)
872     except Exception, e:
873         logger = netsvc.Logger()
874         logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
875         logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
876         raise
877
878     if idref is None:
879         idref={}
880     obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
881     obj.parse(xmlstr)
882     del obj
883     return True
884
885 def convert_xml_export(res):
886     uid=1
887     pool=pooler.get_pool(cr.dbname)
888     cr=pooler.db.cursor()
889     idref = {}
890     page = etree.Element ( 'terp' )
891     doc = etree.ElementTree ( page )
892     data = etree.SubElement ( page, 'data' )
893     text_node = etree.SubElement ( page, 'text' )
894     text_node.text = 'Some textual content.'
895     cr.commit()
896     cr.close()
897
898
899 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
900