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