diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index b599a1002cc..4dd62f11694 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -592,8 +592,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 = {} @@ -650,40 +717,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/i18n/he.po b/bin/addons/base/i18n/he.po index 1f8196732f8..1e3765b48e3 100644 --- a/bin/addons/base/i18n/he.po +++ b/bin/addons/base/i18n/he.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2009-12-18 17:51+0000\n" +"PO-Revision-Date: 2010-04-05 04:29+0000\n" "Last-Translator: Ddorda \n" "Language-Team: Hebrew \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-01-14 04:46+0000\n" +"X-Launchpad-Export-Date: 2010-04-06 04:00+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -3006,7 +3006,7 @@ msgstr "מפריד עשרות" #: view:res.request:0 #: field:res.request,history:0 msgid "History" -msgstr "הסטוריה" +msgstr "היסטוריה" #. module: base #: field:ir.attachment,create_uid:0 @@ -4776,7 +4776,7 @@ msgstr "8. %I:%M:%S %p ==> 06:25:20 PM" #: view:ir.translation:0 #: model:ir.ui.menu,name:base.menu_translation msgid "Translations" -msgstr "תרגום" +msgstr "תרגומים" #. module: base #: field:ir.sequence,padding:0 diff --git a/bin/addons/base/i18n/hr.po b/bin/addons/base/i18n/hr.po index 55f4faa1f59..0069d26fd4f 100644 --- a/bin/addons/base/i18n/hr.po +++ b/bin/addons/base/i18n/hr.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-04-01 04:15+0000\n" +"PO-Revision-Date: 2010-04-06 04:41+0000\n" "Last-Translator: goranc \n" "Language-Team: openerp-translators\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-04-02 03:41+0000\n" +"X-Launchpad-Export-Date: 2010-04-07 03:46+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Language: hr\n" diff --git a/bin/addons/base/i18n/ja.po b/bin/addons/base/i18n/ja.po index b3b586b82a7..98ee2ba6b7b 100644 --- a/bin/addons/base/i18n/ja.po +++ b/bin/addons/base/i18n/ja.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: openobject-server\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-04-01 04:13+0000\n" +"PO-Revision-Date: 2010-04-06 04:39+0000\n" "Last-Translator: Harry (Open ERP) \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-04-02 03:41+0000\n" +"X-Launchpad-Export-Date: 2010-04-07 03:46+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base diff --git a/bin/addons/base/i18n/sv.po b/bin/addons/base/i18n/sv.po index 4021ba05f39..26ba50df07c 100644 --- a/bin/addons/base/i18n/sv.po +++ b/bin/addons/base/i18n/sv.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-04-01 04:09+0000\n" +"PO-Revision-Date: 2010-04-06 04:37+0000\n" "Last-Translator: Anders Wallenquist \n" "Language-Team: <>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-04-02 03:41+0000\n" +"X-Launchpad-Export-Date: 2010-04-07 03:46+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base diff --git a/bin/addons/base/i18n/uk.po b/bin/addons/base/i18n/uk.po index bb304a6d3a1..15eb8509852 100644 --- a/bin/addons/base/i18n/uk.po +++ b/bin/addons/base/i18n/uk.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-04-01 04:14+0000\n" +"PO-Revision-Date: 2010-04-06 04:43+0000\n" "Last-Translator: Fabien (Open ERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-04-02 03:41+0000\n" +"X-Launchpad-Export-Date: 2010-04-07 03:46+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base diff --git a/bin/addons/base/i18n/zh_CN.po b/bin/addons/base/i18n/zh_CN.po index 18feb650de8..fa885c2f0df 100644 --- a/bin/addons/base/i18n/zh_CN.po +++ b/bin/addons/base/i18n/zh_CN.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-04-01 04:12+0000\n" +"PO-Revision-Date: 2010-04-06 04:37+0000\n" "Last-Translator: OpenERP Administrators \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-04-02 03:41+0000\n" +"X-Launchpad-Export-Date: 2010-04-07 03:46+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base 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/osv/orm.py b/bin/osv/orm.py index 560138e9b9f..c5a36be1a84 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -2013,7 +2013,8 @@ class orm(orm_template): for d in data: d['__domain'] = [(groupby,'=',alldata[d['id']][groupby] or False)] + domain - d['__context'] = {'group_by':groupby_list[1:]} + if not isinstance(groupby_list,(str, unicode)): + d['__context'] = {'group_by':groupby_list[1:]} if fget.has_key(groupby): if d[groupby] and fget[groupby]['type'] in ('date','datetime'): dt = datetime.datetime.strptime(alldata[d['id']][groupby][:7],'%Y-%m') @@ -2934,7 +2935,6 @@ class orm(orm_template): for sub_ids in cr.split_for_in_conditions(ids): cr.execute('delete from ' + self._table + ' ' \ 'where id in ('+','.join(['%s'] * len(sub_ids))+')', sub_ids) - for order, object, store_ids, fields in result_store: if object != self._name: obj = self.pool.get(object) diff --git a/bin/service/http_server.py b/bin/service/http_server.py index fe3d98a0004..47ae6c35ae8 100644 --- a/bin/service/http_server.py +++ b/bin/service/http_server.py @@ -251,12 +251,11 @@ class OerpAuthProxy(AuthProxy): self.auth_tries = 0 self.last_auth = None - def checkRequest(self,handler,path = '/'): - if self.auth_creds: - return True + def checkRequest(self,handler,path, db=False): auth_str = handler.headers.get('Authorization',False) try: - db = handler.get_db_from_path(path) + if not db: + db = handler.get_db_from_path(path) print "Got db:",db except: if path.startswith('/'): @@ -267,8 +266,9 @@ class OerpAuthProxy(AuthProxy): else: #FIXME! self.provider.log("Wrong path: %s, failing auth" %path) - raise AuthRejectedExc("Authorization failed. Wrong sub-path.") - + raise AuthRejectedExc("Authorization failed. Wrong sub-path.") + if self.auth_creds.get(db): + return True if auth_str and auth_str.startswith('Basic '): auth_str=auth_str[len('Basic '):] (user,passwd) = base64.decodestring(auth_str).split(':') @@ -276,7 +276,7 @@ class OerpAuthProxy(AuthProxy): acd = self.provider.authenticate(db,user,passwd,handler.client_address) if acd != False: self.auth_creds[db] = acd - self.last_auth=db + self.last_auth = db return True if self.auth_tries > 5: self.provider.log("Failing authorization after 5 requests w/o password") diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index 354311eaa64..464c41ca356 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -234,6 +234,9 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): if fore.close_connection: # print "Closing connection because of handler" self.close_connection = fore.close_connection + if hasattr(fore, '_flush'): + fore._flush() + def parse_rawline(self): """Parse a request (internal). 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)