1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
35 logging.getLogger("init").warning('could not find pytz library, please install it')
36 class pytzclass(object):
41 from datetime import datetime, timedelta
42 from lxml import etree
47 from config import config
48 from yaml_import import convert_yaml_import
50 # Import of XML records requires the unsafe eval as well,
51 # almost everywhere, which is ok because it supposedly comes
52 # from trusted data, but at least we make it obvious now.
54 from tools.safe_eval import safe_eval as eval
56 class ConvertError(Exception):
57 def __init__(self, doc, orig_excpt):
59 self.orig = orig_excpt
62 return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
65 return lambda x: self.id_get(cr, False, x)
67 def _obj(pool, cr, uid, model_str, context=None):
68 model = pool.get(model_str)
69 return lambda x: model.browse(cr, uid, x, context=context)
71 def _fix_multiple_roots(node):
73 Surround the children of the ``node`` element of an XML field with a
74 single root "data" element, to prevent having a document with multiple
75 roots once parsed separately.
77 XML nodes should have one root only, but we'd like to support
78 direct multiple roots in our partial documents (like inherited view architectures).
79 As a convention we'll surround multiple root with a container "data" element, to be
80 ignored later when parsing.
84 data_node = etree.Element("data")
86 data_node.append(child)
87 node.append(data_node)
89 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
92 if node.tag in ('field','value'):
93 t = node.get('type','char')
94 f_model = node.get('model', '').encode('utf-8')
95 if node.get('search'):
96 f_search = node.get("search",'').encode('utf-8')
97 f_use = node.get("use",'id').encode('utf-8')
98 f_name = node.get("name",'').encode('utf-8')
99 q = unsafe_eval(f_search, idref)
100 ids = pool.get(f_model).search(cr, uid, q)
102 ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
103 _cols = pool.get(f_model)._columns
104 if (f_name in _cols) and _cols[f_name]._type=='many2many':
109 if isinstance(f_val, tuple):
112 a_eval = node.get('eval','')
118 version=release.major_version,
119 ref=lambda x: self.id_get(cr, False, x),
122 idref2['obj'] = _obj(self.pool, cr, uid, f_model, context=context)
124 return unsafe_eval(a_eval, idref2)
126 logger = logging.getLogger('init')
127 logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True)
130 def _process(s, idref):
131 m = re.findall('[^%]%\((.*?)\)[ds]', s)
134 idref[id]=self.id_get(cr, False, id)
136 _fix_multiple_roots(node)
137 return '<?xml version="1.0"?>\n'\
138 +_process("".join([etree.tostring(n, encoding='utf-8')
141 if t in ('char', 'int', 'float'):
148 return int(d.strip())
150 return float(d.strip())
152 elif t in ('list','tuple'):
154 for n in node.findall('./value'):
155 res.append(_eval_xml(self,n,pool,cr,uid,idref))
159 elif node.tag == "getitem":
161 res=_eval_xml(self,n,pool,cr,uid,idref)
164 elif node.get('type') in ("int", "list"):
165 return res[int(node.get('index'))]
167 return res[node.get('index','').encode("utf8")]
168 elif node.tag == "function":
170 a_eval = node.get('eval','')
172 idref['ref'] = lambda x: self.id_get(cr, False, x)
173 args = unsafe_eval(a_eval, idref)
175 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
176 if return_val is not None:
177 args.append(return_val)
178 model = pool.get(node.get('model',''))
179 method = node.get('name','')
180 res = getattr(model, method)(cr, uid, *args)
182 elif node.tag == "test":
185 escape_re = re.compile(r'(?<!\\)/')
187 return x.replace('\\/', '/')
189 class assertion_report(object):
193 def record_assertion(self, success, severity):
195 Records the result of an assertion for the failed/success count
198 if severity in self._report:
199 self._report[severity][success] += 1
201 self._report[severity] = {success:1, not success: 0}
204 def get_report(self):
208 res = '\nAssertions report:\nLevel\tsuccess\tfailed\n'
210 for sev in self._report:
211 res += sev + '\t' + str(self._report[sev][True]) + '\t' + str(self._report[sev][False]) + '\n'
212 success += self._report[sev][True]
213 failed += self._report[sev][False]
214 res += 'total\t' + str(success) + '\t' + str(failed) + '\n'
215 res += 'end of report (' + str(success + failed) + ' assertion(s) checked)'
218 class xml_import(object):
219 __logger = logging.getLogger('tools.convert.xml_import')
221 def nodeattr2bool(node, attr, default=False):
222 if not node.get(attr):
224 val = node.get(attr).strip()
227 return val.lower() not in ('0', 'false', 'off')
229 def isnoupdate(self, data_node=None):
230 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
232 def get_context(self, data_node, node, eval_dict):
233 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
234 node_context = node.get("context",'').encode('utf8')
236 for ctx in (data_node_context, node_context):
239 ctx_res = unsafe_eval(ctx, eval_dict)
240 if isinstance(context, dict):
241 context.update(ctx_res)
245 # Some contexts contain references that are only valid at runtime at
246 # client-side, so in that case we keep the original context string
247 # as it is. We also log it, just in case.
249 logging.getLogger("init").debug('Context value (%s) for element with id "%s" or its data node does not parse '\
250 'at server-side, keeping original string, in case it\'s meant for client side only',
251 ctx, node.get('id','n/a'), exc_info=True)
254 def get_uid(self, cr, uid, data_node, node):
255 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
257 return self.id_get(cr, None, node_uid)
260 def _test_xml_id(self, xml_id):
263 module, id = xml_id.split('.', 1)
264 assert '.' not in id, """The ID reference "%s" must contain
265 maximum one dot. They are used to refer to other modules ID, in the
266 form: module.record_id""" % (xml_id,)
267 if module != self.module:
268 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
269 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
272 self.logger.notifyChannel('init', netsvc.LOG_ERROR, 'id: %s is to long (max: 64)'% (id,))
274 def _tag_delete(self, cr, rec, data_node=None):
275 d_model = rec.get("model",'')
276 d_search = rec.get("search",'')
277 d_id = rec.get("id",'')
280 ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search))
283 ids.append(self.id_get(cr, d_model, d_id))
285 # d_id cannot be found. doesn't matter in this case
288 self.pool.get(d_model).unlink(cr, self.uid, ids)
289 self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids)
291 def _remove_ir_values(self, cr, name, value, model):
292 ir_value_ids = self.pool.get('ir.values').search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
294 self.pool.get('ir.values').unlink(cr, self.uid, ir_value_ids)
295 self.pool.get('ir.model.data')._unlink(cr, self.uid, 'ir.values', ir_value_ids)
299 def _tag_report(self, cr, rec, data_node=None):
301 for dest,f in (('name','string'),('model','model'),('report_name','name')):
302 res[dest] = rec.get(f,'').encode('utf8')
303 assert res[dest], "Attribute %s of report is empty !" % (f,)
304 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
306 res[dest] = rec.get(field).encode('utf8')
308 res['auto'] = eval(rec.get('auto','False'))
310 sxw_content = misc.file_open(rec.get('sxw')).read()
311 res['report_sxw_content'] = sxw_content
312 if rec.get('header'):
313 res['header'] = eval(rec.get('header','False'))
314 if rec.get('report_type'):
315 res['report_type'] = rec.get('report_type')
317 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
319 xml_id = rec.get('id','').encode('utf8')
320 self._test_xml_id(xml_id)
322 if rec.get('groups'):
323 g_names = rec.get('groups','').split(',')
325 for group in g_names:
326 if group.startswith('-'):
327 group_id = self.id_get(cr, 'res.groups', group[1:])
328 groups_value.append((3, group_id))
330 group_id = self.id_get(cr, 'res.groups', group)
331 groups_value.append((4, group_id))
332 res['groups_id'] = groups_value
334 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)
335 self.idref[xml_id] = int(id)
337 if not rec.get('menu') or eval(rec.get('menu','False')):
338 keyword = str(rec.get('keyword', 'client_print_multi'))
339 value = 'ir.actions.report.xml,'+str(id)
340 replace = rec.get('replace', True)
341 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)
342 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
343 # Special check for report having attribute menu=False on update
344 value = 'ir.actions.report.xml,'+str(id)
345 self._remove_ir_values(cr, res['name'], value, res['model'])
348 def _tag_function(self, cr, rec, data_node=None):
349 if self.isnoupdate(data_node) and self.mode != 'init':
351 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
352 uid = self.get_uid(cr, self.uid, data_node, rec)
353 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
356 def _tag_wizard(self, cr, rec, data_node=None):
357 string = rec.get("string",'').encode('utf8')
358 model = rec.get("model",'').encode('utf8')
359 name = rec.get("name",'').encode('utf8')
360 xml_id = rec.get('id','').encode('utf8')
361 self._test_xml_id(xml_id)
362 multi = rec.get('multi','') and eval(rec.get('multi','False'))
363 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
365 if rec.get('groups'):
366 g_names = rec.get('groups','').split(',')
368 for group in g_names:
369 if group.startswith('-'):
370 group_id = self.id_get(cr, 'res.groups', group[1:])
371 groups_value.append((3, group_id))
373 group_id = self.id_get(cr, 'res.groups', group)
374 groups_value.append((4, group_id))
375 res['groups_id'] = groups_value
377 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)
378 self.idref[xml_id] = int(id)
380 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
381 keyword = str(rec.get('keyword','') or 'client_action_multi')
382 value = 'ir.actions.wizard,'+str(id)
383 replace = rec.get("replace",'') or True
384 self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
385 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
386 # Special check for wizard having attribute menu=False on update
387 value = 'ir.actions.wizard,'+str(id)
388 self._remove_ir_values(cr, string, value, model)
390 def _tag_url(self, cr, rec, data_node=None):
391 url = rec.get("string",'').encode('utf8')
392 target = rec.get("target",'').encode('utf8')
393 name = rec.get("name",'').encode('utf8')
394 xml_id = rec.get('id','').encode('utf8')
395 self._test_xml_id(xml_id)
397 res = {'name': name, 'url': url, 'target':target}
399 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)
400 self.idref[xml_id] = int(id)
402 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
403 keyword = str(rec.get('keyword','') or 'client_action_multi')
404 value = 'ir.actions.url,'+str(id)
405 replace = rec.get("replace",'') or True
406 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)
407 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
408 # Special check for URL having attribute menu=False on update
409 value = 'ir.actions.url,'+str(id)
410 self._remove_ir_values(cr, url, value, "ir.actions.url")
412 def _tag_act_window(self, cr, rec, data_node=None):
413 name = rec.get('name','').encode('utf-8')
414 xml_id = rec.get('id','').encode('utf8')
415 self._test_xml_id(xml_id)
416 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
419 view_id = self.id_get(cr, 'ir.actions.act_window', rec.get('view','').encode('utf-8'))
420 domain = rec.get('domain','').encode('utf-8') or '{}'
421 res_model = rec.get('res_model','').encode('utf-8')
422 src_model = rec.get('src_model','').encode('utf-8')
423 view_type = rec.get('view_type','').encode('utf-8') or 'form'
424 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
425 usage = rec.get('usage','').encode('utf-8')
426 limit = rec.get('limit','').encode('utf-8')
427 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
429 active_id = str("active_id") # for further reference in client/bin/tools/__init__.py
431 return self.id_get(cr, None, str_id)
433 # Include all locals() in eval_context, for backwards compatibility
440 'res_model': res_model,
441 'src_model': src_model,
442 'view_type': view_type,
443 'view_mode': view_mode,
446 'auto_refresh': auto_refresh,
448 'active_id': active_id,
451 context = self.get_context(data_node, rec, eval_context)
454 domain = unsafe_eval(domain, eval_context)
456 # Some domains contain references that are only valid at runtime at
457 # client-side, so in that case we keep the original domain string
458 # as it is. We also log it, just in case.
459 logging.getLogger("init").debug('Domain value (%s) for element with id "%s" does not parse '\
460 'at server-side, keeping original string, in case it\'s meant for client side only',
461 domain, xml_id or 'n/a', exc_info=True)
468 'res_model': res_model,
469 'src_model': src_model,
470 'view_type': view_type,
471 'view_mode': view_mode,
474 'auto_refresh': auto_refresh,
477 if rec.get('groups'):
478 g_names = rec.get('groups','').split(',')
480 for group in g_names:
481 if group.startswith('-'):
482 group_id = self.id_get(cr, 'res.groups', group[1:])
483 groups_value.append((3, group_id))
485 group_id = self.id_get(cr, 'res.groups', group)
486 groups_value.append((4, group_id))
487 res['groups_id'] = groups_value
489 if rec.get('target'):
490 res['target'] = rec.get('target','')
492 res['multi'] = rec.get('multi', False)
493 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)
494 self.idref[xml_id] = int(id)
497 #keyword = 'client_action_relate'
498 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
499 value = 'ir.actions.act_window,'+str(id)
500 replace = rec.get('replace','') or True
501 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)
502 # TODO add remove ir.model.data
504 def _tag_ir_set(self, cr, rec, data_node=None):
505 if self.mode != 'init':
508 for field in rec.findall('./field'):
509 f_name = field.get("name",'').encode('utf-8')
510 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
512 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))
514 def _tag_workflow(self, cr, rec, data_node=None):
515 if self.isnoupdate(data_node) and self.mode != 'init':
517 model = str(rec.get('model',''))
518 w_ref = rec.get('ref','')
520 id = self.id_get(cr, model, w_ref)
522 number_children = len(rec)
523 assert number_children > 0,\
524 'You must define a child node if you dont give a ref'
525 assert number_children == 1,\
526 'Only one child node is accepted (%d given)' % number_children
527 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
529 uid = self.get_uid(cr, self.uid, data_node, rec)
530 wf_service = netsvc.LocalService("workflow")
531 wf_service.trg_validate(uid, model,
533 str(rec.get('action','')), cr)
536 # Support two types of notation:
537 # name="Inventory Control/Sending Goods"
542 def _tag_menuitem(self, cr, rec, data_node=None):
543 rec_id = rec.get("id",'').encode('ascii')
544 self._test_xml_id(rec_id)
545 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
547 values = {'parent_id': False}
548 if rec.get('parent', False) is False and len(m_l) > 1:
549 # No parent attribute specified and the menu name has several menu components,
550 # try to determine the ID of the parent according to menu path
553 values['name'] = m_l[-1]
554 m_l = m_l[:-1] # last part is our name, not a parent
555 for idx, menu_elem in enumerate(m_l):
557 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
559 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
564 # the menuitem does't exist but we are in branch (not a leaf)
565 self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l)))
566 pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
567 values['parent_id'] = pid
569 # The parent attribute was specified, if non-empty determine its ID, otherwise
570 # explicitly make a top-level menu
571 if rec.get('parent'):
572 menu_parent_id = self.id_get(cr, 'ir.ui.menu', rec.get('parent',''))
574 # we get here with <menuitem parent="">, explicit clear of parent, or
575 # if no parent attribute at all but menu name is not a menu path
576 menu_parent_id = False
577 values = {'parent_id': menu_parent_id}
579 values['name'] = rec.get('name')
581 res = [ self.id_get(cr, 'ir.ui.menu', rec.get('id','')) ]
585 if rec.get('action'):
586 a_action = rec.get('action','').encode('utf8')
587 a_type = rec.get('type','').encode('utf8') or 'act_window'
589 "act_window": 'STOCK_NEW',
590 "report.xml": 'STOCK_PASTE',
591 "wizard": 'STOCK_EXECUTE',
592 "url": 'STOCK_JUMP_TO'
594 values['icon'] = icons.get(a_type,'STOCK_NEW')
595 if a_type=='act_window':
596 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
597 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
598 rrres = cr.fetchone()
599 assert rrres, "No window action defined for this id %s !\n" \
600 "Verify that this is a window action or add a type argument." % (a_action,)
601 action_type,action_mode,action_name,view_id,target = rrres
603 cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
604 action_mode, = cr.fetchone()
605 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
607 action_mode, = cr.fetchone()
608 if action_type=='tree':
609 values['icon'] = 'STOCK_INDENT'
610 elif action_mode and action_mode.startswith('tree'):
611 values['icon'] = 'STOCK_JUSTIFY_FILL'
612 elif action_mode and action_mode.startswith('graph'):
613 values['icon'] = 'terp-graph'
614 elif action_mode and action_mode.startswith('calendar'):
615 values['icon'] = 'terp-calendar'
617 values['icon'] = 'STOCK_EXECUTE'
618 if not values.get('name', False):
619 values['name'] = action_name
620 elif a_type=='wizard':
621 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
622 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
624 if (not values.get('name', False)) and resw:
625 values['name'] = resw[0]
626 if rec.get('sequence'):
627 values['sequence'] = int(rec.get('sequence'))
629 values['icon'] = str(rec.get('icon'))
631 if rec.get('groups'):
632 g_names = rec.get('groups','').split(',')
634 for group in g_names:
635 if group.startswith('-'):
636 group_id = self.id_get(cr, 'res.groups', group[1:])
637 groups_value.append((3, group_id))
639 group_id = self.id_get(cr, 'res.groups', group)
640 groups_value.append((4, group_id))
641 values['groups_id'] = groups_value
643 xml_id = rec.get('id','').encode('utf8')
644 self._test_xml_id(xml_id)
645 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)
648 self.idref[rec_id] = int(pid)
650 if rec.get('action') and pid:
651 a_action = rec.get('action').encode('utf8')
652 a_type = rec.get('type','').encode('utf8') or 'act_window'
653 a_id = self.id_get(cr, 'ir.actions.%s' % a_type, a_action)
654 action = "ir.actions.%s,%d" % (a_type, a_id)
655 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)
656 return ('ir.ui.menu', pid)
658 def _assert_equals(self, f1, f2, prec=4):
659 return not round(f1 - f2, prec)
661 def _tag_assert(self, cr, rec, data_node=None):
662 if self.isnoupdate(data_node) and self.mode != 'init':
665 rec_model = rec.get("model",'').encode('ascii')
666 model = self.pool.get(rec_model)
667 assert model, "The model %s does not exist !" % (rec_model,)
668 rec_id = rec.get("id",'').encode('ascii')
669 self._test_xml_id(rec_id)
670 rec_src = rec.get("search",'').encode('utf8')
671 rec_src_count = rec.get("count")
673 severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
674 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
677 eval_dict = {'ref': _ref(self, cr)}
678 context = self.get_context(data_node, rec, eval_dict)
679 uid = self.get_uid(cr, self.uid, data_node, rec)
681 ids = [self.id_get(cr, rec_model, rec_id)]
683 q = unsafe_eval(rec_src, eval_dict)
684 ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
686 count = int(rec_src_count)
687 if len(ids) != count:
688 self.assert_report.record_assertion(False, severity)
689 msg = 'assertion "%s" failed!\n' \
690 ' Incorrect search count:\n' \
691 ' expected count: %d\n' \
692 ' obtained count: %d\n' \
693 % (rec_string, count, len(ids))
694 self.logger.notifyChannel('init', severity, msg)
695 sevval = getattr(logging, severity.upper())
696 if sevval >= config['assert_exit_level']:
697 # TODO: define a dedicated exception
698 raise Exception('Severe assertion failure')
701 assert ids is not None,\
702 'You must give either an id or a search criteria'
705 brrec = model.browse(cr, uid, id, context)
707 def __getitem__(self2, key):
710 return dict.__getitem__(self2, key)
712 globals_dict['floatEqual'] = self._assert_equals
713 globals_dict['ref'] = ref
714 globals_dict['_ref'] = ref
715 for test in rec.findall('./test'):
716 f_expr = test.get("expr",'').encode('utf-8')
717 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
718 expression_value = unsafe_eval(f_expr, globals_dict)
719 if expression_value != expected_value: # assertion failed
720 self.assert_report.record_assertion(False, severity)
721 msg = 'assertion "%s" failed!\n' \
723 ' expected value: %r\n' \
724 ' obtained value: %r\n' \
725 % (rec_string, etree.tostring(test), expected_value, expression_value)
726 self.logger.notifyChannel('init', severity, msg)
727 sevval = getattr(logging, severity.upper())
728 if sevval >= config['assert_exit_level']:
729 # TODO: define a dedicated exception
730 raise Exception('Severe assertion failure')
732 else: # all tests were successful for this assertion tag (no break)
733 self.assert_report.record_assertion(True, severity)
735 def _tag_record(self, cr, rec, data_node=None):
736 rec_model = rec.get("model").encode('ascii')
737 model = self.pool.get(rec_model)
738 assert model, "The model %s does not exist !" % (rec_model,)
739 rec_id = rec.get("id",'').encode('ascii')
740 rec_context = rec.get("context", None)
742 rec_context = unsafe_eval(rec_context)
743 self._test_xml_id(rec_id)
744 if self.isnoupdate(data_node) and self.mode != 'init':
745 # check if the xml record has an id string
748 module,rec_id2 = rec_id.split('.')
752 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
753 # check if the resource already existed at the last update
755 # if it existed, we don't update the data, but we need to
756 # know the id of the existing record anyway
757 self.idref[rec_id] = int(id)
760 # if the resource didn't exist
761 if not self.nodeattr2bool(rec, 'forcecreate', True):
762 # we don't want to create it, so we skip it
764 # else, we let the record to be created
767 # otherwise it is skipped
770 for field in rec.findall('./field'):
771 #TODO: most of this code is duplicated above (in _eval_xml)...
772 f_name = field.get("name",'').encode('utf-8')
773 f_ref = field.get("ref",'').encode('utf-8')
774 f_search = field.get("search",'').encode('utf-8')
775 f_model = field.get("model",'').encode('utf-8')
776 if not f_model and model._columns.get(f_name,False):
777 f_model = model._columns[f_name]._obj
778 f_use = field.get("use",'').encode('utf-8') or 'id'
782 q = unsafe_eval(f_search, self.idref)
784 assert f_model, 'Define an attribute model="..." in your .XML file !'
785 f_obj = self.pool.get(f_model)
786 # browse the objects searched
787 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
788 # column definitions of the "local" object
789 _cols = self.pool.get(rec_model)._columns
790 # if the current field is many2many
791 if (f_name in _cols) and _cols[f_name]._type=='many2many':
792 f_val = [(6, 0, map(lambda x: x[f_use], s))]
794 # otherwise (we are probably in a many2one field),
795 # take the first element of the search
801 if f_name in model._columns \
802 and model._columns[f_name]._type == 'reference':
803 val = self.model_id_get(cr, f_model, f_ref)
804 f_val = val[0] + ',' + str(val[1])
806 f_val = self.id_get(cr, f_model, f_ref)
808 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
809 if model._columns.has_key(f_name):
810 if isinstance(model._columns[f_name], osv.fields.integer):
814 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 )
816 self.idref[rec_id] = int(id)
817 if config.get('import_partial', False):
821 def id_get(self, cr, model, id_str):
822 if id_str in self.idref:
823 return self.idref[id_str]
824 res = self.model_id_get(cr, model, id_str)
825 if res and len(res)>1: res = res[1]
828 def model_id_get(self, cr, model, id_str):
829 model_data_obj = self.pool.get('ir.model.data')
832 mod,id_str = id_str.split('.')
833 result = model_data_obj._get_id(cr, self.uid, mod, id_str)
834 res = model_data_obj.read(cr, self.uid, [result], ['model', 'res_id'])
835 if res and res[0] and res[0]['res_id']:
836 return res[0]['model'], int(res[0]['res_id'])
840 if not de.tag in ['terp', 'openerp']:
841 self.logger.notifyChannel("init", netsvc.LOG_ERROR, "Mismatch xml format" )
842 raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
845 self.logger.notifyChannel("init", netsvc.LOG_WARNING, "The tag <terp/> is deprecated, use <openerp/>")
847 for n in de.findall('./data'):
849 if rec.tag in self._tags:
851 self._tags[rec.tag](self.cr, rec, n)
853 self.__logger.error('Parse error in %s:%d: \n%s',
854 rec.getroottree().docinfo.URL,
856 etree.tostring(rec).strip())
861 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
863 self.logger = netsvc.Logger()
868 self.pool = pooler.get_pool(cr.dbname)
871 report = assertion_report()
872 self.assert_report = report
873 self.noupdate = noupdate
875 'menuitem': self._tag_menuitem,
876 'record': self._tag_record,
877 'assert': self._tag_assert,
878 'report': self._tag_report,
879 'wizard': self._tag_wizard,
880 'delete': self._tag_delete,
881 'ir_set': self._tag_ir_set,
882 'function': self._tag_function,
883 'workflow': self._tag_workflow,
884 'act_window': self._tag_act_window,
888 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
896 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
897 #remove folder path from model
898 head, model = os.path.split(model)
900 pool = pooler.get_pool(cr.dbname)
902 input = cStringIO.StringIO(csvcontent)
903 reader = csv.reader(input, quotechar='"', delimiter=',')
904 fields = reader.next()
906 if config.get('import_partial'):
907 fname_partial = module + '/'+ fname
908 if not os.path.isfile(config.get('import_partial')):
909 pickle.dump({}, file(config.get('import_partial'),'w+'))
911 data = pickle.load(file(config.get('import_partial')))
912 if fname_partial in data:
913 if not data[fname_partial]:
916 for i in range(data[fname_partial]):
919 if not (mode == 'init' or 'id' in fields):
920 logger = netsvc.Logger()
921 logger.notifyChannel("init", netsvc.LOG_ERROR,
922 "Import specification does not contain 'id' and we are in init mode, Cannot continue.")
928 if (not line) or not reduce(lambda x,y: x or y, line) :
931 datas.append(map(lambda x: misc.ustr(x), line))
933 logger = netsvc.Logger()
934 logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line)
935 pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
936 if config.get('import_partial'):
937 data = pickle.load(file(config.get('import_partial')))
938 data[fname_partial] = 0
939 pickle.dump(data, file(config.get('import_partial'),'wb'))
945 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
946 doc = etree.parse(xmlfile)
947 relaxng = etree.RelaxNG(
948 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
952 logger = netsvc.Logger()
953 logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
954 logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
959 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
960 obj.parse(doc.getroot())