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 ##############################################################################
31 import openerp.release as release
33 import assertion_report
35 _logger = logging.getLogger(__name__)
40 _logger.warning('could not find pytz library, please install it')
41 class pytzclass(object):
46 from datetime import datetime, timedelta
47 from lxml import etree
49 import openerp.pooler as pooler
50 from config import config
51 from translate import _
53 # List of etree._Element subclasses that we choose to ignore when parsing XML.
54 from misc import SKIPPED_ELEMENT_TYPES
56 from misc import unquote
58 # Import of XML records requires the unsafe eval as well,
59 # almost everywhere, which is ok because it supposedly comes
60 # from trusted data, but at least we make it obvious now.
62 from safe_eval import safe_eval as eval
64 class ConvertError(Exception):
65 def __init__(self, doc, orig_excpt):
67 self.orig = orig_excpt
70 return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
73 return lambda x: self.id_get(cr, x)
75 def _obj(pool, cr, uid, model_str, context=None):
76 model = pool.get(model_str)
77 return lambda x: model.browse(cr, uid, x, context=context)
79 def _get_idref(self, cr, uid, model_str, context, idref):
84 version=release.major_version,
88 idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context)
91 def _fix_multiple_roots(node):
93 Surround the children of the ``node`` element of an XML field with a
94 single root "data" element, to prevent having a document with multiple
95 roots once parsed separately.
97 XML nodes should have one root only, but we'd like to support
98 direct multiple roots in our partial documents (like inherited view architectures).
99 As a convention we'll surround multiple root with a container "data" element, to be
100 ignored later when parsing.
102 real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)]
103 if len(real_nodes) > 1:
104 data_node = etree.Element("data")
106 data_node.append(child)
107 node.append(data_node)
109 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
112 if node.tag in ('field','value'):
113 t = node.get('type','char')
114 f_model = node.get('model', '').encode('utf-8')
115 if node.get('search'):
116 f_search = node.get("search",'').encode('utf-8')
117 f_use = node.get("use",'id').encode('utf-8')
118 f_name = node.get("name",'').encode('utf-8')
121 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
122 q = unsafe_eval(f_search, idref2)
123 ids = pool.get(f_model).search(cr, uid, q)
125 ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
126 _cols = pool.get(f_model)._columns
127 if (f_name in _cols) and _cols[f_name]._type=='many2many':
132 if isinstance(f_val, tuple):
135 a_eval = node.get('eval','')
137 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
139 return unsafe_eval(a_eval, idref2)
141 logging.getLogger('openerp.tools.convert.init').error(
142 'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context)
144 def _process(s, idref):
145 m = re.findall('[^%]%\((.*?)\)[ds]', s)
148 idref[id]=self.id_get(cr, id)
151 _fix_multiple_roots(node)
152 return '<?xml version="1.0"?>\n'\
153 +_process("".join([etree.tostring(n, encoding='utf-8')
154 for n in node]), idref)
156 return _process("".join([etree.tostring(n, encoding='utf-8')
157 for n in node]), idref)
158 if t in ('char', 'int', 'float'):
165 return int(d.strip())
167 return float(d.strip())
169 elif t in ('list','tuple'):
171 for n in node.findall('./value'):
172 res.append(_eval_xml(self,n,pool,cr,uid,idref))
176 elif node.tag == "getitem":
178 res=_eval_xml(self,n,pool,cr,uid,idref)
181 elif node.get('type') in ("int", "list"):
182 return res[int(node.get('index'))]
184 return res[node.get('index','').encode("utf8")]
185 elif node.tag == "function":
187 a_eval = node.get('eval','')
189 idref['ref'] = lambda x: self.id_get(cr, x)
190 args = unsafe_eval(a_eval, idref)
192 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
193 if return_val is not None:
194 args.append(return_val)
195 model = pool.get(node.get('model',''))
196 method = node.get('name','')
197 res = getattr(model, method)(cr, uid, *args)
199 elif node.tag == "test":
202 escape_re = re.compile(r'(?<!\\)/')
204 return x.replace('\\/', '/')
206 class xml_import(object):
208 def nodeattr2bool(node, attr, default=False):
209 if not node.get(attr):
211 val = node.get(attr).strip()
214 return val.lower() not in ('0', 'false', 'off')
216 def isnoupdate(self, data_node=None):
217 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
219 def get_context(self, data_node, node, eval_dict):
220 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
221 node_context = node.get("context",'').encode('utf8')
223 for ctx in (data_node_context, node_context):
226 ctx_res = unsafe_eval(ctx, eval_dict)
227 if isinstance(context, dict):
228 context.update(ctx_res)
232 # Some contexts contain references that are only valid at runtime at
233 # client-side, so in that case we keep the original context string
234 # as it is. We also log it, just in case.
236 _logger.debug('Context value (%s) for element with id "%s" or its data node does not parse '\
237 'at server-side, keeping original string, in case it\'s meant for client side only',
238 ctx, node.get('id','n/a'), exc_info=True)
241 def get_uid(self, cr, uid, data_node, node):
242 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
244 return self.id_get(cr, node_uid)
247 def _test_xml_id(self, xml_id):
250 module, id = xml_id.split('.', 1)
251 assert '.' not in id, """The ID reference "%s" must contain
252 maximum one dot. They are used to refer to other modules ID, in the
253 form: module.record_id""" % (xml_id,)
254 if module != self.module:
255 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
256 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
259 _logger.error('id: %s is to long (max: 64)', id)
261 def _tag_delete(self, cr, rec, data_node=None):
262 d_model = rec.get("model",'')
263 d_search = rec.get("search",'').encode('utf-8')
264 d_id = rec.get("id",'')
268 idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
269 ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search, idref))
272 ids.append(self.id_get(cr, d_id))
274 # d_id cannot be found. doesn't matter in this case
277 self.pool.get(d_model).unlink(cr, self.uid, ids)
279 def _remove_ir_values(self, cr, name, value, model):
280 ir_values_obj = self.pool.get('ir.values')
281 ir_value_ids = ir_values_obj.search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
283 ir_values_obj.unlink(cr, self.uid, ir_value_ids)
287 def _tag_report(self, cr, rec, data_node=None):
289 for dest,f in (('name','string'),('model','model'),('report_name','name')):
290 res[dest] = rec.get(f,'').encode('utf8')
291 assert res[dest], "Attribute %s of report is empty !" % (f,)
292 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),
293 ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage')):
295 res[dest] = rec.get(field).encode('utf8')
297 res['auto'] = eval(rec.get('auto','False'))
299 sxw_content = misc.file_open(rec.get('sxw')).read()
300 res['report_sxw_content'] = sxw_content
301 if rec.get('header'):
302 res['header'] = eval(rec.get('header','False'))
303 if rec.get('report_type'):
304 res['report_type'] = rec.get('report_type')
306 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
308 xml_id = rec.get('id','').encode('utf8')
309 self._test_xml_id(xml_id)
311 if rec.get('groups'):
312 g_names = rec.get('groups','').split(',')
314 for group in g_names:
315 if group.startswith('-'):
316 group_id = self.id_get(cr, group[1:])
317 groups_value.append((3, group_id))
319 group_id = self.id_get(cr, group)
320 groups_value.append((4, group_id))
321 res['groups_id'] = groups_value
323 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)
324 self.idref[xml_id] = int(id)
326 if not rec.get('menu') or eval(rec.get('menu','False')):
327 keyword = str(rec.get('keyword', 'client_print_multi'))
328 value = 'ir.actions.report.xml,'+str(id)
329 replace = rec.get('replace', True)
330 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)
331 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
332 # Special check for report having attribute menu=False on update
333 value = 'ir.actions.report.xml,'+str(id)
334 self._remove_ir_values(cr, res['name'], value, res['model'])
337 def _tag_function(self, cr, rec, data_node=None):
338 if self.isnoupdate(data_node) and self.mode != 'init':
340 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
341 uid = self.get_uid(cr, self.uid, data_node, rec)
342 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
345 def _tag_wizard(self, cr, rec, data_node=None):
346 string = rec.get("string",'').encode('utf8')
347 model = rec.get("model",'').encode('utf8')
348 name = rec.get("name",'').encode('utf8')
349 xml_id = rec.get('id','').encode('utf8')
350 self._test_xml_id(xml_id)
351 multi = rec.get('multi','') and eval(rec.get('multi','False'))
352 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
354 if rec.get('groups'):
355 g_names = rec.get('groups','').split(',')
357 for group in g_names:
358 if group.startswith('-'):
359 group_id = self.id_get(cr, group[1:])
360 groups_value.append((3, group_id))
362 group_id = self.id_get(cr, group)
363 groups_value.append((4, group_id))
364 res['groups_id'] = groups_value
366 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)
367 self.idref[xml_id] = int(id)
369 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
370 keyword = str(rec.get('keyword','') or 'client_action_multi')
371 value = 'ir.actions.wizard,'+str(id)
372 replace = rec.get("replace",'') or True
373 self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
374 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
375 # Special check for wizard having attribute menu=False on update
376 value = 'ir.actions.wizard,'+str(id)
377 self._remove_ir_values(cr, string, value, model)
379 def _tag_url(self, cr, rec, data_node=None):
380 url = rec.get("url",'').encode('utf8')
381 target = rec.get("target",'').encode('utf8')
382 name = rec.get("name",'').encode('utf8')
383 xml_id = rec.get('id','').encode('utf8')
384 self._test_xml_id(xml_id)
386 res = {'name': name, 'url': url, 'target':target}
388 id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.act_url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
389 self.idref[xml_id] = int(id)
391 def _tag_act_window(self, cr, rec, data_node=None):
392 name = rec.get('name','').encode('utf-8')
393 xml_id = rec.get('id','').encode('utf8')
394 self._test_xml_id(xml_id)
395 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
397 if rec.get('view_id'):
398 view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8'))
399 domain = rec.get('domain','').encode('utf-8') or '[]'
400 res_model = rec.get('res_model','').encode('utf-8')
401 src_model = rec.get('src_model','').encode('utf-8')
402 view_type = rec.get('view_type','').encode('utf-8') or 'form'
403 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
404 usage = rec.get('usage','').encode('utf-8')
405 limit = rec.get('limit','').encode('utf-8')
406 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
409 # Act_window's 'domain' and 'context' contain mostly literals
410 # but they can also refer to the variables provided below
411 # in eval_context, so we need to eval() them before storing.
412 # Among the context variables, 'active_id' refers to
413 # the currently selected items in a list view, and only
414 # takes meaning at runtime on the client side. For this
415 # reason it must remain a bare variable in domain and context,
416 # even after eval() at server-side. We use the special 'unquote'
417 # class to achieve this effect: a string which has itself, unquoted,
419 active_id = unquote("active_id")
420 active_ids = unquote("active_ids")
421 active_model = unquote("active_model")
424 return self.id_get(cr, str_id)
426 # Include all locals() in eval_context, for backwards compatibility
433 'res_model': res_model,
434 'src_model': src_model,
435 'view_type': view_type,
436 'view_mode': view_mode,
439 'auto_refresh': auto_refresh,
441 'active_id': active_id,
442 'active_ids': active_ids,
443 'active_model': active_model,
446 context = self.get_context(data_node, rec, eval_context)
449 domain = unsafe_eval(domain, eval_context)
451 # Some domains contain references that are only valid at runtime at
452 # client-side, so in that case we keep the original domain string
453 # as it is. We also log it, just in case.
454 _logger.debug('Domain value (%s) for element with id "%s" does not parse '\
455 'at server-side, keeping original string, in case it\'s meant for client side only',
456 domain, xml_id or 'n/a', exc_info=True)
463 'res_model': res_model,
464 'src_model': src_model,
465 'view_type': view_type,
466 'view_mode': view_mode,
469 'auto_refresh': auto_refresh,
472 if rec.get('groups'):
473 g_names = rec.get('groups','').split(',')
475 for group in g_names:
476 if group.startswith('-'):
477 group_id = self.id_get(cr, group[1:])
478 groups_value.append((3, group_id))
480 group_id = self.id_get(cr, group)
481 groups_value.append((4, group_id))
482 res['groups_id'] = groups_value
484 if rec.get('target'):
485 res['target'] = rec.get('target','')
487 res['multi'] = rec.get('multi', False)
488 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)
489 self.idref[xml_id] = int(id)
492 #keyword = 'client_action_relate'
493 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
494 value = 'ir.actions.act_window,'+str(id)
495 replace = rec.get('replace','') or True
496 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)
497 # TODO add remove ir.model.data
499 def _tag_ir_set(self, cr, rec, data_node=None):
500 if self.mode != 'init':
503 for field in rec.findall('./field'):
504 f_name = field.get("name",'').encode('utf-8')
505 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
507 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))
509 def _tag_workflow(self, cr, rec, data_node=None):
510 if self.isnoupdate(data_node) and self.mode != 'init':
512 model = str(rec.get('model',''))
513 w_ref = rec.get('ref','')
515 id = self.id_get(cr, w_ref)
517 number_children = len(rec)
518 assert number_children > 0,\
519 'You must define a child node if you dont give a ref'
520 assert number_children == 1,\
521 'Only one child node is accepted (%d given)' % number_children
522 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
524 uid = self.get_uid(cr, self.uid, data_node, rec)
525 import openerp.netsvc as netsvc
526 wf_service = netsvc.LocalService("workflow")
527 wf_service.trg_validate(uid, model,
529 str(rec.get('action','')), cr)
532 # Support two types of notation:
533 # name="Inventory Control/Sending Goods"
538 def _tag_menuitem(self, cr, rec, data_node=None):
539 rec_id = rec.get("id",'').encode('ascii')
540 self._test_xml_id(rec_id)
541 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
543 values = {'parent_id': False}
544 if rec.get('parent', False) is False and len(m_l) > 1:
545 # No parent attribute specified and the menu name has several menu components,
546 # try to determine the ID of the parent according to menu path
549 values['name'] = m_l[-1]
550 m_l = m_l[:-1] # last part is our name, not a parent
551 for idx, menu_elem in enumerate(m_l):
553 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
555 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
560 # the menuitem does't exist but we are in branch (not a leaf)
561 _logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l))
562 pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
563 values['parent_id'] = pid
565 # The parent attribute was specified, if non-empty determine its ID, otherwise
566 # explicitly make a top-level menu
567 if rec.get('parent'):
568 menu_parent_id = self.id_get(cr, rec.get('parent',''))
570 # we get here with <menuitem parent="">, explicit clear of parent, or
571 # if no parent attribute at all but menu name is not a menu path
572 menu_parent_id = False
573 values = {'parent_id': menu_parent_id}
575 values['name'] = rec.get('name')
577 res = [ self.id_get(cr, rec.get('id','')) ]
581 if rec.get('action'):
582 a_action = rec.get('action','').encode('utf8')
584 # determine the type of action
585 a_type, a_id = self.model_id_get(cr, a_action)
586 a_type = a_type.split('.')[-1] # keep only type part
589 "act_window": 'STOCK_NEW',
590 "report.xml": 'STOCK_PASTE',
591 "wizard": 'STOCK_EXECUTE',
592 "url": 'STOCK_JUMP_TO',
593 "client": 'STOCK_EXECUTE',
594 "server": 'STOCK_EXECUTE',
596 values['icon'] = icons.get(a_type,'STOCK_NEW')
598 if a_type=='act_window':
599 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
600 rrres = cr.fetchone()
601 assert rrres, "No window action defined for this id %s !\n" \
602 "Verify that this is a window action or add a type argument." % (a_action,)
603 action_type,action_mode,action_name,view_id,target = rrres
605 cr.execute('SELECT arch FROM ir_ui_view WHERE id=%s', (int(view_id),))
606 arch, = cr.fetchone()
607 action_mode = etree.fromstring(arch.encode('utf8')).tag
608 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
610 action_mode, = cr.fetchone()
611 if action_type=='tree':
612 values['icon'] = 'STOCK_INDENT'
613 elif action_mode and action_mode.startswith(('tree','kanban','gantt')):
614 values['icon'] = 'STOCK_JUSTIFY_FILL'
615 elif action_mode and action_mode.startswith('graph'):
616 values['icon'] = 'terp-graph'
617 elif action_mode and action_mode.startswith('calendar'):
618 values['icon'] = 'terp-calendar'
620 values['icon'] = 'STOCK_EXECUTE'
621 if not values.get('name', False):
622 values['name'] = action_name
624 elif a_type in ['wizard', 'url', 'client', 'server'] and not values.get('name'):
625 a_table = 'ir_act_%s' % a_type
626 cr.execute('select name from %s where id=%%s' % a_table, (int(a_id),))
629 values['name'] = resw[0]
631 if not values.get('name'):
632 # ensure menu has a name
633 values['name'] = rec_id or '?'
635 if rec.get('sequence'):
636 values['sequence'] = int(rec.get('sequence'))
638 values['icon'] = str(rec.get('icon'))
639 if rec.get('web_icon'):
640 values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon')))
641 if rec.get('web_icon_hover'):
642 values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover')))
644 if rec.get('groups'):
645 g_names = rec.get('groups','').split(',')
647 for group in g_names:
648 if group.startswith('-'):
649 group_id = self.id_get(cr, group[1:])
650 groups_value.append((3, group_id))
652 group_id = self.id_get(cr, group)
653 groups_value.append((4, group_id))
654 values['groups_id'] = groups_value
656 pid = self.pool.get('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)
659 self.idref[rec_id] = int(pid)
661 if rec.get('action') and pid:
662 action = "ir.actions.%s,%d" % (a_type, a_id)
663 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)
664 return 'ir.ui.menu', pid
666 def _assert_equals(self, f1, f2, prec=4):
667 return not round(f1 - f2, prec)
669 def _tag_assert(self, cr, rec, data_node=None):
670 if self.isnoupdate(data_node) and self.mode != 'init':
673 rec_model = rec.get("model",'').encode('ascii')
674 model = self.pool.get(rec_model)
675 assert model, "The model %s does not exist !" % (rec_model,)
676 rec_id = rec.get("id",'').encode('ascii')
677 self._test_xml_id(rec_id)
678 rec_src = rec.get("search",'').encode('utf8')
679 rec_src_count = rec.get("count")
681 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
684 eval_dict = {'ref': _ref(self, cr)}
685 context = self.get_context(data_node, rec, eval_dict)
686 uid = self.get_uid(cr, self.uid, data_node, rec)
688 ids = [self.id_get(cr, rec_id)]
690 q = unsafe_eval(rec_src, eval_dict)
691 ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
693 count = int(rec_src_count)
694 if len(ids) != count:
695 self.assertion_report.record_failure()
696 msg = 'assertion "%s" failed!\n' \
697 ' Incorrect search count:\n' \
698 ' expected count: %d\n' \
699 ' obtained count: %d\n' \
700 % (rec_string, count, len(ids))
704 assert ids is not None,\
705 'You must give either an id or a search criteria'
708 brrec = model.browse(cr, uid, id, context)
710 def __getitem__(self2, key):
713 return dict.__getitem__(self2, key)
715 globals_dict['floatEqual'] = self._assert_equals
716 globals_dict['ref'] = ref
717 globals_dict['_ref'] = ref
718 for test in rec.findall('./test'):
719 f_expr = test.get("expr",'').encode('utf-8')
720 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
721 expression_value = unsafe_eval(f_expr, globals_dict)
722 if expression_value != expected_value: # assertion failed
723 self.assertion_report.record_failure()
724 msg = 'assertion "%s" failed!\n' \
726 ' expected value: %r\n' \
727 ' obtained value: %r\n' \
728 % (rec_string, etree.tostring(test), expected_value, expression_value)
731 else: # all tests were successful for this assertion tag (no break)
732 self.assertion_report.record_success()
734 def _tag_record(self, cr, rec, data_node=None):
735 rec_model = rec.get("model").encode('ascii')
736 model = self.pool.get(rec_model)
737 assert model, "The model %s does not exist !" % (rec_model,)
738 rec_id = rec.get("id",'').encode('ascii')
739 rec_context = rec.get("context", None)
741 rec_context = unsafe_eval(rec_context)
742 self._test_xml_id(rec_id)
743 if self.isnoupdate(data_node) and self.mode != 'init':
744 # check if the xml record has an id string
747 module,rec_id2 = rec_id.split('.')
751 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
752 # check if the resource already existed at the last update
754 # if it existed, we don't update the data, but we need to
755 # know the id of the existing record anyway
756 self.idref[rec_id] = int(id)
759 # if the resource didn't exist
760 if not self.nodeattr2bool(rec, 'forcecreate', True):
761 # we don't want to create it, so we skip it
763 # else, we let the record to be created
766 # otherwise it is skipped
769 for field in rec.findall('./field'):
770 #TODO: most of this code is duplicated above (in _eval_xml)...
771 f_name = field.get("name",'').encode('utf-8')
772 f_ref = field.get("ref",'').encode('utf-8')
773 f_search = field.get("search",'').encode('utf-8')
774 f_model = field.get("model",'').encode('utf-8')
775 if not f_model and model._all_columns.get(f_name,False):
776 f_model = model._all_columns[f_name].column._obj
777 f_use = field.get("use",'').encode('utf-8') or 'id'
781 q = unsafe_eval(f_search, self.idref)
783 assert f_model, 'Define an attribute model="..." in your .XML file !'
784 f_obj = self.pool.get(f_model)
785 # browse the objects searched
786 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
787 # column definitions of the "local" object
788 _cols = self.pool.get(rec_model)._all_columns
789 # if the current field is many2many
790 if (f_name in _cols) and _cols[f_name].column._type=='many2many':
791 f_val = [(6, 0, map(lambda x: x[f_use], s))]
793 # otherwise (we are probably in a many2one field),
794 # take the first element of the search
800 if f_name in model._all_columns \
801 and model._all_columns[f_name].column._type == 'reference':
802 val = self.model_id_get(cr, f_ref)
803 f_val = val[0] + ',' + str(val[1])
805 f_val = self.id_get(cr, f_ref)
807 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
808 if f_name in model._all_columns:
809 import openerp.osv as osv
810 if isinstance(model._all_columns[f_name].column, osv.fields.integer):
814 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 )
816 self.idref[rec_id] = int(id)
817 if config.get('import_partial', False):
821 def id_get(self, cr, id_str):
822 if id_str in self.idref:
823 return self.idref[id_str]
824 res = self.model_id_get(cr, id_str)
825 if res and len(res)>1: res = res[1]
828 def model_id_get(self, cr, id_str):
829 model_data_obj = self.pool.get('ir.model.data')
832 mod,id_str = id_str.split('.')
833 return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
836 if not de.tag in ['terp', 'openerp']:
837 _logger.error("Mismatch xml format")
838 raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
841 _logger.warning("The tag <terp/> is deprecated, use <openerp/>")
843 for n in de.findall('./data'):
845 if rec.tag in self._tags:
847 self._tags[rec.tag](self.cr, rec, n)
849 _logger.error('Parse error in %s:%d: \n%s',
850 rec.getroottree().docinfo.URL,
852 etree.tostring(rec).strip(), exc_info=True)
857 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
863 self.pool = pooler.get_pool(cr.dbname)
866 report = assertion_report.assertion_report()
867 self.assertion_report = report
868 self.noupdate = noupdate
870 'menuitem': self._tag_menuitem,
871 'record': self._tag_record,
872 'assert': self._tag_assert,
873 'report': self._tag_report,
874 'wizard': self._tag_wizard,
875 'delete': self._tag_delete,
876 'ir_set': self._tag_ir_set,
877 'function': self._tag_function,
878 'workflow': self._tag_workflow,
879 'act_window': self._tag_act_window,
883 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
891 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
892 #remove folder path from model
893 head, model = os.path.split(model)
895 pool = pooler.get_pool(cr.dbname)
897 input = cStringIO.StringIO(csvcontent) #FIXME
898 reader = csv.reader(input, quotechar='"', delimiter=',')
899 fields = reader.next()
901 if config.get('import_partial'):
902 fname_partial = module + '/'+ fname
903 if not os.path.isfile(config.get('import_partial')):
904 pickle.dump({}, file(config.get('import_partial'),'w+'))
906 data = pickle.load(file(config.get('import_partial')))
907 if fname_partial in data:
908 if not data[fname_partial]:
911 for i in range(data[fname_partial]):
914 if not (mode == 'init' or 'id' in fields):
915 _logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
921 if (not line) or not reduce(lambda x,y: x or y, line) :
924 datas.append(map(lambda x: misc.ustr(x), line))
926 _logger.error("Cannot import the line: %s", line)
927 result, rows, warning_msg, dummy = pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
929 # Report failed import and abort module install
930 raise Exception(_('Module loading %s failed: file %s could not be processed:\n %s') % (module, fname, warning_msg))
931 if config.get('import_partial'):
932 data = pickle.load(file(config.get('import_partial')))
933 data[fname_partial] = 0
934 pickle.dump(data, file(config.get('import_partial'),'wb'))
940 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
941 doc = etree.parse(xmlfile)
942 relaxng = etree.RelaxNG(
943 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
947 _logger.error('The XML file does not fit the required schema !')
948 _logger.error(misc.ustr(relaxng.error_log.last_error))
953 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
954 obj.parse(doc.getroot())
958 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: