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):
220 def nodeattr2bool(node, attr, default=False):
221 if not node.get(attr):
223 val = node.get(attr).strip()
226 return val.lower() not in ('0', 'false', 'off')
228 def isnoupdate(self, data_node=None):
229 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
231 def get_context(self, data_node, node, eval_dict):
232 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
233 node_context = node.get("context",'').encode('utf8')
235 for ctx in (data_node_context, node_context):
238 ctx_res = unsafe_eval(ctx, eval_dict)
239 if isinstance(context, dict):
240 context.update(ctx_res)
244 # Some contexts contain references that are only valid at runtime at
245 # client-side, so in that case we keep the original context string
246 # as it is. We also log it, just in case.
248 logging.getLogger("init").debug('Context value (%s) for element with id "%s" or its data node does not parse '\
249 'at server-side, keeping original string, in case it\'s meant for client side only',
250 ctx, node.get('id','n/a'), exc_info=True)
253 def get_uid(self, cr, uid, data_node, node):
254 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
256 return self.id_get(cr, None, node_uid)
259 def _test_xml_id(self, xml_id):
262 module, id = xml_id.split('.', 1)
263 assert '.' not in id, """The ID reference "%s" must contain
264 maximum one dot. They are used to refer to other modules ID, in the
265 form: module.record_id""" % (xml_id,)
266 if module != self.module:
267 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
268 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
271 self.logger.notifyChannel('init', netsvc.LOG_ERROR, 'id: %s is to long (max: 64)'% (id,))
273 def _tag_delete(self, cr, rec, data_node=None):
274 d_model = rec.get("model",'')
275 d_search = rec.get("search",'')
276 d_id = rec.get("id",'')
279 ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search))
282 ids.append(self.id_get(cr, d_model, d_id))
284 # d_id cannot be found. doesn't matter in this case
287 self.pool.get(d_model).unlink(cr, self.uid, ids)
288 self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids)
290 def _remove_ir_values(self, cr, name, value, model):
291 ir_value_ids = self.pool.get('ir.values').search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
293 self.pool.get('ir.values').unlink(cr, self.uid, ir_value_ids)
294 self.pool.get('ir.model.data')._unlink(cr, self.uid, 'ir.values', ir_value_ids)
298 def _tag_report(self, cr, rec, data_node=None):
300 for dest,f in (('name','string'),('model','model'),('report_name','name')):
301 res[dest] = rec.get(f,'').encode('utf8')
302 assert res[dest], "Attribute %s of report is empty !" % (f,)
303 for field,dest in (('rml','report_rml'),('file','report_file'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
305 res[dest] = rec.get(field).encode('utf8')
307 res['auto'] = eval(rec.get('auto','False'))
309 sxw_content = misc.file_open(rec.get('sxw')).read()
310 res['report_sxw_content'] = sxw_content
311 if rec.get('header'):
312 res['header'] = eval(rec.get('header','False'))
313 if rec.get('report_type'):
314 res['report_type'] = rec.get('report_type')
316 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
318 xml_id = rec.get('id','').encode('utf8')
319 self._test_xml_id(xml_id)
321 if rec.get('groups'):
322 g_names = rec.get('groups','').split(',')
324 for group in g_names:
325 if group.startswith('-'):
326 group_id = self.id_get(cr, 'res.groups', group[1:])
327 groups_value.append((3, group_id))
329 group_id = self.id_get(cr, 'res.groups', group)
330 groups_value.append((4, group_id))
331 res['groups_id'] = groups_value
333 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)
334 self.idref[xml_id] = int(id)
336 if not rec.get('menu') or eval(rec.get('menu','False')):
337 keyword = str(rec.get('keyword', 'client_print_multi'))
338 value = 'ir.actions.report.xml,'+str(id)
339 replace = rec.get('replace', True)
340 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)
341 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
342 # Special check for report having attribute menu=False on update
343 value = 'ir.actions.report.xml,'+str(id)
344 self._remove_ir_values(cr, res['name'], value, res['model'])
347 def _tag_function(self, cr, rec, data_node=None):
348 if self.isnoupdate(data_node) and self.mode != 'init':
350 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
351 uid = self.get_uid(cr, self.uid, data_node, rec)
352 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
355 def _tag_wizard(self, cr, rec, data_node=None):
356 string = rec.get("string",'').encode('utf8')
357 model = rec.get("model",'').encode('utf8')
358 name = rec.get("name",'').encode('utf8')
359 xml_id = rec.get('id','').encode('utf8')
360 self._test_xml_id(xml_id)
361 multi = rec.get('multi','') and eval(rec.get('multi','False'))
362 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
364 if rec.get('groups'):
365 g_names = rec.get('groups','').split(',')
367 for group in g_names:
368 if group.startswith('-'):
369 group_id = self.id_get(cr, 'res.groups', group[1:])
370 groups_value.append((3, group_id))
372 group_id = self.id_get(cr, 'res.groups', group)
373 groups_value.append((4, group_id))
374 res['groups_id'] = groups_value
376 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)
377 self.idref[xml_id] = int(id)
379 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
380 keyword = str(rec.get('keyword','') or 'client_action_multi')
381 value = 'ir.actions.wizard,'+str(id)
382 replace = rec.get("replace",'') or True
383 self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
384 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
385 # Special check for wizard having attribute menu=False on update
386 value = 'ir.actions.wizard,'+str(id)
387 self._remove_ir_values(cr, string, value, model)
389 def _tag_url(self, cr, rec, data_node=None):
390 url = rec.get("string",'').encode('utf8')
391 target = rec.get("target",'').encode('utf8')
392 name = rec.get("name",'').encode('utf8')
393 xml_id = rec.get('id','').encode('utf8')
394 self._test_xml_id(xml_id)
396 res = {'name': name, 'url': url, 'target':target}
398 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)
399 self.idref[xml_id] = int(id)
401 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
402 keyword = str(rec.get('keyword','') or 'client_action_multi')
403 value = 'ir.actions.url,'+str(id)
404 replace = rec.get("replace",'') or True
405 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)
406 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
407 # Special check for URL having attribute menu=False on update
408 value = 'ir.actions.url,'+str(id)
409 self._remove_ir_values(cr, url, value, "ir.actions.url")
411 def _tag_act_window(self, cr, rec, data_node=None):
412 name = rec.get('name','').encode('utf-8')
413 xml_id = rec.get('id','').encode('utf8')
414 self._test_xml_id(xml_id)
415 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
418 view_id = self.id_get(cr, 'ir.actions.act_window', rec.get('view','').encode('utf-8'))
419 domain = rec.get('domain','').encode('utf-8') or '{}'
420 res_model = rec.get('res_model','').encode('utf-8')
421 src_model = rec.get('src_model','').encode('utf-8')
422 view_type = rec.get('view_type','').encode('utf-8') or 'form'
423 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
424 usage = rec.get('usage','').encode('utf-8')
425 limit = rec.get('limit','').encode('utf-8')
426 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
428 active_id = str("active_id") # for further reference in client/bin/tools/__init__.py
430 return self.id_get(cr, None, str_id)
432 # Include all locals() in eval_context, for backwards compatibility
439 'res_model': res_model,
440 'src_model': src_model,
441 'view_type': view_type,
442 'view_mode': view_mode,
445 'auto_refresh': auto_refresh,
447 'active_id': active_id,
450 context = self.get_context(data_node, rec, eval_context)
453 domain = unsafe_eval(domain, eval_context)
455 # Some domains contain references that are only valid at runtime at
456 # client-side, so in that case we keep the original domain string
457 # as it is. We also log it, just in case.
458 logging.getLogger("init").debug('Domain value (%s) for element with id "%s" does not parse '\
459 'at server-side, keeping original string, in case it\'s meant for client side only',
460 domain, xml_id or 'n/a', exc_info=True)
467 'res_model': res_model,
468 'src_model': src_model,
469 'view_type': view_type,
470 'view_mode': view_mode,
473 'auto_refresh': auto_refresh,
476 if rec.get('groups'):
477 g_names = rec.get('groups','').split(',')
479 for group in g_names:
480 if group.startswith('-'):
481 group_id = self.id_get(cr, 'res.groups', group[1:])
482 groups_value.append((3, group_id))
484 group_id = self.id_get(cr, 'res.groups', group)
485 groups_value.append((4, group_id))
486 res['groups_id'] = groups_value
488 if rec.get('target'):
489 res['target'] = rec.get('target','')
490 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)
491 self.idref[xml_id] = int(id)
494 #keyword = 'client_action_relate'
495 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
496 value = 'ir.actions.act_window,'+str(id)
497 replace = rec.get('replace','') or True
498 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)
499 # TODO add remove ir.model.data
501 def _tag_ir_set(self, cr, rec, data_node=None):
502 if self.mode != 'init':
505 for field in rec.findall('./field'):
506 f_name = field.get("name",'').encode('utf-8')
507 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
509 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))
511 def _tag_workflow(self, cr, rec, data_node=None):
512 if self.isnoupdate(data_node) and self.mode != 'init':
514 model = str(rec.get('model',''))
515 w_ref = rec.get('ref','')
517 id = self.id_get(cr, model, w_ref)
519 number_children = len(rec)
520 assert number_children > 0,\
521 'You must define a child node if you dont give a ref'
522 assert number_children == 1,\
523 'Only one child node is accepted (%d given)' % number_children
524 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
526 uid = self.get_uid(cr, self.uid, data_node, rec)
527 wf_service = netsvc.LocalService("workflow")
528 wf_service.trg_validate(uid, model,
530 str(rec.get('action','')), cr)
533 # Support two types of notation:
534 # name="Inventory Control/Sending Goods"
539 def _tag_menuitem(self, cr, rec, data_node=None):
540 rec_id = rec.get("id",'').encode('ascii')
541 self._test_xml_id(rec_id)
542 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
544 values = {'parent_id': False}
545 if rec.get('parent', False) is False and len(m_l) > 1:
546 # No parent attribute specified and the menu name has several menu components,
547 # try to determine the ID of the parent according to menu path
550 values['name'] = m_l[-1]
551 m_l = m_l[:-1] # last part is our name, not a parent
552 for idx, menu_elem in enumerate(m_l):
554 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
556 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
561 # the menuitem does't exist but we are in branch (not a leaf)
562 self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l)))
563 pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
564 values['parent_id'] = pid
566 # The parent attribute was specified, if non-empty determine its ID, otherwise
567 # explicitly make a top-level menu
568 if rec.get('parent'):
569 menu_parent_id = self.id_get(cr, 'ir.ui.menu', rec.get('parent',''))
571 # we get here with <menuitem parent="">, explicit clear of parent, or
572 # if no parent attribute at all but menu name is not a menu path
573 menu_parent_id = False
574 values = {'parent_id': menu_parent_id}
576 values['name'] = rec.get('name')
578 res = [ self.id_get(cr, 'ir.ui.menu', rec.get('id','')) ]
582 if rec.get('action'):
583 a_action = rec.get('action','').encode('utf8')
584 a_type = rec.get('type','').encode('utf8') or 'act_window'
586 "act_window": 'STOCK_NEW',
587 "report.xml": 'STOCK_PASTE',
588 "wizard": 'STOCK_EXECUTE',
589 "url": 'STOCK_JUMP_TO'
591 values['icon'] = icons.get(a_type,'STOCK_NEW')
592 if a_type=='act_window':
593 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
594 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
595 rrres = cr.fetchone()
596 assert rrres, "No window action defined for this id %s !\n" \
597 "Verify that this is a window action or add a type argument." % (a_action,)
598 action_type,action_mode,action_name,view_id,target = rrres
600 cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
601 action_mode, = cr.fetchone()
602 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
604 action_mode, = cr.fetchone()
605 if action_type=='tree':
606 values['icon'] = 'STOCK_INDENT'
607 elif action_mode and action_mode.startswith('tree'):
608 values['icon'] = 'STOCK_JUSTIFY_FILL'
609 elif action_mode and action_mode.startswith('graph'):
610 values['icon'] = 'terp-graph'
611 elif action_mode and action_mode.startswith('calendar'):
612 values['icon'] = 'terp-calendar'
614 values['icon'] = 'STOCK_EXECUTE'
615 if not values.get('name', False):
616 values['name'] = action_name
617 elif a_type=='wizard':
618 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
619 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
621 if (not values.get('name', False)) and resw:
622 values['name'] = resw[0]
623 if rec.get('sequence'):
624 values['sequence'] = int(rec.get('sequence'))
626 values['icon'] = str(rec.get('icon'))
628 if rec.get('groups'):
629 g_names = rec.get('groups','').split(',')
631 for group in g_names:
632 if group.startswith('-'):
633 group_id = self.id_get(cr, 'res.groups', group[1:])
634 groups_value.append((3, group_id))
636 group_id = self.id_get(cr, 'res.groups', group)
637 groups_value.append((4, group_id))
638 values['groups_id'] = groups_value
640 xml_id = rec.get('id','').encode('utf8')
641 self._test_xml_id(xml_id)
642 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)
645 self.idref[rec_id] = int(pid)
647 if rec.get('action') and pid:
648 a_action = rec.get('action').encode('utf8')
649 a_type = rec.get('type','').encode('utf8') or 'act_window'
650 a_id = self.id_get(cr, 'ir.actions.%s' % a_type, a_action)
651 action = "ir.actions.%s,%d" % (a_type, a_id)
652 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)
653 return ('ir.ui.menu', pid)
655 def _assert_equals(self, f1, f2, prec = 4):
656 return not round(f1 - f2, prec)
658 def _tag_assert(self, cr, rec, data_node=None):
659 if self.isnoupdate(data_node) and self.mode != 'init':
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 self._test_xml_id(rec_id)
667 rec_src = rec.get("search",'').encode('utf8')
668 rec_src_count = rec.get("count")
670 severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
671 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
674 eval_dict = {'ref': _ref(self, cr)}
675 context = self.get_context(data_node, rec, eval_dict)
676 uid = self.get_uid(cr, self.uid, data_node, rec)
678 ids = [self.id_get(cr, rec_model, rec_id)]
680 q = unsafe_eval(rec_src, eval_dict)
681 ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
683 count = int(rec_src_count)
684 if len(ids) != count:
685 self.assert_report.record_assertion(False, severity)
686 msg = 'assertion "%s" failed!\n' \
687 ' Incorrect search count:\n' \
688 ' expected count: %d\n' \
689 ' obtained count: %d\n' \
690 % (rec_string, count, len(ids))
691 self.logger.notifyChannel('init', severity, msg)
692 sevval = getattr(logging, severity.upper())
693 if sevval >= config['assert_exit_level']:
694 # TODO: define a dedicated exception
695 raise Exception('Severe assertion failure')
698 assert ids is not None,\
699 'You must give either an id or a search criteria'
702 brrec = model.browse(cr, uid, id, context)
704 def __getitem__(self2, key):
707 return dict.__getitem__(self2, key)
709 globals_dict['floatEqual'] = self._assert_equals
710 globals_dict['ref'] = ref
711 globals_dict['_ref'] = ref
712 for test in rec.findall('./test'):
713 f_expr = test.get("expr",'').encode('utf-8')
714 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
715 expression_value = unsafe_eval(f_expr, globals_dict)
716 if expression_value != expected_value: # assertion failed
717 self.assert_report.record_assertion(False, severity)
718 msg = 'assertion "%s" failed!\n' \
720 ' expected value: %r\n' \
721 ' obtained value: %r\n' \
722 % (rec_string, etree.tostring(test), expected_value, expression_value)
723 self.logger.notifyChannel('init', severity, msg)
724 sevval = getattr(logging, severity.upper())
725 if sevval >= config['assert_exit_level']:
726 # TODO: define a dedicated exception
727 raise Exception('Severe assertion failure')
729 else: # all tests were successful for this assertion tag (no break)
730 self.assert_report.record_assertion(True, severity)
732 def _tag_record(self, cr, rec, data_node=None):
733 rec_model = rec.get("model").encode('ascii')
734 model = self.pool.get(rec_model)
735 assert model, "The model %s does not exist !" % (rec_model,)
736 rec_id = rec.get("id",'').encode('ascii')
737 rec_context = rec.get("context", None)
739 rec_context = unsafe_eval(rec_context)
740 self._test_xml_id(rec_id)
741 if self.isnoupdate(data_node) and self.mode != 'init':
742 # check if the xml record has an id string
745 module,rec_id2 = rec_id.split('.')
749 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
750 # check if the resource already existed at the last update
752 # if it existed, we don't update the data, but we need to
753 # know the id of the existing record anyway
754 self.idref[rec_id] = int(id)
757 # if the resource didn't exist
758 if not self.nodeattr2bool(rec, 'forcecreate', True):
759 # we don't want to create it, so we skip it
761 # else, we let the record to be created
764 # otherwise it is skipped
767 for field in rec.findall('./field'):
768 #TODO: most of this code is duplicated above (in _eval_xml)...
769 f_name = field.get("name",'').encode('utf-8')
770 f_ref = field.get("ref",'').encode('utf-8')
771 f_search = field.get("search",'').encode('utf-8')
772 f_model = field.get("model",'').encode('utf-8')
773 if not f_model and model._columns.get(f_name,False):
774 f_model = model._columns[f_name]._obj
775 f_use = field.get("use",'').encode('utf-8') or 'id'
779 q = unsafe_eval(f_search, self.idref)
781 assert f_model, 'Define an attribute model="..." in your .XML file !'
782 f_obj = self.pool.get(f_model)
783 # browse the objects searched
784 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
785 # column definitions of the "local" object
786 _cols = self.pool.get(rec_model)._columns
787 # if the current field is many2many
788 if (f_name in _cols) and _cols[f_name]._type=='many2many':
789 f_val = [(6, 0, map(lambda x: x[f_use], s))]
791 # otherwise (we are probably in a many2one field),
792 # take the first element of the search
798 if f_name in model._columns \
799 and model._columns[f_name]._type == 'reference':
800 val = self.model_id_get(cr, f_model, f_ref)
801 f_val = val[0] + ',' + str(val[1])
803 f_val = self.id_get(cr, f_model, f_ref)
805 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
806 if model._columns.has_key(f_name):
807 if isinstance(model._columns[f_name], osv.fields.integer):
811 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 )
813 self.idref[rec_id] = int(id)
814 if config.get('import_partial', False):
818 def id_get(self, cr, model, id_str):
819 if id_str in self.idref:
820 return self.idref[id_str]
821 res = self.model_id_get(cr, model, id_str)
822 if res and len(res)>1: res = res[1]
825 def model_id_get(self, cr, model, id_str):
826 model_data_obj = self.pool.get('ir.model.data')
829 mod,id_str = id_str.split('.')
830 result = model_data_obj._get_id(cr, self.uid, mod, id_str)
831 res = model_data_obj.read(cr, self.uid, [result], ['model', 'res_id'])
832 if res and res[0] and res[0]['res_id']:
833 return res[0]['model'], int(res[0]['res_id'])
837 if not de.tag in ['terp', 'openerp']:
838 self.logger.notifyChannel("init", netsvc.LOG_ERROR, "Mismatch xml format" )
839 raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
842 self.logger.notifyChannel("init", netsvc.LOG_WARNING, "The tag <terp/> is deprecated, use <openerp/>")
844 for n in de.findall('./data'):
846 if rec.tag in self._tags:
848 self._tags[rec.tag](self.cr, rec, n)
850 self.logger.notifyChannel("init", netsvc.LOG_ERROR, '\n'+etree.tostring(rec))
855 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
857 self.logger = netsvc.Logger()
862 self.pool = pooler.get_pool(cr.dbname)
865 report = assertion_report()
866 self.assert_report = report
867 self.noupdate = noupdate
869 'menuitem': self._tag_menuitem,
870 'record': self._tag_record,
871 'assert': self._tag_assert,
872 'report': self._tag_report,
873 'wizard': self._tag_wizard,
874 'delete': self._tag_delete,
875 'ir_set': self._tag_ir_set,
876 'function': self._tag_function,
877 'workflow': self._tag_workflow,
878 'act_window': self._tag_act_window,
882 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
890 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
891 #remove folder path from model
892 head, model = os.path.split(model)
894 pool = pooler.get_pool(cr.dbname)
896 input = cStringIO.StringIO(csvcontent)
897 reader = csv.reader(input, quotechar='"', delimiter=',')
898 fields = reader.next()
900 if config.get('import_partial'):
901 fname_partial = module + '/'+ fname
902 if not os.path.isfile(config.get('import_partial')):
903 pickle.dump({}, file(config.get('import_partial'),'w+'))
905 data = pickle.load(file(config.get('import_partial')))
906 if fname_partial in data:
907 if not data[fname_partial]:
910 for i in range(data[fname_partial]):
913 if not (mode == 'init' or 'id' in fields):
914 logger = netsvc.Logger()
915 logger.notifyChannel("init", netsvc.LOG_ERROR,
916 "Import specification does not contain 'id' and we are in init mode, Cannot continue.")
922 if (not line) or not reduce(lambda x,y: x or y, line) :
925 datas.append(map(lambda x: misc.ustr(x), line))
927 logger = netsvc.Logger()
928 logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line)
929 pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
930 if config.get('import_partial'):
931 data = pickle.load(file(config.get('import_partial')))
932 data[fname_partial] = 0
933 pickle.dump(data, file(config.get('import_partial'),'wb'))
939 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
940 doc = etree.parse(xmlfile)
941 relaxng = etree.RelaxNG(
942 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
946 logger = netsvc.Logger()
947 logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
948 logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
953 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
954 obj.parse(doc.getroot())