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 ##############################################################################
34 import openerp.release
35 import openerp.workflow
36 from yaml_import import convert_yaml_import
38 import assertion_report
40 _logger = logging.getLogger(__name__)
45 _logger.warning('could not find pytz library, please install it')
46 class pytzclass(object):
51 from datetime import datetime, timedelta
52 from dateutil.relativedelta import relativedelta
53 from lxml import etree
55 from config import config
56 from translate import _
58 # List of etree._Element subclasses that we choose to ignore when parsing XML.
59 from misc import SKIPPED_ELEMENT_TYPES
61 from misc import unquote
63 # Import of XML records requires the unsafe eval as well,
64 # almost everywhere, which is ok because it supposedly comes
65 # from trusted data, but at least we make it obvious now.
67 from safe_eval import safe_eval as eval
69 class ParseError(Exception):
70 def __init__(self, msg, text, filename, lineno):
73 self.filename = filename
77 return '"%s" while parsing %s:%s, near\n%s' \
78 % (self.msg, self.filename, self.lineno, self.text)
81 return lambda x: self.id_get(cr, x)
83 def _obj(pool, cr, uid, model_str, context=None):
84 model = pool[model_str]
85 return lambda x: model.browse(cr, uid, x, context=context)
87 def _get_idref(self, cr, uid, model_str, context, idref):
93 relativedelta=relativedelta,
94 version=openerp.release.major_version,
98 idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context)
101 def _fix_multiple_roots(node):
103 Surround the children of the ``node`` element of an XML field with a
104 single root "data" element, to prevent having a document with multiple
105 roots once parsed separately.
107 XML nodes should have one root only, but we'd like to support
108 direct multiple roots in our partial documents (like inherited view architectures).
109 As a convention we'll surround multiple root with a container "data" element, to be
110 ignored later when parsing.
112 real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)]
113 if len(real_nodes) > 1:
114 data_node = etree.Element("data")
116 data_node.append(child)
117 node.append(data_node)
119 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
122 if node.tag in ('field','value'):
123 t = node.get('type','char')
124 f_model = node.get('model', '').encode('utf-8')
125 if node.get('search'):
126 f_search = node.get("search",'').encode('utf-8')
127 f_use = node.get("use",'id').encode('utf-8')
128 f_name = node.get("name",'').encode('utf-8')
131 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
132 q = unsafe_eval(f_search, idref2)
133 ids = pool[f_model].search(cr, uid, q)
135 ids = map(lambda x: x[f_use], pool[f_model].read(cr, uid, ids, [f_use]))
136 _cols = pool[f_model]._columns
137 if (f_name in _cols) and _cols[f_name]._type=='many2many':
142 if isinstance(f_val, tuple):
145 a_eval = node.get('eval','')
147 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
149 return unsafe_eval(a_eval, idref2)
151 logging.getLogger('openerp.tools.convert.init').error(
152 'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context)
154 def _process(s, idref):
155 m = re.findall('[^%]%\((.*?)\)[ds]', s)
158 idref[id]=self.id_get(cr, id)
161 _fix_multiple_roots(node)
162 return '<?xml version="1.0"?>\n'\
163 +_process("".join([etree.tostring(n, encoding='utf-8')
164 for n in node]), idref)
166 return _process("".join([etree.tostring(n, encoding='utf-8')
167 for n in node]), idref)
168 if t in ('char', 'int', 'float'):
175 return int(d.strip())
177 return float(d.strip())
179 elif t in ('list','tuple'):
181 for n in node.findall('./value'):
182 res.append(_eval_xml(self,n,pool,cr,uid,idref))
186 elif node.tag == "getitem":
188 res=_eval_xml(self,n,pool,cr,uid,idref)
191 elif node.get('type') in ("int", "list"):
192 return res[int(node.get('index'))]
194 return res[node.get('index','').encode("utf8")]
195 elif node.tag == "function":
197 a_eval = node.get('eval','')
199 idref['ref'] = lambda x: self.id_get(cr, x)
200 args = unsafe_eval(a_eval, idref)
202 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
203 if return_val is not None:
204 args.append(return_val)
205 model = pool[node.get('model','')]
206 method = node.get('name','')
207 res = getattr(model, method)(cr, uid, *args)
209 elif node.tag == "test":
212 escape_re = re.compile(r'(?<!\\)/')
214 return x.replace('\\/', '/')
216 class xml_import(object):
218 def nodeattr2bool(node, attr, default=False):
219 if not node.get(attr):
221 val = node.get(attr).strip()
224 return val.lower() not in ('0', 'false', 'off')
226 def isnoupdate(self, data_node=None):
227 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
229 def get_context(self, data_node, node, eval_dict):
230 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
231 node_context = node.get("context",'').encode('utf8')
233 for ctx in (data_node_context, node_context):
236 ctx_res = unsafe_eval(ctx, eval_dict)
237 if isinstance(context, dict):
238 context.update(ctx_res)
242 # Some contexts contain references that are only valid at runtime at
243 # client-side, so in that case we keep the original context string
244 # as it is. We also log it, just in case.
246 _logger.debug('Context value (%s) for element with id "%s" or its data node does not parse '\
247 'at server-side, keeping original string, in case it\'s meant for client side only',
248 ctx, node.get('id','n/a'), exc_info=True)
251 def get_uid(self, cr, uid, data_node, node):
252 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
254 return self.id_get(cr, node_uid)
257 def _test_xml_id(self, xml_id):
260 module, id = xml_id.split('.', 1)
261 assert '.' not in id, """The ID reference "%s" must contain
262 maximum one dot. They are used to refer to other modules ID, in the
263 form: module.record_id""" % (xml_id,)
264 if module != self.module:
265 modcnt = self.pool['ir.module.module'].search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
266 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
269 _logger.error('id: %s is to long (max: 64)', id)
271 def _tag_delete(self, cr, rec, data_node=None):
272 d_model = rec.get("model",'')
273 d_search = rec.get("search",'').encode('utf-8')
274 d_id = rec.get("id",'')
278 idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
279 ids = self.pool[d_model].search(cr, self.uid, unsafe_eval(d_search, idref))
282 ids.append(self.id_get(cr, d_id))
284 # d_id cannot be found. doesn't matter in this case
287 self.pool[d_model].unlink(cr, self.uid, ids)
289 def _remove_ir_values(self, cr, name, value, model):
290 ir_values_obj = self.pool['ir.values']
291 ir_value_ids = ir_values_obj.search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
293 ir_values_obj.unlink(cr, self.uid, ir_value_ids)
297 def _tag_report(self, cr, rec, data_node=None):
299 for dest,f in (('name','string'),('model','model'),('report_name','name')):
300 res[dest] = rec.get(f,'').encode('utf8')
301 assert res[dest], "Attribute %s of report is empty !" % (f,)
302 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),
303 ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage'),
304 ('report_type', 'report_type'), ('parser', 'parser')):
306 res[dest] = rec.get(field).encode('utf8')
308 res['auto'] = eval(rec.get('auto','False'))
310 sxw_content = misc.file_open(rec.get('sxw')).read()
311 res['report_sxw_content'] = sxw_content
312 if rec.get('header'):
313 res['header'] = eval(rec.get('header','False'))
315 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
317 xml_id = rec.get('id','').encode('utf8')
318 self._test_xml_id(xml_id)
320 if rec.get('groups'):
321 g_names = rec.get('groups','').split(',')
323 for group in g_names:
324 if group.startswith('-'):
325 group_id = self.id_get(cr, group[1:])
326 groups_value.append((3, group_id))
328 group_id = self.id_get(cr, group)
329 groups_value.append((4, group_id))
330 res['groups_id'] = groups_value
332 id = self.pool['ir.model.data']._update(cr, self.uid, "ir.actions.report.xml", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
333 self.idref[xml_id] = int(id)
335 if not rec.get('menu') or eval(rec.get('menu','False')):
336 keyword = str(rec.get('keyword', 'client_print_multi'))
337 value = 'ir.actions.report.xml,'+str(id)
338 replace = rec.get('replace', True)
339 self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', keyword, res['name'], [res['model']], value, replace=replace, isobject=True, xml_id=xml_id)
340 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
341 # Special check for report having attribute menu=False on update
342 value = 'ir.actions.report.xml,'+str(id)
343 self._remove_ir_values(cr, res['name'], value, res['model'])
346 def _tag_function(self, cr, rec, data_node=None):
347 if self.isnoupdate(data_node) and self.mode != 'init':
349 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
350 uid = self.get_uid(cr, self.uid, data_node, rec)
351 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
354 def _tag_wizard(self, cr, rec, data_node=None):
355 string = rec.get("string",'').encode('utf8')
356 model = rec.get("model",'').encode('utf8')
357 name = rec.get("name",'').encode('utf8')
358 xml_id = rec.get('id','').encode('utf8')
359 self._test_xml_id(xml_id)
360 multi = rec.get('multi','') and eval(rec.get('multi','False'))
361 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
363 if rec.get('groups'):
364 g_names = rec.get('groups','').split(',')
366 for group in g_names:
367 if group.startswith('-'):
368 group_id = self.id_get(cr, group[1:])
369 groups_value.append((3, group_id))
371 group_id = self.id_get(cr, group)
372 groups_value.append((4, group_id))
373 res['groups_id'] = groups_value
375 id = self.pool['ir.model.data']._update(cr, self.uid, "ir.actions.wizard", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
376 self.idref[xml_id] = int(id)
378 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
379 keyword = str(rec.get('keyword','') or 'client_action_multi')
380 value = 'ir.actions.wizard,'+str(id)
381 replace = rec.get("replace",'') or True
382 self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
383 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
384 # Special check for wizard having attribute menu=False on update
385 value = 'ir.actions.wizard,'+str(id)
386 self._remove_ir_values(cr, string, value, model)
388 def _tag_url(self, cr, rec, data_node=None):
389 url = rec.get("url",'').encode('utf8')
390 target = rec.get("target",'').encode('utf8')
391 name = rec.get("name",'').encode('utf8')
392 xml_id = rec.get('id','').encode('utf8')
393 self._test_xml_id(xml_id)
395 res = {'name': name, 'url': url, 'target':target}
397 id = self.pool['ir.model.data']._update(cr, self.uid, "ir.actions.act_url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
398 self.idref[xml_id] = int(id)
400 def _tag_act_window(self, cr, rec, data_node=None):
401 name = rec.get('name','').encode('utf-8')
402 xml_id = rec.get('id','').encode('utf8')
403 self._test_xml_id(xml_id)
404 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
406 if rec.get('view_id'):
407 view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8'))
408 domain = rec.get('domain','').encode('utf-8') or '[]'
409 res_model = rec.get('res_model','').encode('utf-8')
410 src_model = rec.get('src_model','').encode('utf-8')
411 view_type = rec.get('view_type','').encode('utf-8') or 'form'
412 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
413 usage = rec.get('usage','').encode('utf-8')
414 limit = rec.get('limit','').encode('utf-8')
415 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
418 # Act_window's 'domain' and 'context' contain mostly literals
419 # but they can also refer to the variables provided below
420 # in eval_context, so we need to eval() them before storing.
421 # Among the context variables, 'active_id' refers to
422 # the currently selected items in a list view, and only
423 # takes meaning at runtime on the client side. For this
424 # reason it must remain a bare variable in domain and context,
425 # even after eval() at server-side. We use the special 'unquote'
426 # class to achieve this effect: a string which has itself, unquoted,
428 active_id = unquote("active_id")
429 active_ids = unquote("active_ids")
430 active_model = unquote("active_model")
433 return self.id_get(cr, str_id)
435 # Include all locals() in eval_context, for backwards compatibility
442 'res_model': res_model,
443 'src_model': src_model,
444 'view_type': view_type,
445 'view_mode': view_mode,
448 'auto_refresh': auto_refresh,
450 'active_id': active_id,
451 'active_ids': active_ids,
452 'active_model': active_model,
455 context = self.get_context(data_node, rec, eval_context)
458 domain = unsafe_eval(domain, eval_context)
460 # Some domains contain references that are only valid at runtime at
461 # client-side, so in that case we keep the original domain string
462 # as it is. We also log it, just in case.
463 _logger.debug('Domain value (%s) for element with id "%s" does not parse '\
464 'at server-side, keeping original string, in case it\'s meant for client side only',
465 domain, xml_id or 'n/a', exc_info=True)
472 'res_model': res_model,
473 'src_model': src_model,
474 'view_type': view_type,
475 'view_mode': view_mode,
478 'auto_refresh': auto_refresh,
481 if rec.get('groups'):
482 g_names = rec.get('groups','').split(',')
484 for group in g_names:
485 if group.startswith('-'):
486 group_id = self.id_get(cr, group[1:])
487 groups_value.append((3, group_id))
489 group_id = self.id_get(cr, group)
490 groups_value.append((4, group_id))
491 res['groups_id'] = groups_value
493 if rec.get('target'):
494 res['target'] = rec.get('target','')
496 res['multi'] = eval(rec.get('multi', 'False'))
497 id = self.pool['ir.model.data']._update(cr, self.uid, 'ir.actions.act_window', self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
498 self.idref[xml_id] = int(id)
501 #keyword = 'client_action_relate'
502 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
503 value = 'ir.actions.act_window,'+str(id)
504 replace = rec.get('replace','') or True
505 self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', keyword, xml_id, [src_model], value, replace=replace, isobject=True, xml_id=xml_id)
506 # TODO add remove ir.model.data
508 def _tag_ir_set(self, cr, rec, data_node=None):
509 if self.mode != 'init':
512 for field in rec.findall('./field'):
513 f_name = field.get("name",'').encode('utf-8')
514 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
516 self.pool['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))
518 def _tag_workflow(self, cr, rec, data_node=None):
519 if self.isnoupdate(data_node) and self.mode != 'init':
521 model = str(rec.get('model',''))
522 w_ref = rec.get('ref','')
524 id = self.id_get(cr, w_ref)
526 number_children = len(rec)
527 assert number_children > 0,\
528 'You must define a child node if you dont give a ref'
529 assert number_children == 1,\
530 'Only one child node is accepted (%d given)' % number_children
531 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
533 uid = self.get_uid(cr, self.uid, data_node, rec)
534 openerp.workflow.trg_validate(uid, model,
536 str(rec.get('action','')), cr)
539 # Support two types of notation:
540 # name="Inventory Control/Sending Goods"
545 def _tag_menuitem(self, cr, rec, data_node=None):
546 rec_id = rec.get("id",'').encode('ascii')
547 self._test_xml_id(rec_id)
548 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
550 values = {'parent_id': False}
551 if rec.get('parent', False) is False and len(m_l) > 1:
552 # No parent attribute specified and the menu name has several menu components,
553 # try to determine the ID of the parent according to menu path
556 values['name'] = m_l[-1]
557 m_l = m_l[:-1] # last part is our name, not a parent
558 for idx, menu_elem in enumerate(m_l):
560 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
562 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
567 # the menuitem does't exist but we are in branch (not a leaf)
568 _logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l))
569 pid = self.pool['ir.ui.menu'].create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
570 values['parent_id'] = pid
572 # The parent attribute was specified, if non-empty determine its ID, otherwise
573 # explicitly make a top-level menu
574 if rec.get('parent'):
575 menu_parent_id = self.id_get(cr, rec.get('parent',''))
577 # we get here with <menuitem parent="">, explicit clear of parent, or
578 # if no parent attribute at all but menu name is not a menu path
579 menu_parent_id = False
580 values = {'parent_id': menu_parent_id}
582 values['name'] = rec.get('name')
584 res = [ self.id_get(cr, rec.get('id','')) ]
588 if rec.get('action'):
589 a_action = rec.get('action','').encode('utf8')
591 # determine the type of action
592 a_type, a_id = self.model_id_get(cr, a_action)
593 a_type = a_type.split('.')[-1] # keep only type part
596 "act_window": 'STOCK_NEW',
597 "report.xml": 'STOCK_PASTE',
598 "wizard": 'STOCK_EXECUTE',
599 "url": 'STOCK_JUMP_TO',
600 "client": 'STOCK_EXECUTE',
601 "server": 'STOCK_EXECUTE',
603 values['icon'] = icons.get(a_type,'STOCK_NEW')
605 if a_type=='act_window':
606 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
607 rrres = cr.fetchone()
608 assert rrres, "No window action defined for this id %s !\n" \
609 "Verify that this is a window action or add a type argument." % (a_action,)
610 action_type,action_mode,action_name,view_id,target = rrres
612 cr.execute('SELECT arch FROM ir_ui_view WHERE id=%s', (int(view_id),))
613 arch, = cr.fetchone()
614 action_mode = etree.fromstring(arch.encode('utf8')).tag
615 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
617 action_mode, = cr.fetchone()
618 if action_type=='tree':
619 values['icon'] = 'STOCK_INDENT'
620 elif action_mode and action_mode.startswith(('tree','kanban','gantt')):
621 values['icon'] = 'STOCK_JUSTIFY_FILL'
622 elif action_mode and action_mode.startswith('graph'):
623 values['icon'] = 'terp-graph'
624 elif action_mode and action_mode.startswith('calendar'):
625 values['icon'] = 'terp-calendar'
627 values['icon'] = 'STOCK_EXECUTE'
628 if not values.get('name', False):
629 values['name'] = action_name
631 elif a_type in ['wizard', 'url', 'client', 'server'] and not values.get('name'):
632 a_table = 'ir_act_%s' % a_type
633 cr.execute('select name from %s where id=%%s' % a_table, (int(a_id),))
636 values['name'] = resw[0]
638 if not values.get('name'):
639 # ensure menu has a name
640 values['name'] = rec_id or '?'
642 if rec.get('sequence'):
643 values['sequence'] = int(rec.get('sequence'))
645 values['icon'] = str(rec.get('icon'))
646 if rec.get('web_icon'):
647 values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon')))
648 if rec.get('web_icon_hover'):
649 values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover')))
651 if rec.get('groups'):
652 g_names = rec.get('groups','').split(',')
654 for group in g_names:
655 if group.startswith('-'):
656 group_id = self.id_get(cr, group[1:])
657 groups_value.append((3, group_id))
659 group_id = self.id_get(cr, group)
660 groups_value.append((4, group_id))
661 values['groups_id'] = groups_value
663 pid = self.pool['ir.model.data']._update(cr, self.uid, 'ir.ui.menu', self.module, values, rec_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 action = "ir.actions.%s,%d" % (a_type, a_id)
670 self.pool['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)
671 return 'ir.ui.menu', pid
673 def _assert_equals(self, f1, f2, prec=4):
674 return not round(f1 - f2, prec)
676 def _tag_assert(self, cr, rec, data_node=None):
677 if self.isnoupdate(data_node) and self.mode != 'init':
680 rec_model = rec.get("model",'').encode('ascii')
681 model = self.pool[rec_model]
682 rec_id = rec.get("id",'').encode('ascii')
683 self._test_xml_id(rec_id)
684 rec_src = rec.get("search",'').encode('utf8')
685 rec_src_count = rec.get("count")
687 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
690 eval_dict = {'ref': _ref(self, cr)}
691 context = self.get_context(data_node, rec, eval_dict)
692 uid = self.get_uid(cr, self.uid, data_node, rec)
694 ids = [self.id_get(cr, rec_id)]
696 q = unsafe_eval(rec_src, eval_dict)
697 ids = self.pool[rec_model].search(cr, uid, q, context=context)
699 count = int(rec_src_count)
700 if len(ids) != count:
701 self.assertion_report.record_failure()
702 msg = 'assertion "%s" failed!\n' \
703 ' Incorrect search count:\n' \
704 ' expected count: %d\n' \
705 ' obtained count: %d\n' \
706 % (rec_string, count, len(ids))
710 assert ids is not None,\
711 'You must give either an id or a search criteria'
714 brrec = model.browse(cr, uid, id, context)
716 def __getitem__(self2, key):
719 return dict.__getitem__(self2, key)
721 globals_dict['floatEqual'] = self._assert_equals
722 globals_dict['ref'] = ref
723 globals_dict['_ref'] = ref
724 for test in rec.findall('./test'):
725 f_expr = test.get("expr",'').encode('utf-8')
726 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
727 expression_value = unsafe_eval(f_expr, globals_dict)
728 if expression_value != expected_value: # assertion failed
729 self.assertion_report.record_failure()
730 msg = 'assertion "%s" failed!\n' \
732 ' expected value: %r\n' \
733 ' obtained value: %r\n' \
734 % (rec_string, etree.tostring(test), expected_value, expression_value)
737 else: # all tests were successful for this assertion tag (no break)
738 self.assertion_report.record_success()
740 def _tag_record(self, cr, rec, data_node=None):
741 rec_model = rec.get("model").encode('ascii')
742 model = self.pool[rec_model]
743 rec_id = rec.get("id",'').encode('ascii')
744 rec_context = rec.get("context", None)
746 rec_context = unsafe_eval(rec_context)
747 self._test_xml_id(rec_id)
748 if self.isnoupdate(data_node) and self.mode != 'init':
749 # check if the xml record has an id string
752 module,rec_id2 = rec_id.split('.')
756 id = self.pool['ir.model.data']._update_dummy(cr, self.uid, rec_model, module, rec_id2)
757 # check if the resource already existed at the last update
759 # if it existed, we don't update the data, but we need to
760 # know the id of the existing record anyway
761 self.idref[rec_id] = int(id)
764 # if the resource didn't exist
765 if not self.nodeattr2bool(rec, 'forcecreate', True):
766 # we don't want to create it, so we skip it
768 # else, we let the record to be created
771 # otherwise it is skipped
774 for field in rec.findall('./field'):
775 #TODO: most of this code is duplicated above (in _eval_xml)...
776 f_name = field.get("name",'').encode('utf-8')
777 f_ref = field.get("ref",'').encode('utf-8')
778 f_search = field.get("search",'').encode('utf-8')
779 f_model = field.get("model",'').encode('utf-8')
780 if not f_model and model._columns.get(f_name,False):
781 f_model = model._columns[f_name]._obj
782 f_use = field.get("use",'').encode('utf-8') or 'id'
786 q = unsafe_eval(f_search, self.idref)
788 assert f_model, 'Define an attribute model="..." in your .XML file !'
789 f_obj = self.pool[f_model]
790 # browse the objects searched
791 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
792 # column definitions of the "local" object
793 _cols = self.pool[rec_model]._columns
794 # if the current field is many2many
795 if (f_name in _cols) and _cols[f_name]._type=='many2many':
796 f_val = [(6, 0, map(lambda x: x[f_use], s))]
798 # otherwise (we are probably in a many2one field),
799 # take the first element of the search
805 if f_name in model._columns \
806 and model._columns[f_name]._type == 'reference':
807 val = self.model_id_get(cr, f_ref)
808 f_val = val[0] + ',' + str(val[1])
810 f_val = self.id_get(cr, f_ref)
812 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
813 if model._columns.has_key(f_name):
814 import openerp.osv as osv
815 if isinstance(model._columns[f_name], osv.fields.integer):
819 id = self.pool['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 )
821 self.idref[rec_id] = int(id)
822 if config.get('import_partial', False):
826 def id_get(self, cr, id_str):
827 if id_str in self.idref:
828 return self.idref[id_str]
829 res = self.model_id_get(cr, id_str)
830 if res and len(res)>1: res = res[1]
833 def model_id_get(self, cr, id_str):
834 model_data_obj = self.pool['ir.model.data']
837 mod,id_str = id_str.split('.')
838 return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
841 if de.tag != 'openerp':
842 raise Exception("Mismatch xml format: root tag must be `openerp`.")
844 for n in de.findall('./data'):
846 if rec.tag in self._tags:
848 self._tags[rec.tag](self.cr, rec, n)
851 exc_info = sys.exc_info()
852 raise ParseError, (misc.ustr(e), etree.tostring(rec).rstrip(), rec.getroottree().docinfo.URL, rec.sourceline), exc_info[2]
855 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
861 self.pool = openerp.registry(cr.dbname)
864 report = assertion_report.assertion_report()
865 self.assertion_report = report
866 self.noupdate = noupdate
868 'menuitem': self._tag_menuitem,
869 'record': self._tag_record,
870 'assert': self._tag_assert,
871 'report': self._tag_report,
872 'wizard': self._tag_wizard,
873 'delete': self._tag_delete,
874 'ir_set': self._tag_ir_set,
875 'function': self._tag_function,
876 'workflow': self._tag_workflow,
877 'act_window': self._tag_act_window,
881 def convert_file(cr, module, filename, idref, mode='update', noupdate=False, kind=None, report=None):
882 pathname = os.path.join(module, filename)
883 fp = misc.file_open(pathname)
884 ext = os.path.splitext(filename)[1].lower()
887 convert_csv_import(cr, module, pathname, fp.read(), idref, mode, noupdate)
889 convert_sql_import(cr, fp)
891 convert_yaml_import(cr, module, fp, kind, idref, mode, noupdate, report)
893 convert_xml_import(cr, module, fp, idref, mode, noupdate, report)
895 pass # .js files are valid but ignored here.
897 _logger.warning("Can't load unknown file type %s.", filename)
901 def convert_sql_import(cr, fp):
902 queries = fp.read().split(';')
903 for query in queries:
904 new_query = ' '.join(query.split())
906 cr.execute(new_query)
908 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
916 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
917 #remove folder path from model
918 head, model = os.path.split(model)
920 input = cStringIO.StringIO(csvcontent) #FIXME
921 reader = csv.reader(input, quotechar='"', delimiter=',')
922 fields = reader.next()
924 if config.get('import_partial'):
925 fname_partial = module + '/'+ fname
926 if not os.path.isfile(config.get('import_partial')):
927 pickle.dump({}, file(config.get('import_partial'),'w+'))
929 data = pickle.load(file(config.get('import_partial')))
930 if fname_partial in data:
931 if not data[fname_partial]:
934 for i in range(data[fname_partial]):
937 if not (mode == 'init' or 'id' in fields):
938 _logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
944 if not (line and any(line)):
947 datas.append(map(misc.ustr, line))
949 _logger.error("Cannot import the line: %s", line)
951 registry = openerp.registry(cr.dbname)
952 result, rows, warning_msg, dummy = registry[model].import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
954 # Report failed import and abort module install
955 raise Exception(_('Module loading %s failed: file %s could not be processed:\n %s') % (module, fname, warning_msg))
956 if config.get('import_partial'):
957 data = pickle.load(file(config.get('import_partial')))
958 data[fname_partial] = 0
959 pickle.dump(data, file(config.get('import_partial'),'wb'))
965 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
966 doc = etree.parse(xmlfile)
967 relaxng = etree.RelaxNG(
968 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
972 _logger.error('The XML file does not fit the required schema !')
973 _logger.error(misc.ustr(relaxng.error_log.last_error))
978 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
979 obj.parse(doc.getroot())
983 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: