1 # -*- coding: utf-8 -*-
4 import time # used to eval time.strftime expressions
5 from datetime import datetime, timedelta
9 import openerp.sql_db as sql_db
10 import openerp.workflow
12 from config import config
16 from lxml import etree
17 from openerp import SUPERUSER_ID
19 # YAML import needs both safe and unsafe eval, but let's
22 from safe_eval import safe_eval as eval
24 import assertion_report
26 _logger = logging.getLogger(__name__)
28 class YamlImportException(Exception):
31 class YamlImportAbortion(Exception):
34 def _is_yaml_mapping(node, tag_constructor):
35 value = isinstance(node, types.DictionaryType) \
36 and len(node.keys()) == 1 \
37 and isinstance(node.keys()[0], tag_constructor)
41 return isinstance(node, types.StringTypes)
44 return isinstance(node, yaml_tag.Assert) \
45 or _is_yaml_mapping(node, yaml_tag.Assert)
48 return _is_yaml_mapping(node, yaml_tag.Record)
51 return _is_yaml_mapping(node, yaml_tag.Python)
53 def is_menuitem(node):
54 return isinstance(node, yaml_tag.Menuitem) \
55 or _is_yaml_mapping(node, yaml_tag.Menuitem)
57 def is_function(node):
58 return isinstance(node, yaml_tag.Function) \
59 or _is_yaml_mapping(node, yaml_tag.Function)
62 return isinstance(node, yaml_tag.Report)
64 def is_workflow(node):
65 return isinstance(node, yaml_tag.Workflow)
67 def is_act_window(node):
68 return isinstance(node, yaml_tag.ActWindow)
71 return isinstance(node, yaml_tag.Delete)
74 return isinstance(node, yaml_tag.Context)
77 return isinstance(node, yaml_tag.Url)
80 return isinstance(node, yaml_tag.Eval)
83 return isinstance(node, yaml_tag.Ref) \
84 or _is_yaml_mapping(node, yaml_tag.Ref)
87 return _is_yaml_mapping(node, yaml_tag.IrSet)
90 return isinstance(node, basestring)
92 class RecordDictWrapper(dict):
94 Used to pass a record as locals in eval:
95 records do not strictly behave like dict, so we force them to.
97 def __init__(self, record):
99 def __getitem__(self, key):
100 if key in self.record:
101 return self.record[key]
102 return dict.__getitem__(self, key)
104 class YamlInterpreter(object):
105 def __init__(self, cr, module, id_map, mode, filename, report=None, noupdate=False, loglevel=logging.DEBUG):
110 self.filename = filename
112 report = assertion_report.assertion_report()
113 self.assertion_report = report
114 self.noupdate = noupdate
115 self.loglevel = loglevel
116 self.pool = openerp.registry(cr.dbname)
118 self.context = {} # opererp context
119 self.eval_context = {'ref': self._ref(),
120 '_ref': self._ref(), # added '_ref' so that record['ref'] is possible
122 'datetime': datetime,
123 'timedelta': timedelta}
125 def _log(self, *args, **kwargs):
126 _logger.log(self.loglevel, *args, **kwargs)
129 return lambda xml_id: self.get_id(xml_id)
131 def get_model(self, model_name):
132 return self.pool[model_name]
134 def validate_xml_id(self, xml_id):
137 module, id = xml_id.split('.', 1)
138 assert '.' not in id, "The ID reference '%s' must contains maximum one dot.\n" \
139 "It is used to refer to other modules ID, in the form: module.record_id" \
141 if module != self.module:
142 module_count = self.pool['ir.module.module'].search_count(self.cr, self.uid, \
143 ['&', ('name', '=', module), ('state', 'in', ['installed'])])
144 assert module_count == 1, 'The ID "%s" refers to an uninstalled module.' % (xml_id,)
145 if len(id) > 64: # TODO where does 64 come from (DB is 128)? should be a constant or loaded form DB
146 _logger.error('id: %s is to long (max: 64)', id)
148 def get_id(self, xml_id):
149 if xml_id is False or xml_id is None:
152 # raise YamlImportException("The xml_id should be a non empty string.")
153 elif isinstance(xml_id, types.IntType):
155 elif xml_id in self.id_map:
156 id = self.id_map[xml_id]
159 module, checked_xml_id = xml_id.split('.', 1)
162 checked_xml_id = xml_id
164 _, id = self.pool['ir.model.data'].get_object_reference(self.cr, self.uid, module, checked_xml_id)
165 self.id_map[xml_id] = id
167 raise ValueError("""%s not found when processing %s.
168 This Yaml file appears to depend on missing data. This often happens for
169 tests that belong to a module's test suite and depend on each other.""" % (checked_xml_id, self.filename))
173 def get_context(self, node, eval_dict):
174 context = self.context.copy()
176 context.update(eval(node.context, eval_dict))
179 def isnoupdate(self, node):
180 return self.noupdate or node.noupdate or False
182 def _get_first_result(self, results, default=False):
185 if isinstance(value, types.TupleType):
191 def process_comment(self, node):
194 def _log_assert_failure(self, msg, *args):
195 self.assertion_report.record_failure()
196 _logger.error(msg, *args)
198 def _get_assertion_id(self, assertion):
200 ids = [self.get_id(assertion.id)]
201 elif assertion.search:
202 q = eval(assertion.search, self.eval_context)
203 ids = self.pool[assertion.model].search(self.cr, self.uid, q, context=assertion.context)
205 raise YamlImportException('Nothing to assert: you must give either an id or a search criteria.')
208 def process_assert(self, node):
209 if isinstance(node, dict):
210 assertion, expressions = node.items()[0]
212 assertion, expressions = node, []
214 if self.isnoupdate(assertion) and self.mode != 'init':
215 _logger.warning('This assertion was not evaluated ("%s").', assertion.string)
217 model = self.get_model(assertion.model)
218 ids = self._get_assertion_id(assertion)
219 if assertion.count is not None and len(ids) != assertion.count:
220 msg = 'assertion "%s" failed!\n' \
221 ' Incorrect search count:\n' \
222 ' expected count: %d\n' \
223 ' obtained count: %d\n'
224 args = (assertion.string, assertion.count, len(ids))
225 self._log_assert_failure(msg, *args)
227 context = self.get_context(assertion, self.eval_context)
229 record = model.browse(self.cr, self.uid, id, context)
230 for test in expressions:
232 success = unsafe_eval(test, self.eval_context, RecordDictWrapper(record))
234 _logger.debug('Exception during evaluation of !assert block in yaml_file %s.', self.filename, exc_info=True)
235 raise YamlImportAbortion(e)
237 msg = 'Assertion "%s" FAILED\ntest: %s\n'
238 args = (assertion.string, test)
239 for aop in ('==', '!=', '<>', 'in', 'not in', '>=', '<=', '>', '<'):
241 left, right = test.split(aop,1)
245 lmsg = unsafe_eval(left, self.eval_context, RecordDictWrapper(record))
250 rmsg = unsafe_eval(right, self.eval_context, RecordDictWrapper(record))
254 msg += 'values: ! %s %s %s'
255 args += ( lmsg, aop, rmsg )
258 self._log_assert_failure(msg, *args)
260 else: # all tests were successful for this assertion tag (no break)
261 self.assertion_report.record_success()
263 def _coerce_bool(self, value, default=False):
264 if isinstance(value, types.BooleanType):
266 if isinstance(value, types.StringTypes):
267 b = value.strip().lower() not in ('0', 'false', 'off', 'no')
268 elif isinstance(value, types.IntType):
274 def create_osv_memory_record(self, record, fields):
275 model = self.get_model(record.model)
276 context = self.get_context(record, self.eval_context)
277 record_dict = self._create_record(model, fields)
278 id_new = model.create(self.cr, self.uid, record_dict, context=context)
279 self.id_map[record.id] = int(id_new)
282 def process_record(self, node):
283 record, fields = node.items()[0]
284 model = self.get_model(record.model)
286 view_id = record.view
287 if view_id and (view_id is not True) and isinstance(view_id, basestring):
290 module, view_id = view_id.split('.',1)
291 view_id = self.pool['ir.model.data'].get_object_reference(self.cr, SUPERUSER_ID, module, view_id)[1]
293 if model.is_transient():
294 record_dict=self.create_osv_memory_record(record, fields)
296 self.validate_xml_id(record.id)
298 self.pool['ir.model.data']._get_id(self.cr, SUPERUSER_ID, self.module, record.id)
303 if self.isnoupdate(record) and self.mode != 'init':
304 id = self.pool['ir.model.data']._update_dummy(self.cr, SUPERUSER_ID, record.model, self.module, record.id)
305 # check if the resource already existed at the last update
307 self.id_map[record] = int(id)
310 if not self._coerce_bool(record.forcecreate):
314 #context = self.get_context(record, self.eval_context)
315 #TOFIX: record.context like {'withoutemployee':True} should pass from self.eval_context. example: test_project.yml in project module
316 context = record.context
320 if view_id is True: varg = False
321 view_info = model.fields_view_get(self.cr, SUPERUSER_ID, varg, 'form', context)
323 record_dict = self._create_record(model, fields, view_info, default=default)
324 id = self.pool['ir.model.data']._update(self.cr, SUPERUSER_ID, record.model, \
325 self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode, context=context)
326 self.id_map[record.id] = int(id)
327 if config.get('import_partial'):
330 def _create_record(self, model, fields, view_info=None, parent={}, default=True):
331 """This function processes the !record tag in yalm files. It simulates the record creation through an xml
332 view (either specified on the !record tag or the default one for this object), including the calls to
333 on_change() functions, and sending only values for fields that aren't set as readonly.
334 :param model: model instance
335 :param fields: dictonary mapping the field names and their values
336 :param view_info: result of fields_view_get() called on the object
337 :param parent: dictionary containing the values already computed for the parent, in case of one2many fields
338 :param default: if True, the default values must be processed too or not
339 :return: dictionary mapping the field names and their values, ready to use when calling the create() function
342 def _get_right_one2many_view(fg, field_name, view_type):
343 one2many_view = fg[field_name]['views'].get(view_type)
344 # if the view is not defined inline, we call fields_view_get()
345 if not one2many_view:
346 one2many_view = self.pool[fg[field_name]['relation']].fields_view_get(self.cr, SUPERUSER_ID, False, view_type, self.context)
349 def process_val(key, val):
350 if fg[key]['type'] == 'many2one':
351 if type(val) in (tuple,list):
353 elif fg[key]['type'] == 'one2many':
354 if val and isinstance(val, (list,tuple)) and isinstance(val[0], dict):
355 # we want to return only the fields that aren't readonly
356 # For that, we need to first get the right tree view to consider for the field `key´
357 one2many_tree_view = _get_right_one2many_view(fg, key, 'tree')
358 arch = etree.fromstring(one2many_tree_view['arch'].encode('utf-8'))
360 # make a copy for the iteration, as we will alter `rec´
361 rec_copy = rec.copy()
362 for field_key in rec_copy:
363 # if field is missing in view or has a readonly modifier, drop it
364 field_elem = arch.xpath("//field[@name='%s']" % field_key)
365 if field_elem and (field_elem[0].get('modifiers', '{}').find('"readonly": true') >= 0):
366 # TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in
367 # order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]}
369 # now that unwanted values have been removed from val, we can encapsulate it in a tuple as returned value
370 val = map(lambda x: (0,0,x), val)
371 elif fg[key]['type'] == 'many2many':
372 if val and isinstance(val,(list,tuple)) and isinstance(val[0], (int,long)):
375 # we want to return only the fields that aren't readonly
376 if el.get('modifiers', '{}').find('"readonly": true') >= 0:
377 # TODO: currently we only support if readonly is True in the modifiers. Some improvement may be done in
378 # order to support also modifiers that look like {"readonly": [["state", "not in", ["draft", "confirm"]]]}
384 arch = etree.fromstring(view_info['arch'].decode('utf-8'))
385 view = arch if len(arch) else False
388 fields = fields or {}
389 if view is not False:
390 fg = view_info['fields']
391 # gather the default values on the object. (Can't use `fields´ as parameter instead of {} because we may
392 # have references like `base.main_company´ in the yaml file and it's not compatible with the function)
393 defaults = default and model._add_missing_default_values(self.cr, SUPERUSER_ID, {}, context=self.context) or {}
395 # copy the default values in record_dict, only if they are in the view (because that's what the client does)
396 # the other default values will be added later on by the create().
397 record_dict = dict([(key, val) for key, val in defaults.items() if key in fg])
399 # Process all on_change calls
404 field_name = el.attrib['name']
405 assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name)
406 if field_name in fields:
407 one2many_form_view = None
408 if (view is not False) and (fg[field_name]['type']=='one2many'):
409 # for one2many fields, we want to eval them using the inline form view defined on the parent
410 one2many_form_view = _get_right_one2many_view(fg, field_name, 'form')
412 field_value = self._eval_field(model, field_name, fields[field_name], one2many_form_view or view_info, parent=record_dict, default=default)
414 #call process_val to not update record_dict if values were given for readonly fields
415 val = process_val(field_name, field_value)
417 record_dict[field_name] = val
418 #if (field_name in defaults) and defaults[field_name] == field_value:
419 # print '*** You can remove these lines:', field_name, field_value
421 #if field_name has a default value or a value is given in the yaml file, we must call its on_change()
422 elif field_name not in defaults:
425 if not el.attrib.get('on_change', False):
427 match = re.match("([a-z_1-9A-Z]+)\((.*)\)", el.attrib['on_change'])
428 assert match, "Unable to parse the on_change '%s'!" % (el.attrib['on_change'], )
430 # creating the context
431 class parent2(object):
432 def __init__(self, d):
434 def __getattr__(self, name):
435 return self.d.get(name, False)
437 ctx = record_dict.copy()
438 ctx['context'] = self.context
439 ctx['uid'] = SUPERUSER_ID
440 ctx['parent'] = parent2(parent)
443 ctx[a] = process_val(a, defaults.get(a, False))
446 args = map(lambda x: eval(x, ctx), match.group(2).split(','))
447 result = getattr(model, match.group(1))(self.cr, SUPERUSER_ID, [], *args)
448 for key, val in (result or {}).get('value', {}).items():
450 "The field %r returned from the onchange call %r "
451 "does not exist in the source view %r (of object "
452 "%r). This field will be ignored (and thus not "
453 "populated) when clients saves the new record" % (
454 key, match.group(1), view_info.get('name', '?'), model._name
456 if key not in fields:
457 # do not shadow values explicitly set in yaml.
458 record_dict[key] = process_val(key, val)
460 nodes = list(el) + nodes
464 for field_name, expression in fields.items():
465 if field_name in record_dict:
467 field_value = self._eval_field(model, field_name, expression, default=False)
468 record_dict[field_name] = field_value
471 def process_ref(self, node, column=None):
472 assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute'
475 model_name = node.model
477 model_name = column._obj
479 raise YamlImportException('You need to give a model for the search, or a column to infer it.')
480 model = self.get_model(model_name)
481 q = eval(node.search, self.eval_context)
482 ids = model.search(self.cr, self.uid, q)
484 instances = model.browse(self.cr, self.uid, ids)
485 value = [inst[node.use] for inst in instances]
489 value = self.get_id(node.id)
494 def process_eval(self, node):
495 return eval(node.expression, self.eval_context)
497 def _eval_field(self, model, field_name, expression, view_info=False, parent={}, default=True):
498 # TODO this should be refactored as something like model.get_field() in bin/osv
499 if field_name in model._columns:
500 column = model._columns[field_name]
501 elif field_name in model._inherit_fields:
502 column = model._inherit_fields[field_name][2]
504 raise KeyError("Object '%s' does not contain field '%s'" % (model, field_name))
505 if is_ref(expression):
506 elements = self.process_ref(expression, column)
507 if column._type in ("many2many", "one2many"):
508 value = [(6, 0, elements)]
510 if isinstance(elements, (list,tuple)):
511 value = self._get_first_result(elements)
514 elif column._type == "many2one":
515 value = self.get_id(expression)
516 elif column._type == "one2many":
517 other_model = self.get_model(column._obj)
518 value = [(0, 0, self._create_record(other_model, fields, view_info, parent, default=default)) for fields in expression]
519 elif column._type == "many2many":
520 ids = [self.get_id(xml_id) for xml_id in expression]
521 value = [(6, 0, ids)]
522 elif column._type == "date" and is_string(expression):
523 # enforce ISO format for string date values, to be locale-agnostic during tests
524 time.strptime(expression, misc.DEFAULT_SERVER_DATE_FORMAT)
526 elif column._type == "datetime" and is_string(expression):
527 # enforce ISO format for string datetime values, to be locale-agnostic during tests
528 time.strptime(expression, misc.DEFAULT_SERVER_DATETIME_FORMAT)
531 if is_eval(expression):
532 value = self.process_eval(expression)
535 # raise YamlImportException('Unsupported column "%s" or value %s:%s' % (field_name, type(expression), expression))
538 def process_context(self, node):
539 self.context = node.__dict__
541 self.uid = self.get_id(node.uid)
543 self.noupdate = node.noupdate
545 def process_python(self, node):
546 python, statements = node.items()[0]
547 model = self.get_model(python.model)
548 statements = statements.replace("\r\n", "\n")
554 'context': self.context,
557 code_context.update({'self': model}) # remove me when no !python block test uses 'self' anymore
559 code_obj = compile(statements, self.filename, 'exec')
560 unsafe_eval(code_obj, {'ref': self.get_id}, code_context)
561 except AssertionError, e:
562 self._log_assert_failure('AssertionError in Python code %s: %s', python.name, e)
565 _logger.debug('Exception during evaluation of !python block in yaml_file %s.', self.filename, exc_info=True)
568 self.assertion_report.record_success()
570 def process_workflow(self, node):
571 workflow, values = node.items()[0]
572 if self.isnoupdate(workflow) and self.mode != 'init':
575 id = self.get_id(workflow.ref)
578 raise YamlImportException('You must define a child node if you do not give a ref.')
579 if not len(values) == 1:
580 raise YamlImportException('Only one child node is accepted (%d given).' % len(values))
582 if not 'model' in value and (not 'eval' in value or not 'search' in value):
583 raise YamlImportException('You must provide a "model" and an "eval" or "search" to evaluate.')
584 value_model = self.get_model(value['model'])
585 local_context = {'obj': lambda x: value_model.browse(self.cr, self.uid, x, context=self.context)}
586 local_context.update(self.id_map)
587 id = eval(value['eval'], self.eval_context, local_context)
589 if workflow.uid is not None:
593 self.cr.execute('select distinct signal from wkf_transition')
594 signals=[x['signal'] for x in self.cr.dictfetchall()]
595 if workflow.action not in signals:
596 raise YamlImportException('Incorrect action %s. No such action defined' % workflow.action)
597 openerp.workflow.trg_validate(uid, workflow.model, id, workflow.action, self.cr)
599 def _eval_params(self, model, params):
601 for i, param in enumerate(params):
602 if isinstance(param, types.ListType):
603 value = self._eval_params(model, param)
605 value = self.process_ref(param)
607 value = self.process_eval(param)
608 elif isinstance(param, types.DictionaryType): # supports XML syntax
609 param_model = self.get_model(param.get('model', model))
610 if 'search' in param:
611 q = eval(param['search'], self.eval_context)
612 ids = param_model.search(self.cr, self.uid, q)
613 value = self._get_first_result(ids)
614 elif 'eval' in param:
615 local_context = {'obj': lambda x: param_model.browse(self.cr, self.uid, x, self.context)}
616 local_context.update(self.id_map)
617 value = eval(param['eval'], self.eval_context, local_context)
619 raise YamlImportException('You must provide either a !ref or at least a "eval" or a "search" to function parameter #%d.' % i)
621 value = param # scalar value
625 def process_function(self, node):
626 function, params = node.items()[0]
627 if self.isnoupdate(function) and self.mode != 'init':
629 model = self.get_model(function.model)
631 args = self.process_eval(function.eval)
633 args = self._eval_params(function.model, params)
634 method = function.name
635 getattr(model, method)(self.cr, self.uid, *args)
637 def _set_group_values(self, node, values):
639 group_names = node.groups.split(',')
641 for group in group_names:
642 if group.startswith('-'):
643 group_id = self.get_id(group[1:])
644 groups_value.append((3, group_id))
646 group_id = self.get_id(group)
647 groups_value.append((4, group_id))
648 values['groups_id'] = groups_value
650 def process_menuitem(self, node):
651 self.validate_xml_id(node.id)
655 self.cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (node.name,))
656 res = self.cr.fetchone()
657 values = {'parent_id': parent_id, 'name': node.name}
659 parent_id = self.get_id(node.parent)
660 values = {'parent_id': parent_id}
662 values['name'] = node.name
664 res = [ self.get_id(node.id) ]
665 except: # which exception ?
669 action_type = node.type or 'act_window'
671 "act_window": 'STOCK_NEW',
672 "report.xml": 'STOCK_PASTE',
673 "wizard": 'STOCK_EXECUTE',
674 "url": 'STOCK_JUMP_TO',
676 values['icon'] = icons.get(action_type, 'STOCK_NEW')
677 if action_type == 'act_window':
678 action_id = self.get_id(node.action)
679 self.cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (action_id,))
680 ir_act_window_result = self.cr.fetchone()
681 assert ir_act_window_result, "No window action defined for this id %s !\n" \
682 "Verify that this is a window action or add a type argument." % (node.action,)
683 action_type, action_mode, action_name, view_id, target = ir_act_window_result
685 self.cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (view_id,))
686 # TODO guess why action_mode is ir_act_window.view_mode above and ir_ui_view.type here
687 action_mode = self.cr.fetchone()
688 self.cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (action_id,))
690 action_mode = self.cr.fetchone()
691 if action_type == 'tree':
692 values['icon'] = 'STOCK_INDENT'
693 elif action_mode and action_mode.startswith('tree'):
694 values['icon'] = 'STOCK_JUSTIFY_FILL'
695 elif action_mode and action_mode.startswith('graph'):
696 values['icon'] = 'terp-graph'
697 elif action_mode and action_mode.startswith('calendar'):
698 values['icon'] = 'terp-calendar'
700 values['icon'] = 'STOCK_EXECUTE'
701 if not values.get('name', False):
702 values['name'] = action_name
703 elif action_type == 'wizard':
704 action_id = self.get_id(node.action)
705 self.cr.execute('select name from ir_act_wizard where id=%s', (action_id,))
706 ir_act_wizard_result = self.cr.fetchone()
707 if (not values.get('name', False)) and ir_act_wizard_result:
708 values['name'] = ir_act_wizard_result[0]
710 raise YamlImportException("Unsupported type '%s' in menuitem tag." % action_type)
712 values['sequence'] = node.sequence
714 values['icon'] = node.icon
716 self._set_group_values(node, values)
718 pid = self.pool['ir.model.data']._update(self.cr, SUPERUSER_ID, \
719 'ir.ui.menu', self.module, values, node.id, mode=self.mode, \
720 noupdate=self.isnoupdate(node), res_id=res and res[0] or False)
722 if node.id and parent_id:
723 self.id_map[node.id] = int(parent_id)
725 if node.action and pid:
726 action_type = node.type or 'act_window'
727 action_id = self.get_id(node.action)
728 action = "ir.actions.%s,%d" % (action_type, action_id)
729 self.pool['ir.model.data'].ir_set(self.cr, SUPERUSER_ID, 'action', \
730 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(parent_id))], action, True, True, xml_id=node.id)
732 def process_act_window(self, node):
733 assert getattr(node, 'id'), "Attribute %s of act_window is empty !" % ('id',)
734 assert getattr(node, 'name'), "Attribute %s of act_window is empty !" % ('name',)
735 assert getattr(node, 'res_model'), "Attribute %s of act_window is empty !" % ('res_model',)
736 self.validate_xml_id(node.id)
739 view_id = self.get_id(node.view)
742 context = eval(str(node.context), self.eval_context)
745 'type': node.type or 'ir.actions.act_window',
747 'domain': node.domain,
749 'res_model': node.res_model,
750 'src_model': node.src_model,
751 'view_type': node.view_type or 'form',
752 'view_mode': node.view_mode or 'tree,form',
755 'auto_refresh': node.auto_refresh,
756 'multi': getattr(node, 'multi', False),
759 self._set_group_values(node, values)
762 values['target'] = node.target
763 id = self.pool['ir.model.data']._update(self.cr, SUPERUSER_ID, \
764 'ir.actions.act_window', self.module, values, node.id, mode=self.mode)
765 self.id_map[node.id] = int(id)
768 keyword = 'client_action_relate'
769 value = 'ir.actions.act_window,%s' % id
770 replace = node.replace or True
771 self.pool['ir.model.data'].ir_set(self.cr, SUPERUSER_ID, 'action', keyword, \
772 node.id, [node.src_model], value, replace=replace, noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id)
773 # TODO add remove ir.model.data
775 def process_delete(self, node):
776 assert getattr(node, 'model'), "Attribute %s of delete tag is empty !" % ('model',)
777 if node.model in self.pool:
779 ids = self.pool[node.model].search(self.cr, self.uid, eval(node.search, self.eval_context))
781 ids = [self.get_id(node.id)]
783 self.pool[node.model].unlink(self.cr, self.uid, ids)
785 self._log("Record not deleted.")
787 def process_url(self, node):
788 self.validate_xml_id(node.id)
790 res = {'name': node.name, 'url': node.url, 'target': node.target}
792 id = self.pool['ir.model.data']._update(self.cr, SUPERUSER_ID, \
793 "ir.actions.act_url", self.module, res, node.id, mode=self.mode)
794 self.id_map[node.id] = int(id)
796 if (not node.menu or eval(node.menu)) and id:
797 keyword = node.keyword or 'client_action_multi'
798 value = 'ir.actions.act_url,%s' % id
799 replace = node.replace or True
800 self.pool['ir.model.data'].ir_set(self.cr, SUPERUSER_ID, 'action', \
801 keyword, node.url, ["ir.actions.act_url"], value, replace=replace, \
802 noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id)
804 def process_ir_set(self, node):
805 if not self.mode == 'init':
807 _, fields = node.items()[0]
809 for fieldname, expression in fields.items():
810 if is_eval(expression):
811 value = eval(expression.expression, self.eval_context)
814 res[fieldname] = value
815 self.pool['ir.model.data'].ir_set(self.cr, SUPERUSER_ID, res['key'], res['key2'], \
816 res['name'], res['models'], res['value'], replace=res.get('replace',True), \
817 isobject=res.get('isobject', False), meta=res.get('meta',None))
819 def process_report(self, node):
821 for dest, f in (('name','string'), ('model','model'), ('report_name','name')):
822 values[dest] = getattr(node, f)
823 assert values[dest], "Attribute %s of report is empty !" % (f,)
824 for field,dest in (('rml','report_rml'),('file','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')):
825 if getattr(node, field):
826 values[dest] = getattr(node, field)
828 values['auto'] = eval(node.auto)
830 sxw_file = misc.file_open(node.sxw)
832 sxw_content = sxw_file.read()
833 values['report_sxw_content'] = sxw_content
837 values['header'] = eval(node.header)
838 values['multi'] = node.multi and eval(node.multi)
840 self.validate_xml_id(xml_id)
842 self._set_group_values(node, values)
844 id = self.pool['ir.model.data']._update(self.cr, SUPERUSER_ID, "ir.actions.report.xml", \
845 self.module, values, xml_id, noupdate=self.isnoupdate(node), mode=self.mode)
846 self.id_map[xml_id] = int(id)
848 if not node.menu or eval(node.menu):
849 keyword = node.keyword or 'client_print_multi'
850 value = 'ir.actions.report.xml,%s' % id
851 replace = node.replace or True
852 self.pool['ir.model.data'].ir_set(self.cr, SUPERUSER_ID, 'action', \
853 keyword, values['name'], [values['model']], value, replace=replace, isobject=True, xml_id=xml_id)
855 def process_none(self):
857 Empty node or commented node should not pass silently.
859 self._log_assert_failure("You have an empty block in your tests.")
862 def process(self, yaml_string):
864 Processes a Yaml string. Custom tags are interpreted by 'process_' instance methods.
866 yaml_tag.add_constructors()
868 is_preceded_by_comment = False
869 for node in yaml.load(yaml_string):
870 is_preceded_by_comment = self._log_node(node, is_preceded_by_comment)
872 self._process_node(node)
877 def _process_node(self, node):
879 self.process_comment(node)
880 elif is_assert(node):
881 self.process_assert(node)
882 elif is_record(node):
883 self.process_record(node)
884 elif is_python(node):
885 self.process_python(node)
886 elif is_menuitem(node):
887 self.process_menuitem(node)
888 elif is_delete(node):
889 self.process_delete(node)
891 self.process_url(node)
892 elif is_context(node):
893 self.process_context(node)
894 elif is_ir_set(node):
895 self.process_ir_set(node)
896 elif is_act_window(node):
897 self.process_act_window(node)
898 elif is_report(node):
899 self.process_report(node)
900 elif is_workflow(node):
901 if isinstance(node, types.DictionaryType):
902 self.process_workflow(node)
904 self.process_workflow({node: []})
905 elif is_function(node):
906 if isinstance(node, types.DictionaryType):
907 self.process_function(node)
909 self.process_function({node: []})
913 raise YamlImportException("Can not process YAML block: %s" % node)
915 def _log_node(self, node, is_preceded_by_comment):
917 is_preceded_by_comment = True
919 elif not is_preceded_by_comment:
920 if isinstance(node, types.DictionaryType):
921 msg = "Creating %s\n with %s"
922 args = node.items()[0]
923 self._log(msg, *args)
927 is_preceded_by_comment = False
928 return is_preceded_by_comment
930 def yaml_import(cr, module, yamlfile, kind, idref=None, mode='init', noupdate=False, report=None):
933 loglevel = logging.DEBUG
934 yaml_string = yamlfile.read()
935 yaml_interpreter = YamlInterpreter(cr, module, idref, mode, filename=yamlfile.name, report=report, noupdate=noupdate, loglevel=loglevel)
936 yaml_interpreter.process(yaml_string)
938 # keeps convention of convert.py
939 convert_yaml_import = yaml_import
941 def threaded_yaml_import(db_name, module_name, file_name, delay=0):
947 cr = sql_db.db_connect(db_name).cursor()
948 fp = misc.file_open(file_name)
949 convert_yaml_import(cr, module_name, fp, {}, 'update', True)
953 threading.Thread(target=f).start()
956 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: