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.loglevels as loglevels
50 import openerp.pooler as pooler
51 from config import config
52 from translate import _
54 # List of etree._Element subclasses that we choose to ignore when parsing XML.
55 from misc import SKIPPED_ELEMENT_TYPES
57 from misc import unquote
59 # Import of XML records requires the unsafe eval as well,
60 # almost everywhere, which is ok because it supposedly comes
61 # from trusted data, but at least we make it obvious now.
63 from safe_eval import safe_eval as eval
65 class ConvertError(Exception):
66 def __init__(self, doc, orig_excpt):
68 self.orig = orig_excpt
71 return 'Exception:\n\t%s\nUsing file:\n%s' % (self.orig, self.d)
74 return lambda x: self.id_get(cr, x)
76 def _obj(pool, cr, uid, model_str, context=None):
77 model = pool.get(model_str)
78 return lambda x: model.browse(cr, uid, x, context=context)
80 def _get_idref(self, cr, uid, model_str, context, idref):
85 version=release.major_version,
89 idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context)
92 def _fix_multiple_roots(node):
94 Surround the children of the ``node`` element of an XML field with a
95 single root "data" element, to prevent having a document with multiple
96 roots once parsed separately.
98 XML nodes should have one root only, but we'd like to support
99 direct multiple roots in our partial documents (like inherited view architectures).
100 As a convention we'll surround multiple root with a container "data" element, to be
101 ignored later when parsing.
103 real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)]
104 if len(real_nodes) > 1:
105 data_node = etree.Element("data")
107 data_node.append(child)
108 node.append(data_node)
110 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
113 if node.tag in ('field','value'):
114 t = node.get('type','char')
115 f_model = node.get('model', '').encode('utf-8')
116 if node.get('search'):
117 f_search = node.get("search",'').encode('utf-8')
118 f_use = node.get("use",'id').encode('utf-8')
119 f_name = node.get("name",'').encode('utf-8')
122 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
123 q = unsafe_eval(f_search, idref2)
124 ids = pool.get(f_model).search(cr, uid, q)
126 ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use]))
127 _cols = pool.get(f_model)._columns
128 if (f_name in _cols) and _cols[f_name]._type=='many2many':
133 if isinstance(f_val, tuple):
136 a_eval = node.get('eval','')
138 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
140 return unsafe_eval(a_eval, idref2)
142 logging.getLogger('openerp.tools.convert.init').error(
143 'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context)
145 def _process(s, idref):
146 m = re.findall('[^%]%\((.*?)\)[ds]', s)
149 idref[id]=self.id_get(cr, id)
152 _fix_multiple_roots(node)
153 return '<?xml version="1.0"?>\n'\
154 +_process("".join([etree.tostring(n, encoding='utf-8')
155 for n in node]), idref)
157 return _process("".join([etree.tostring(n, encoding='utf-8')
158 for n in node]), idref)
159 if t in ('char', 'int', 'float'):
166 return int(d.strip())
168 return float(d.strip())
170 elif t in ('list','tuple'):
172 for n in node.findall('./value'):
173 res.append(_eval_xml(self,n,pool,cr,uid,idref))
177 elif node.tag == "getitem":
179 res=_eval_xml(self,n,pool,cr,uid,idref)
182 elif node.get('type') in ("int", "list"):
183 return res[int(node.get('index'))]
185 return res[node.get('index','').encode("utf8")]
186 elif node.tag == "function":
188 a_eval = node.get('eval','')
190 idref['ref'] = lambda x: self.id_get(cr, x)
191 args = unsafe_eval(a_eval, idref)
193 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
194 if return_val is not None:
195 args.append(return_val)
196 model = pool.get(node.get('model',''))
197 method = node.get('name','')
198 res = getattr(model, method)(cr, uid, *args)
200 elif node.tag == "test":
203 escape_re = re.compile(r'(?<!\\)/')
205 return x.replace('\\/', '/')
207 class xml_import(object):
209 def nodeattr2bool(node, attr, default=False):
210 if not node.get(attr):
212 val = node.get(attr).strip()
215 return val.lower() not in ('0', 'false', 'off')
217 def isnoupdate(self, data_node=None):
218 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
220 def get_context(self, data_node, node, eval_dict):
221 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
222 node_context = node.get("context",'').encode('utf8')
224 for ctx in (data_node_context, node_context):
227 ctx_res = unsafe_eval(ctx, eval_dict)
228 if isinstance(context, dict):
229 context.update(ctx_res)
233 # Some contexts contain references that are only valid at runtime at
234 # client-side, so in that case we keep the original context string
235 # as it is. We also log it, just in case.
237 _logger.debug('Context value (%s) for element with id "%s" or its data node does not parse '\
238 'at server-side, keeping original string, in case it\'s meant for client side only',
239 ctx, node.get('id','n/a'), exc_info=True)
242 def get_uid(self, cr, uid, data_node, node):
243 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
245 return self.id_get(cr, node_uid)
248 def _test_xml_id(self, xml_id):
251 module, id = xml_id.split('.', 1)
252 assert '.' not in id, """The ID reference "%s" must contain
253 maximum one dot. They are used to refer to other modules ID, in the
254 form: module.record_id""" % (xml_id,)
255 if module != self.module:
256 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
257 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
260 _logger.error('id: %s is to long (max: 64)', id)
262 def _tag_delete(self, cr, rec, data_node=None):
263 d_model = rec.get("model",'')
264 d_search = rec.get("search",'').encode('utf-8')
265 d_id = rec.get("id",'')
269 idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
270 ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search, idref))
273 ids.append(self.id_get(cr, d_id))
275 # d_id cannot be found. doesn't matter in this case
278 self.pool.get(d_model).unlink(cr, self.uid, ids)
280 def _remove_ir_values(self, cr, name, value, model):
281 ir_values_obj = self.pool.get('ir.values')
282 ir_value_ids = ir_values_obj.search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
284 ir_values_obj.unlink(cr, self.uid, ir_value_ids)
288 def _tag_report(self, cr, rec, data_node=None):
290 for dest,f in (('name','string'),('model','model'),('report_name','name')):
291 res[dest] = rec.get(f,'').encode('utf8')
292 assert res[dest], "Attribute %s of report is empty !" % (f,)
293 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),
294 ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage')):
296 res[dest] = rec.get(field).encode('utf8')
298 res['auto'] = eval(rec.get('auto','False'))
300 sxw_content = misc.file_open(rec.get('sxw')).read()
301 res['report_sxw_content'] = sxw_content
302 if rec.get('header'):
303 res['header'] = eval(rec.get('header','False'))
304 if rec.get('report_type'):
305 res['report_type'] = rec.get('report_type')
307 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
309 xml_id = rec.get('id','').encode('utf8')
310 self._test_xml_id(xml_id)
312 if rec.get('groups'):
313 g_names = rec.get('groups','').split(',')
315 for group in g_names:
316 if group.startswith('-'):
317 group_id = self.id_get(cr, group[1:])
318 groups_value.append((3, group_id))
320 group_id = self.id_get(cr, group)
321 groups_value.append((4, group_id))
322 res['groups_id'] = groups_value
324 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)
325 self.idref[xml_id] = int(id)
327 if not rec.get('menu') or eval(rec.get('menu','False')):
328 keyword = str(rec.get('keyword', 'client_print_multi'))
329 value = 'ir.actions.report.xml,'+str(id)
330 replace = rec.get('replace', True)
331 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)
332 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
333 # Special check for report having attribute menu=False on update
334 value = 'ir.actions.report.xml,'+str(id)
335 self._remove_ir_values(cr, res['name'], value, res['model'])
338 def _tag_function(self, cr, rec, data_node=None):
339 if self.isnoupdate(data_node) and self.mode != 'init':
341 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
342 uid = self.get_uid(cr, self.uid, data_node, rec)
343 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
346 def _tag_wizard(self, cr, rec, data_node=None):
347 string = rec.get("string",'').encode('utf8')
348 model = rec.get("model",'').encode('utf8')
349 name = rec.get("name",'').encode('utf8')
350 xml_id = rec.get('id','').encode('utf8')
351 self._test_xml_id(xml_id)
352 multi = rec.get('multi','') and eval(rec.get('multi','False'))
353 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
355 if rec.get('groups'):
356 g_names = rec.get('groups','').split(',')
358 for group in g_names:
359 if group.startswith('-'):
360 group_id = self.id_get(cr, group[1:])
361 groups_value.append((3, group_id))
363 group_id = self.id_get(cr, group)
364 groups_value.append((4, group_id))
365 res['groups_id'] = groups_value
367 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)
368 self.idref[xml_id] = int(id)
370 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
371 keyword = str(rec.get('keyword','') or 'client_action_multi')
372 value = 'ir.actions.wizard,'+str(id)
373 replace = rec.get("replace",'') or True
374 self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
375 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
376 # Special check for wizard having attribute menu=False on update
377 value = 'ir.actions.wizard,'+str(id)
378 self._remove_ir_values(cr, string, value, model)
380 def _tag_url(self, cr, rec, data_node=None):
381 url = rec.get("url",'').encode('utf8')
382 target = rec.get("target",'').encode('utf8')
383 name = rec.get("name",'').encode('utf8')
384 xml_id = rec.get('id','').encode('utf8')
385 self._test_xml_id(xml_id)
387 res = {'name': name, 'url': url, 'target':target}
389 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)
390 self.idref[xml_id] = int(id)
392 def _tag_act_window(self, cr, rec, data_node=None):
393 name = rec.get('name','').encode('utf-8')
394 xml_id = rec.get('id','').encode('utf8')
395 self._test_xml_id(xml_id)
396 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
398 if rec.get('view_id'):
399 view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8'))
400 domain = rec.get('domain','').encode('utf-8') or '[]'
401 res_model = rec.get('res_model','').encode('utf-8')
402 src_model = rec.get('src_model','').encode('utf-8')
403 view_type = rec.get('view_type','').encode('utf-8') or 'form'
404 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
405 usage = rec.get('usage','').encode('utf-8')
406 limit = rec.get('limit','').encode('utf-8')
407 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
410 # Act_window's 'domain' and 'context' contain mostly literals
411 # but they can also refer to the variables provided below
412 # in eval_context, so we need to eval() them before storing.
413 # Among the context variables, 'active_id' refers to
414 # the currently selected items in a list view, and only
415 # takes meaning at runtime on the client side. For this
416 # reason it must remain a bare variable in domain and context,
417 # even after eval() at server-side. We use the special 'unquote'
418 # class to achieve this effect: a string which has itself, unquoted,
420 active_id = unquote("active_id")
421 active_ids = unquote("active_ids")
422 active_model = unquote("active_model")
425 return self.id_get(cr, str_id)
427 # Include all locals() in eval_context, for backwards compatibility
434 'res_model': res_model,
435 'src_model': src_model,
436 'view_type': view_type,
437 'view_mode': view_mode,
440 'auto_refresh': auto_refresh,
442 'active_id': active_id,
443 'active_ids': active_ids,
444 'active_model': active_model,
447 context = self.get_context(data_node, rec, eval_context)
450 domain = unsafe_eval(domain, eval_context)
452 # Some domains contain references that are only valid at runtime at
453 # client-side, so in that case we keep the original domain string
454 # as it is. We also log it, just in case.
455 _logger.debug('Domain value (%s) for element with id "%s" does not parse '\
456 'at server-side, keeping original string, in case it\'s meant for client side only',
457 domain, xml_id or 'n/a', exc_info=True)
464 'res_model': res_model,
465 'src_model': src_model,
466 'view_type': view_type,
467 'view_mode': view_mode,
470 'auto_refresh': auto_refresh,
473 if rec.get('groups'):
474 g_names = rec.get('groups','').split(',')
476 for group in g_names:
477 if group.startswith('-'):
478 group_id = self.id_get(cr, group[1:])
479 groups_value.append((3, group_id))
481 group_id = self.id_get(cr, group)
482 groups_value.append((4, group_id))
483 res['groups_id'] = groups_value
485 if rec.get('target'):
486 res['target'] = rec.get('target','')
488 res['multi'] = rec.get('multi', False)
489 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)
490 self.idref[xml_id] = int(id)
493 #keyword = 'client_action_relate'
494 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
495 value = 'ir.actions.act_window,'+str(id)
496 replace = rec.get('replace','') or True
497 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)
498 # TODO add remove ir.model.data
500 def _tag_ir_set(self, cr, rec, data_node=None):
501 if self.mode != 'init':
504 for field in rec.findall('./field'):
505 f_name = field.get("name",'').encode('utf-8')
506 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
508 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))
510 def _tag_workflow(self, cr, rec, data_node=None):
511 if self.isnoupdate(data_node) and self.mode != 'init':
513 model = str(rec.get('model',''))
514 w_ref = rec.get('ref','')
516 id = self.id_get(cr, w_ref)
518 number_children = len(rec)
519 assert number_children > 0,\
520 'You must define a child node if you dont give a ref'
521 assert number_children == 1,\
522 'Only one child node is accepted (%d given)' % number_children
523 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
525 uid = self.get_uid(cr, self.uid, data_node, rec)
526 import openerp.netsvc as netsvc
527 wf_service = netsvc.LocalService("workflow")
528 wf_service.trg_validate(uid, model,
530 str(rec.get('action','')), cr)
533 # Support two types of notation:
534 # name="Inventory Control/Sending Goods"
539 def _tag_menuitem(self, cr, rec, data_node=None):
540 rec_id = rec.get("id",'').encode('ascii')
541 self._test_xml_id(rec_id)
542 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
544 values = {'parent_id': False}
545 if rec.get('parent', False) is False and len(m_l) > 1:
546 # No parent attribute specified and the menu name has several menu components,
547 # try to determine the ID of the parent according to menu path
550 values['name'] = m_l[-1]
551 m_l = m_l[:-1] # last part is our name, not a parent
552 for idx, menu_elem in enumerate(m_l):
554 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
556 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
561 # the menuitem does't exist but we are in branch (not a leaf)
562 _logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l))
563 pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
564 values['parent_id'] = pid
566 # The parent attribute was specified, if non-empty determine its ID, otherwise
567 # explicitly make a top-level menu
568 if rec.get('parent'):
569 menu_parent_id = self.id_get(cr, rec.get('parent',''))
571 # we get here with <menuitem parent="">, explicit clear of parent, or
572 # if no parent attribute at all but menu name is not a menu path
573 menu_parent_id = False
574 values = {'parent_id': menu_parent_id}
576 values['name'] = rec.get('name')
578 res = [ self.id_get(cr, rec.get('id','')) ]
582 if rec.get('action'):
583 a_action = rec.get('action','').encode('utf8')
585 # determine the type of action
586 a_type, a_id = self.model_id_get(cr, a_action)
587 a_type = a_type.split('.')[-1] # keep only type part
590 "act_window": 'STOCK_NEW',
591 "report.xml": 'STOCK_PASTE',
592 "wizard": 'STOCK_EXECUTE',
593 "url": 'STOCK_JUMP_TO',
594 "client": 'STOCK_EXECUTE',
595 "server": 'STOCK_EXECUTE',
597 values['icon'] = icons.get(a_type,'STOCK_NEW')
599 if a_type=='act_window':
600 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
601 rrres = cr.fetchone()
602 assert rrres, "No window action defined for this id %s !\n" \
603 "Verify that this is a window action or add a type argument." % (a_action,)
604 action_type,action_mode,action_name,view_id,target = rrres
606 cr.execute('SELECT arch FROM ir_ui_view WHERE id=%s', (int(view_id),))
607 arch, = cr.fetchone()
608 action_mode = etree.fromstring(arch.encode('utf8')).tag
609 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
611 action_mode, = cr.fetchone()
612 if action_type=='tree':
613 values['icon'] = 'STOCK_INDENT'
614 elif action_mode and action_mode.startswith(('tree','kanban','gantt')):
615 values['icon'] = 'STOCK_JUSTIFY_FILL'
616 elif action_mode and action_mode.startswith('graph'):
617 values['icon'] = 'terp-graph'
618 elif action_mode and action_mode.startswith('calendar'):
619 values['icon'] = 'terp-calendar'
621 values['icon'] = 'STOCK_EXECUTE'
622 if not values.get('name', False):
623 values['name'] = action_name
625 elif a_type in ['wizard', 'url', 'client', 'server'] and not values.get('name'):
626 a_table = 'ir_act_%s' % a_type
627 cr.execute('select name from %s where id=%%s' % a_table, (int(a_id),))
630 values['name'] = resw[0]
632 if not values.get('name'):
633 # ensure menu has a name
634 values['name'] = rec_id or '?'
636 if rec.get('sequence'):
637 values['sequence'] = int(rec.get('sequence'))
639 values['icon'] = str(rec.get('icon'))
640 if rec.get('web_icon'):
641 values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon')))
642 if rec.get('web_icon_hover'):
643 values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover')))
645 if rec.get('groups'):
646 g_names = rec.get('groups','').split(',')
648 for group in g_names:
649 if group.startswith('-'):
650 group_id = self.id_get(cr, group[1:])
651 groups_value.append((3, group_id))
653 group_id = self.id_get(cr, group)
654 groups_value.append((4, group_id))
655 values['groups_id'] = groups_value
657 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)
660 self.idref[rec_id] = int(pid)
662 if rec.get('action') and pid:
663 action = "ir.actions.%s,%d" % (a_type, a_id)
664 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)
665 return ('ir.ui.menu', pid)
667 def _assert_equals(self, f1, f2, prec=4):
668 return not round(f1 - f2, prec)
670 def _tag_assert(self, cr, rec, data_node=None):
671 if self.isnoupdate(data_node) and self.mode != 'init':
674 rec_model = rec.get("model",'').encode('ascii')
675 model = self.pool.get(rec_model)
676 assert model, "The model %s does not exist !" % (rec_model,)
677 rec_id = rec.get("id",'').encode('ascii')
678 self._test_xml_id(rec_id)
679 rec_src = rec.get("search",'').encode('utf8')
680 rec_src_count = rec.get("count")
682 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
685 eval_dict = {'ref': _ref(self, cr)}
686 context = self.get_context(data_node, rec, eval_dict)
687 uid = self.get_uid(cr, self.uid, data_node, rec)
689 ids = [self.id_get(cr, rec_id)]
691 q = unsafe_eval(rec_src, eval_dict)
692 ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
694 count = int(rec_src_count)
695 if len(ids) != count:
696 self.assertion_report.record_failure()
697 msg = 'assertion "%s" failed!\n' \
698 ' Incorrect search count:\n' \
699 ' expected count: %d\n' \
700 ' obtained count: %d\n' \
701 % (rec_string, count, len(ids))
705 assert ids is not None,\
706 'You must give either an id or a search criteria'
709 brrec = model.browse(cr, uid, id, context)
711 def __getitem__(self2, key):
714 return dict.__getitem__(self2, key)
716 globals_dict['floatEqual'] = self._assert_equals
717 globals_dict['ref'] = ref
718 globals_dict['_ref'] = ref
719 for test in rec.findall('./test'):
720 f_expr = test.get("expr",'').encode('utf-8')
721 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
722 expression_value = unsafe_eval(f_expr, globals_dict)
723 if expression_value != expected_value: # assertion failed
724 self.assertion_report.record_failure()
725 msg = 'assertion "%s" failed!\n' \
727 ' expected value: %r\n' \
728 ' obtained value: %r\n' \
729 % (rec_string, etree.tostring(test), expected_value, expression_value)
732 else: # all tests were successful for this assertion tag (no break)
733 self.assertion_report.record_success()
735 def _tag_record(self, cr, rec, data_node=None):
736 rec_model = rec.get("model").encode('ascii')
737 model = self.pool.get(rec_model)
738 assert model, "The model %s does not exist !" % (rec_model,)
739 rec_id = rec.get("id",'').encode('ascii')
740 rec_context = rec.get("context", None)
742 rec_context = unsafe_eval(rec_context)
743 self._test_xml_id(rec_id)
744 if self.isnoupdate(data_node) and self.mode != 'init':
745 # check if the xml record has an id string
748 module,rec_id2 = rec_id.split('.')
752 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
753 # check if the resource already existed at the last update
755 # if it existed, we don't update the data, but we need to
756 # know the id of the existing record anyway
757 self.idref[rec_id] = int(id)
760 # if the resource didn't exist
761 if not self.nodeattr2bool(rec, 'forcecreate', True):
762 # we don't want to create it, so we skip it
764 # else, we let the record to be created
767 # otherwise it is skipped
770 for field in rec.findall('./field'):
771 #TODO: most of this code is duplicated above (in _eval_xml)...
772 f_name = field.get("name",'').encode('utf-8')
773 f_ref = field.get("ref",'').encode('utf-8')
774 f_search = field.get("search",'').encode('utf-8')
775 f_model = field.get("model",'').encode('utf-8')
776 if not f_model and model._columns.get(f_name,False):
777 f_model = model._columns[f_name]._obj
778 f_use = field.get("use",'').encode('utf-8') or 'id'
782 q = unsafe_eval(f_search, self.idref)
784 assert f_model, 'Define an attribute model="..." in your .XML file !'
785 f_obj = self.pool.get(f_model)
786 # browse the objects searched
787 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
788 # column definitions of the "local" object
789 _cols = self.pool.get(rec_model)._columns
790 # if the current field is many2many
791 if (f_name in _cols) and _cols[f_name]._type=='many2many':
792 f_val = [(6, 0, map(lambda x: x[f_use], s))]
794 # otherwise (we are probably in a many2one field),
795 # take the first element of the search
801 if f_name in model._columns \
802 and model._columns[f_name]._type == 'reference':
803 val = self.model_id_get(cr, f_ref)
804 f_val = val[0] + ',' + str(val[1])
806 f_val = self.id_get(cr, f_ref)
808 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
809 if model._columns.has_key(f_name):
810 import openerp.osv as osv
811 if isinstance(model._columns[f_name], osv.fields.integer):
815 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 )
817 self.idref[rec_id] = int(id)
818 if config.get('import_partial', False):
822 def id_get(self, cr, id_str):
823 if id_str in self.idref:
824 return self.idref[id_str]
825 res = self.model_id_get(cr, id_str)
826 if res and len(res)>1: res = res[1]
829 def model_id_get(self, cr, id_str):
830 model_data_obj = self.pool.get('ir.model.data')
833 mod,id_str = id_str.split('.')
834 return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
837 if not de.tag in ['terp', 'openerp']:
838 _logger.error("Mismatch xml format")
839 raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
842 _logger.warning("The tag <terp/> is deprecated, use <openerp/>")
844 for n in de.findall('./data'):
846 if rec.tag in self._tags:
848 self._tags[rec.tag](self.cr, rec, n)
850 _logger.error('Parse error in %s:%d: \n%s',
851 rec.getroottree().docinfo.URL,
853 etree.tostring(rec).strip(), exc_info=True)
858 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
864 self.pool = pooler.get_pool(cr.dbname)
867 report = assertion_report.assertion_report()
868 self.assertion_report = report
869 self.noupdate = noupdate
871 'menuitem': self._tag_menuitem,
872 'record': self._tag_record,
873 'assert': self._tag_assert,
874 'report': self._tag_report,
875 'wizard': self._tag_wizard,
876 'delete': self._tag_delete,
877 'ir_set': self._tag_ir_set,
878 'function': self._tag_function,
879 'workflow': self._tag_workflow,
880 'act_window': self._tag_act_window,
884 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
892 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
893 #remove folder path from model
894 head, model = os.path.split(model)
896 pool = pooler.get_pool(cr.dbname)
898 input = cStringIO.StringIO(csvcontent) #FIXME
899 reader = csv.reader(input, quotechar='"', delimiter=',')
900 fields = reader.next()
902 if config.get('import_partial'):
903 fname_partial = module + '/'+ fname
904 if not os.path.isfile(config.get('import_partial')):
905 pickle.dump({}, file(config.get('import_partial'),'w+'))
907 data = pickle.load(file(config.get('import_partial')))
908 if fname_partial in data:
909 if not data[fname_partial]:
912 for i in range(data[fname_partial]):
915 if not (mode == 'init' or 'id' in fields):
916 _logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
922 if (not line) or not reduce(lambda x,y: x or y, line) :
925 datas.append(map(lambda x: misc.ustr(x), line))
927 _logger.error("Cannot import the line: %s", line)
928 result, rows, warning_msg, dummy = pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
930 # Report failed import and abort module install
931 raise Exception(_('Module loading failed: file %s/%s could not be processed:\n %s') % (module, fname, warning_msg))
932 if config.get('import_partial'):
933 data = pickle.load(file(config.get('import_partial')))
934 data[fname_partial] = 0
935 pickle.dump(data, file(config.get('import_partial'),'wb'))
941 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
942 doc = etree.parse(xmlfile)
943 relaxng = etree.RelaxNG(
944 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
948 _logger.error('The XML file does not fit the required schema !')
949 _logger.error(misc.ustr(relaxng.error_log.last_error))
954 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
955 obj.parse(doc.getroot())
959 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: