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 tools.translate import _
49 from yaml_import import convert_yaml_import
51 # List of etree._Element subclasses that we choose to ignore when parsing XML.
52 from tools import SKIPPED_ELEMENT_TYPES
54 # Import of XML records requires the unsafe eval as well,
55 # almost everywhere, which is ok because it supposedly comes
56 # from trusted data, but at least we make it obvious now.
58 from tools.safe_eval import safe_eval as eval
60 class ConvertError(Exception):
61 def __init__(self, doc, orig_excpt):
63 self.orig = orig_excpt
66 return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
69 return lambda x: self.id_get(cr, x)
71 def _obj(pool, cr, uid, model_str, context=None):
72 model = pool.get(model_str)
73 return lambda x: model.browse(cr, uid, x, context=context)
75 def _get_idref(self, cr, uid, model_str, context, idref):
80 version=release.major_version,
84 idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context)
87 def _fix_multiple_roots(node):
89 Surround the children of the ``node`` element of an XML field with a
90 single root "data" element, to prevent having a document with multiple
91 roots once parsed separately.
93 XML nodes should have one root only, but we'd like to support
94 direct multiple roots in our partial documents (like inherited view architectures).
95 As a convention we'll surround multiple root with a container "data" element, to be
96 ignored later when parsing.
98 real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)]
99 if len(real_nodes) > 1:
100 data_node = etree.Element("data")
102 data_node.append(child)
103 node.append(data_node)
105 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
108 if node.tag in ('field','value'):
109 t = node.get('type','char')
110 f_model = node.get('model', '').encode('utf-8')
111 if node.get('search'):
112 f_search = node.get("search",'').encode('utf-8')
113 f_use = node.get("use",'id').encode('utf-8')
114 f_name = node.get("name",'').encode('utf-8')
117 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
118 q = unsafe_eval(f_search, idref2)
119 ids = pool.get(f_model).search(cr, uid, q)
121 ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
122 _cols = pool.get(f_model)._columns
123 if (f_name in _cols) and _cols[f_name]._type=='many2many':
128 if isinstance(f_val, tuple):
131 a_eval = node.get('eval','')
134 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
136 return unsafe_eval(a_eval, idref2)
138 logger = logging.getLogger('init')
139 logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True)
142 def _process(s, idref):
143 m = re.findall('[^%]%\((.*?)\)[ds]', s)
146 idref[id]=self.id_get(cr, id)
148 _fix_multiple_roots(node)
149 return '<?xml version="1.0"?>\n'\
150 +_process("".join([etree.tostring(n, encoding='utf-8')
153 if t in ('char', 'int', 'float'):
160 return int(d.strip())
162 return float(d.strip())
164 elif t in ('list','tuple'):
166 for n in node.findall('./value'):
167 res.append(_eval_xml(self,n,pool,cr,uid,idref))
171 elif node.tag == "getitem":
173 res=_eval_xml(self,n,pool,cr,uid,idref)
176 elif node.get('type') in ("int", "list"):
177 return res[int(node.get('index'))]
179 return res[node.get('index','').encode("utf8")]
180 elif node.tag == "function":
182 a_eval = node.get('eval','')
184 idref['ref'] = lambda x: self.id_get(cr, x)
185 args = unsafe_eval(a_eval, idref)
187 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
188 if return_val is not None:
189 args.append(return_val)
190 model = pool.get(node.get('model',''))
191 method = node.get('name','')
192 res = getattr(model, method)(cr, uid, *args)
194 elif node.tag == "test":
197 escape_re = re.compile(r'(?<!\\)/')
199 return x.replace('\\/', '/')
201 class assertion_report(object):
205 def record_assertion(self, success, severity):
207 Records the result of an assertion for the failed/success count
210 if severity in self._report:
211 self._report[severity][success] += 1
213 self._report[severity] = {success:1, not success: 0}
216 def get_report(self):
220 res = '\nAssertions report:\nLevel\tsuccess\tfailed\n'
222 for sev in self._report:
223 res += sev + '\t' + str(self._report[sev][True]) + '\t' + str(self._report[sev][False]) + '\n'
224 success += self._report[sev][True]
225 failed += self._report[sev][False]
226 res += 'total\t' + str(success) + '\t' + str(failed) + '\n'
227 res += 'end of report (' + str(success + failed) + ' assertion(s) checked)'
230 class xml_import(object):
231 __logger = logging.getLogger('tools.convert.xml_import')
233 def nodeattr2bool(node, attr, default=False):
234 if not node.get(attr):
236 val = node.get(attr).strip()
239 return val.lower() not in ('0', 'false', 'off')
241 def isnoupdate(self, data_node=None):
242 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
244 def get_context(self, data_node, node, eval_dict):
245 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
246 node_context = node.get("context",'').encode('utf8')
248 for ctx in (data_node_context, node_context):
251 ctx_res = unsafe_eval(ctx, eval_dict)
252 if isinstance(context, dict):
253 context.update(ctx_res)
257 # Some contexts contain references that are only valid at runtime at
258 # client-side, so in that case we keep the original context string
259 # as it is. We also log it, just in case.
261 logging.getLogger("init").debug('Context value (%s) for element with id "%s" or its data node does not parse '\
262 'at server-side, keeping original string, in case it\'s meant for client side only',
263 ctx, node.get('id','n/a'), exc_info=True)
266 def get_uid(self, cr, uid, data_node, node):
267 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
269 return self.id_get(cr, node_uid)
272 def _test_xml_id(self, xml_id):
275 module, id = xml_id.split('.', 1)
276 assert '.' not in id, """The ID reference "%s" must contain
277 maximum one dot. They are used to refer to other modules ID, in the
278 form: module.record_id""" % (xml_id,)
279 if module != self.module:
280 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
281 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
284 self.logger.error('id: %s is to long (max: 64)', id)
286 def _tag_delete(self, cr, rec, data_node=None):
287 d_model = rec.get("model",'')
288 d_search = rec.get("search",'').encode('utf-8')
289 d_id = rec.get("id",'')
293 idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
294 ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search, idref))
297 ids.append(self.id_get(cr, d_id))
299 # d_id cannot be found. doesn't matter in this case
302 self.pool.get(d_model).unlink(cr, self.uid, ids)
303 self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids)
305 def _remove_ir_values(self, cr, name, value, model):
306 ir_value_ids = self.pool.get('ir.values').search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
308 self.pool.get('ir.values').unlink(cr, self.uid, ir_value_ids)
309 self.pool.get('ir.model.data')._unlink(cr, self.uid, 'ir.values', ir_value_ids)
313 def _tag_report(self, cr, rec, data_node=None):
315 for dest,f in (('name','string'),('model','model'),('report_name','name')):
316 res[dest] = rec.get(f,'').encode('utf8')
317 assert res[dest], "Attribute %s of report is empty !" % (f,)
318 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
320 res[dest] = rec.get(field).encode('utf8')
322 res['auto'] = eval(rec.get('auto','False'))
324 sxw_content = misc.file_open(rec.get('sxw')).read()
325 res['report_sxw_content'] = sxw_content
326 if rec.get('header'):
327 res['header'] = eval(rec.get('header','False'))
328 if rec.get('report_type'):
329 res['report_type'] = rec.get('report_type')
331 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
333 xml_id = rec.get('id','').encode('utf8')
334 self._test_xml_id(xml_id)
336 if rec.get('groups'):
337 g_names = rec.get('groups','').split(',')
339 for group in g_names:
340 if group.startswith('-'):
341 group_id = self.id_get(cr, group[1:])
342 groups_value.append((3, group_id))
344 group_id = self.id_get(cr, group)
345 groups_value.append((4, group_id))
346 res['groups_id'] = groups_value
348 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)
349 self.idref[xml_id] = int(id)
351 if not rec.get('menu') or eval(rec.get('menu','False')):
352 keyword = str(rec.get('keyword', 'client_print_multi'))
353 value = 'ir.actions.report.xml,'+str(id)
354 replace = rec.get('replace', True)
355 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)
356 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
357 # Special check for report having attribute menu=False on update
358 value = 'ir.actions.report.xml,'+str(id)
359 self._remove_ir_values(cr, res['name'], value, res['model'])
362 def _tag_function(self, cr, rec, data_node=None):
363 if self.isnoupdate(data_node) and self.mode != 'init':
365 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
366 uid = self.get_uid(cr, self.uid, data_node, rec)
367 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
370 def _tag_wizard(self, cr, rec, data_node=None):
371 string = rec.get("string",'').encode('utf8')
372 model = rec.get("model",'').encode('utf8')
373 name = rec.get("name",'').encode('utf8')
374 xml_id = rec.get('id','').encode('utf8')
375 self._test_xml_id(xml_id)
376 multi = rec.get('multi','') and eval(rec.get('multi','False'))
377 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
379 if rec.get('groups'):
380 g_names = rec.get('groups','').split(',')
382 for group in g_names:
383 if group.startswith('-'):
384 group_id = self.id_get(cr, group[1:])
385 groups_value.append((3, group_id))
387 group_id = self.id_get(cr, group)
388 groups_value.append((4, group_id))
389 res['groups_id'] = groups_value
391 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)
392 self.idref[xml_id] = int(id)
394 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
395 keyword = str(rec.get('keyword','') or 'client_action_multi')
396 value = 'ir.actions.wizard,'+str(id)
397 replace = rec.get("replace",'') or True
398 self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
399 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
400 # Special check for wizard having attribute menu=False on update
401 value = 'ir.actions.wizard,'+str(id)
402 self._remove_ir_values(cr, string, value, model)
404 def _tag_url(self, cr, rec, data_node=None):
405 url = rec.get("string",'').encode('utf8')
406 target = rec.get("target",'').encode('utf8')
407 name = rec.get("name",'').encode('utf8')
408 xml_id = rec.get('id','').encode('utf8')
409 self._test_xml_id(xml_id)
411 res = {'name': name, 'url': url, 'target':target}
413 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)
414 self.idref[xml_id] = int(id)
416 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
417 keyword = str(rec.get('keyword','') or 'client_action_multi')
418 value = 'ir.actions.url,'+str(id)
419 replace = rec.get("replace",'') or True
420 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)
421 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
422 # Special check for URL having attribute menu=False on update
423 value = 'ir.actions.url,'+str(id)
424 self._remove_ir_values(cr, url, value, "ir.actions.url")
426 def _tag_act_window(self, cr, rec, data_node=None):
427 name = rec.get('name','').encode('utf-8')
428 xml_id = rec.get('id','').encode('utf8')
429 self._test_xml_id(xml_id)
430 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
432 if rec.get('view_id'):
433 view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8'))
434 domain = rec.get('domain','').encode('utf-8') or '[]'
435 res_model = rec.get('res_model','').encode('utf-8')
436 src_model = rec.get('src_model','').encode('utf-8')
437 view_type = rec.get('view_type','').encode('utf-8') or 'form'
438 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
439 usage = rec.get('usage','').encode('utf-8')
440 limit = rec.get('limit','').encode('utf-8')
441 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
443 active_id = str("active_id") # for further reference in client/bin/tools/__init__.py
445 return self.id_get(cr, str_id)
447 # Include all locals() in eval_context, for backwards compatibility
454 'res_model': res_model,
455 'src_model': src_model,
456 'view_type': view_type,
457 'view_mode': view_mode,
460 'auto_refresh': auto_refresh,
462 'active_id': active_id,
465 context = self.get_context(data_node, rec, eval_context)
468 domain = unsafe_eval(domain, eval_context)
470 # Some domains contain references that are only valid at runtime at
471 # client-side, so in that case we keep the original domain string
472 # as it is. We also log it, just in case.
473 logging.getLogger("init").debug('Domain value (%s) for element with id "%s" does not parse '\
474 'at server-side, keeping original string, in case it\'s meant for client side only',
475 domain, xml_id or 'n/a', exc_info=True)
482 'res_model': res_model,
483 'src_model': src_model,
484 'view_type': view_type,
485 'view_mode': view_mode,
488 'auto_refresh': auto_refresh,
491 if rec.get('groups'):
492 g_names = rec.get('groups','').split(',')
494 for group in g_names:
495 if group.startswith('-'):
496 group_id = self.id_get(cr, group[1:])
497 groups_value.append((3, group_id))
499 group_id = self.id_get(cr, group)
500 groups_value.append((4, group_id))
501 res['groups_id'] = groups_value
503 if rec.get('target'):
504 res['target'] = rec.get('target','')
506 res['multi'] = rec.get('multi', False)
507 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)
508 self.idref[xml_id] = int(id)
511 #keyword = 'client_action_relate'
512 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
513 value = 'ir.actions.act_window,'+str(id)
514 replace = rec.get('replace','') or True
515 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)
516 # TODO add remove ir.model.data
518 def _tag_ir_set(self, cr, rec, data_node=None):
519 if self.mode != 'init':
522 for field in rec.findall('./field'):
523 f_name = field.get("name",'').encode('utf-8')
524 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
526 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))
528 def _tag_workflow(self, cr, rec, data_node=None):
529 if self.isnoupdate(data_node) and self.mode != 'init':
531 model = str(rec.get('model',''))
532 w_ref = rec.get('ref','')
534 id = self.id_get(cr, w_ref)
536 number_children = len(rec)
537 assert number_children > 0,\
538 'You must define a child node if you dont give a ref'
539 assert number_children == 1,\
540 'Only one child node is accepted (%d given)' % number_children
541 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
543 uid = self.get_uid(cr, self.uid, data_node, rec)
544 wf_service = netsvc.LocalService("workflow")
545 wf_service.trg_validate(uid, model,
547 str(rec.get('action','')), cr)
550 # Support two types of notation:
551 # name="Inventory Control/Sending Goods"
556 def _tag_menuitem(self, cr, rec, data_node=None):
557 rec_id = rec.get("id",'').encode('ascii')
558 self._test_xml_id(rec_id)
559 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
561 values = {'parent_id': False}
562 if rec.get('parent', False) is False and len(m_l) > 1:
563 # No parent attribute specified and the menu name has several menu components,
564 # try to determine the ID of the parent according to menu path
567 values['name'] = m_l[-1]
568 m_l = m_l[:-1] # last part is our name, not a parent
569 for idx, menu_elem in enumerate(m_l):
571 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
573 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
578 # the menuitem does't exist but we are in branch (not a leaf)
579 self.logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l))
580 pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
581 values['parent_id'] = pid
583 # The parent attribute was specified, if non-empty determine its ID, otherwise
584 # explicitly make a top-level menu
585 if rec.get('parent'):
586 menu_parent_id = self.id_get(cr, rec.get('parent',''))
588 # we get here with <menuitem parent="">, explicit clear of parent, or
589 # if no parent attribute at all but menu name is not a menu path
590 menu_parent_id = False
591 values = {'parent_id': menu_parent_id}
593 values['name'] = rec.get('name')
595 res = [ self.id_get(cr, rec.get('id','')) ]
599 if rec.get('action'):
600 a_action = rec.get('action','').encode('utf8')
601 a_type = rec.get('type','').encode('utf8') or 'act_window'
603 "act_window": 'STOCK_NEW',
604 "report.xml": 'STOCK_PASTE',
605 "wizard": 'STOCK_EXECUTE',
606 "url": 'STOCK_JUMP_TO'
608 values['icon'] = icons.get(a_type,'STOCK_NEW')
609 if a_type=='act_window':
610 a_id = self.id_get(cr, a_action)
611 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
612 rrres = cr.fetchone()
613 assert rrres, "No window action defined for this id %s !\n" \
614 "Verify that this is a window action or add a type argument." % (a_action,)
615 action_type,action_mode,action_name,view_id,target = rrres
617 cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
618 action_mode, = cr.fetchone()
619 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
621 action_mode, = cr.fetchone()
622 if action_type=='tree':
623 values['icon'] = 'STOCK_INDENT'
624 elif action_mode and action_mode.startswith('tree'):
625 values['icon'] = 'STOCK_JUSTIFY_FILL'
626 elif action_mode and action_mode.startswith('graph'):
627 values['icon'] = 'terp-graph'
628 elif action_mode and action_mode.startswith('calendar'):
629 values['icon'] = 'terp-calendar'
631 values['icon'] = 'STOCK_EXECUTE'
632 if not values.get('name', False):
633 values['name'] = action_name
634 elif a_type=='wizard':
635 a_id = self.id_get(cr, a_action)
636 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
638 if (not values.get('name', False)) and resw:
639 values['name'] = resw[0]
640 if rec.get('sequence'):
641 values['sequence'] = int(rec.get('sequence'))
643 values['icon'] = str(rec.get('icon'))
644 if rec.get('web_icon'):
645 values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon')))
646 if rec.get('web_icon_hover'):
647 values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover')))
649 if rec.get('groups'):
650 g_names = rec.get('groups','').split(',')
652 for group in g_names:
653 if group.startswith('-'):
654 group_id = self.id_get(cr, group[1:])
655 groups_value.append((3, group_id))
657 group_id = self.id_get(cr, group)
658 groups_value.append((4, group_id))
659 values['groups_id'] = groups_value
661 xml_id = rec.get('id','').encode('utf8')
662 self._test_xml_id(xml_id)
663 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)
666 self.idref[rec_id] = int(pid)
668 if rec.get('action') and pid:
669 a_action = rec.get('action').encode('utf8')
670 a_type = rec.get('type','').encode('utf8') or 'act_window'
671 a_id = self.id_get(cr, a_action)
672 action = "ir.actions.%s,%d" % (a_type, a_id)
673 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)
674 return ('ir.ui.menu', pid)
676 def _assert_equals(self, f1, f2, prec=4):
677 return not round(f1 - f2, prec)
679 def _tag_assert(self, cr, rec, data_node=None):
680 if self.isnoupdate(data_node) and self.mode != 'init':
683 rec_model = rec.get("model",'').encode('ascii')
684 model = self.pool.get(rec_model)
685 assert model, "The model %s does not exist !" % (rec_model,)
686 rec_id = rec.get("id",'').encode('ascii')
687 self._test_xml_id(rec_id)
688 rec_src = rec.get("search",'').encode('utf8')
689 rec_src_count = rec.get("count")
691 severity = rec.get("severity",'').encode('ascii') or netsvc.LOG_ERROR
692 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
695 eval_dict = {'ref': _ref(self, cr)}
696 context = self.get_context(data_node, rec, eval_dict)
697 uid = self.get_uid(cr, self.uid, data_node, rec)
699 ids = [self.id_get(cr, rec_id)]
701 q = unsafe_eval(rec_src, eval_dict)
702 ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
704 count = int(rec_src_count)
705 if len(ids) != count:
706 self.assert_report.record_assertion(False, severity)
707 msg = 'assertion "%s" failed!\n' \
708 ' Incorrect search count:\n' \
709 ' expected count: %d\n' \
710 ' obtained count: %d\n' \
711 % (rec_string, count, len(ids))
712 sevval = getattr(logging, severity.upper())
713 self.logger.log(sevval, msg)
714 if sevval >= config['assert_exit_level']:
715 # TODO: define a dedicated exception
716 raise Exception('Severe assertion failure')
719 assert ids is not None,\
720 'You must give either an id or a search criteria'
723 brrec = model.browse(cr, uid, id, context)
725 def __getitem__(self2, key):
728 return dict.__getitem__(self2, key)
730 globals_dict['floatEqual'] = self._assert_equals
731 globals_dict['ref'] = ref
732 globals_dict['_ref'] = ref
733 for test in rec.findall('./test'):
734 f_expr = test.get("expr",'').encode('utf-8')
735 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
736 expression_value = unsafe_eval(f_expr, globals_dict)
737 if expression_value != expected_value: # assertion failed
738 self.assert_report.record_assertion(False, severity)
739 msg = 'assertion "%s" failed!\n' \
741 ' expected value: %r\n' \
742 ' obtained value: %r\n' \
743 % (rec_string, etree.tostring(test), expected_value, expression_value)
744 sevval = getattr(logging, severity.upper())
745 self.logger.log(sevval, msg)
746 if sevval >= config['assert_exit_level']:
747 # TODO: define a dedicated exception
748 raise Exception('Severe assertion failure')
750 else: # all tests were successful for this assertion tag (no break)
751 self.assert_report.record_assertion(True, severity)
753 def _tag_record(self, cr, rec, data_node=None):
754 rec_model = rec.get("model").encode('ascii')
755 model = self.pool.get(rec_model)
756 assert model, "The model %s does not exist !" % (rec_model,)
757 rec_id = rec.get("id",'').encode('ascii')
758 rec_context = rec.get("context", None)
760 rec_context = unsafe_eval(rec_context)
761 self._test_xml_id(rec_id)
762 if self.isnoupdate(data_node) and self.mode != 'init':
763 # check if the xml record has an id string
766 module,rec_id2 = rec_id.split('.')
770 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
771 # check if the resource already existed at the last update
773 # if it existed, we don't update the data, but we need to
774 # know the id of the existing record anyway
775 self.idref[rec_id] = int(id)
778 # if the resource didn't exist
779 if not self.nodeattr2bool(rec, 'forcecreate', True):
780 # we don't want to create it, so we skip it
782 # else, we let the record to be created
785 # otherwise it is skipped
788 for field in rec.findall('./field'):
789 #TODO: most of this code is duplicated above (in _eval_xml)...
790 f_name = field.get("name",'').encode('utf-8')
791 f_ref = field.get("ref",'').encode('utf-8')
792 f_search = field.get("search",'').encode('utf-8')
793 f_model = field.get("model",'').encode('utf-8')
794 if not f_model and model._columns.get(f_name,False):
795 f_model = model._columns[f_name]._obj
796 f_use = field.get("use",'').encode('utf-8') or 'id'
800 q = unsafe_eval(f_search, self.idref)
802 assert f_model, 'Define an attribute model="..." in your .XML file !'
803 f_obj = self.pool.get(f_model)
804 # browse the objects searched
805 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
806 # column definitions of the "local" object
807 _cols = self.pool.get(rec_model)._columns
808 # if the current field is many2many
809 if (f_name in _cols) and _cols[f_name]._type=='many2many':
810 f_val = [(6, 0, map(lambda x: x[f_use], s))]
812 # otherwise (we are probably in a many2one field),
813 # take the first element of the search
819 if f_name in model._columns \
820 and model._columns[f_name]._type == 'reference':
821 val = self.model_id_get(cr, f_ref)
822 f_val = val[0] + ',' + str(val[1])
824 f_val = self.id_get(cr, f_ref)
826 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
827 if model._columns.has_key(f_name):
828 if isinstance(model._columns[f_name], osv.fields.integer):
832 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 )
834 self.idref[rec_id] = int(id)
835 if config.get('import_partial', False):
839 def id_get(self, cr, id_str):
840 if id_str in self.idref:
841 return self.idref[id_str]
842 res = self.model_id_get(cr, id_str)
843 if res and len(res)>1: res = res[1]
846 def model_id_get(self, cr, id_str):
847 model_data_obj = self.pool.get('ir.model.data')
850 mod,id_str = id_str.split('.')
851 return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
854 if not de.tag in ['terp', 'openerp']:
855 self.logger.error("Mismatch xml format")
856 raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
859 self.logger.warning("The tag <terp/> is deprecated, use <openerp/>")
861 for n in de.findall('./data'):
863 if rec.tag in self._tags:
865 self._tags[rec.tag](self.cr, rec, n)
867 self.__logger.error('Parse error in %s:%d: \n%s',
868 rec.getroottree().docinfo.URL,
870 etree.tostring(rec).strip(), exc_info=True)
875 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
877 self.logger = logging.getLogger('init')
882 self.pool = pooler.get_pool(cr.dbname)
885 report = assertion_report()
886 self.assert_report = report
887 self.noupdate = noupdate
889 'menuitem': self._tag_menuitem,
890 'record': self._tag_record,
891 'assert': self._tag_assert,
892 'report': self._tag_report,
893 'wizard': self._tag_wizard,
894 'delete': self._tag_delete,
895 'ir_set': self._tag_ir_set,
896 'function': self._tag_function,
897 'workflow': self._tag_workflow,
898 'act_window': self._tag_act_window,
902 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
910 logger = logging.getLogger('init')
911 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
912 #remove folder path from model
913 head, model = os.path.split(model)
915 pool = pooler.get_pool(cr.dbname)
917 input = cStringIO.StringIO(csvcontent) #FIXME
918 reader = csv.reader(input, quotechar='"', delimiter=',')
919 fields = reader.next()
921 if config.get('import_partial'):
922 fname_partial = module + '/'+ fname
923 if not os.path.isfile(config.get('import_partial')):
924 pickle.dump({}, file(config.get('import_partial'),'w+'))
926 data = pickle.load(file(config.get('import_partial')))
927 if fname_partial in data:
928 if not data[fname_partial]:
931 for i in range(data[fname_partial]):
934 if not (mode == 'init' or 'id' in fields):
935 logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
941 if (not line) or not reduce(lambda x,y: x or y, line) :
944 datas.append(map(lambda x: misc.ustr(x), line))
946 logger.error("Cannot import the line: %s", line)
947 result, rows, warning_msg, dummy = pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
949 # Report failed import and abort module install
950 raise Exception(_('Module loading failed: file %s/%s could not be processed:\n %s') % (module, fname, warning_msg))
951 if config.get('import_partial'):
952 data = pickle.load(file(config.get('import_partial')))
953 data[fname_partial] = 0
954 pickle.dump(data, file(config.get('import_partial'),'wb'))
960 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
961 doc = etree.parse(xmlfile)
962 relaxng = etree.RelaxNG(
963 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
967 logger = netsvc.Logger()
968 logger.notifyChannel('init', netsvc.LOG_ERROR, 'The XML file does not fit the required schema !')
969 logger.notifyChannel('init', netsvc.LOG_ERROR, misc.ustr(relaxng.error_log.last_error))
974 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
975 obj.parse(doc.getroot())