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','')
139 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
141 return unsafe_eval(a_eval, idref2)
143 _logger.warning('could not eval(%s) for %s in %s' % (a_eval, node.get('name'), context), exc_info=True)
146 def _process(s, idref):
147 m = re.findall('[^%]%\((.*?)\)[ds]', s)
150 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')
157 if t in ('char', 'int', 'float'):
164 return int(d.strip())
166 return float(d.strip())
168 elif t in ('list','tuple'):
170 for n in node.findall('./value'):
171 res.append(_eval_xml(self,n,pool,cr,uid,idref))
175 elif node.tag == "getitem":
177 res=_eval_xml(self,n,pool,cr,uid,idref)
180 elif node.get('type') in ("int", "list"):
181 return res[int(node.get('index'))]
183 return res[node.get('index','').encode("utf8")]
184 elif node.tag == "function":
186 a_eval = node.get('eval','')
188 idref['ref'] = lambda x: self.id_get(cr, x)
189 args = unsafe_eval(a_eval, idref)
191 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
192 if return_val is not None:
193 args.append(return_val)
194 model = pool.get(node.get('model',''))
195 method = node.get('name','')
196 res = getattr(model, method)(cr, uid, *args)
198 elif node.tag == "test":
201 escape_re = re.compile(r'(?<!\\)/')
203 return x.replace('\\/', '/')
205 class xml_import(object):
207 def nodeattr2bool(node, attr, default=False):
208 if not node.get(attr):
210 val = node.get(attr).strip()
213 return val.lower() not in ('0', 'false', 'off')
215 def isnoupdate(self, data_node=None):
216 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
218 def get_context(self, data_node, node, eval_dict):
219 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
220 node_context = node.get("context",'').encode('utf8')
222 for ctx in (data_node_context, node_context):
225 ctx_res = unsafe_eval(ctx, eval_dict)
226 if isinstance(context, dict):
227 context.update(ctx_res)
231 # Some contexts contain references that are only valid at runtime at
232 # client-side, so in that case we keep the original context string
233 # as it is. We also log it, just in case.
235 _logger.debug('Context value (%s) for element with id "%s" or its data node does not parse '\
236 'at server-side, keeping original string, in case it\'s meant for client side only',
237 ctx, node.get('id','n/a'), exc_info=True)
240 def get_uid(self, cr, uid, data_node, node):
241 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
243 return self.id_get(cr, node_uid)
246 def _test_xml_id(self, xml_id):
249 module, id = xml_id.split('.', 1)
250 assert '.' not in id, """The ID reference "%s" must contain
251 maximum one dot. They are used to refer to other modules ID, in the
252 form: module.record_id""" % (xml_id,)
253 if module != self.module:
254 modcnt = self.pool.get('ir.module.module').search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
255 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
258 _logger.error('id: %s is to long (max: 64)', id)
260 def _tag_delete(self, cr, rec, data_node=None):
261 d_model = rec.get("model",'')
262 d_search = rec.get("search",'').encode('utf-8')
263 d_id = rec.get("id",'')
267 idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
268 ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search, idref))
271 ids.append(self.id_get(cr, d_id))
273 # d_id cannot be found. doesn't matter in this case
276 self.pool.get(d_model).unlink(cr, self.uid, ids)
278 def _remove_ir_values(self, cr, name, value, model):
279 ir_values_obj = self.pool.get('ir.values')
280 ir_value_ids = ir_values_obj.search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
282 ir_values_obj.unlink(cr, self.uid, ir_value_ids)
286 def _tag_report(self, cr, rec, data_node=None):
288 for dest,f in (('name','string'),('model','model'),('report_name','name')):
289 res[dest] = rec.get(f,'').encode('utf8')
290 assert res[dest], "Attribute %s of report is empty !" % (f,)
291 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),
292 ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage')):
294 res[dest] = rec.get(field).encode('utf8')
296 res['auto'] = eval(rec.get('auto','False'))
298 sxw_content = misc.file_open(rec.get('sxw')).read()
299 res['report_sxw_content'] = sxw_content
300 if rec.get('header'):
301 res['header'] = eval(rec.get('header','False'))
302 if rec.get('report_type'):
303 res['report_type'] = rec.get('report_type')
305 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
307 xml_id = rec.get('id','').encode('utf8')
308 self._test_xml_id(xml_id)
310 if rec.get('groups'):
311 g_names = rec.get('groups','').split(',')
313 for group in g_names:
314 if group.startswith('-'):
315 group_id = self.id_get(cr, group[1:])
316 groups_value.append((3, group_id))
318 group_id = self.id_get(cr, group)
319 groups_value.append((4, group_id))
320 res['groups_id'] = groups_value
322 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)
323 self.idref[xml_id] = int(id)
325 if not rec.get('menu') or eval(rec.get('menu','False')):
326 keyword = str(rec.get('keyword', 'client_print_multi'))
327 value = 'ir.actions.report.xml,'+str(id)
328 replace = rec.get('replace', True)
329 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)
330 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
331 # Special check for report having attribute menu=False on update
332 value = 'ir.actions.report.xml,'+str(id)
333 self._remove_ir_values(cr, res['name'], value, res['model'])
336 def _tag_function(self, cr, rec, data_node=None):
337 if self.isnoupdate(data_node) and self.mode != 'init':
339 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
340 uid = self.get_uid(cr, self.uid, data_node, rec)
341 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
344 def _tag_wizard(self, cr, rec, data_node=None):
345 string = rec.get("string",'').encode('utf8')
346 model = rec.get("model",'').encode('utf8')
347 name = rec.get("name",'').encode('utf8')
348 xml_id = rec.get('id','').encode('utf8')
349 self._test_xml_id(xml_id)
350 multi = rec.get('multi','') and eval(rec.get('multi','False'))
351 res = {'name': string, 'wiz_name': name, 'multi': multi, 'model': model}
353 if rec.get('groups'):
354 g_names = rec.get('groups','').split(',')
356 for group in g_names:
357 if group.startswith('-'):
358 group_id = self.id_get(cr, group[1:])
359 groups_value.append((3, group_id))
361 group_id = self.id_get(cr, group)
362 groups_value.append((4, group_id))
363 res['groups_id'] = groups_value
365 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)
366 self.idref[xml_id] = int(id)
368 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
369 keyword = str(rec.get('keyword','') or 'client_action_multi')
370 value = 'ir.actions.wizard,'+str(id)
371 replace = rec.get("replace",'') or True
372 self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, string, [model], value, replace=replace, isobject=True, xml_id=xml_id)
373 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
374 # Special check for wizard having attribute menu=False on update
375 value = 'ir.actions.wizard,'+str(id)
376 self._remove_ir_values(cr, string, value, model)
378 def _tag_url(self, cr, rec, data_node=None):
379 url = rec.get("url",'').encode('utf8')
380 target = rec.get("target",'').encode('utf8')
381 name = rec.get("name",'').encode('utf8')
382 xml_id = rec.get('id','').encode('utf8')
383 self._test_xml_id(xml_id)
385 res = {'name': name, 'url': url, 'target':target}
387 id = self.pool.get('ir.model.data')._update(cr, self.uid, "ir.actions.url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
388 self.idref[xml_id] = int(id)
390 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
391 keyword = str(rec.get('keyword','') or 'client_action_multi')
392 value = 'ir.actions.url,'+str(id)
393 replace = rec.get("replace",'') or True
394 self.pool.get('ir.model.data').ir_set(cr, self.uid, 'action', keyword, url, ["ir.actions.url"], value, replace=replace, isobject=True, xml_id=xml_id)
395 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
396 # Special check for URL having attribute menu=False on update
397 value = 'ir.actions.url,'+str(id)
398 self._remove_ir_values(cr, url, value, "ir.actions.url")
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'] = rec.get('multi', False)
497 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)
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.get('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.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))
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 import openerp.netsvc as netsvc
535 wf_service = netsvc.LocalService("workflow")
536 wf_service.trg_validate(uid, model,
538 str(rec.get('action','')), cr)
541 # Support two types of notation:
542 # name="Inventory Control/Sending Goods"
547 def _tag_menuitem(self, cr, rec, data_node=None):
548 rec_id = rec.get("id",'').encode('ascii')
549 self._test_xml_id(rec_id)
550 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
552 values = {'parent_id': False}
553 if rec.get('parent', False) is False and len(m_l) > 1:
554 # No parent attribute specified and the menu name has several menu components,
555 # try to determine the ID of the parent according to menu path
558 values['name'] = m_l[-1]
559 m_l = m_l[:-1] # last part is our name, not a parent
560 for idx, menu_elem in enumerate(m_l):
562 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
564 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
569 # the menuitem does't exist but we are in branch (not a leaf)
570 _logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l))
571 pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
572 values['parent_id'] = pid
574 # The parent attribute was specified, if non-empty determine its ID, otherwise
575 # explicitly make a top-level menu
576 if rec.get('parent'):
577 menu_parent_id = self.id_get(cr, rec.get('parent',''))
579 # we get here with <menuitem parent="">, explicit clear of parent, or
580 # if no parent attribute at all but menu name is not a menu path
581 menu_parent_id = False
582 values = {'parent_id': menu_parent_id}
584 values['name'] = rec.get('name')
586 res = [ self.id_get(cr, rec.get('id','')) ]
590 if rec.get('action'):
591 a_action = rec.get('action','').encode('utf8')
592 a_type = rec.get('type','').encode('utf8') or 'act_window'
594 "act_window": 'STOCK_NEW',
595 "report.xml": 'STOCK_PASTE',
596 "wizard": 'STOCK_EXECUTE',
597 "url": 'STOCK_JUMP_TO'
599 values['icon'] = icons.get(a_type,'STOCK_NEW')
600 if a_type=='act_window':
601 a_id = self.id_get(cr, a_action)
602 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
603 rrres = cr.fetchone()
604 assert rrres, "No window action defined for this id %s !\n" \
605 "Verify that this is a window action or add a type argument." % (a_action,)
606 action_type,action_mode,action_name,view_id,target = rrres
608 cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (int(view_id),))
609 action_mode, = cr.fetchone()
610 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
612 action_mode, = cr.fetchone()
613 if action_type=='tree':
614 values['icon'] = 'STOCK_INDENT'
615 elif action_mode and action_mode.startswith(('tree','kanban','gantt')):
616 values['icon'] = 'STOCK_JUSTIFY_FILL'
617 elif action_mode and action_mode.startswith('graph'):
618 values['icon'] = 'terp-graph'
619 elif action_mode and action_mode.startswith('calendar'):
620 values['icon'] = 'terp-calendar'
622 values['icon'] = 'STOCK_EXECUTE'
623 if not values.get('name', False):
624 values['name'] = action_name
625 elif a_type=='wizard':
626 a_id = self.id_get(cr, a_action)
627 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
629 if (not values.get('name', False)) and resw:
630 values['name'] = resw[0]
632 a_id = self.id_get(cr, a_action)
633 cr.execute('select name from ir_act_url where id=%s', (int(a_id),))
635 if (not values.get('name')) and resw:
636 values['name'] = resw[0]
637 if rec.get('sequence'):
638 values['sequence'] = int(rec.get('sequence'))
640 values['icon'] = str(rec.get('icon'))
641 if rec.get('web_icon'):
642 values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon')))
643 if rec.get('web_icon_hover'):
644 values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover')))
646 if rec.get('groups'):
647 g_names = rec.get('groups','').split(',')
649 for group in g_names:
650 if group.startswith('-'):
651 group_id = self.id_get(cr, group[1:])
652 groups_value.append((3, group_id))
654 group_id = self.id_get(cr, group)
655 groups_value.append((4, group_id))
656 values['groups_id'] = groups_value
658 xml_id = rec.get('id','').encode('utf8')
659 self._test_xml_id(xml_id)
660 pid = self.pool.get('ir.model.data')._update(cr, self.uid, 'ir.ui.menu', self.module, values, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode, res_id=res and res[0] or False)
663 self.idref[rec_id] = int(pid)
665 if rec.get('action') and pid:
666 a_action = rec.get('action').encode('utf8')
667 a_type = rec.get('type','').encode('utf8') or 'act_window'
668 a_id = self.id_get(cr, a_action)
669 action = "ir.actions.%s,%d" % (a_type, a_id)
670 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)
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.get(rec_model)
682 assert model, "The model %s does not exist !" % (rec_model,)
683 rec_id = rec.get("id",'').encode('ascii')
684 self._test_xml_id(rec_id)
685 rec_src = rec.get("search",'').encode('utf8')
686 rec_src_count = rec.get("count")
688 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
691 eval_dict = {'ref': _ref(self, cr)}
692 context = self.get_context(data_node, rec, eval_dict)
693 uid = self.get_uid(cr, self.uid, data_node, rec)
695 ids = [self.id_get(cr, rec_id)]
697 q = unsafe_eval(rec_src, eval_dict)
698 ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
700 count = int(rec_src_count)
701 if len(ids) != count:
702 self.assertion_report.record_failure()
703 msg = 'assertion "%s" failed!\n' \
704 ' Incorrect search count:\n' \
705 ' expected count: %d\n' \
706 ' obtained count: %d\n' \
707 % (rec_string, count, len(ids))
711 assert ids is not None,\
712 'You must give either an id or a search criteria'
715 brrec = model.browse(cr, uid, id, context)
717 def __getitem__(self2, key):
720 return dict.__getitem__(self2, key)
722 globals_dict['floatEqual'] = self._assert_equals
723 globals_dict['ref'] = ref
724 globals_dict['_ref'] = ref
725 for test in rec.findall('./test'):
726 f_expr = test.get("expr",'').encode('utf-8')
727 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
728 expression_value = unsafe_eval(f_expr, globals_dict)
729 if expression_value != expected_value: # assertion failed
730 self.assertion_report.record_failure()
731 msg = 'assertion "%s" failed!\n' \
733 ' expected value: %r\n' \
734 ' obtained value: %r\n' \
735 % (rec_string, etree.tostring(test), expected_value, expression_value)
738 else: # all tests were successful for this assertion tag (no break)
739 self.assertion_report.record_success()
741 def _tag_record(self, cr, rec, data_node=None):
742 rec_model = rec.get("model").encode('ascii')
743 model = self.pool.get(rec_model)
744 assert model, "The model %s does not exist !" % (rec_model,)
745 rec_id = rec.get("id",'').encode('ascii')
746 rec_context = rec.get("context", None)
748 rec_context = unsafe_eval(rec_context)
749 self._test_xml_id(rec_id)
750 if self.isnoupdate(data_node) and self.mode != 'init':
751 # check if the xml record has an id string
754 module,rec_id2 = rec_id.split('.')
758 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
759 # check if the resource already existed at the last update
761 # if it existed, we don't update the data, but we need to
762 # know the id of the existing record anyway
763 self.idref[rec_id] = int(id)
766 # if the resource didn't exist
767 if not self.nodeattr2bool(rec, 'forcecreate', True):
768 # we don't want to create it, so we skip it
770 # else, we let the record to be created
773 # otherwise it is skipped
776 for field in rec.findall('./field'):
777 #TODO: most of this code is duplicated above (in _eval_xml)...
778 f_name = field.get("name",'').encode('utf-8')
779 f_ref = field.get("ref",'').encode('utf-8')
780 f_search = field.get("search",'').encode('utf-8')
781 f_model = field.get("model",'').encode('utf-8')
782 if not f_model and model._columns.get(f_name,False):
783 f_model = model._columns[f_name]._obj
784 f_use = field.get("use",'').encode('utf-8') or 'id'
788 q = unsafe_eval(f_search, self.idref)
790 assert f_model, 'Define an attribute model="..." in your .XML file !'
791 f_obj = self.pool.get(f_model)
792 # browse the objects searched
793 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
794 # column definitions of the "local" object
795 _cols = self.pool.get(rec_model)._columns
796 # if the current field is many2many
797 if (f_name in _cols) and _cols[f_name]._type=='many2many':
798 f_val = [(6, 0, map(lambda x: x[f_use], s))]
800 # otherwise (we are probably in a many2one field),
801 # take the first element of the search
807 if f_name in model._columns \
808 and model._columns[f_name]._type == 'reference':
809 val = self.model_id_get(cr, f_ref)
810 f_val = val[0] + ',' + str(val[1])
812 f_val = self.id_get(cr, f_ref)
814 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
815 if model._columns.has_key(f_name):
816 import openerp.osv as osv
817 if isinstance(model._columns[f_name], osv.fields.integer):
821 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 )
823 self.idref[rec_id] = int(id)
824 if config.get('import_partial', False):
828 def id_get(self, cr, id_str):
829 if id_str in self.idref:
830 return self.idref[id_str]
831 res = self.model_id_get(cr, id_str)
832 if res and len(res)>1: res = res[1]
835 def model_id_get(self, cr, id_str):
836 model_data_obj = self.pool.get('ir.model.data')
839 mod,id_str = id_str.split('.')
840 return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
843 if not de.tag in ['terp', 'openerp']:
844 _logger.error("Mismatch xml format")
845 raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
848 _logger.warning("The tag <terp/> is deprecated, use <openerp/>")
850 for n in de.findall('./data'):
852 if rec.tag in self._tags:
854 self._tags[rec.tag](self.cr, rec, n)
856 _logger.error('Parse error in %s:%d: \n%s',
857 rec.getroottree().docinfo.URL,
859 etree.tostring(rec).strip(), exc_info=True)
864 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
870 self.pool = pooler.get_pool(cr.dbname)
873 report = assertion_report.assertion_report()
874 self.assertion_report = report
875 self.noupdate = noupdate
877 'menuitem': self._tag_menuitem,
878 'record': self._tag_record,
879 'assert': self._tag_assert,
880 'report': self._tag_report,
881 'wizard': self._tag_wizard,
882 'delete': self._tag_delete,
883 'ir_set': self._tag_ir_set,
884 'function': self._tag_function,
885 'workflow': self._tag_workflow,
886 'act_window': self._tag_act_window,
890 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
898 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
899 #remove folder path from model
900 head, model = os.path.split(model)
902 pool = pooler.get_pool(cr.dbname)
904 input = cStringIO.StringIO(csvcontent) #FIXME
905 reader = csv.reader(input, quotechar='"', delimiter=',')
906 fields = reader.next()
908 if config.get('import_partial'):
909 fname_partial = module + '/'+ fname
910 if not os.path.isfile(config.get('import_partial')):
911 pickle.dump({}, file(config.get('import_partial'),'w+'))
913 data = pickle.load(file(config.get('import_partial')))
914 if fname_partial in data:
915 if not data[fname_partial]:
918 for i in range(data[fname_partial]):
921 if not (mode == 'init' or 'id' in fields):
922 _logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
928 if (not line) or not reduce(lambda x,y: x or y, line) :
931 datas.append(map(lambda x: misc.ustr(x), line))
933 _logger.error("Cannot import the line: %s", line)
934 result, rows, warning_msg, dummy = pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
936 # Report failed import and abort module install
937 raise Exception(_('Module loading failed: file %s/%s could not be processed:\n %s') % (module, fname, warning_msg))
938 if config.get('import_partial'):
939 data = pickle.load(file(config.get('import_partial')))
940 data[fname_partial] = 0
941 pickle.dump(data, file(config.get('import_partial'),'wb'))
947 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
948 doc = etree.parse(xmlfile)
949 relaxng = etree.RelaxNG(
950 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
954 _logger.error('The XML file does not fit the required schema !')
955 _logger.error(misc.ustr(relaxng.error_log.last_error))
960 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
961 obj.parse(doc.getroot())
965 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: