1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
34 import openerp.release
35 import openerp.workflow
36 from yaml_import import convert_yaml_import
38 import assertion_report
40 _logger = logging.getLogger(__name__)
45 _logger.warning('could not find pytz library, please install it')
46 class pytzclass(object):
51 from datetime import datetime, timedelta
52 from dateutil.relativedelta import relativedelta
53 from lxml import etree, builder
55 from config import config
56 from translate import _
58 # List of etree._Element subclasses that we choose to ignore when parsing XML.
59 from misc import SKIPPED_ELEMENT_TYPES
61 from misc import unquote
63 from openerp import SUPERUSER_ID
65 # Import of XML records requires the unsafe eval as well,
66 # almost everywhere, which is ok because it supposedly comes
67 # from trusted data, but at least we make it obvious now.
69 from safe_eval import safe_eval as eval
71 class ParseError(Exception):
72 def __init__(self, msg, text, filename, lineno):
75 self.filename = filename
79 return '"%s" while parsing %s:%s, near\n%s' \
80 % (self.msg, self.filename, self.lineno, self.text)
83 return lambda x: self.id_get(cr, x)
85 def _obj(pool, cr, uid, model_str, context=None):
86 model = pool[model_str]
87 return lambda x: model.browse(cr, uid, x, context=context)
89 def _get_idref(self, cr, uid, model_str, context, idref):
95 relativedelta=relativedelta,
96 version=openerp.release.major_version,
100 idref2['obj'] = _obj(self.pool, cr, uid, model_str, context=context)
103 def _fix_multiple_roots(node):
105 Surround the children of the ``node`` element of an XML field with a
106 single root "data" element, to prevent having a document with multiple
107 roots once parsed separately.
109 XML nodes should have one root only, but we'd like to support
110 direct multiple roots in our partial documents (like inherited view architectures).
111 As a convention we'll surround multiple root with a container "data" element, to be
112 ignored later when parsing.
114 real_nodes = [x for x in node if not isinstance(x, SKIPPED_ELEMENT_TYPES)]
115 if len(real_nodes) > 1:
116 data_node = etree.Element("data")
118 data_node.append(child)
119 node.append(data_node)
121 def _eval_xml(self, node, pool, cr, uid, idref, context=None):
124 if node.tag in ('field','value'):
125 t = node.get('type','char')
126 f_model = node.get('model', '').encode('utf-8')
127 if node.get('search'):
128 f_search = node.get("search",'').encode('utf-8')
129 f_use = node.get("use",'id').encode('utf-8')
130 f_name = node.get("name",'').encode('utf-8')
133 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
134 q = unsafe_eval(f_search, idref2)
135 ids = pool[f_model].search(cr, uid, q)
137 ids = map(lambda x: x[f_use], pool[f_model].read(cr, uid, ids, [f_use]))
138 _cols = pool[f_model]._columns
139 if (f_name in _cols) and _cols[f_name]._type=='many2many':
144 if isinstance(f_val, tuple):
147 a_eval = node.get('eval','')
149 idref2 = _get_idref(self, cr, uid, f_model, context, idref)
151 return unsafe_eval(a_eval, idref2)
153 logging.getLogger('openerp.tools.convert.init').error(
154 'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context)
156 def _process(s, idref):
157 matches = re.finditer('[^%]%\((.*?)\)[ds]', s)
160 found = m.group()[1:]
166 idref[id] = self.id_get(cr, id)
167 s = s.replace(found, str(idref[id]))
169 s = s.replace('%%', '%') # Quite wierd but it's for (somewhat) backward compatibility sake
174 _fix_multiple_roots(node)
175 return '<?xml version="1.0"?>\n'\
176 +_process("".join([etree.tostring(n, encoding='utf-8')
177 for n in node]), idref)
179 return _process("".join([etree.tostring(n, encoding='utf-8')
180 for n in node]), idref)
184 with openerp.tools.file_open(node.get('file'), 'rb') as f:
188 from ..modules import module
190 if not module.get_module_resource(self.module, path):
191 raise IOError("No such file or directory: '%s' in %s" % (
193 return '%s,%s' % (self.module, path)
199 return data.encode('base64')
208 return float(data.strip())
210 if t in ('list','tuple'):
212 for n in node.iterchildren(tag='value'):
213 res.append(_eval_xml(self,n,pool,cr,uid,idref))
217 elif node.tag == "function":
219 a_eval = node.get('eval','')
220 # FIXME: should probably be exclusive
222 idref['ref'] = lambda x: self.id_get(cr, x)
223 args = unsafe_eval(a_eval, idref)
225 return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
226 if return_val is not None:
227 args.append(return_val)
228 model = pool[node.get('model', '')]
229 method = node.get('name')
230 res = getattr(model, method)(cr, uid, *args)
232 elif node.tag == "test":
235 escape_re = re.compile(r'(?<!\\)/')
237 return x.replace('\\/', '/')
239 class xml_import(object):
241 def nodeattr2bool(node, attr, default=False):
242 if not node.get(attr):
244 val = node.get(attr).strip()
247 return val.lower() not in ('0', 'false', 'off')
249 def isnoupdate(self, data_node=None):
250 return self.noupdate or (len(data_node) and self.nodeattr2bool(data_node, 'noupdate', False))
252 def get_context(self, data_node, node, eval_dict):
253 data_node_context = (len(data_node) and data_node.get('context','').encode('utf8'))
254 node_context = node.get("context",'').encode('utf8')
256 for ctx in (data_node_context, node_context):
259 ctx_res = unsafe_eval(ctx, eval_dict)
260 if isinstance(context, dict):
261 context.update(ctx_res)
265 # Some contexts contain references that are only valid at runtime at
266 # client-side, so in that case we keep the original context string
267 # as it is. We also log it, just in case.
269 _logger.debug('Context value (%s) for element with id "%s" or its data node does not parse '\
270 'at server-side, keeping original string, in case it\'s meant for client side only',
271 ctx, node.get('id','n/a'), exc_info=True)
274 def get_uid(self, cr, uid, data_node, node):
275 node_uid = node.get('uid','') or (len(data_node) and data_node.get('uid',''))
277 return self.id_get(cr, node_uid)
280 def _test_xml_id(self, xml_id):
283 module, id = xml_id.split('.', 1)
284 assert '.' not in id, """The ID reference "%s" must contain
285 maximum one dot. They are used to refer to other modules ID, in the
286 form: module.record_id""" % (xml_id,)
287 if module != self.module:
288 modcnt = self.pool['ir.module.module'].search_count(self.cr, self.uid, ['&', ('name', '=', module), ('state', 'in', ['installed'])])
289 assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,)
292 _logger.error('id: %s is to long (max: 64)', id)
294 def _tag_delete(self, cr, rec, data_node=None, mode=None):
295 d_model = rec.get("model")
296 d_search = rec.get("search",'').encode('utf-8')
301 idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
303 ids = self.pool[d_model].search(cr, self.uid, unsafe_eval(d_search, idref))
305 _logger.warning('Skipping deletion for failed search `%r`', d_search, exc_info=True)
309 ids.append(self.id_get(cr, d_id))
311 # d_id cannot be found. doesn't matter in this case
312 _logger.warning('Skipping deletion for missing XML ID `%r`', d_id, exc_info=True)
315 self.pool[d_model].unlink(cr, self.uid, ids)
317 def _remove_ir_values(self, cr, name, value, model):
318 ir_values_obj = self.pool['ir.values']
319 ir_value_ids = ir_values_obj.search(cr, self.uid, [('name','=',name),('value','=',value),('model','=',model)])
321 ir_values_obj.unlink(cr, self.uid, ir_value_ids)
325 def _tag_report(self, cr, rec, data_node=None, mode=None):
327 for dest,f in (('name','string'),('model','model'),('report_name','name')):
328 res[dest] = rec.get(f,'').encode('utf8')
329 assert res[dest], "Attribute %s of report is empty !" % (f,)
330 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),
331 ('attachment','attachment'),('attachment_use','attachment_use'), ('usage','usage'),
332 ('report_type', 'report_type'), ('parser', 'parser')):
334 res[dest] = rec.get(field).encode('utf8')
336 res['auto'] = eval(rec.get('auto','False'))
338 sxw_content = misc.file_open(rec.get('sxw')).read()
339 res['report_sxw_content'] = sxw_content
340 if rec.get('header'):
341 res['header'] = eval(rec.get('header','False'))
343 res['multi'] = rec.get('multi') and eval(rec.get('multi','False'))
345 xml_id = rec.get('id','').encode('utf8')
346 self._test_xml_id(xml_id)
348 if rec.get('groups'):
349 g_names = rec.get('groups','').split(',')
351 for group in g_names:
352 if group.startswith('-'):
353 group_id = self.id_get(cr, group[1:])
354 groups_value.append((3, group_id))
356 group_id = self.id_get(cr, group)
357 groups_value.append((4, group_id))
358 res['groups_id'] = groups_value
360 id = self.pool['ir.model.data']._update(cr, self.uid, "ir.actions.report.xml", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
361 self.idref[xml_id] = int(id)
363 if not rec.get('menu') or eval(rec.get('menu','False')):
364 keyword = str(rec.get('keyword', 'client_print_multi'))
365 value = 'ir.actions.report.xml,'+str(id)
366 replace = rec.get('replace', True)
367 self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', keyword, res['name'], [res['model']], value, replace=replace, isobject=True, xml_id=xml_id)
368 elif self.mode=='update' and eval(rec.get('menu','False'))==False:
369 # Special check for report having attribute menu=False on update
370 value = 'ir.actions.report.xml,'+str(id)
371 self._remove_ir_values(cr, res['name'], value, res['model'])
374 def _tag_function(self, cr, rec, data_node=None, mode=None):
375 if self.isnoupdate(data_node) and self.mode != 'init':
377 context = self.get_context(data_node, rec, {'ref': _ref(self, cr)})
378 uid = self.get_uid(cr, self.uid, data_node, rec)
379 _eval_xml(self,rec, self.pool, cr, uid, self.idref, context=context)
382 def _tag_url(self, cr, rec, data_node=None, mode=None):
383 url = rec.get("url",'').encode('utf8')
384 target = rec.get("target",'').encode('utf8')
385 name = rec.get("name",'').encode('utf8')
386 xml_id = rec.get('id','').encode('utf8')
387 self._test_xml_id(xml_id)
389 res = {'name': name, 'url': url, 'target':target}
391 id = self.pool['ir.model.data']._update(cr, self.uid, "ir.actions.act_url", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
392 self.idref[xml_id] = int(id)
394 def _tag_act_window(self, cr, rec, data_node=None, mode=None):
395 name = rec.get('name','').encode('utf-8')
396 xml_id = rec.get('id','').encode('utf8')
397 self._test_xml_id(xml_id)
398 type = rec.get('type','').encode('utf-8') or 'ir.actions.act_window'
400 if rec.get('view_id'):
401 view_id = self.id_get(cr, rec.get('view_id','').encode('utf-8'))
402 domain = rec.get('domain','').encode('utf-8') or '[]'
403 res_model = rec.get('res_model','').encode('utf-8')
404 src_model = rec.get('src_model','').encode('utf-8')
405 view_type = rec.get('view_type','').encode('utf-8') or 'form'
406 view_mode = rec.get('view_mode','').encode('utf-8') or 'tree,form'
407 usage = rec.get('usage','').encode('utf-8')
408 limit = rec.get('limit','').encode('utf-8')
409 auto_refresh = rec.get('auto_refresh','').encode('utf-8')
412 # Act_window's 'domain' and 'context' contain mostly literals
413 # but they can also refer to the variables provided below
414 # in eval_context, so we need to eval() them before storing.
415 # Among the context variables, 'active_id' refers to
416 # the currently selected items in a list view, and only
417 # takes meaning at runtime on the client side. For this
418 # reason it must remain a bare variable in domain and context,
419 # even after eval() at server-side. We use the special 'unquote'
420 # class to achieve this effect: a string which has itself, unquoted,
422 active_id = unquote("active_id")
423 active_ids = unquote("active_ids")
424 active_model = unquote("active_model")
427 return self.id_get(cr, str_id)
429 # Include all locals() in eval_context, for backwards compatibility
436 'res_model': res_model,
437 'src_model': src_model,
438 'view_type': view_type,
439 'view_mode': view_mode,
442 'auto_refresh': auto_refresh,
444 'active_id': active_id,
445 'active_ids': active_ids,
446 'active_model': active_model,
449 context = self.get_context(data_node, rec, eval_context)
452 domain = unsafe_eval(domain, eval_context)
454 # Some domains contain references that are only valid at runtime at
455 # client-side, so in that case we keep the original domain string
456 # as it is. We also log it, just in case.
457 _logger.debug('Domain value (%s) for element with id "%s" does not parse '\
458 'at server-side, keeping original string, in case it\'s meant for client side only',
459 domain, xml_id or 'n/a', exc_info=True)
466 'res_model': res_model,
467 'src_model': src_model,
468 'view_type': view_type,
469 'view_mode': view_mode,
472 'auto_refresh': auto_refresh,
475 if rec.get('groups'):
476 g_names = rec.get('groups','').split(',')
478 for group in g_names:
479 if group.startswith('-'):
480 group_id = self.id_get(cr, group[1:])
481 groups_value.append((3, group_id))
483 group_id = self.id_get(cr, group)
484 groups_value.append((4, group_id))
485 res['groups_id'] = groups_value
487 if rec.get('target'):
488 res['target'] = rec.get('target','')
490 res['multi'] = eval(rec.get('multi', 'False'))
491 id = self.pool['ir.model.data']._update(cr, self.uid, 'ir.actions.act_window', self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
492 self.idref[xml_id] = int(id)
495 #keyword = 'client_action_relate'
496 keyword = rec.get('key2','').encode('utf-8') or 'client_action_relate'
497 value = 'ir.actions.act_window,'+str(id)
498 replace = rec.get('replace','') or True
499 self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', keyword, xml_id, [src_model], value, replace=replace, isobject=True, xml_id=xml_id)
500 # TODO add remove ir.model.data
502 def _tag_ir_set(self, cr, rec, data_node=None, mode=None):
503 if self.mode != 'init':
506 for field in rec.findall('./field'):
507 f_name = field.get("name",'').encode('utf-8')
508 f_val = _eval_xml(self,field,self.pool, cr, self.uid, self.idref)
510 self.pool['ir.model.data'].ir_set(cr, self.uid, res['key'], res['key2'], res['name'], res['models'], res['value'], replace=res.get('replace',True), isobject=res.get('isobject', False), meta=res.get('meta',None))
512 def _tag_workflow(self, cr, rec, data_node=None, mode=None):
513 if self.isnoupdate(data_node) and self.mode != 'init':
515 model = rec.get('model').encode('ascii')
516 w_ref = rec.get('ref')
518 id = self.id_get(cr, w_ref)
520 number_children = len(rec)
521 assert number_children > 0,\
522 'You must define a child node if you dont give a ref'
523 assert number_children == 1,\
524 'Only one child node is accepted (%d given)' % number_children
525 id = _eval_xml(self, rec[0], self.pool, cr, self.uid, self.idref)
527 uid = self.get_uid(cr, self.uid, data_node, rec)
528 openerp.workflow.trg_validate(
529 uid, model, id, rec.get('action').encode('ascii'), cr)
532 # Support two types of notation:
533 # name="Inventory Control/Sending Goods"
538 def _tag_menuitem(self, cr, rec, data_node=None, mode=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['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 action_type, action_id = self.model_id_get(cr, a_action)
586 action_type = action_type.split('.')[-1] # keep only type part
588 if not values.get('name') and action_type in ('act_window', 'wizard', 'url', 'client', 'server'):
589 a_table = 'ir_act_%s' % action_type.replace('act_', '')
590 cr.execute('select name from "%s" where id=%%s' % a_table, (int(action_id),))
593 values['name'] = resw[0]
595 if not values.get('name'):
596 # ensure menu has a name
597 values['name'] = rec_id or '?'
599 if rec.get('sequence'):
600 values['sequence'] = int(rec.get('sequence'))
602 if rec.get('groups'):
603 g_names = rec.get('groups','').split(',')
605 for group in g_names:
606 if group.startswith('-'):
607 group_id = self.id_get(cr, group[1:])
608 groups_value.append((3, group_id))
610 group_id = self.id_get(cr, group)
611 groups_value.append((4, group_id))
612 values['groups_id'] = groups_value
614 pid = self.pool['ir.model.data']._update(cr, self.uid, 'ir.ui.menu', self.module, values, rec_id, noupdate=self.isnoupdate(data_node), mode=self.mode, res_id=res and res[0] or False)
617 self.idref[rec_id] = int(pid)
619 if rec.get('action') and pid:
620 action = "ir.actions.%s,%d" % (action_type, action_id)
621 self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(pid))], action, True, True, xml_id=rec_id)
622 return 'ir.ui.menu', pid
624 def _assert_equals(self, f1, f2, prec=4):
625 return not round(f1 - f2, prec)
627 def _tag_assert(self, cr, rec, data_node=None, mode=None):
628 if self.isnoupdate(data_node) and self.mode != 'init':
631 rec_model = rec.get("model",'').encode('ascii')
632 model = self.pool[rec_model]
633 rec_id = rec.get("id",'').encode('ascii')
634 self._test_xml_id(rec_id)
635 rec_src = rec.get("search",'').encode('utf8')
636 rec_src_count = rec.get("count")
638 rec_string = rec.get("string",'').encode('utf8') or 'unknown'
641 eval_dict = {'ref': _ref(self, cr)}
642 context = self.get_context(data_node, rec, eval_dict)
643 uid = self.get_uid(cr, self.uid, data_node, rec)
645 ids = [self.id_get(cr, rec_id)]
647 q = unsafe_eval(rec_src, eval_dict)
648 ids = self.pool[rec_model].search(cr, uid, q, context=context)
650 count = int(rec_src_count)
651 if len(ids) != count:
652 self.assertion_report.record_failure()
653 msg = 'assertion "%s" failed!\n' \
654 ' Incorrect search count:\n' \
655 ' expected count: %d\n' \
656 ' obtained count: %d\n' \
657 % (rec_string, count, len(ids))
661 assert ids is not None,\
662 'You must give either an id or a search criteria'
665 brrec = model.browse(cr, uid, id, context)
667 def __getitem__(self2, key):
670 return dict.__getitem__(self2, key)
672 globals_dict['floatEqual'] = self._assert_equals
673 globals_dict['ref'] = ref
674 globals_dict['_ref'] = ref
675 for test in rec.findall('./test'):
676 f_expr = test.get("expr",'').encode('utf-8')
677 expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
678 expression_value = unsafe_eval(f_expr, globals_dict)
679 if expression_value != expected_value: # assertion failed
680 self.assertion_report.record_failure()
681 msg = 'assertion "%s" failed!\n' \
683 ' expected value: %r\n' \
684 ' obtained value: %r\n' \
685 % (rec_string, etree.tostring(test), expected_value, expression_value)
688 else: # all tests were successful for this assertion tag (no break)
689 self.assertion_report.record_success()
691 def _tag_record(self, cr, rec, data_node=None, mode=None):
692 rec_model = rec.get("model").encode('ascii')
693 model = self.pool[rec_model]
694 rec_id = rec.get("id",'').encode('ascii')
695 rec_context = rec.get("context", {})
697 rec_context = unsafe_eval(rec_context)
699 if self.xml_filename and rec_id:
700 rec_context['install_mode_data'] = dict(
701 xml_file=self.xml_filename,
706 self._test_xml_id(rec_id)
707 # in update mode, the record won't be updated if the data node explicitely
708 # opt-out using @noupdate="1". A second check will be performed in
709 # ir.model.data#_update() using the record's ir.model.data `noupdate` field.
710 if self.isnoupdate(data_node) and self.mode != 'init':
711 # check if the xml record has no id, skip
716 module,rec_id2 = rec_id.split('.')
720 id = self.pool['ir.model.data']._update_dummy(cr, self.uid, rec_model, module, rec_id2)
722 # if the resource already exists, don't update it but store
723 # its database id (can be useful)
724 self.idref[rec_id] = int(id)
726 elif not self.nodeattr2bool(rec, 'forcecreate', True):
727 # if it doesn't exist and we shouldn't create it, skip it
729 # else create it normally
732 for field in rec.findall('./field'):
733 #TODO: most of this code is duplicated above (in _eval_xml)...
734 f_name = field.get("name").encode('utf-8')
735 f_ref = field.get("ref",'').encode('utf-8')
736 f_search = field.get("search",'').encode('utf-8')
737 f_model = field.get("model",'').encode('utf-8')
738 if not f_model and f_name in model._fields:
739 f_model = model._fields[f_name].comodel_name
740 f_use = field.get("use",'').encode('utf-8') or 'id'
744 q = unsafe_eval(f_search, self.idref)
745 assert f_model, 'Define an attribute model="..." in your .XML file !'
746 f_obj = self.pool[f_model]
747 # browse the objects searched
748 s = f_obj.browse(cr, self.uid, f_obj.search(cr, self.uid, q))
749 # column definitions of the "local" object
750 _fields = self.pool[rec_model]._fields
751 # if the current field is many2many
752 if (f_name in _fields) and _fields[f_name].type == 'many2many':
753 f_val = [(6, 0, map(lambda x: x[f_use], s))]
755 # otherwise (we are probably in a many2one field),
756 # take the first element of the search
759 if f_name in model._fields and model._fields[f_name].type == 'reference':
760 val = self.model_id_get(cr, f_ref)
761 f_val = val[0] + ',' + str(val[1])
763 f_val = self.id_get(cr, f_ref)
765 f_val = _eval_xml(self,field, self.pool, cr, self.uid, self.idref)
766 if f_name in model._fields:
767 if model._fields[f_name].type == 'integer':
771 id = self.pool['ir.model.data']._update(cr, self.uid, rec_model, self.module, res, rec_id or False, not self.isnoupdate(data_node), noupdate=self.isnoupdate(data_node), mode=self.mode, context=rec_context )
773 self.idref[rec_id] = int(id)
774 if config.get('import_partial'):
778 def _tag_template(self, cr, el, data_node=None, mode=None):
779 # This helper transforms a <template> element into a <record> and forwards it
780 tpl_id = el.get('id', el.get('t-name', '')).encode('ascii')
782 if '.' not in full_tpl_id:
783 full_tpl_id = '%s.%s' % (self.module, tpl_id)
784 # set the full template name for qweb <module>.<id>
785 if not el.get('inherit_id'):
786 el.set('t-name', full_tpl_id)
790 el.attrib.pop('id', None)
794 'model': 'ir.ui.view',
796 for att in ['forcecreate', 'context']:
798 record_attrs[att] = el.attrib.pop(att)
800 Field = builder.E.field
801 name = el.get('name', tpl_id)
803 record = etree.Element('record', attrib=record_attrs)
804 record.append(Field(name, name='name'))
805 record.append(Field(full_tpl_id, name='key'))
806 record.append(Field("qweb", name='type'))
807 record.append(Field(el.get('priority', "16"), name='priority'))
808 if 'inherit_id' in el.attrib:
809 record.append(Field(name='inherit_id', ref=el.get('inherit_id')))
810 if el.get('active') in ("True", "False"):
811 view_id = self.id_get(cr, tpl_id, raise_if_not_found=False)
812 if mode != "update" or not view_id:
813 record.append(Field(name='active', eval=el.get('active')))
814 if el.get('customize_show') in ("True", "False"):
815 record.append(Field(name='customize_show', eval=el.get('customize_show')))
816 groups = el.attrib.pop('groups', None)
818 grp_lst = map(lambda x: "ref('%s')" % x, groups.split(','))
819 record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]"))
820 if el.attrib.pop('page', None) == 'True':
821 record.append(Field(name="page", eval="True"))
822 if el.get('primary') == 'True':
823 # Pseudo clone mode, we'll set the t-name to the full canonical xmlid
826 builder.E.attribute(full_tpl_id, name='t-name'),
828 position="attributes",
831 record.append(Field('primary', name='mode'))
832 # inject complete <template> element (after changing node name) into
834 record.append(Field(el, name="arch", type="xml"))
836 return self._tag_record(cr, record, data_node)
838 def id_get(self, cr, id_str, raise_if_not_found=True):
839 if id_str in self.idref:
840 return self.idref[id_str]
841 res = self.model_id_get(cr, id_str, raise_if_not_found)
842 if res and len(res)>1: res = res[1]
845 def model_id_get(self, cr, id_str, raise_if_not_found=True):
846 model_data_obj = self.pool['ir.model.data']
848 if '.' not in id_str:
849 id_str = '%s.%s' % (mod, id_str)
850 return model_data_obj.xmlid_to_res_model_res_id(
851 cr, self.uid, id_str,
852 raise_if_not_found=raise_if_not_found)
854 def parse(self, de, mode=None):
855 if de.tag != 'openerp':
856 raise Exception("Mismatch xml format: root tag must be `openerp`.")
858 for n in de.findall('./data'):
860 if rec.tag in self._tags:
862 self._tags[rec.tag](self.cr, rec, n, mode=mode)
865 exc_info = sys.exc_info()
866 raise ParseError, (misc.ustr(e), etree.tostring(rec).rstrip(), rec.getroottree().docinfo.URL, rec.sourceline), exc_info[2]
869 def __init__(self, cr, module, idref, mode, report=None, noupdate=False, xml_filename=None):
875 self.pool = openerp.registry(cr.dbname)
878 report = assertion_report.assertion_report()
879 self.assertion_report = report
880 self.noupdate = noupdate
881 self.xml_filename = xml_filename
883 'record': self._tag_record,
884 'delete': self._tag_delete,
885 'function': self._tag_function,
886 'menuitem': self._tag_menuitem,
887 'template': self._tag_template,
888 'workflow': self._tag_workflow,
889 'report': self._tag_report,
891 'ir_set': self._tag_ir_set,
892 'act_window': self._tag_act_window,
893 'url': self._tag_url,
894 'assert': self._tag_assert,
897 def convert_file(cr, module, filename, idref, mode='update', noupdate=False, kind=None, report=None, pathname=None):
899 pathname = os.path.join(module, filename)
900 fp = misc.file_open(pathname)
901 ext = os.path.splitext(filename)[1].lower()
905 convert_csv_import(cr, module, pathname, fp.read(), idref, mode, noupdate)
907 convert_sql_import(cr, fp)
909 convert_yaml_import(cr, module, fp, kind, idref, mode, noupdate, report)
911 convert_xml_import(cr, module, fp, idref, mode, noupdate, report)
913 pass # .js files are valid but ignored here.
915 _logger.warning("Can't load unknown file type %s.", filename)
919 def convert_sql_import(cr, fp):
920 queries = fp.read().split(';')
921 for query in queries:
922 new_query = ' '.join(query.split())
924 cr.execute(new_query)
926 def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init',
934 model = ('.'.join(fname.split('.')[:-1]).split('-'))[0]
935 #remove folder path from model
936 head, model = os.path.split(model)
938 input = cStringIO.StringIO(csvcontent) #FIXME
939 reader = csv.reader(input, quotechar='"', delimiter=',')
940 fields = reader.next()
942 if config.get('import_partial'):
943 fname_partial = module + '/'+ fname
944 if not os.path.isfile(config.get('import_partial')):
945 pickle.dump({}, file(config.get('import_partial'),'w+'))
947 data = pickle.load(file(config.get('import_partial')))
948 if fname_partial in data:
949 if not data[fname_partial]:
952 for i in range(data[fname_partial]):
955 if not (mode == 'init' or 'id' in fields):
956 _logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.")
962 if not (line and any(line)):
965 datas.append(map(misc.ustr, line))
967 _logger.error("Cannot import the line: %s", line)
969 registry = openerp.registry(cr.dbname)
970 result, rows, warning_msg, dummy = registry[model].import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial)
972 # Report failed import and abort module install
973 raise Exception(_('Module loading %s failed: file %s could not be processed:\n %s') % (module, fname, warning_msg))
974 if config.get('import_partial'):
975 data = pickle.load(file(config.get('import_partial')))
976 data[fname_partial] = 0
977 pickle.dump(data, file(config.get('import_partial'),'wb'))
983 def convert_xml_import(cr, module, xmlfile, idref=None, mode='init', noupdate=False, report=None):
984 doc = etree.parse(xmlfile)
985 relaxng = etree.RelaxNG(
986 etree.parse(os.path.join(config['root_path'],'import_xml.rng' )))
990 _logger.error('The XML file does not fit the required schema !')
991 _logger.error(misc.ustr(relaxng.error_log.last_error))
996 if isinstance(xmlfile, file):
997 xml_filename = xmlfile.name
999 xml_filename = xmlfile
1000 obj = xml_import(cr, module, idref, mode, report=report, noupdate=noupdate, xml_filename=xml_filename)
1001 obj.parse(doc.getroot(), mode=mode)
1005 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: