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)
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.url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
390 self.idref[xml_id] = int(id)
392 if (not rec.get('menu') or eval(rec.get('menu','False'))) and id:
393 keyword = str(rec.get('keyword','') or 'client_action_multi')
394 value = 'ir.actions.url,'+str(id)
395 replace = rec.get("replace",'') or True
396 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)
397 elif self.mode=='update' and (rec.get('menu') and eval(rec.get('menu','False'))==False):
398 # Special check for URL having attribute menu=False on update
399 value = 'ir.actions.url,'+str(id)
400 self._remove_ir_values(cr, url, value, "ir.actions.url")
402 def _tag_act_window(self, cr, rec, data_node=None):
403 name = rec.get('name','').encode('utf-8')
404 xml_id = rec.get('id','').encode('utf8')
405 self._test_xml_id(xml_id)
406 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
408 if rec.get('view_id'):
409 view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8'))
410 domain = rec.get('domain','').encode('utf-8') or '[]'
411 res_model = rec.get('res_model','').encode('utf-8')
412 src_model = rec.get('src_model','').encode('utf-8')
413 view_type = rec.get('view_type','').encode('utf-8') or 'form'
414 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
415 usage = rec.get('usage','').encode('utf-8')
416 limit = rec.get('limit','').encode('utf-8')
417 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
420 # Act_window's 'domain' and 'context' contain mostly literals
421 # but they can also refer to the variables provided below
422 # in eval_context, so we need to eval() them before storing.
423 # Among the context variables, 'active_id' refers to
424 # the currently selected items in a list view, and only
425 # takes meaning at runtime on the client side. For this
426 # reason it must remain a bare variable in domain and context,
427 # even after eval() at server-side. We use the special 'unquote'
428 # class to achieve this effect: a string which has itself, unquoted,
430 active_id = unquote("active_id")
431 active_ids = unquote("active_ids")
432 active_model = unquote("active_model")
435 return self.id_get(cr, str_id)
437 # Include all locals() in eval_context, for backwards compatibility
444 'res_model': res_model,
445 'src_model': src_model,
446 'view_type': view_type,
447 'view_mode': view_mode,
450 'auto_refresh': auto_refresh,
452 'active_id': active_id,
453 'active_ids': active_ids,
454 'active_model': active_model,
457 context = self.get_context(data_node, rec, eval_context)
460 domain = unsafe_eval(domain, eval_context)
462 # Some domains contain references that are only valid at runtime at
463 # client-side, so in that case we keep the original domain string
464 # as it is. We also log it, just in case.
465 _logger.debug('Domain value (%s) for element with id "%s" does not parse '\
466 'at server-side, keeping original string, in case it\'s meant for client side only',
467 domain, xml_id or 'n/a', exc_info=True)
474 'res_model': res_model,
475 'src_model': src_model,
476 'view_type': view_type,
477 'view_mode': view_mode,
480 'auto_refresh': auto_refresh,
483 if rec.get('groups'):
484 g_names = rec.get('groups','').split(',')
486 for group in g_names:
487 if group.startswith('-'):
488 group_id = self.id_get(cr, group[1:])
489 groups_value.append((3, group_id))
491 group_id = self.id_get(cr, group)
492 groups_value.append((4, group_id))
493 res['groups_id'] = groups_value
495 if rec.get('target'):
496 res['target'] = rec.get('target','')
498 res['multi'] = rec.get('multi', False)
499 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)
500 self.idref[xml_id] = int(id)
503 #keyword = 'client_action_relate'
504 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
505 value = 'ir.actions.act_window,'+str(id)
506 replace = rec.get('replace','') or True
507 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)
508 # TODO add remove ir.model.data
510 def _tag_ir_set(self, cr, rec, data_node=None):
511 if self.mode != 'init':
514 for field in rec.findall('./field'):
515 f_name = field.get("name",'').encode('utf-8')
516 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
518 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))
520 def _tag_workflow(self, cr, rec, data_node=None):
521 if self.isnoupdate(data_node) and self.mode != 'init':
523 model = str(rec.get('model',''))
524 w_ref = rec.get('ref','')
526 id = self.id_get(cr, w_ref)
528 number_children = len(rec)
529 assert number_children > 0,\
530 'You must define a child node if you dont give a ref'
531 assert number_children == 1,\
532 'Only one child node is accepted (%d given)' % number_children
533 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
535 uid = self.get_uid(cr, self.uid, data_node, rec)
536 import openerp.netsvc as netsvc
537 wf_service = netsvc.LocalService("workflow")
538 wf_service.trg_validate(uid, model,
540 str(rec.get('action','')), cr)
543 # Support two types of notation:
544 # name="Inventory Control/Sending Goods"
549 def _tag_menuitem(self, cr, rec, data_node=None):
550 rec_id = rec.get("id",'').encode('ascii')
551 self._test_xml_id(rec_id)
552 m_l = map(escape, escape_re.split(rec.get("name",'').encode('utf8')))
554 values = {'parent_id': False}
555 if rec.get('parent', False) is False and len(m_l) > 1:
556 # No parent attribute specified and the menu name has several menu components,
557 # try to determine the ID of the parent according to menu path
560 values['name'] = m_l[-1]
561 m_l = m_l[:-1] # last part is our name, not a parent
562 for idx, menu_elem in enumerate(m_l):
564 cr.execute('select id from ir_ui_menu where parent_id=%s and name=%s', (pid, menu_elem))
566 cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (menu_elem,))
571 # the menuitem does't exist but we are in branch (not a leaf)
572 _logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l))
573 pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem})
574 values['parent_id'] = pid
576 # The parent attribute was specified, if non-empty determine its ID, otherwise
577 # explicitly make a top-level menu
578 if rec.get('parent'):
579 menu_parent_id = self.id_get(cr, rec.get('parent',''))
581 # we get here with <menuitem parent="">, explicit clear of parent, or
582 # if no parent attribute at all but menu name is not a menu path
583 menu_parent_id = False
584 values = {'parent_id': menu_parent_id}
586 values['name'] = rec.get('name')
588 res = [ self.id_get(cr, rec.get('id','')) ]
592 if rec.get('action'):
593 a_action = rec.get('action','').encode('utf8')
594 a_type = rec.get('type','').encode('utf8') or 'act_window'
596 "act_window": 'STOCK_NEW',
597 "report.xml": 'STOCK_PASTE',
598 "wizard": 'STOCK_EXECUTE',
599 "url": 'STOCK_JUMP_TO'
601 values['icon'] = icons.get(a_type,'STOCK_NEW')
602 if a_type=='act_window':
603 a_id = self.id_get(cr, a_action)
604 cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (int(a_id),))
605 rrres = cr.fetchone()
606 assert rrres, "No window action defined for this id %s !\n" \
607 "Verify that this is a window action or add a type argument." % (a_action,)
608 action_type,action_mode,action_name,view_id,target = rrres
610 cr.execute('SELECT arch FROM ir_ui_view WHERE id=%s', (int(view_id),))
611 arch, = cr.fetchone()
612 action_mode = etree.fromstring(arch.encode('utf8')).tag
613 cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (int(a_id),))
615 action_mode, = cr.fetchone()
616 if action_type=='tree':
617 values['icon'] = 'STOCK_INDENT'
618 elif action_mode and action_mode.startswith(('tree','kanban','gantt')):
619 values['icon'] = 'STOCK_JUSTIFY_FILL'
620 elif action_mode and action_mode.startswith('graph'):
621 values['icon'] = 'terp-graph'
622 elif action_mode and action_mode.startswith('calendar'):
623 values['icon'] = 'terp-calendar'
625 values['icon'] = 'STOCK_EXECUTE'
626 if not values.get('name', False):
627 values['name'] = action_name
628 elif a_type=='wizard':
629 a_id = self.id_get(cr, a_action)
630 cr.execute('select name from ir_act_wizard where id=%s', (int(a_id),))
632 if (not values.get('name', False)) and resw:
633 values['name'] = resw[0]
635 a_id = self.id_get(cr, a_action)
636 cr.execute('select name from ir_act_url where id=%s', (int(a_id),))
638 if (not values.get('name')) and resw:
639 values['name'] = resw[0]
640 if rec.get('sequence'):
641 values['sequence'] = int(rec.get('sequence'))
643 values['icon'] = str(rec.get('icon'))
644 if rec.get('web_icon'):
645 values['web_icon'] = "%s,%s" %(self.module, str(rec.get('web_icon')))
646 if rec.get('web_icon_hover'):
647 values['web_icon_hover'] = "%s,%s" %(self.module, str(rec.get('web_icon_hover')))
649 if rec.get('groups'):
650 g_names = rec.get('groups','').split(',')
652 for group in g_names:
653 if group.startswith('-'):
654 group_id = self.id_get(cr, group[1:])
655 groups_value.append((3, group_id))
657 group_id = self.id_get(cr, group)
658 groups_value.append((4, group_id))
659 values['groups_id'] = groups_value
661 xml_id = rec.get('id','').encode('utf8')
662 self._test_xml_id(xml_id)
663 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)
666 self.idref[rec_id] = int(pid)
668 if rec.get('action') and pid:
669 a_action = rec.get('action').encode('utf8')
670 a_type = rec.get('type','').encode('utf8') or 'act_window'
671 a_id = self.id_get(cr, a_action)
672 action = "ir.actions.%s,%d" % (a_type, a_id)
673 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)
674 return ('ir.ui.menu', pid)
676 def _assert_equals(self, f1, f2, prec=4):
677 return not round(f1 - f2, prec)
679 def _tag_assert(self, cr, rec, data_node=None):
680 if self.isnoupdate(data_node) and self.mode != 'init':
683 rec_model = rec.get("model",'').encode('ascii')
684 model = self.pool.get(rec_model)
685 assert model, "The model %s does not exist !" % (rec_model,)
686 rec_id = rec.get("id",'').encode('ascii')
687 self._test_xml_id(rec_id)
688 rec_src = rec.get("search",'').encode('utf8')
689 rec_src_count = rec.get("count")
691 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
694 eval_dict = {'ref': _ref(self, cr)}
695 context = self.get_context(data_node, rec, eval_dict)
696 uid = self.get_uid(cr, self.uid, data_node, rec)
698 ids = [self.id_get(cr, rec_id)]
700 q = unsafe_eval(rec_src, eval_dict)
701 ids = self.pool.get(rec_model).search(cr, uid, q, context=context)
703 count = int(rec_src_count)
704 if len(ids) != count:
705 self.assertion_report.record_failure()
706 msg = 'assertion "%s" failed!\n' \
707 ' Incorrect search count:\n' \
708 ' expected count: %d\n' \
709 ' obtained count: %d\n' \
710 % (rec_string, count, len(ids))
714 assert ids is not None,\
715 'You must give either an id or a search criteria'
718 brrec = model.browse(cr, uid, id, context)
720 def __getitem__(self2, key):
723 return dict.__getitem__(self2, key)
725 globals_dict['floatEqual'] = self._assert_equals
726 globals_dict['ref'] = ref
727 globals_dict['_ref'] = ref
728 for test in rec.findall('./test'):
729 f_expr = test.get("expr",'').encode('utf-8')
730 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
731 expression_value = unsafe_eval(f_expr, globals_dict)
732 if expression_value != expected_value: # assertion failed
733 self.assertion_report.record_failure()
734 msg = 'assertion "%s" failed!\n' \
736 ' expected value: %r\n' \
737 ' obtained value: %r\n' \
738 % (rec_string, etree.tostring(test), expected_value, expression_value)
741 else: # all tests were successful for this assertion tag (no break)
742 self.assertion_report.record_success()
744 def _tag_record(self, cr, rec, data_node=None):
745 rec_model = rec.get("model").encode('ascii')
746 model = self.pool.get(rec_model)
747 assert model, "The model %s does not exist !" % (rec_model,)
748 rec_id = rec.get("id",'').encode('ascii')
749 rec_context = rec.get("context", None)
751 rec_context = unsafe_eval(rec_context)
752 self._test_xml_id(rec_id)
753 if self.isnoupdate(data_node) and self.mode != 'init':
754 # check if the xml record has an id string
757 module,rec_id2 = rec_id.split('.')
761 id = self.pool.get('ir.model.data')._update_dummy(cr, self.uid, rec_model, module, rec_id2)
762 # check if the resource already existed at the last update
764 # if it existed, we don't update the data, but we need to
765 # know the id of the existing record anyway
766 self.idref[rec_id] = int(id)
769 # if the resource didn't exist
770 if not self.nodeattr2bool(rec, 'forcecreate', True):
771 # we don't want to create it, so we skip it
773 # else, we let the record to be created
776 # otherwise it is skipped
779 for field in rec.findall('./field'):
780 #TODO: most of this code is duplicated above (in _eval_xml)...
781 f_name = field.get("name",'').encode('utf-8')
782 f_ref = field.get("ref",'').encode('utf-8')
783 f_search = field.get("search",'').encode('utf-8')
784 f_model = field.get("model",'').encode('utf-8')
785 if not f_model and model._columns.get(f_name,False):
786 f_model = model._columns[f_name]._obj
787 f_use = field.get("use",'').encode('utf-8') or 'id'
791 q = unsafe_eval(f_search, self.idref)
793 assert f_model, 'Define an attribute model="..." in your .XML file !'
794 f_obj = self.pool.get(f_model)
795 # browse the objects searched
796 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
797 # column definitions of the "local" object
798 _cols = self.pool.get(rec_model)._columns
799 # if the current field is many2many
800 if (f_name in _cols) and _cols[f_name]._type=='many2many':
801 f_val = [(6, 0, map(lambda x: x[f_use], s))]
803 # otherwise (we are probably in a many2one field),
804 # take the first element of the search
810 if f_name in model._columns \
811 and model._columns[f_name]._type == 'reference':
812 val = self.model_id_get(cr, f_ref)
813 f_val = val[0] + ',' + str(val[1])
815 f_val = self.id_get(cr, f_ref)
817 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
818 if model._columns.has_key(f_name):
819 import openerp.osv as osv
820 if isinstance(model._columns[f_name], osv.fields.integer):
824 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 )
826 self.idref[rec_id] = int(id)
827 if config.get('import_partial', False):
831 def id_get(self, cr, id_str):
832 if id_str in self.idref:
833 return self.idref[id_str]
834 res = self.model_id_get(cr, id_str)
835 if res and len(res)>1: res = res[1]
838 def model_id_get(self, cr, id_str):
839 model_data_obj = self.pool.get('ir.model.data')
842 mod,id_str = id_str.split('.')
843 return model_data_obj.get_object_reference(cr, self.uid, mod, id_str)
846 if not de.tag in ['terp', 'openerp']:
847 _logger.error("Mismatch xml format")
848 raise Exception( "Mismatch xml format: only terp or openerp as root tag" )
851 _logger.warning("The tag <terp/> is deprecated, use <openerp/>")
853 for n in de.findall('./data'):
855 if rec.tag in self._tags:
857 self._tags[rec.tag](self.cr, rec, n)
859 _logger.error('Parse error in %s:%d: \n%s',
860 rec.getroottree().docinfo.URL,
862 etree.tostring(rec).strip(), exc_info=True)
867 def __init__(self, cr, module, idref, mode, report=None, noupdate=False):
873 self.pool = pooler.get_pool(cr.dbname)
876 report = assertion_report.assertion_report()
877 self.assertion_report = report
878 self.noupdate = noupdate
880 'menuitem': self._tag_menuitem,
881 'record': self._tag_record,
882 'assert': self._tag_assert,
883 'report': self._tag_report,
884 'wizard': self._tag_wizard,
885 'delete': self._tag_delete,
886 'ir_set': self._tag_ir_set,
887 'function': self._tag_function,
888 'workflow': self._tag_workflow,
889 'act_window': self._tag_act_window,
893 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
901 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
902 #remove folder path from model
903 head, model = os.path.split(model)
905 pool = pooler.get_pool(cr.dbname)
907 input = cStringIO.StringIO(csvcontent) #FIXME
908 reader = csv.reader(input, quotechar='"', delimiter=',')
909 fields = reader.next()
911 if config.get('import_partial'):
912 fname_partial = module + '/'+ fname
913 if not os.path.isfile(config.get('import_partial')):
914 pickle.dump({}, file(config.get('import_partial'),'w+'))
916 data = pickle.load(file(config.get('import_partial')))
917 if fname_partial in data:
918 if not data[fname_partial]:
921 for i in range(data[fname_partial]):
924 if not (mode == 'init' or 'id' in fields):
925 _logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
931 if (not line) or not reduce(lambda x,y: x or y, line) :
934 datas.append(map(lambda x: misc.ustr(x), line))
936 _logger.error("Cannot import the line: %s", line)
937 result, rows, warning_msg, dummy = pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
939 # Report failed import and abort module install
940 raise Exception(_('Module loading failed: file %s/%s could not be processed:\n %s') % (module, fname, warning_msg))
941 if config.get('import_partial'):
942 data = pickle.load(file(config.get('import_partial')))
943 data[fname_partial] = 0
944 pickle.dump(data, file(config.get('import_partial'),'wb'))
950 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
951 doc = etree.parse(xmlfile)
952 relaxng = etree.RelaxNG(
953 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
957 _logger.error('The XML file does not fit the required schema !')
958 _logger.error(misc.ustr(relaxng.error_log.last_error))
963 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate)
964 obj.parse(doc.getroot())
968 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: