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
37 import assertion_report
39 _logger = logging.getLogger(__name__)
44 _logger.warning('could not find pytz library, please install it')
45 class pytzclass(object):
50 from datetime import datetime, timedelta
51 from dateutil.relativedelta import relativedelta
52 from lxml import etree
54 from config import config
55 from translate import _
57 # List of etree._Element subclasses that we choose to ignore when parsing XML.
58 from misc import SKIPPED_ELEMENT_TYPES
60 from misc import unquote
62 # Import of XML records requires the unsafe eval as well,
63 # almost everywhere, which is ok because it supposedly comes
64 # from trusted data, but at least we make it obvious now.
66 from safe_eval import safe_eval as eval
68 class ParseError(Exception):
69 def __init__(self, msg, text, filename, lineno):
72 self.filename = filename
76 return '"%s" while parsing %s:%s, near\n%s' \
77 % (self.msg, self.filename, self.lineno, self.text)
80 return lambda x: self.id_get(cr, x)
82 def _obj(pool, cr, uid, model_str, context=None):
83 model = pool[model_str]
84 return lambda x: model.browse(cr, uid, x, context=context)
86 def _get_idref(self, cr, uid, model_str, context, idref):
92 relativedelta=relativedelta,
93 version=openerp.release.major_version,
97 idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context)
100 def _fix_multiple_roots(node):
102 Surround the children of the ``node`` element of an XML field with a
103 single root "data" element, to prevent having a document with multiple
104 roots once parsed separately.
106 XML nodes should have one root only, but we'd like to support
107 direct multiple roots in our partial documents (like inherited view architectures).
108 As a convention we'll surround multiple root with a container "data" element, to be
109 ignored later when parsing.
111 real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)]
112 if len(real_nodes) > 1:
113 data_node = etree.Element("data")
115 data_node.append(child)
116 node.append(data_node)
118 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
121 if node.tag in ('field','value'):
122 t = node.get('type','char')
123 f_model = node.get('model', '').encode('utf-8')
124 if node.get('search'):
125 f_search = node.get("search",'').encode('utf-8')
126 f_use = node.get("use",'id').encode('utf-8')
127 f_name = node.get("name",'').encode('utf-8')
130 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
131 q = unsafe_eval(f_search, idref2)
132 ids = pool[f_model].search(cr, uid, q)
134 ids = map(lambda x: x[f_use], pool[f_model].read(cr, uid, ids, [f_use]))
135 _cols = pool[f_model]._columns
136 if (f_name in _cols) and _cols[f_name]._type=='many2many':
141 if isinstance(f_val, tuple):
144 a_eval = node.get('eval','')
146 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
148 return unsafe_eval(a_eval, idref2)
150 logging.getLogger('openerp.tools.convert.init').error(
151 'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context)
153 def _process(s, idref):
154 m = re.findall('[^%]%\((.*?)\)[ds]', s)
157 idref[id]=self.id_get(cr, id)
160 _fix_multiple_roots(node)
161 return '<?xml version="1.0"?>\n'\
162 +_process("".join([etree.tostring(n, encoding='utf-8')
163 for n in node]), idref)
165 return _process("".join([etree.tostring(n, encoding='utf-8')
166 for n in node]), idref)
167 if t in ('char', 'int', 'float'):
174 return int(d.strip())
176 return float(d.strip())
178 elif t in ('list','tuple'):
180 for n in node.findall('./value'):
181 res.append(_eval_xml(self,n,pool,cr,uid,idref))
185 elif node.tag == "getitem":
187 res=_eval_xml(self,n,pool,cr,uid,idref)
190 elif node.get('type') in ("int", "list"):
191 return res[int(node.get('index'))]
193 return res[node.get('index','').encode("utf8")]
194 elif node.tag == "function":
196 a_eval = node.get('eval','')
198 idref['ref'] = lambda x: self.id_get(cr, x)
199 args = unsafe_eval(a_eval, idref)
201 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
202 if return_val is not None:
203 args.append(return_val)
204 model = pool[node.get('model','')]
205 method = node.get('name','')
206 res = getattr(model, method)(cr, uid, *args)
208 elif node.tag == "test":
211 escape_re = re.compile(r'(?<!\\)/')
213 return x.replace('\\/', '/')
215 class xml_import(object):
217 def nodeattr2bool(node, attr, default=False):
218 if not node.get(attr):
220 val = node.get(attr).strip()
223 return val.lower() not in ('0', 'false', 'off')
225 def isnoupdate(self, data_node=None):
226 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
228 def get_context(self, data_node, node, eval_dict):
229 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
230 node_context = node.get("context",'').encode('utf8')
232 for ctx in (data_node_context, node_context):
235 ctx_res = unsafe_eval(ctx, eval_dict)
236 if isinstance(context, dict):
237 context.update(ctx_res)
241 # Some contexts contain references that are only valid at runtime at
242 # client-side, so in that case we keep the original context string
243 # as it is. We also log it, just in case.
245 _logger.debug('Context value (%s) for element with id "%s" or its data node does not parse '\
246 'at server-side, keeping original string, in case it\'s meant for client side only',
247 ctx, node.get('id','n/a'), exc_info=True)
250 def get_uid(self, cr, uid, data_node, node):
251 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
253 return self.id_get(cr, node_uid)
256 def _test_xml_id(self, xml_id):
259 module, id = xml_id.split('.', 1)
260 assert '.' not in id, """The ID reference "%s" must contain
261 maximum one dot. They are used to refer to other modules ID, in the
262 form: module.record_id""" % (xml_id,)
263 if module != self.module:
264 modcnt = self.pool['ir.module.module'].search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
265 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
268 _logger.error('id: %s is to long (max: 64)', id)
270 def _tag_delete(self, cr, rec, data_node=None):
271 d_model = rec.get("model",'')
272 d_search = rec.get("search",'').encode('utf-8')
273 d_id = rec.get("id",'')
277 idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
278 ids = self.pool[d_model].search(cr, self.uid, unsafe_eval(d_search, idref))
281 ids.append(self.id_get(cr, d_id))
283 # d_id cannot be found. doesn't matter in this case
286 self.pool[d_model].unlink(cr, self.uid, ids)
288 def _remove_ir_values(self, cr, name, value, model):
289 ir_values_obj = self.pool['ir.values']
290 ir_value_ids = ir_values_obj.search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
292 ir_values_obj.unlink(cr, self.uid, ir_value_ids)
296 def _tag_report(self, cr, rec, data_node=None):
298 for dest,f in (('name','string'),('model','model'),('report_name','name')):
299 res[dest] = rec.get(f,'').encode('utf8')
300 assert res[dest], "Attribute %s of report is empty !" % (f,)
301 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),
302 ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage'),
303 ('report_type', 'report_type'), ('parser', 'parser')):
305 res[dest] = rec.get(field).encode('utf8')
307 res['auto'] = eval(rec.get('auto','False'))
309 sxw_content = misc.file_open(rec.get('sxw')).read()
310 res['report_sxw_content'] = sxw_content
311 if rec.get('header'):
312 res['header'] = eval(rec.get('header','False'))
314 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
316 xml_id = rec.get('id','').encode('utf8')
317 self._test_xml_id(xml_id)
319 if rec.get('groups'):
320 g_names = rec.get('groups','').split(',')
322 for group in g_names:
323 if group.startswith('-'):
324 group_id = self.id_get(cr, group[1:])
325 groups_value.append((3, group_id))
327 group_id = self.id_get(cr, group)
328 groups_value.append((4, group_id))
329 res['groups_id'] = groups_value
331 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)
332 self.idref[xml_id] = int(id)
334 if not rec.get('menu') or eval(rec.get('menu','False')):
335 keyword = str(rec.get('keyword', 'client_print_multi'))
336 value = 'ir.actions.report.xml,'+str(id)
337 replace = rec.get('replace', True)
338 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)
339 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
340 # Special check for report having attribute menu=False on update
341 value = 'ir.actions.report.xml,'+str(id)
342 self._remove_ir_values(cr, res['name'], value, res['model'])
345 def _tag_function(self, cr, rec, data_node=None):
346 if self.isnoupdate(data_node) and self.mode != 'init':
348 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
349 uid = self.get_uid(cr, self.uid, data_node, rec)
350 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
353 def _tag_wizard(self, cr, rec, data_node=None):
354 string = rec.get("string",'').encode('utf8')
355 model = rec.get("model",'').encode('utf8')
356 name = rec.get("name",'').encode('utf8')
357 xml_id = rec.get('id','').encode('utf8')
358 self._test_xml_id(xml_id)
359 multi = rec.get('multi','') and eval(rec.get('multi','False'))
360 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
362 if rec.get('groups'):
363 g_names = rec.get('groups','').split(',')
365 for group in g_names:
366 if group.startswith('-'):
367 group_id = self.id_get(cr, group[1:])
368 groups_value.append((3, group_id))
370 group_id = self.id_get(cr, group)
371 groups_value.append((4, group_id))
372 res['groups_id'] = groups_value
374 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)
375 self.idref[xml_id] = int(id)
377 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
378 keyword = str(rec.get('keyword','') or 'client_action_multi')
379 value = 'ir.actions.wizard,'+str(id)
380 replace = rec.get("replace",'') or True
381 self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
382 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
383 # Special check for wizard having attribute menu=False on update
384 value = 'ir.actions.wizard,'+str(id)
385 self._remove_ir_values(cr, string, value, model)
387 def _tag_url(self, cr, rec, data_node=None):
388 url = rec.get("url",'').encode('utf8')
389 target = rec.get("target",'').encode('utf8')
390 name = rec.get("name",'').encode('utf8')
391 xml_id = rec.get('id','').encode('utf8')
392 self._test_xml_id(xml_id)
394 res = {'name': name, 'url': url, 'target':target}
396 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)
397 self.idref[xml_id] = int(id)
399 def _tag_act_window(self, cr, rec, data_node=None):
400 name = rec.get('name','').encode('utf-8')
401 xml_id = rec.get('id','').encode('utf8')
402 self._test_xml_id(xml_id)
403 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
405 if rec.get('view_id'):
406 view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8'))
407 domain = rec.get('domain','').encode('utf-8') or '[]'
408 res_model = rec.get('res_model','').encode('utf-8')
409 src_model = rec.get('src_model','').encode('utf-8')
410 view_type = rec.get('view_type','').encode('utf-8') or 'form'
411 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
412 usage = rec.get('usage','').encode('utf-8')
413 limit = rec.get('limit','').encode('utf-8')
414 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
417 # Act_window's 'domain' and 'context' contain mostly literals
418 # but they can also refer to the variables provided below
419 # in eval_context, so we need to eval() them before storing.
420 # Among the context variables, 'active_id' refers to
421 # the currently selected items in a list view, and only
422 # takes meaning at runtime on the client side. For this
423 # reason it must remain a bare variable in domain and context,
424 # even after eval() at server-side. We use the special 'unquote'
425 # class to achieve this effect: a string which has itself, unquoted,
427 active_id = unquote("active_id")
428 active_ids = unquote("active_ids")
429 active_model = unquote("active_model")
432 return self.id_get(cr, str_id)
434 # Include all locals() in eval_context, for backwards compatibility
441 'res_model': res_model,
442 'src_model': src_model,
443 'view_type': view_type,
444 'view_mode': view_mode,
447 'auto_refresh': auto_refresh,
449 'active_id': active_id,
450 'active_ids': active_ids,
451 'active_model': active_model,
454 context = self.get_context(data_node, rec, eval_context)
457 domain = unsafe_eval(domain, eval_context)
459 # Some domains contain references that are only valid at runtime at
460 # client-side, so in that case we keep the original domain string
461 # as it is. We also log it, just in case.
462 _logger.debug('Domain value (%s) for element with id "%s" does not parse '\
463 'at server-side, keeping original string, in case it\'s meant for client side only',
464 domain, xml_id or 'n/a', exc_info=True)
471 'res_model': res_model,
472 'src_model': src_model,
473 'view_type': view_type,
474 'view_mode': view_mode,
477 'auto_refresh': auto_refresh,
480 if rec.get('groups'):
481 g_names = rec.get('groups','').split(',')
483 for group in g_names:
484 if group.startswith('-'):
485 group_id = self.id_get(cr, group[1:])
486 groups_value.append((3, group_id))
488 group_id = self.id_get(cr, group)
489 groups_value.append((4, group_id))
490 res['groups_id'] = groups_value
492 if rec.get('target'):
493 res['target'] = rec.get('target','')
495 res['multi'] = eval(rec.get('multi', 'False'))
496 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)
497 self.idref[xml_id] = int(id)
500 #keyword = 'client_action_relate'
501 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
502 value = 'ir.actions.act_window,'+str(id)
503 replace = rec.get('replace','') or True
504 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)
505 # TODO add remove ir.model.data
507 def _tag_ir_set(self, cr, rec, data_node=None):
508 if self.mode != 'init':
511 for field in rec.findall('./field'):
512 f_name = field.get("name",'').encode('utf-8')
513 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
515 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))
517 def _tag_workflow(self, cr, rec, data_node=None):
518 if self.isnoupdate(data_node) and self.mode != 'init':
520 model = str(rec.get('model',''))
521 w_ref = rec.get('ref','')
523 id = self.id_get(cr, w_ref)
525 number_children = len(rec)
526 assert number_children > 0,\
527 'You must define a child node if you dont give a ref'
528 assert number_children == 1,\
529 'Only one child node is accepted (%d given)' % number_children
530 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
532 uid = self.get_uid(cr, self.uid, data_node, rec)
533 openerp.workflow.trg_validate(uid, model,
535 str(rec.get('action','')), cr)
538 # Support two types of notation:
539 # name="Inventory Control/Sending Goods"
544 def _tag_menuitem(self, cr, rec, data_node=None):
545 rec_id = rec.get("id",'').encode('ascii')
546 self._test_xml_id(rec_id)
547 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
549 values = {'parent_id': False}
550 if rec.get('parent', False) is False and len(m_l) > 1:
551 # No parent attribute specified and the menu name has several menu components,
552 # try to determine the ID of the parent according to menu path
555 values['name'] = m_l[-1]
556 m_l = m_l[:-1] # last part is our name, not a parent
557 for idx, menu_elem in enumerate(m_l):
559 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
561 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
566 # the menuitem does't exist but we are in branch (not a leaf)
567 _logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l))
568 pid = self.pool['ir.ui.menu'].create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
569 values['parent_id'] = pid
571 # The parent attribute was specified, if non-empty determine its ID, otherwise
572 # explicitly make a top-level menu
573 if rec.get('parent'):
574 menu_parent_id = self.id_get(cr, rec.get('parent',''))
576 # we get here with <menuitem parent="">, explicit clear of parent, or
577 # if no parent attribute at all but menu name is not a menu path
578 menu_parent_id = False
579 values = {'parent_id': menu_parent_id}
581 values['name'] = rec.get('name')
583 res = [ self.id_get(cr, rec.get('id','')) ]
587 if rec.get('action'):
588 a_action = rec.get('action','').encode('utf8')
590 # determine the type of action
591 a_type, a_id = self.model_id_get(cr, a_action)
592 a_type = a_type.split('.')[-1] # keep only type part
595 "act_window": 'STOCK_NEW',
596 "report.xml": 'STOCK_PASTE',
597 "wizard": 'STOCK_EXECUTE',
598 "url": 'STOCK_JUMP_TO',
599 "client": 'STOCK_EXECUTE',
600 "server": 'STOCK_EXECUTE',
602 values['icon'] = icons.get(a_type,'STOCK_NEW')
604 if a_type=='act_window':
605 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
606 rrres = cr.fetchone()
607 assert rrres, "No window action defined for this id %s !\n" \
608 "Verify that this is a window action or add a type argument." % (a_action,)
609 action_type,action_mode,action_name,view_id,target = rrres
611 cr.execute('SELECT arch FROM ir_ui_view WHERE id=%s', (int(view_id),))
612 arch, = cr.fetchone()
613 action_mode = etree.fromstring(arch.encode('utf8')).tag
614 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
616 action_mode, = cr.fetchone()
617 if action_type=='tree':
618 values['icon'] = 'STOCK_INDENT'
619 elif action_mode and action_mode.startswith(('tree','kanban','gantt')):
620 values['icon'] = 'STOCK_JUSTIFY_FILL'
621 elif action_mode and action_mode.startswith('graph'):
622 values['icon'] = 'terp-graph'
623 elif action_mode and action_mode.startswith('calendar'):
624 values['icon'] = 'terp-calendar'
626 values['icon'] = 'STOCK_EXECUTE'
627 if not values.get('name', False):
628 values['name'] = action_name
630 elif a_type in ['wizard', 'url', 'client', 'server'] and not values.get('name'):
631 a_table = 'ir_act_%s' % a_type
632 cr.execute('select name from %s where id=%%s' % a_table, (int(a_id),))
635 values['name'] = resw[0]
637 if not values.get('name'):
638 # ensure menu has a name
639 values['name'] = rec_id or '?'
641 if rec.get('sequence'):
642 values['sequence'] = int(rec.get('sequence'))
644 values['icon'] = str(rec.get('icon'))
645 if rec.get('web_icon'):
646 values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon')))
647 if rec.get('web_icon_hover'):
648 values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover')))
650 if rec.get('groups'):
651 g_names = rec.get('groups','').split(',')
653 for group in g_names:
654 if group.startswith('-'):
655 group_id = self.id_get(cr, group[1:])
656 groups_value.append((3, group_id))
658 group_id = self.id_get(cr, group)
659 groups_value.append((4, group_id))
660 values['groups_id'] = groups_value
662 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)
665 self.idref[rec_id] = int(pid)
667 if rec.get('action') and pid:
668 action = "ir.actions.%s,%d" % (a_type, a_id)
669 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)
670 return 'ir.ui.menu', pid
672 def _assert_equals(self, f1, f2, prec=4):
673 return not round(f1 - f2, prec)
675 def _tag_assert(self, cr, rec, data_node=None):
676 if self.isnoupdate(data_node) and self.mode != 'init':
679 rec_model = rec.get("model",'').encode('ascii')
680 model = self.pool[rec_model]
681 rec_id = rec.get("id",'').encode('ascii')
682 self._test_xml_id(rec_id)
683 rec_src = rec.get("search",'').encode('utf8')
684 rec_src_count = rec.get("count")
686 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
689 eval_dict = {'ref': _ref(self, cr)}
690 context = self.get_context(data_node, rec, eval_dict)
691 uid = self.get_uid(cr, self.uid, data_node, rec)
693 ids = [self.id_get(cr, rec_id)]
695 q = unsafe_eval(rec_src, eval_dict)
696 ids = self.pool[rec_model].search(cr, uid, q, context=context)
698 count = int(rec_src_count)
699 if len(ids) != count:
700 self.assertion_report.record_failure()
701 msg = 'assertion "%s" failed!\n' \
702 ' Incorrect search count:\n' \
703 ' expected count: %d\n' \
704 ' obtained count: %d\n' \
705 % (rec_string, count, len(ids))
709 assert ids is not None,\
710 'You must give either an id or a search criteria'
713 brrec = model.browse(cr, uid, id, context)
715 def __getitem__(self2, key):
718 return dict.__getitem__(self2, key)
720 globals_dict['floatEqual'] = self._assert_equals
721 globals_dict['ref'] = ref
722 globals_dict['_ref'] = ref
723 for test in rec.findall('./test'):
724 f_expr = test.get("expr",'').encode('utf-8')
725 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
726 expression_value = unsafe_eval(f_expr, globals_dict)
727 if expression_value != expected_value: # assertion failed
728 self.assertion_report.record_failure()
729 msg = 'assertion "%s" failed!\n' \
731 ' expected value: %r\n' \
732 ' obtained value: %r\n' \
733 % (rec_string, etree.tostring(test), expected_value, expression_value)
736 else: # all tests were successful for this assertion tag (no break)
737 self.assertion_report.record_success()
739 def _tag_record(self, cr, rec, data_node=None):
740 rec_model = rec.get("model").encode('ascii')
741 model = self.pool[rec_model]
742 rec_id = rec.get("id",'').encode('ascii')
743 rec_context = rec.get("context", None)
745 rec_context = unsafe_eval(rec_context)
746 self._test_xml_id(rec_id)
747 if self.isnoupdate(data_node) and self.mode != 'init':
748 # check if the xml record has an id string
751 module,rec_id2 = rec_id.split('.')
755 id = self.pool['ir.model.data']._update_dummy(cr, self.uid, rec_model, module, rec_id2)
756 # check if the resource already existed at the last update
758 # if it existed, we don't update the data, but we need to
759 # know the id of the existing record anyway
760 self.idref[rec_id] = int(id)
763 # if the resource didn't exist
764 if not self.nodeattr2bool(rec, 'forcecreate', True):
765 # we don't want to create it, so we skip it
767 # else, we let the record to be created
770 # otherwise it is skipped
773 for field in rec.findall('./field'):
774 #TODO: most of this code is duplicated above (in _eval_xml)...
775 f_name = field.get("name",'').encode('utf-8')
776 f_ref = field.get("ref",'').encode('utf-8')
777 f_search = field.get("search",'').encode('utf-8')
778 f_model = field.get("model",'').encode('utf-8')
779 if not f_model and model._columns.get(f_name,False):
780 f_model = model._columns[f_name]._obj
781 f_use = field.get("use",'').encode('utf-8') or 'id'
785 q = unsafe_eval(f_search, self.idref)
787 assert f_model, 'Define an attribute model="..." in your .XML file !'
788 f_obj = self.pool[f_model]
789 # browse the objects searched
790 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
791 # column definitions of the "local" object
792 _cols = self.pool[rec_model]._columns
793 # if the current field is many2many
794 if (f_name in _cols) and _cols[f_name]._type=='many2many':
795 f_val = [(6, 0, map(lambda x: x[f_use], s))]
797 # otherwise (we are probably in a many2one field),
798 # take the first element of the search
804 if f_name in model._columns \
805 and model._columns[f_name]._type == 'reference':
806 val = self.model_id_get(cr, f_ref)
807 f_val = val[0] + ',' + str(val[1])
809 f_val = self.id_get(cr, f_ref)
811 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
812 if model._columns.has_key(f_name):
813 import openerp.osv as osv
814 if isinstance(model._columns[f_name], osv.fields.integer):
818 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 )
820 self.idref[rec_id] = int(id)
821 if config.get('import_partial', False):
825 def id_get(self, cr, id_str):
826 if id_str in self.idref:
827 return self.idref[id_str]
828 res = self.model_id_get(cr, id_str)
829 if res and len(res)>1: res = res[1]
832 def model_id_get(self, cr, id_str):
833 model_data_obj = self.pool['ir.model.data']
836 mod,id_str = id_str.split('.')
837 return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
840 if de.tag != 'openerp':
841 raise Exception("Mismatch xml format: root tag must be `openerp`.")
843 for n in de.findall('./data'):
845 if rec.tag in self._tags:
847 self._tags[rec.tag](self.cr, rec, n)
850 exc_info = sys.exc_info()
851 raise ParseError, (misc.ustr(e), etree.tostring(rec).rstrip(), rec.getroottree().docinfo.URL, rec.sourceline), exc_info[2]
854 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
860 self.pool = openerp.registry(cr.dbname)
863 report = assertion_report.assertion_report()
864 self.assertion_report = report
865 self.noupdate = noupdate
867 'menuitem': self._tag_menuitem,
868 'record': self._tag_record,
869 'assert': self._tag_assert,
870 'report': self._tag_report,
871 'wizard': self._tag_wizard,
872 'delete': self._tag_delete,
873 'ir_set': self._tag_ir_set,
874 'function': self._tag_function,
875 'workflow': self._tag_workflow,
876 'act_window': self._tag_act_window,
880 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
888 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
889 #remove folder path from model
890 head, model = os.path.split(model)
892 input = cStringIO.StringIO(csvcontent) #FIXME
893 reader = csv.reader(input, quotechar='"', delimiter=',')
894 fields = reader.next()
896 if config.get('import_partial'):
897 fname_partial = module + '/'+ fname
898 if not os.path.isfile(config.get('import_partial')):
899 pickle.dump({}, file(config.get('import_partial'),'w+'))
901 data = pickle.load(file(config.get('import_partial')))
902 if fname_partial in data:
903 if not data[fname_partial]:
906 for i in range(data[fname_partial]):
909 if not (mode == 'init' or 'id' in fields):
910 _logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
916 if not (line and any(line)):
919 datas.append(map(misc.ustr, line))
921 _logger.error("Cannot import the line: %s", line)
923 registry = openerp.registry(cr.dbname)
924 result, rows, warning_msg, dummy = registry[model].import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
926 # Report failed import and abort module install
927 raise Exception(_('Module loading failed: file %s/%s could not be processed:\n %s') % (module, fname, warning_msg))
928 if config.get('import_partial'):
929 data = pickle.load(file(config.get('import_partial')))
930 data[fname_partial] = 0
931 pickle.dump(data, file(config.get('import_partial'),'wb'))
937 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
938 doc = etree.parse(xmlfile)
939 relaxng = etree.RelaxNG(
940 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
944 _logger.error('The XML file does not fit the required schema !')
945 _logger.error(misc.ustr(relaxng.error_log.last_error))
950 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
951 obj.parse(doc.getroot())
955 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: