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 ##############################################################################
36 logging.getLogger("init").warning('could not find pytz library, please install it')
37 class pytzclass(object):
42 from datetime import datetime, timedelta
43 from lxml import etree
48 from config import config
49 from yaml_import import convert_yaml_import
51 # Import of XML records requires the unsafe eval as well,
52 # almost everywhere, which is ok because it supposedly comes
53 # from trusted data, but at least we make it obvious now.
55 from tools.safe_eval import safe_eval as eval
57 class ConvertError(Exception):
58 def __init__(self, doc, orig_excpt):
60 self.orig = orig_excpt
63 return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
66 return lambda x: self.id_get(cr, False, x)
68 def _obj(pool, cr, uid, model_str, context=None):
69 model = pool.get(model_str)
70 return lambda x: model.browse(cr, uid, x, context=context)
72 def _get_idref(self, cr, uid, model_str, context, idref):
77 version=release.major_version,
81 idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context)
84 def _fix_multiple_roots(node):
86 Surround the children of the ``node`` element of an XML field with a
87 single root "data" element, to prevent having a document with multiple
88 roots once parsed separately.
90 XML nodes should have one root only, but we'd like to support
91 direct multiple roots in our partial documents (like inherited view architectures).
92 As a convention we'll surround multiple root with a container "data" element, to be
93 ignored later when parsing.
97 data_node = etree.Element("data")
99 data_node.append(child)
100 node.append(data_node)
102 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
105 if node.tag in ('field','value'):
106 t = node.get('type','char')
107 f_model = node.get('model', '').encode('utf-8')
108 if node.get('search'):
109 f_search = node.get("search",'').encode('utf-8')
110 f_use = node.get("use",'id').encode('utf-8')
111 f_name = node.get("name",'').encode('utf-8')
114 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
115 q = unsafe_eval(f_search, idref2)
116 ids = pool.get(f_model).search(cr, uid, q)
118 ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
119 _cols = pool.get(f_model)._columns
120 if (f_name in _cols) and _cols[f_name]._type=='many2many':
125 if isinstance(f_val, tuple):
128 a_eval = node.get('eval','')
131 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
133 return unsafe_eval(a_eval, idref2)
135 logger = logging.getLogger('init')
136 logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True)
139 def _process(s, idref):
140 m = re.findall('[^%]%\((.*?)\)[ds]', s)
143 idref[id]=self.id_get(cr, False, id)
145 _fix_multiple_roots(node)
146 return '<?xml version="1.0"?>\n'\
147 +_process("".join([etree.tostring(n, encoding='utf-8')
150 if t in ('char', 'int', 'float'):
157 return int(d.strip())
159 return float(d.strip())
161 elif t in ('list','tuple'):
163 for n in node.findall('./value'):
164 res.append(_eval_xml(self,n,pool,cr,uid,idref))
168 elif node.tag == "getitem":
170 res=_eval_xml(self,n,pool,cr,uid,idref)
173 elif node.get('type') in ("int", "list"):
174 return res[int(node.get('index'))]
176 return res[node.get('index','').encode("utf8")]
177 elif node.tag == "function":
179 a_eval = node.get('eval','')
181 idref['ref'] = lambda x: self.id_get(cr, False, x)
182 args = unsafe_eval(a_eval, idref)
184 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
185 if return_val is not None:
186 args.append(return_val)
187 model = pool.get(node.get('model',''))
188 method = node.get('name','')
189 res = getattr(model, method)(cr, uid, *args)
191 elif node.tag == "test":
194 escape_re = re.compile(r'(?<!\\)/')
196 return x.replace('\\/', '/')
198 class assertion_report(object):
202 def record_assertion(self, success, severity):
204 Records the result of an assertion for the failed/success count
207 if severity in self._report:
208 self._report[severity][success] += 1
210 self._report[severity] = {success:1, not success: 0}
213 def get_report(self):
217 res = '\nAssertions report:\nLevel\tsuccess\tfailed\n'
219 for sev in self._report:
220 res += sev + '\t' + str(self._report[sev][True]) + '\t' + str(self._report[sev][False]) + '\n'
221 success += self._report[sev][True]
222 failed += self._report[sev][False]
223 res += 'total\t' + str(success) + '\t' + str(failed) + '\n'
224 res += 'end of report (' + str(success + failed) + ' assertion(s) checked)'
227 class xml_import(object):
228 __logger = logging.getLogger('tools.convert.xml_import')
230 def nodeattr2bool(node, attr, default=False):
231 if not node.get(attr):
233 val = node.get(attr).strip()
236 return val.lower() not in ('0', 'false', 'off')
238 def isnoupdate(self, data_node=None):
239 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
241 def get_context(self, data_node, node, eval_dict):
242 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
243 node_context = node.get("context",'').encode('utf8')
245 for ctx in (data_node_context, node_context):
248 ctx_res = unsafe_eval(ctx, eval_dict)
249 if isinstance(context, dict):
250 context.update(ctx_res)
254 # Some contexts contain references that are only valid at runtime at
255 # client-side, so in that case we keep the original context string
256 # as it is. We also log it, just in case.
258 logging.getLogger("init").debug('Context value (%s) for element with id "%s" or its data node does not parse '\
259 'at server-side, keeping original string, in case it\'s meant for client side only',
260 ctx, node.get('id','n/a'), exc_info=True)
263 def get_uid(self, cr, uid, data_node, node):
264 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
266 return self.id_get(cr, None, node_uid)
269 def _test_xml_id(self, xml_id):
272 module, id = xml_id.split('.', 1)
273 assert '.' not in id, """The ID reference "%s" must contain
274 maximum one dot. They are used to refer to other modules ID, in the
275 form: module.record_id""" % (xml_id,)
276 if module != self.module:
277 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
278 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
281 self.logger.notifyChannel('init', netsvc.LOG_ERROR, 'id: %s is to long (max: 64)'% (id,))
283 def _tag_delete(self, cr, rec, data_node=None):
284 d_model = rec.get("model",'')
285 d_search = rec.get("search",'').encode('utf-8')
286 d_id = rec.get("id",'')
290 idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
291 ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search, idref))
294 ids.append(self.id_get(cr, d_model, d_id))
296 # d_id cannot be found. doesn't matter in this case
299 self.pool.get(d_model).unlink(cr, self.uid, ids)
300 self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids)
302 def _remove_ir_values(self, cr, name, value, model):
303 ir_value_ids = self.pool.get('ir.values').search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
305 self.pool.get('ir.values').unlink(cr, self.uid, ir_value_ids)
306 self.pool.get('ir.model.data')._unlink(cr, self.uid, 'ir.values', ir_value_ids)
310 def _tag_report(self, cr, rec, data_node=None):
312 for dest,f in (('name','string'),('model','model'),('report_name','name')):
313 res[dest] = rec.get(f,'').encode('utf8')
314 assert res[dest], "Attribute %s of report is empty !" % (f,)
315 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
317 res[dest] = rec.get(field).encode('utf8')
319 res['auto'] = eval(rec.get('auto','False'))
321 sxw_content = misc.file_open(rec.get('sxw')).read()
322 res['report_sxw_content'] = sxw_content
323 if rec.get('header'):
324 res['header'] = eval(rec.get('header','False'))
325 if rec.get('report_type'):
326 res['report_type'] = rec.get('report_type')
328 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
330 xml_id = rec.get('id','').encode('utf8')
331 self._test_xml_id(xml_id)
333 if rec.get('groups'):
334 g_names = rec.get('groups','').split(',')
336 for group in g_names:
337 if group.startswith('-'):
338 group_id = self.id_get(cr, 'res.groups', group[1:])
339 groups_value.append((3, group_id))
341 group_id = self.id_get(cr, 'res.groups', group)
342 groups_value.append((4, group_id))
343 res['groups_id'] = groups_value
345 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)
346 self.idref[xml_id] = int(id)
348 if not rec.get('menu') or eval(rec.get('menu','False')):
349 keyword = str(rec.get('keyword', 'client_print_multi'))
350 value = 'ir.actions.report.xml,'+str(id)
351 replace = rec.get('replace', True)
352 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)
353 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
354 # Special check for report having attribute menu=False on update
355 value = 'ir.actions.report.xml,'+str(id)
356 self._remove_ir_values(cr, res['name'], value, res['model'])
359 def _tag_function(self, cr, rec, data_node=None):
360 if self.isnoupdate(data_node) and self.mode != 'init':
362 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
363 uid = self.get_uid(cr, self.uid, data_node, rec)
364 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
367 def _tag_wizard(self, cr, rec, data_node=None):
368 string = rec.get("string",'').encode('utf8')
369 model = rec.get("model",'').encode('utf8')
370 name = rec.get("name",'').encode('utf8')
371 xml_id = rec.get('id','').encode('utf8')
372 self._test_xml_id(xml_id)
373 multi = rec.get('multi','') and eval(rec.get('multi','False'))
374 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
376 if rec.get('groups'):
377 g_names = rec.get('groups','').split(',')
379 for group in g_names:
380 if group.startswith('-'):
381 group_id = self.id_get(cr, 'res.groups', group[1:])
382 groups_value.append((3, group_id))
384 group_id = self.id_get(cr, 'res.groups', group)
385 groups_value.append((4, group_id))
386 res['groups_id'] = groups_value
388 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)
389 self.idref[xml_id] = int(id)
391 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
392 keyword = str(rec.get('keyword','') or 'client_action_multi')
393 value = 'ir.actions.wizard,'+str(id)
394 replace = rec.get("replace",'') or True
395 self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
396 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
397 # Special check for wizard having attribute menu=False on update
398 value = 'ir.actions.wizard,'+str(id)
399 self._remove_ir_values(cr, string, value, model)
401 def _tag_url(self, cr, rec, data_node=None):
402 url = rec.get("string",'').encode('utf8')
403 target = rec.get("target",'').encode('utf8')
404 name = rec.get("name",'').encode('utf8')
405 xml_id = rec.get('id','').encode('utf8')
406 self._test_xml_id(xml_id)
408 res = {'name': name, 'url': url, 'target':target}
410 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)
411 self.idref[xml_id] = int(id)
413 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
414 keyword = str(rec.get('keyword','') or 'client_action_multi')
415 value = 'ir.actions.url,'+str(id)
416 replace = rec.get("replace",'') or True
417 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)
418 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
419 # Special check for URL having attribute menu=False on update
420 value = 'ir.actions.url,'+str(id)
421 self._remove_ir_values(cr, url, value, "ir.actions.url")
423 def _tag_act_window(self, cr, rec, data_node=None):
424 name = rec.get('name','').encode('utf-8')
425 xml_id = rec.get('id','').encode('utf8')
426 self._test_xml_id(xml_id)
427 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
430 view_id = self.id_get(cr, 'ir.actions.act_window', rec.get('view','').encode('utf-8'))
431 domain = rec.get('domain','').encode('utf-8') or '{}'
432 res_model = rec.get('res_model','').encode('utf-8')
433 src_model = rec.get('src_model','').encode('utf-8')
434 view_type = rec.get('view_type','').encode('utf-8') or 'form'
435 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
436 usage = rec.get('usage','').encode('utf-8')
437 limit = rec.get('limit','').encode('utf-8')
438 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
440 active_id = str("active_id") # for further reference in client/bin/tools/__init__.py
442 return self.id_get(cr, None, str_id)
444 # Include all locals() in eval_context, for backwards compatibility
451 'res_model': res_model,
452 'src_model': src_model,
453 'view_type': view_type,
454 'view_mode': view_mode,
457 'auto_refresh': auto_refresh,
459 'active_id': active_id,
462 context = self.get_context(data_node, rec, eval_context)
465 domain = unsafe_eval(domain, eval_context)
467 # Some domains contain references that are only valid at runtime at
468 # client-side, so in that case we keep the original domain string
469 # as it is. We also log it, just in case.
470 logging.getLogger("init").debug('Domain value (%s) for element with id "%s" does not parse '\
471 'at server-side, keeping original string, in case it\'s meant for client side only',
472 domain, xml_id or 'n/a', exc_info=True)
479 'res_model': res_model,
480 'src_model': src_model,
481 'view_type': view_type,
482 'view_mode': view_mode,
485 'auto_refresh': auto_refresh,
488 if rec.get('groups'):
489 g_names = rec.get('groups','').split(',')
491 for group in g_names:
492 if group.startswith('-'):
493 group_id = self.id_get(cr, 'res.groups', group[1:])
494 groups_value.append((3, group_id))
496 group_id = self.id_get(cr, 'res.groups', group)
497 groups_value.append((4, group_id))
498 res['groups_id'] = groups_value
500 if rec.get('target'):
501 res['target'] = rec.get('target','')
503 res['multi'] = rec.get('multi', False)
504 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)
505 self.idref[xml_id] = int(id)
508 #keyword = 'client_action_relate'
509 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
510 value = 'ir.actions.act_window,'+str(id)
511 replace = rec.get('replace','') or True
512 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)
513 # TODO add remove ir.model.data
515 def _tag_ir_set(self, cr, rec, data_node=None):
516 if self.mode != 'init':
519 for field in rec.findall('./field'):
520 f_name = field.get("name",'').encode('utf-8')
521 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
523 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))
525 def _tag_workflow(self, cr, rec, data_node=None):
526 if self.isnoupdate(data_node) and self.mode != 'init':
528 model = str(rec.get('model',''))
529 w_ref = rec.get('ref','')
531 id = self.id_get(cr, model, w_ref)
533 number_children = len(rec)
534 assert number_children > 0,\
535 'You must define a child node if you dont give a ref'
536 assert number_children == 1,\
537 'Only one child node is accepted (%d given)' % number_children
538 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
540 uid = self.get_uid(cr, self.uid, data_node, rec)
541 wf_service = netsvc.LocalService("workflow")
542 wf_service.trg_validate(uid, model,
544 str(rec.get('action','')), cr)
547 # Support two types of notation:
548 # name="Inventory Control/Sending Goods"
553 def _tag_menuitem(self, cr, rec, data_node=None):
554 rec_id = rec.get("id",'').encode('ascii')
555 self._test_xml_id(rec_id)
556 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
558 values = {'parent_id': False}
559 if rec.get('parent', False) is False and len(m_l) > 1:
560 # No parent attribute specified and the menu name has several menu components,
561 # try to determine the ID of the parent according to menu path
564 values['name'] = m_l[-1]
565 m_l = m_l[:-1] # last part is our name, not a parent
566 for idx, menu_elem in enumerate(m_l):
568 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
570 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
575 # the menuitem does't exist but we are in branch (not a leaf)
576 self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l)))
577 pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
578 values['parent_id'] = pid
580 # The parent attribute was specified, if non-empty determine its ID, otherwise
581 # explicitly make a top-level menu
582 if rec.get('parent'):
583 menu_parent_id = self.id_get(cr, 'ir.ui.menu', rec.get('parent',''))
585 # we get here with <menuitem parent="">, explicit clear of parent, or
586 # if no parent attribute at all but menu name is not a menu path
587 menu_parent_id = False
588 values = {'parent_id': menu_parent_id}
590 values['name'] = rec.get('name')
592 res = [ self.id_get(cr, 'ir.ui.menu', rec.get('id','')) ]
596 if rec.get('action'):
597 a_action = rec.get('action','').encode('utf8')
598 a_type = rec.get('type','').encode('utf8') or 'act_window'
600 "act_window": 'STOCK_NEW',
601 "report.xml": 'STOCK_PASTE',
602 "wizard": 'STOCK_EXECUTE',
603 "url": 'STOCK_JUMP_TO'
605 values['icon'] = icons.get(a_type,'STOCK_NEW')
606 if a_type=='act_window':
607 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
608 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
609 rrres = cr.fetchone()
610 assert rrres, "No window action defined for this id %s !\n" \
611 "Verify that this is a window action or add a type argument." % (a_action,)
612 action_type,action_mode,action_name,view_id,target = rrres
614 cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
615 action_mode, = cr.fetchone()
616 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
618 action_mode, = cr.fetchone()
619 if action_type=='tree':
620 values['icon'] = 'STOCK_INDENT'
621 elif action_mode and action_mode.startswith('tree'):
622 values['icon'] = 'STOCK_JUSTIFY_FILL'
623 elif action_mode and action_mode.startswith('graph'):
624 values['icon'] = 'terp-graph'
625 elif action_mode and action_mode.startswith('calendar'):
626 values['icon'] = 'terp-calendar'
628 values['icon'] = 'STOCK_EXECUTE'
629 if not values.get('name', False):
630 values['name'] = action_name
631 elif a_type=='wizard':
632 a_id = self.id_get(cr, 'ir.actions.%s'% a_type, a_action)
633 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
635 if (not values.get('name', False)) and resw:
636 values['name'] = resw[0]
637 if rec.get('sequence'):
638 values['sequence'] = int(rec.get('sequence'))
640 values['icon'] = str(rec.get('icon'))
642 if rec.get('web_icon'):
643 values['web_icon'] = addons.get_module_resource(self.module,str(rec.get('web_icon')))
644 if rec.get('web_icon_hover'):
645 values['web_icon_hover'] = addons.get_module_resource(self.module,str(rec.get('web_icon_hover')))
647 if rec.get('groups'):
648 g_names = rec.get('groups','').split(',')
650 for group in g_names:
651 if group.startswith('-'):
652 group_id = self.id_get(cr, 'res.groups', group[1:])
653 groups_value.append((3, group_id))
655 group_id = self.id_get(cr, 'res.groups', group)
656 groups_value.append((4, group_id))
657 values['groups_id'] = groups_value
659 xml_id = rec.get('id','').encode('utf8')
660 self._test_xml_id(xml_id)
661 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)
664 self.idref[rec_id] = int(pid)
666 if rec.get('action') and pid:
667 a_action = rec.get('action').encode('utf8')
668 a_type = rec.get('type','').encode('utf8') or 'act_window'
669 a_id = self.id_get(cr, 'ir.actions.%s' % a_type, a_action)
670 action = "ir.actions.%s,%d" % (a_type, a_id)
671 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)
672 return ('ir.ui.menu', pid)
674 def _assert_equals(self, f1, f2, prec=4):
675 return not round(f1 - f2, prec)
677 def _tag_assert(self, cr, rec, data_node=None):
678 if self.isnoupdate(data_node) and self.mode != 'init':
681 rec_model = rec.get("model",'').encode('ascii')
682 model = self.pool.get(rec_model)
683 assert model, "The model %s does not exist !" % (rec_model,)
684 rec_id = rec.get("id",'').encode('ascii')
685 self._test_xml_id(rec_id)
686 rec_src = rec.get("search",'').encode('utf8')
687 rec_src_count = rec.get("count")
689 severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
690 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
693 eval_dict = {'ref': _ref(self, cr)}
694 context = self.get_context(data_node, rec, eval_dict)
695 uid = self.get_uid(cr, self.uid, data_node, rec)
697 ids = [self.id_get(cr, rec_model, rec_id)]
699 q = unsafe_eval(rec_src, eval_dict)
700 ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
702 count = int(rec_src_count)
703 if len(ids) != count:
704 self.assert_report.record_assertion(False, severity)
705 msg = 'assertion "%s" failed!\n' \
706 ' Incorrect search count:\n' \
707 ' expected count: %d\n' \
708 ' obtained count: %d\n' \
709 % (rec_string, count, len(ids))
710 self.logger.notifyChannel('init', severity, msg)
711 sevval = getattr(logging, severity.upper())
712 if sevval >= config['assert_exit_level']:
713 # TODO: define a dedicated exception
714 raise Exception('Severe assertion failure')
717 assert ids is not None,\
718 'You must give either an id or a search criteria'
721 brrec = model.browse(cr, uid, id, context)
723 def __getitem__(self2, key):
726 return dict.__getitem__(self2, key)
728 globals_dict['floatEqual'] = self._assert_equals
729 globals_dict['ref'] = ref
730 globals_dict['_ref'] = ref
731 for test in rec.findall('./test'):
732 f_expr = test.get("expr",'').encode('utf-8')
733 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
734 expression_value = unsafe_eval(f_expr, globals_dict)
735 if expression_value != expected_value: # assertion failed
736 self.assert_report.record_assertion(False, severity)
737 msg = 'assertion "%s" failed!\n' \
739 ' expected value: %r\n' \
740 ' obtained value: %r\n' \
741 % (rec_string, etree.tostring(test), expected_value, expression_value)
742 self.logger.notifyChannel('init', severity, msg)
743 sevval = getattr(logging, severity.upper())
744 if sevval >= config['assert_exit_level']:
745 # TODO: define a dedicated exception
746 raise Exception('Severe assertion failure')
748 else: # all tests were successful for this assertion tag (no break)
749 self.assert_report.record_assertion(True, severity)
751 def _tag_record(self, cr, rec, data_node=None):
752 rec_model = rec.get("model").encode('ascii')
753 model = self.pool.get(rec_model)
754 assert model, "The model %s does not exist !" % (rec_model,)
755 rec_id = rec.get("id",'').encode('ascii')
756 rec_context = rec.get("context", None)
758 rec_context = unsafe_eval(rec_context)
759 self._test_xml_id(rec_id)
760 if self.isnoupdate(data_node) and self.mode != 'init':
761 # check if the xml record has an id string
764 module,rec_id2 = rec_id.split('.')
768 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
769 # check if the resource already existed at the last update
771 # if it existed, we don't update the data, but we need to
772 # know the id of the existing record anyway
773 self.idref[rec_id] = int(id)
776 # if the resource didn't exist
777 if not self.nodeattr2bool(rec, 'forcecreate', True):
778 # we don't want to create it, so we skip it
780 # else, we let the record to be created
783 # otherwise it is skipped
786 for field in rec.findall('./field'):
787 #TODO: most of this code is duplicated above (in _eval_xml)...
788 f_name = field.get("name",'').encode('utf-8')
789 f_ref = field.get("ref",'').encode('utf-8')
790 f_search = field.get("search",'').encode('utf-8')
791 f_model = field.get("model",'').encode('utf-8')
792 if not f_model and model._columns.get(f_name,False):
793 f_model = model._columns[f_name]._obj
794 f_use = field.get("use",'').encode('utf-8') or 'id'
798 q = unsafe_eval(f_search, self.idref)
800 assert f_model, 'Define an attribute model="..." in your .XML file !'
801 f_obj = self.pool.get(f_model)
802 # browse the objects searched
803 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
804 # column definitions of the "local" object
805 _cols = self.pool.get(rec_model)._columns
806 # if the current field is many2many
807 if (f_name in _cols) and _cols[f_name]._type=='many2many':
808 f_val = [(6, 0, map(lambda x: x[f_use], s))]
810 # otherwise (we are probably in a many2one field),
811 # take the first element of the search
817 if f_name in model._columns \
818 and model._columns[f_name]._type == 'reference':
819 val = self.model_id_get(cr, f_model, f_ref)
820 f_val = val[0] + ',' + str(val[1])
822 f_val = self.id_get(cr, f_model, f_ref)
824 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
825 if model._columns.has_key(f_name):
826 if isinstance(model._columns[f_name], osv.fields.integer):
830 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 )
832 self.idref[rec_id] = int(id)
833 if config.get('import_partial', False):
837 def id_get(self, cr, model, id_str):
838 if id_str in self.idref:
839 return self.idref[id_str]
840 res = self.model_id_get(cr, model, id_str)
841 if res and len(res)>1: res = res[1]
844 def model_id_get(self, cr, model, id_str):
845 model_data_obj = self.pool.get('ir.model.data')
848 mod,id_str = id_str.split('.')
849 result = model_data_obj._get_id(cr, self.uid, mod, id_str)
850 res = model_data_obj.read(cr, self.uid, [result], ['model', 'res_id'])
851 if res and res[0] and res[0]['res_id']:
852 return res[0]['model'], int(res[0]['res_id'])
856 if not de.tag in ['terp', 'openerp']:
857 self.logger.notifyChannel("init", netsvc.LOG_ERROR, "Mismatch xml format" )
858 raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
861 self.logger.notifyChannel("init", netsvc.LOG_WARNING, "The tag <terp/> is deprecated, use <openerp/>")
863 for n in de.findall('./data'):
865 if rec.tag in self._tags:
867 self._tags[rec.tag](self.cr, rec, n)
869 self.__logger.error('Parse error in %s:%d: \n%s',
870 rec.getroottree().docinfo.URL,
872 etree.tostring(rec).strip())
877 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
879 self.logger = netsvc.Logger()
884 self.pool = pooler.get_pool(cr.dbname)
887 report = assertion_report()
888 self.assert_report = report
889 self.noupdate = noupdate
891 'menuitem': self._tag_menuitem,
892 'record': self._tag_record,
893 'assert': self._tag_assert,
894 'report': self._tag_report,
895 'wizard': self._tag_wizard,
896 'delete': self._tag_delete,
897 'ir_set': self._tag_ir_set,
898 'function': self._tag_function,
899 'workflow': self._tag_workflow,
900 'act_window': self._tag_act_window,
904 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
912 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
913 #remove folder path from model
914 head, model = os.path.split(model)
916 pool = pooler.get_pool(cr.dbname)
918 input = cStringIO.StringIO(csvcontent)
919 reader = csv.reader(input, quotechar='"', delimiter=',')
920 fields = reader.next()
922 if config.get('import_partial'):
923 fname_partial = module + '/'+ fname
924 if not os.path.isfile(config.get('import_partial')):
925 pickle.dump({}, file(config.get('import_partial'),'w+'))
927 data = pickle.load(file(config.get('import_partial')))
928 if fname_partial in data:
929 if not data[fname_partial]:
932 for i in range(data[fname_partial]):
935 if not (mode == 'init' or 'id' in fields):
936 logger = netsvc.Logger()
937 logger.notifyChannel("init", netsvc.LOG_ERROR,
938 "Import specification does not contain 'id' and we are in init mode, Cannot continue.")
944 if (not line) or not reduce(lambda x,y: x or y, line) :
947 datas.append(map(lambda x: misc.ustr(x), line))
949 logger = netsvc.Logger()
950 logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line)
951 pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
952 if config.get('import_partial'):
953 data = pickle.load(file(config.get('import_partial')))
954 data[fname_partial] = 0
955 pickle.dump(data, file(config.get('import_partial'),'wb'))
961 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
962 doc = etree.parse(xmlfile)
963 relaxng = etree.RelaxNG(
964 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
968 logger = netsvc.Logger()
969 logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
970 logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
975 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
976 obj.parse(doc.getroot())