diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index e07fdd07bbc..2b0f90bc5cf 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -589,8 +589,75 @@ class MigrationManager(object): if mod: del mod +log = logging.getLogger('init') def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): + + def process_sql_file(cr, file): + queries = fp.read().split(';') + for query in queries: + new_query = ' '.join(query.split()) + if new_query: + cr.execute(new_query) + + def load_init_update_xml(cr, m, idref, mode, kind): + for filename in package.data.get('%s_xml' % kind, []): + logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename)) + _, ext = os.path.splitext(filename) + fp = tools.file_open(opj(m, filename)) + if ext == '.csv': + noupdate = (kind == 'init') + tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate) + elif ext == '.sql': + process_sql_file(cr, fp) + elif ext == '.yml': + tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs) + else: + tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs) + fp.close() + + def load_demo_xml(cr, m, idref, mode): + for xml in package.data.get('demo_xml', []): + name, ext = os.path.splitext(xml) + logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, xml)) + fp = tools.file_open(opj(m, xml)) + if ext == '.csv': + tools.convert_csv_import(cr, m, os.path.basename(xml), fp.read(), idref, mode=mode, noupdate=True) + elif ext == '.yml': + tools.convert_yaml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs) + else: + tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs) + fp.close() + + def load_data(cr, module_name, id_map, mode): + _load_data(cr, module_name, id_map, mode, 'data') + + def load_demo(cr, module_name, id_map, mode): + _load_data(cr, module_name, id_map, mode, 'demo') + + def load_test(cr, module_name, id_map, mode): + cr.commit() + try: + _load_data(cr, module_name, id_map, mode, 'test') + finally: + cr.rollback() + + def _load_data(cr, module_name, id_map, mode, kind): + noupdate = (kind == 'demo') + for filename in package.data.get(kind, []): + _, ext = os.path.splitext(filename) + log.info("module %s: loading %s", module_name, filename) + pathname = os.path.join(module_name, filename) + file = tools.file_open(pathname) + # TODO manage .csv file with noupdate == (kind == 'init') + if ext == '.sql': + process_sql_file(cr, fp) + elif ext == '.yml': + tools.convert_yaml_import(cr, module_name, file, id_map, mode, noupdate) + else: + tools.convert_xml_import(cr, module_name, file, id_map, mode, noupdate) + file.close() + # **kwargs is passed directly to convert_xml_import if not status: status = {} @@ -647,40 +714,16 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): 'license': package.data.get('license', 'GPL-2'), 'certificate': package.data.get('certificate') or None, }) - for filename in package.data.get('%s_xml' % kind, []): - logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename)) - name, ext = os.path.splitext(filename) - fp = tools.file_open(opj(m, filename)) - if ext == '.csv': - noupdate=False - if kind == 'init': - noupdate=True - tools.convert_csv_import(cr, m, os.path.basename(filename), fp.read(), idref, mode=mode, noupdate=noupdate) - elif ext == '.sql': - queries = fp.read().split(';') - for query in queries: - new_query = ' '.join(query.split()) - if new_query: - cr.execute(new_query) - elif ext == '.yml': - tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs) - else: - tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs) - fp.close() + load_init_update_xml(cr, m, idref, mode, kind) + load_data(cr, m, idref, mode) if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'): status['progress'] = (float(statusi)+0.75) / len(graph) - for xml in package.data.get('demo_xml', []): - name, ext = os.path.splitext(xml) - logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, xml)) - fp = tools.file_open(opj(m, xml)) - if ext == '.csv': - tools.convert_csv_import(cr, m, os.path.basename(xml), fp.read(), idref, mode=mode, noupdate=True) - elif ext == '.yml': - tools.convert_yaml_import(cr, m, fp, idref, mode=mode, **kwargs) - else: - tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs) - fp.close() + load_demo_xml(cr, m, idref, mode) + load_demo(cr, m, idref, mode) cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid)) + + load_test(cr, m, idref, mode) + package_todo.append(package.name) migrations.migrate_module(package, 'post') diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index db03cef116f..a4afff1973d 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -548,10 +548,10 @@ class ir_model_data(osv.osv): table.replace('.', '_'))] = (table, inherit_id) return res_id - def _unlink(self, cr, uid, model, ids, direct=False): - for id in ids: - self.unlink_mark[(model, id)]=False - cr.execute('delete from ir_model_data where res_id=%s and model=%s', (id, model)) + def _unlink(self, cr, uid, model, res_ids): + for res_id in res_ids: + self.unlink_mark[(model, res_id)] = False + cr.execute('delete from ir_model_data where res_id=%s and model=%s', (res_id, model)) return True def ir_set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=None, xml_id=False): diff --git a/bin/addons/base/ir/ir_sequence.py b/bin/addons/base/ir/ir_sequence.py index 8b0e7795a7c..fcc197116a1 100644 --- a/bin/addons/base/ir/ir_sequence.py +++ b/bin/addons/base/ir/ir_sequence.py @@ -73,18 +73,15 @@ class ir_sequence(osv.osv): } def get_id(self, cr, uid, sequence_id, test='id', context=None): - try: - assert test in ('code','id') - cr.execute('SELECT id, number_next, prefix, suffix, padding FROM ir_sequence WHERE '+test+'=%s AND active=%s FOR UPDATE', (sequence_id, True)) - res = cr.dictfetchone() - if res: - cr.execute('UPDATE ir_sequence SET number_next=number_next+number_increment WHERE id=%s AND active=%s', (res['id'], True)) - if res['number_next']: - return self._process(res['prefix']) + '%%0%sd' % res['padding'] % res['number_next'] + self._process(res['suffix']) - else: - return self._process(res['prefix']) + self._process(res['suffix']) - finally: - cr.commit() + assert test in ('code','id') + cr.execute('SELECT id, number_next, prefix, suffix, padding FROM ir_sequence WHERE '+test+'=%s AND active=%s FOR UPDATE NOWAIT', (sequence_id, True)) + res = cr.dictfetchone() + if res: + cr.execute('UPDATE ir_sequence SET number_next=number_next+number_increment WHERE id=%s AND active=%s', (res['id'], True)) + if res['number_next']: + return self._process(res['prefix']) + '%%0%sd' % res['padding'] % res['number_next'] + self._process(res['suffix']) + else: + return self._process(res['prefix']) + self._process(res['suffix']) return False def get(self, cr, uid, code): diff --git a/bin/import_xml.rng b/bin/import_xml.rng index e637d42f123..c852d421e17 100644 --- a/bin/import_xml.rng +++ b/bin/import_xml.rng @@ -148,8 +148,10 @@ - - + + + + diff --git a/bin/netsvc.py b/bin/netsvc.py index 475bbc33ba7..6aaff59ead3 100644 --- a/bin/netsvc.py +++ b/bin/netsvc.py @@ -205,7 +205,10 @@ def init_logger(): for level, (fg, bg) in mapping.items(): msg = "\x1b[%dm\x1b[%dm%s\x1b[0m" % (foreground(fg), background(bg), level) - logging.addLevelName(getattr(logging, level), msg) +# jth: we should not override default level name +# jth: use this instead http://stackoverflow.com/questions/384076/how-can-i-make-the-python-logging-output-to-be-colored +# jth: also, do not output ANSI if terminal doesn't support it (test it in Eclipse console and on windows) + #logging.addLevelName(getattr(logging, level), msg) class Logger(object): diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 1d4b1fd9d0b..36f2740dbc9 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -251,7 +251,7 @@ form: module.record_id""" % (xml_id,) pass if ids: self.pool.get(d_model).unlink(cr, self.uid, ids) - self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids, direct=True) + self.pool.get('ir.model.data')._unlink(cr, self.uid, d_model, ids) def _tag_report(self, cr, rec, data_node=None): res = {} diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py index fed3e81299e..e77999a49f1 100644 --- a/bin/tools/yaml_import.py +++ b/bin/tools/yaml_import.py @@ -7,6 +7,7 @@ import pooler import netsvc import misc from config import config +import yaml_tag import yaml @@ -18,161 +19,6 @@ class YamlImportException(Exception): class YamlImportAbortion(Exception): pass -class YamlTag(object): - """ - Superclass for constructors of custom tags defined in yaml file. - """ - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - def __getitem__(self, key): - return getattr(self, key) - def __getattr__(self, attr): - return None - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, sorted(self.__dict__.items())) - -class Assert(YamlTag): - def __init__(self, model, id, severity=logging.ERROR, string="NONAME", **kwargs): - self.model = model - self.id = id - self.severity = severity - self.string = string - super(Assert, self).__init__(**kwargs) - -class Record(YamlTag): - def __init__(self, model, id, **kwargs): - self.model = model - self.id = id - super(Record, self).__init__(**kwargs) - -class Python(YamlTag): - def __init__(self, model, severity=logging.ERROR, name="", **kwargs): - self.model= model - self.severity = severity - self.name = name - super(Python, self).__init__(**kwargs) - -class Menuitem(YamlTag): - def __init__(self, id, name, **kwargs): - self.id = id - self.name = name - super(Menuitem, self).__init__(**kwargs) - -class Workflow(YamlTag): - def __init__(self, model, action, **kwargs): - self.model = model - self.action = action - super(Workflow, self).__init__(**kwargs) - -class ActWindow(YamlTag): - def __init__(self, model, action, **kwargs): - self.model = model - self.action = action - super(ActWindow, self).__init__(**kwargs) - -class Function(YamlTag): - def __init__(self, model, name, **kwargs): - self.model = model - self.name = name - super(Function, self).__init__(**kwargs) - -class Report(YamlTag): - def __init__(self, model, name, string, **kwargs): - self.model = model - self.name = name - self.string = string - super(Report, self).__init__(**kwargs) - -class Delete(YamlTag): - def __init__(self, model, id, search, **kwargs): - self.model = model - self.id = id - self.search = search - super(Delete, self).__init__(**kwargs) - -class Context(YamlTag): - def __init__(self, **kwargs): - super(Context, self).__init__(**kwargs) - -class Url(YamlTag): - def __init__(self, **kwargs): - super(Url, self).__init__(**kwargs) - -class Eval(YamlTag): - def __init__(self, expression): - self.expression = expression - super(Eval, self).__init__() - -class IrSet(YamlTag): - def __init__(self): - super(IrSet, self).__init__() - -def assert_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Assert(**kwargs) - -def record_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Record(**kwargs) - -def python_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Python(**kwargs) - -def menuitem_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Menuitem(**kwargs) - -def workflow_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Workflow(**kwargs) - -def act_window_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return ActWindow(**kwargs) - -def function_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Function(**kwargs) - -def report_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Report(**kwargs) - -def delete_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Delete(**kwargs) - -def context_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Context(**kwargs) - -def url_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return Url(**kwargs) - -def eval_constructor(loader, node): - expression = loader.construct_scalar(node) - return Eval(expression) - -def ir_set_constructor(loader, node): - kwargs = loader.construct_mapping(node) - return IrSet(**kwargs) - -yaml.add_constructor(u"!assert", assert_constructor) -yaml.add_constructor(u"!record", record_constructor) -yaml.add_constructor(u"!python", python_constructor) -yaml.add_constructor(u"!menuitem", menuitem_constructor) -yaml.add_constructor(u"!workflow", workflow_constructor) -yaml.add_constructor(u"!act_window", act_window_constructor) -yaml.add_constructor(u"!function", function_constructor) -yaml.add_constructor(u"!report", report_constructor) -yaml.add_constructor(u"!context", context_constructor) -yaml.add_constructor(u"!delete", delete_constructor) -yaml.add_constructor(u"!url", url_constructor) -yaml.add_constructor(u"!eval", eval_constructor) -yaml.add_constructor(u"!ir_set", ir_set_constructor) - def _is_yaml_mapping(node, tag_constructor): value = isinstance(node, types.DictionaryType) \ and len(node.keys()) == 1 \ @@ -183,45 +29,49 @@ def is_comment(node): return isinstance(node, types.StringTypes) def is_assert(node): - return _is_yaml_mapping(node, Assert) + return _is_yaml_mapping(node, yaml_tag.Assert) def is_record(node): - return _is_yaml_mapping(node, Record) + return _is_yaml_mapping(node, yaml_tag.Record) def is_python(node): - return _is_yaml_mapping(node, Python) + return _is_yaml_mapping(node, yaml_tag.Python) def is_menuitem(node): - return isinstance(node, Menuitem) \ - or _is_yaml_mapping(node, Menuitem) + return isinstance(node, yaml_tag.Menuitem) \ + or _is_yaml_mapping(node, yaml_tag.Menuitem) def is_function(node): - return isinstance(node, Function) \ - or _is_yaml_mapping(node, Function) + return isinstance(node, yaml_tag.Function) \ + or _is_yaml_mapping(node, yaml_tag.Function) def is_report(node): - return isinstance(node, Report) + return isinstance(node, yaml_tag.Report) def is_workflow(node): - return isinstance(node, Workflow) + return isinstance(node, yaml_tag.Workflow) def is_act_window(node): - return isinstance(node, ActWindow) + return isinstance(node, yaml_tag.ActWindow) def is_delete(node): - return isinstance(node, Delete) + return isinstance(node, yaml_tag.Delete) def is_context(node): - return isinstance(node, Context) + return isinstance(node, yaml_tag.Context) def is_url(node): - return isinstance(node, Url) + return isinstance(node, yaml_tag.Url) def is_eval(node): - return isinstance(node, Eval) + return isinstance(node, yaml_tag.Eval) + +def is_ref(node): + return isinstance(node, yaml_tag.Ref) \ + or _is_yaml_mapping(node, yaml_tag.Ref) def is_ir_set(node): - return _is_yaml_mapping(node, IrSet) + return _is_yaml_mapping(node, yaml_tag.IrSet) class TestReport(object): @@ -276,7 +126,7 @@ class YamlInterpreter(object): self.pool = pooler.get_pool(cr.dbname) self.uid = 1 self.context = {} # opererp context - self.eval_context = {'ref': self._ref, '_ref': self._ref} # added '_ref' so that record['ref'] is possible + self.eval_context = {'ref': self._ref(), '_ref': self._ref(), 'time': time} # added '_ref' so that record['ref'] is possible def _ref(self): return lambda xml_id: self.get_id(xml_id) @@ -309,10 +159,11 @@ class YamlInterpreter(object): id = self.id_map[xml_id] else: if '.' in xml_id: - module, xml_id = xml_id.split('.', 1) + module, checked_xml_id = xml_id.split('.', 1) else: module = self.module - ir_id = self.pool.get('ir.model.data')._get_id(self.cr, self.uid, module, xml_id) + checked_xml_id = xml_id + ir_id = self.pool.get('ir.model.data')._get_id(self.cr, self.uid, module, checked_xml_id) obj = self.pool.get('ir.model.data').read(self.cr, self.uid, ir_id, ['res_id']) id = int(obj['res_id']) self.id_map[xml_id] = id @@ -327,14 +178,29 @@ class YamlInterpreter(object): def isnoupdate(self, node): return self.noupdate or node.noupdate or False + def _get_first_result(self, results, default=False): + if len(results): + value = results[0] + if isinstance(value, types.TupleType): + value = value[0] + else: + value = default + return value + def process_comment(self, node): return node def _log_assert_failure(self, severity, msg, *args): - self.assert_report.record(False, severity) - self.logger.log(severity, msg, *args) - if severity >= config['assert_exit_level']: - raise YamlImportAbortion('Severe assertion failure (%s), aborting.' % logging.getLevelName(severity)) + if isinstance(severity, types.StringTypes): + levelname = severity.strip().upper() + level = logging.getLevelName(levelname) + else: + level = severity + levelname = logging.getLevelName(level) + self.assert_report.record(False, levelname) + self.logger.log(level, msg, *args) + if level >= config['assert_exit_level']: + raise YamlImportAbortion('Severe assertion failure (%s), aborting.' % levelname) return def _get_assertion_id(self, assertion): @@ -351,6 +217,7 @@ class YamlInterpreter(object): assertion, expressions = node.items()[0] if self.isnoupdate(assertion) and self.mode != 'init': + self.logger.warn('This assertion was not evaluated ("%s").' % assertion.string) return model = self.get_model(assertion.model) ids = self._get_assertion_id(assertion) @@ -365,7 +232,7 @@ class YamlInterpreter(object): context = self.get_context(assertion, self.eval_context) for id in ids: record = model.browse(self.cr, self.uid, id, context) - for test in expressions.get('test', ''): + for test in expressions: try: success = eval(test, self.eval_context, RecordDictWrapper(record)) except Exception, e: @@ -378,19 +245,39 @@ class YamlInterpreter(object): else: # all tests were successful for this assertion tag (no break) self.assert_report.record(True, assertion.severity) + def _coerce_bool(self, value, default=False): + if isinstance(value, types.BooleanType): + b = value + if isinstance(value, types.StringTypes): + b = value.strip().lower() not in ('0', 'false', 'off', 'no') + elif isinstance(value, types.IntType): + b = bool(value) + else: + b = default + return b + def process_record(self, node): record, fields = node.items()[0] self.validate_xml_id(record.id) if self.isnoupdate(record) and self.mode != 'init': - model = self.get_model(record.model) - record_dict = self._create_record(model, fields) - self.logger.debug("RECORD_DICT %s" % record_dict) - id = self.pool.get('ir.model.data')._update(self.cr, self.uid, record.model, \ - self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode) - self.id_map[record.id] = int(id) - if config.get('import_partial', False): - self.cr.commit() + id = self.pool.get('ir.model.data')._update_dummy(self.cr, self.uid, record.model, self.module, record.id) + # check if the resource already existed at the last update + if id: + self.id_map[record] = int(id) + return None + else: + if not self._coerce_bool(record.forcecreate): + return None + + model = self.get_model(record.model) + record_dict = self._create_record(model, fields) + self.logger.debug("RECORD_DICT %s" % record_dict) + id = self.pool.get('ir.model.data')._update(self.cr, self.uid, record.model, \ + self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode) + self.id_map[record.id] = int(id) + if config.get('import_partial', False): + self.cr.commit() def _create_record(self, model, fields): record_dict = {} @@ -399,19 +286,50 @@ class YamlInterpreter(object): record_dict[field_name] = field_value return record_dict + def process_ref(self, node, column=None): + if node.search: + if node.model: + model_name = node.model + elif column: + model_name = column._obj + else: + raise YamlImportException('You need to give a model for the search, or a column to infer it.') + model = self.get_model(model_name) + q = eval(node.search, self.eval_context) + ids = model.search(self.cr, self.uid, q) + if node.use: + instances = model.browse(self.cr, self.uid, ids) + value = [inst[node.use] for inst in instances] + else: + value = ids + elif node.id: + value = self.get_id(node.id) + else: + value = None + return value + + def process_eval(self, node): + return eval(node.expression, self.eval_context) + def _eval_field(self, model, field_name, expression): column = model._columns[field_name] - if column._type == "many2one": + if is_ref(expression): + elements = self.process_ref(expression) + if column._type in ("many2many", "one2many"): + value = [(6, 0, elements)] + else: # many2one + value = self._get_first_result(elements) + elif column._type == "many2one": value = self.get_id(expression) elif column._type == "one2many": other_model = self.get_model(column._obj) value = [(0, 0, self._create_record(other_model, fields)) for fields in expression] elif column._type == "many2many": ids = [self.get_id(xml_id) for xml_id in expression] - value= [(6, 0, ids)] + value = [(6, 0, ids)] else: # scalar field - if isinstance(expression, Eval): - value = eval(expression.expression) + if is_eval(expression): + value = self.process_eval(expression) else: value = expression # raise YamlImportException('Unsupported column "%s" or value %s:%s' % (field_name, type(expression), expression)) @@ -446,7 +364,6 @@ class YamlInterpreter(object): workflow, values = node.items()[0] if self.isnoupdate(workflow) and self.mode != 'init': return - model = self.get_model(workflow.model) if workflow.ref: id = self.get_id(workflow.ref) else: @@ -467,26 +384,44 @@ class YamlInterpreter(object): else: uid = self.uid wf_service = netsvc.LocalService("workflow") - wf_service.trg_validate(uid, model, id, workflow.action, self.cr) + wf_service.trg_validate(uid, workflow.model, id, workflow.action, self.cr) + + def _eval_params(self, model, params): + args = [] + for i, param in enumerate(params): + if isinstance(param, types.ListType): + value = self._eval_params(model, param) + elif is_ref(param): + value = self.process_ref(param) + elif is_eval(param): + value = self.process_eval(param) + elif isinstance(param, types.DictionaryType): # supports XML syntax + param_model = self.get_model(param.get('model', model)) + if 'search' in param: + q = eval(param['search'], self.eval_context) + ids = param_model.search(cr, uid, q) + value = self._get_first_result(ids) + elif 'eval' in param: + local_context = {'obj': lambda x: param_model.browse(self.cr, self.uid, x, context)} + local_context.update(self.id_map) + value = eval(param['eval'], self.eval_context, local_context) + else: + raise YamlImportException('You must provide either a !ref or at least a "eval" or a "search" to function parameter #%d.' % i) + else: + value = param # scalar value + args.append(value) + return args def process_function(self, node): - function, values = node.items()[0] + function, params = node.items()[0] if self.isnoupdate(function) and self.mode != 'init': return - context = self.get_context(function, self.eval_context) - args = [] - if function.eval: - args = eval(function.eval, self.eval_context) - for value in values: - if not 'model' in value and (not 'eval' in value or not 'search' in value): - raise YamlImportException('You must provide a "model" and an "eval" or "search" to evaluate.') - value_model = self.get_model(value['model']) - local_context = {'obj': lambda x: value_model.browse(self.cr, self.uid, x, context=context)} - local_context.update(self.id_map) - id = eval(value['eval'], self.eval_context, local_context) - if id != None: - args.append(id) model = self.get_model(function.model) + context = self.get_context(function, self.eval_context) + if function.eval: + args = self.process_eval(function.eval) + else: + args = self._eval_params(function.model, params) method = function.name getattr(model, method)(self.cr, self.uid, *args) @@ -624,17 +559,13 @@ class YamlInterpreter(object): # TODO add remove ir.model.data def process_delete(self, node): - ids = [] if len(node.search): ids = self.pool.get(node.model).search(self.cr, self.uid, eval(node.search, self.eval_context)) - if len(node.id): - try: - ids.append(self.get_id(node.id)) - except: - pass + else: + ids = [self.get_id(node.id)] if len(ids): self.pool.get(node.model).unlink(self.cr, self.uid, ids) - self.pool.get('ir.model.data')._unlink(self.cr, self.uid, node.model, ids, direct=True) + self.pool.get('ir.model.data')._unlink(self.cr, self.uid, node.model, ids) def process_url(self, node): self.validate_xml_id(node.id) @@ -717,9 +648,9 @@ class YamlInterpreter(object): try: self._process_node(node) except YamlImportException, e: - self.logger.log(logging.ERROR, e) + self.logger.exception(e) except Exception, e: - self.logger.log(logging.ERROR, e) + self.logger.exception(e) raise e def _process_node(self, node): diff --git a/bin/tools/yaml_tag.py b/bin/tools/yaml_tag.py new file mode 100644 index 00000000000..97b29db1ac0 --- /dev/null +++ b/bin/tools/yaml_tag.py @@ -0,0 +1,172 @@ +import yaml +import logging + +class YamlTag(object): + """ + Superclass for constructors of custom tags defined in yaml file. + """ + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + def __getitem__(self, key): + return getattr(self, key) + def __getattr__(self, attr): + return None + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, sorted(self.__dict__.items())) + +class Assert(YamlTag): + def __init__(self, model, id, severity=logging.ERROR, string="NONAME", **kwargs): + self.model = model + self.id = id + self.severity = severity + self.string = string + super(Assert, self).__init__(**kwargs) + +class Record(YamlTag): + def __init__(self, model, id, use='id', **kwargs): + self.model = model + self.id = id + super(Record, self).__init__(**kwargs) + +class Python(YamlTag): + def __init__(self, model, severity=logging.ERROR, name="", **kwargs): + self.model= model + self.severity = severity + self.name = name + super(Python, self).__init__(**kwargs) + +class Menuitem(YamlTag): + def __init__(self, id, name, **kwargs): + self.id = id + self.name = name + super(Menuitem, self).__init__(**kwargs) + +class Workflow(YamlTag): + def __init__(self, model, action, **kwargs): + self.model = model + self.action = action + super(Workflow, self).__init__(**kwargs) + +class ActWindow(YamlTag): + def __init__(self, model, action, **kwargs): + self.model = model + self.action = action + super(ActWindow, self).__init__(**kwargs) + +class Function(YamlTag): + def __init__(self, model, name, **kwargs): + self.model = model + self.name = name + super(Function, self).__init__(**kwargs) + +class Report(YamlTag): + def __init__(self, model, name, string, **kwargs): + self.model = model + self.name = name + self.string = string + super(Report, self).__init__(**kwargs) + +class Delete(YamlTag): + def __init__(self, model, id, search, **kwargs): + self.model = model + self.id = id + self.search = search + super(Delete, self).__init__(**kwargs) + +class Context(YamlTag): + def __init__(self, **kwargs): + super(Context, self).__init__(**kwargs) + +class Url(YamlTag): + def __init__(self, **kwargs): + super(Url, self).__init__(**kwargs) + +class Eval(YamlTag): + def __init__(self, expression): + self.expression = expression + super(Eval, self).__init__() + +class Ref(YamlTag): + def __init__(self, *args, **kwargs): + super(Ref, self).__init__(*args, **kwargs) + +class IrSet(YamlTag): + def __init__(self): + super(IrSet, self).__init__() + +def assert_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Assert(**kwargs) + +def record_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Record(**kwargs) + +def python_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Python(**kwargs) + +def menuitem_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Menuitem(**kwargs) + +def workflow_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Workflow(**kwargs) + +def act_window_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return ActWindow(**kwargs) + +def function_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Function(**kwargs) + +def report_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Report(**kwargs) + +def delete_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Delete(**kwargs) + +def context_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Context(**kwargs) + +def url_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return Url(**kwargs) + +def eval_constructor(loader, node): + expression = loader.construct_scalar(node) + return Eval(expression) + +def ref_constructor(loader, tag_suffix, node): + if tag_suffix == "id": + kwargs = {"id": loader.construct_scalar(node)} + else: + kwargs = loader.construct_mapping(node) + return Ref(**kwargs) + +def ir_set_constructor(loader, node): + kwargs = loader.construct_mapping(node) + return IrSet(**kwargs) + +# Registers constructors for custom tags. +# Constructors are actually defined globally: do not redefined them in another +# class/file/package. This means that module recorder need import this file. +yaml.add_constructor(u"!assert", assert_constructor) +yaml.add_constructor(u"!record", record_constructor) +yaml.add_constructor(u"!python", python_constructor) +yaml.add_constructor(u"!menuitem", menuitem_constructor) +yaml.add_constructor(u"!workflow", workflow_constructor) +yaml.add_constructor(u"!act_window", act_window_constructor) +yaml.add_constructor(u"!function", function_constructor) +yaml.add_constructor(u"!report", report_constructor) +yaml.add_constructor(u"!context", context_constructor) +yaml.add_constructor(u"!delete", delete_constructor) +yaml.add_constructor(u"!url", url_constructor) +yaml.add_constructor(u"!eval", eval_constructor) +yaml.add_multi_constructor(u"!ref", ref_constructor) +yaml.add_constructor(u"!ir_set", ir_set_constructor)