diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 2910b9fe7c1..8137a7fecbb 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -644,6 +644,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): 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() diff --git a/bin/addons/base/i18n/hr.po b/bin/addons/base/i18n/hr.po index c6f03c89808..ee315793a50 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-03-02 05:28+0000\n" +"PO-Revision-Date: 2010-03-04 05:27+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-03-03 04:45+0000\n" +"X-Launchpad-Export-Date: 2010-03-05 05:00+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 834624306a4..afe3a167746 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-03-02 05:31+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2010-03-04 05:29+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-03-03 04:45+0000\n" +"X-Launchpad-Export-Date: 2010-03-05 05:00+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 66bb3b18544..86d2c6c5acd 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-01-27 05:47+0000\n" +"PO-Revision-Date: 2010-03-04 05:29+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-01-28 04:34+0000\n" +"X-Launchpad-Export-Date: 2010-03-05 05:00+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 849d1b5064b..472e4852d4c 100644 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -192,8 +192,15 @@ class act_window(osv.osv): return s for act in self.browse(cr, uid, ids): fields_from_fields_get = self.pool.get(act.res_model).fields_get(cr, uid) + search_view_id = False if act.search_view_id: - field_get = self.pool.get(act.res_model).fields_view_get(cr, uid, act.search_view_id.id, 'search', context) + search_view_id = act.search_view_id.id + else: + res_view = self.pool.get('ir.ui.view').search(cr, uid, [('model','=',act.res_model),('type','=','search'),('inherit_id','=',False)]) + if res_view: + search_view_id = res_view[0] + if search_view_id: + field_get = self.pool.get(act.res_model).fields_view_get(cr, uid, search_view_id, 'search', context) fields_from_fields_get.update(field_get['fields']) field_get['fields'] = fields_from_fields_get res[act.id] = str(field_get) diff --git a/bin/addons/base/ir/ir_sequence.py b/bin/addons/base/ir/ir_sequence.py index 46428c80262..8b0e7795a7c 100644 --- a/bin/addons/base/ir/ir_sequence.py +++ b/bin/addons/base/ir/ir_sequence.py @@ -1,7 +1,7 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # -# OpenERP, Open Source Management Solution +# OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved # $Id$ # @@ -59,8 +59,8 @@ class ir_sequence(osv.osv): def _process(self, s): return (s or '') % { - 'year':time.strftime('%Y'), - 'month': time.strftime('%m'), + 'year':time.strftime('%Y'), + 'month': time.strftime('%m'), 'day':time.strftime('%d'), 'y': time.strftime('%y'), 'doy': time.strftime('%j'), @@ -93,4 +93,3 @@ ir_sequence() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/bin/addons/base/res/partner/partner.py b/bin/addons/base/res/partner/partner.py index 155698dcb04..d2024294059 100644 --- a/bin/addons/base/res/partner/partner.py +++ b/bin/addons/base/res/partner/partner.py @@ -149,9 +149,10 @@ class res_partner(osv.osv): 'active': fields.boolean('Active'), 'customer': fields.boolean('Customer', help="Check this box if the partner is a customer."), 'supplier': fields.boolean('Supplier', help="Check this box if the partner is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."), - 'city':fields.related('address','city',type='char', string='City'), - 'country':fields.related('address','country_id',type='many2one', relation='res.country', string='Country'), - 'company_id': fields.many2one('res.company', 'Company',select=1), + 'city': fields.related('address', 'city', type='char', string='City'), + 'phone': fields.related('address', 'phone', type='char', string='Phone'), + 'country': fields.related('address', 'country_id', type='many2one', relation='res.country', string='Country'), + 'company_id': fields.many2one('res.company', 'Company', select=1), } def _default_category(self, cr, uid, context={}): diff --git a/bin/addons/base/res/partner/partner_view.xml b/bin/addons/base/res/partner/partner_view.xml index 3eb53ff2aa7..91c7532219e 100644 --- a/bin/addons/base/res/partner/partner_view.xml +++ b/bin/addons/base/res/partner/partner_view.xml @@ -236,11 +236,12 @@ - + + @@ -335,12 +336,14 @@ - + + @@ -376,7 +379,8 @@ ir.actions.act_window res.partner form - [('customer','=',1)] + [] + {'default_customer':1} @@ -385,8 +389,8 @@ ir.actions.act_window res.partner form - [('supplier','=',1)] - {'default_customer':0} + [] + {'default_customer':0, 'default_supplier':1} @@ -582,7 +586,9 @@ tree,form [('category_id','child_of', [active_id])] {'category_id':active_id} + + diff --git a/bin/addons/base/rng/view.rng b/bin/addons/base/rng/view.rng index 766b75bf9c3..4f99a03a8c1 100644 --- a/bin/addons/base/rng/view.rng +++ b/bin/addons/base/rng/view.rng @@ -12,7 +12,7 @@ - + @@ -23,6 +23,7 @@ + @@ -46,13 +47,14 @@ - + + @@ -74,6 +76,7 @@ + @@ -86,6 +89,15 @@ + + + + + + + + + @@ -111,6 +123,7 @@ + @@ -126,6 +139,9 @@ + + + @@ -140,6 +156,7 @@ + @@ -149,6 +166,7 @@ + @@ -157,7 +175,8 @@ - + + @@ -190,7 +209,7 @@ - + @@ -200,6 +219,7 @@ + @@ -230,10 +250,11 @@ - + + @@ -249,6 +270,7 @@ + @@ -269,6 +291,7 @@ + @@ -349,6 +372,7 @@ + @@ -376,7 +400,7 @@ - + @@ -386,6 +410,7 @@ + @@ -404,6 +429,9 @@ + + + @@ -414,8 +442,11 @@ - + + + + @@ -454,6 +485,7 @@ + @@ -493,6 +525,7 @@ + @@ -528,6 +561,7 @@ + diff --git a/bin/netsvc.py b/bin/netsvc.py index d8548db5d6a..eae53826a74 100644 --- a/bin/netsvc.py +++ b/bin/netsvc.py @@ -127,6 +127,7 @@ class ExportService(object): LOG_NOTSET = 'notset' LOG_DEBUG_RPC = 'debug_rpc' LOG_DEBUG = 'debug' +LOG_TEST = 'test' LOG_INFO = 'info' LOG_WARNING = 'warn' LOG_ERROR = 'error' @@ -135,6 +136,9 @@ LOG_CRITICAL = 'critical' logging.DEBUG_RPC = logging.DEBUG - 2 logging.addLevelName(logging.DEBUG_RPC, 'DEBUG_RPC') +logging.TEST = logging.INFO - 5 +logging.addLevelName(logging.TEST, 'TEST') + def init_logger(): import os from tools.translate import resetlocale @@ -195,6 +199,7 @@ def init_logger(): 'DEBUG_RPC': ('blue', 'white'), 'DEBUG': ('blue', 'default'), 'INFO': ('green', 'default'), + 'TEST': ('white', 'blue'), 'WARNING': ('yellow', 'default'), 'ERROR': ('red', 'default'), 'CRITICAL': ('white', 'red'), @@ -220,9 +225,10 @@ class Logger(object): log = logging.getLogger(tools.ustr(name)) - if level == LOG_DEBUG_RPC and not hasattr(log, level): - fct = lambda msg, *args, **kwargs: log.log(logging.DEBUG_RPC, msg, *args, **kwargs) - setattr(log, LOG_DEBUG_RPC, fct) + if level in [LOG_DEBUG_RPC, LOG_TEST] and not hasattr(log, level): + fct = lambda msg, *args, **kwargs: log.log(getattr(logging, level.upper()), msg, *args, **kwargs) + setattr(log, level, fct) + level_method = getattr(log, level) diff --git a/bin/osv/fields.py b/bin/osv/fields.py index 4398c896a5a..5d373a168ea 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -172,10 +172,16 @@ class float(_column): _symbol_f = lambda x: __builtin__.float(x or 0.0) _symbol_set = (_symbol_c, _symbol_f) - def __init__(self, string='unknown', digits=None, **args): + def __init__(self, string='unknown', digits=None, digits_compute=None, **args): _column.__init__(self, string=string, **args) self.digits = digits + self.digits_compute = digits_compute + def digits_change(self, cr): + if self.digits_compute: + t = self.digits_compute(cr) + self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),)) + self.digits = t class date(_column): _type = 'date' @@ -624,11 +630,9 @@ class function(_column): if 'relation' in args: self._obj = args['relation'] - if 'digits' in args: - self.digits = args['digits'] - else: - self.digits = (16,2) - + self.digits = args.get('digits', (16,2)) + self.digits_compute = args.get('digits_compute', None) + self._fnct_inv_arg = fnct_inv_arg if not fnct_inv: self.readonly = 1 @@ -650,6 +654,13 @@ class function(_column): self._symbol_f = float._symbol_f self._symbol_set = float._symbol_set + def digits_change(self, cr): + if self.digits_compute: + t = self.digits_compute(cr) + self._symbol_set=('%s', lambda x: ('%.'+str(t[1])+'f') % (__builtin__.float(x or 0.0),)) + self.digits = t + + def search(self, cr, uid, obj, name, args, context=None): if not self._fnct_search: #CHECKME: should raise an exception diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 4dd222f561c..0165d4e7a4a 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -315,7 +315,7 @@ def get_pg_type(f): f_type = (type_dict[type(f)], type_dict[type(f)]) elif isinstance(f, fields.float): if f.digits: - f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0], f.digits[1])) + f_type = ('numeric', 'NUMERIC') else: f_type = ('float8', 'DOUBLE PRECISION') elif isinstance(f, (fields.char, fields.reference)): @@ -337,7 +337,7 @@ def get_pg_type(f): f_type = (type_dict[t], type_dict[t]) elif isinstance(f, fields.function) and f._type == 'float': if f.digits: - f_type = ('numeric', 'NUMERIC(%d,%d)' % (f.digits[0], f.digits[1])) + f_type = ('numeric', 'NUMERIC') else: f_type = ('float8', 'DOUBLE PRECISION') elif isinstance(f, fields.function) and f._type == 'selection': @@ -2177,19 +2177,9 @@ class orm(orm_template): cr.commit() for c in casts: if (f_pg_type==c[0]) and (f._type==c[1]): - # Adding upcoming 6 lines to check whether only the size of the fields got changed or not.E.g. :(16,3) to (16,4) - field_size_change = False - if f_pg_type in ['int4','numeric','float8']: - if f.digits: - field_size = (65535 * f.digits[0]) + f.digits[0] + f.digits[1] - if field_size != f_pg_size: - field_size_change = True - - if f_pg_type != f_obj_type or field_size_change: + if f_pg_type != f_obj_type: if f_pg_type != f_obj_type: logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed type to %s." % (k, self._table, c[1])) - if field_size_change: - logger.notifyChannel('orm', netsvc.LOG_INFO, "column '%s' in table '%s' changed in the size." % (k, self._table)) ok = True cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO temp_change_size' % (self._table, k)) cr.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self._table, k, c[2])) @@ -2306,6 +2296,8 @@ class orm(orm_template): self._columns = self._columns.copy() for store_field in self._columns: f = self._columns[store_field] + if hasattr(f, 'digits_change'): + f.digits_change(cr) if not isinstance(f, fields.function): continue if not f.store: @@ -2590,8 +2582,6 @@ class orm(orm_template): # all fields which need to be post-processed by a simple function (symbol_get) fields_post = filter(lambda x: x in self._columns and self._columns[x]._symbol_get, fields_to_read) if fields_post: - # maybe it would be faster to iterate on the fields then on res, so that we wouldn't need - # to get the _symbol_get in each occurence for r in res: for f in fields_post: r[f] = self._columns[f]._symbol_get(r[f]) diff --git a/bin/service/http_server.py b/bin/service/http_server.py index a35701dc9e5..fe3d98a0004 100644 --- a/bin/service/http_server.py +++ b/bin/service/http_server.py @@ -1,5 +1,4 @@ -# -*- encoding: utf-8 -*- - +# -*- coding: utf-8 -*- # # Copyright P. Christeas 2008,2009 # @@ -28,7 +27,7 @@ """ This file contains instance of the http server. - + """ from websrv_lib import * import netsvc @@ -44,7 +43,7 @@ try: import fcntl except ImportError: fcntl = None - + try: from ssl import SSLError except ImportError: @@ -52,7 +51,7 @@ except ImportError: class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer): """ A threaded httpd server, with all the necessary functionality for us. - + It also inherits the xml-rpc dispatcher, so that some xml-rpc functions will be available to the request handler """ @@ -192,20 +191,20 @@ def reg_http_service(hts, secure_only = False): global httpd, httpsd if not isinstance(hts, HTTPDir): raise Exception("Wrong class for http service") - + if httpd and not secure_only: httpd.server.vdirs.append(hts) - + if httpsd: httpsd.server.vdirs.append(hts) - + if (not httpd) and (not httpsd): netsvc.Logger().notifyChannel('httpd',netsvc.LOG_WARNING,"No httpd available to register service %s" % hts.path) return import SimpleXMLRPCServer class XMLRPCRequestHandler(netsvc.OpenERPDispatcher,FixSendError,SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): - rpc_paths = [] + rpc_paths = [] protocol_version = 'HTTP/1.1' def _dispatch(self, method, params): try: @@ -236,13 +235,13 @@ def init_xmlrpc(): reg_http_service(HTTPDir('/xmlrpc/',XMLRPCRequestHandler)) # Example of http file serving: # reg_http_service(HTTPDir('/test/',HTTPHandler)) - netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, + netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO, "Registered XML-RPC over HTTP") - + class OerpAuthProxy(AuthProxy): """ Require basic authentication.. - + This is a copy of the BasicAuthProxy, which however checks/caches the db as well. """ @@ -304,7 +303,7 @@ class OpenERPAuthProvider(AuthProvider): except Exception,e: netsvc.Logger().notifyChannel("auth",netsvc.LOG_DEBUG,"Fail auth:"+ str(e)) return False - + def log(self, msg): netsvc.Logger().notifyChannel("auth",netsvc.LOG_INFO,msg) diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index 1913b5d5102..354311eaa64 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -1,5 +1,4 @@ -# -*- encoding: utf-8 -*- - +# -*- coding: utf-8 -*- # # Copyright P. Christeas 2008,2009 # @@ -41,7 +40,7 @@ class AuthRequiredExc(Exception): Exception.__init__(self) self.atype = atype self.realm = realm - + class AuthRejectedExc(Exception): pass @@ -56,7 +55,7 @@ class AuthProvider: def authenticate(self, user, passwd, client_address): return False - + def log(self, msg): print msg @@ -64,7 +63,7 @@ class BasicAuthProvider(AuthProvider): def setupAuth(self, multi, handler): if not multi.sec_realms.has_key(self.realm): multi.sec_realms[self.realm] = BasicAuthProxy(self) - + class AuthProxy: """ This class will hold authentication information for a handler, @@ -110,15 +109,15 @@ class HTTPHandler(SimpleHTTPRequestHandler): # print "Handler for %s inited" % str(client_address) self.protocol_version = 'HTTP/1.1' self.connection = dummyconn() - + def handle(self): """ Classes here should NOT handle inside their constructor """ pass - + def finish(self): pass - + def setup(self): pass @@ -129,7 +128,7 @@ class HTTPDir: self.path = path self.handler = handler self.auth_provider = auth_provider - + def matches(self, request): """ Test if some request matches us. If so, return the matched path. """ @@ -176,14 +175,14 @@ class FixSendError: class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): """ this is a multiple handler, that will dispatch each request to a nested handler, iff it matches - + The handler will also have *one* dict of authentication proxies, groupped by their realm. """ protocol_version = "HTTP/1.1" default_request_version = "HTTP/0.9" # compatibility with py2.5 - + auth_required_msg = """ Authorization required You must authenticate to use this service\r\r""" @@ -312,7 +311,7 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): if not self.parse_rawline(): self.log_message("Could not parse rawline.") return - # self.parse_request(): # Do NOT parse here. the first line should be the only + # self.parse_request(): # Do NOT parse here. the first line should be the only for vdir in self.server.vdirs: p = vdir.matches(self.path) if p == False: @@ -351,11 +350,11 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): class SecureMultiHTTPHandler(MultiHTTPHandler): def getcert_fnames(self): """ Return a pair with the filenames of ssl cert,key - + Override this to direct to other filenames """ return ('server.cert','server.key') - + def setup(self): import ssl certfile, keyfile = self.getcert_fnames() @@ -385,7 +384,7 @@ class SecureMultiHTTPHandler(MultiHTTPHandler): import threading class ConnThreadingMixIn: """Mix-in class to handle each _connection_ in a new thread. - + This is necessary for persistent connections, where multiple requests should be handled synchronously at each connection, but multiple connections can run in parallel. @@ -401,7 +400,7 @@ class ConnThreadingMixIn: if self.daemon_threads: t.setDaemon (1) t.start() - + def _handle_request2(self): """Handle one request, without blocking. diff --git a/bin/tools/config.py b/bin/tools/config.py index 2179a6901fa..23a1666b6c6 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -120,7 +120,7 @@ class configmanager(object): parser.add_option('--debug', dest='debug_mode', action='store_true', default=False, help='enable debug mode') parser.add_option("--assert-exit-level", dest='assert_exit_level', type="choice", choices=self._LOGLEVELS.keys(), help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (self._LOGLEVELS.keys(),)) - parser.add_option('--price_accuracy', dest='price_accuracy', default='2', help='specify the price accuracy') + parser.add_option('--price_accuracy', dest='price_accuracy', default='2', help='deprecated since v6.0, replaced by module decimal_precision') parser.add_option('--no-database-list', action="store_false", dest='list_db', default=True, help="disable the ability to return the list of databases") if hasSSL: diff --git a/bin/tools/convert.py b/bin/tools/convert.py index db3edf380f7..2e16237f50a 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -37,6 +37,7 @@ import netsvc import osv import pooler from config import config +from yaml_import import convert_yaml_import class ConvertError(Exception): def __init__(self, doc, orig_excpt): diff --git a/bin/tools/safe_eval.py b/bin/tools/safe_eval.py index bfbc0b34fe9..a7fce27d2e0 100644 --- a/bin/tools/safe_eval.py +++ b/bin/tools/safe_eval.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # # Copyright P. Christeas 2008,2009 # @@ -30,8 +30,8 @@ import sys def __init_ebis(): global __export_bis - - _evars = [ 'abs', 'all', 'any', 'basestring' , 'bool', + + _evars = [ 'abs', 'all', 'any', 'basestring' , 'bool', 'chr', 'cmp','complex', 'dict', 'divmod', 'enumerate', 'float', 'frozenset', 'getattr', 'hasattr', 'hash', 'hex', 'id','int', 'iter', 'len', 'list', 'long', 'map', 'max', @@ -39,12 +39,12 @@ def __init_ebis(): 'reversed', 'round', 'set', 'setattr', 'slice','sorted', 'str', 'sum', 'tuple','type', 'unichr','unicode', 'xrange', 'True','False', 'None', 'NotImplemented', 'Ellipsis', ] - + if sys.version_info[0:2] >= (2,6): _evars.extend(['bin', 'format', 'next']) for v in _evars: __export_bis[v] = __builtins__[v] - + __init_ebis() @@ -53,14 +53,14 @@ def safe_eval(expr,sglobals,slocals = None): """ A little safer version of eval(). This one, will use fewer builtin functions, so that only arithmetic and logic expressions can really work """ - + global __export_bis if not sglobals.has_key('__builtins__'): # we copy, because we wouldn't want successive calls to safe_eval # to be able to alter the builtins. sglobals['__builtins__'] = __export_bis.copy() - + return eval(expr,sglobals,slocals) - + #eof diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py new file mode 100644 index 00000000000..2c334c9cd3c --- /dev/null +++ b/bin/tools/yaml_import.py @@ -0,0 +1,760 @@ +# -*- encoding: utf-8 -*- +import types +import yaml +import time # used to eval time.strftime expressions +import logging + +import pooler +import netsvc +import misc + +from config import config + +logger_channel = 'tests' + +class YamlImportException(Exception): + pass + +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="NONAME", **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 \ + and isinstance(node.keys()[0], tag_constructor) + return value + +def is_comment(node): + return isinstance(node, types.StringTypes) + +def is_assert(node): + return _is_yaml_mapping(node, Assert) + +def is_record(node): + return _is_yaml_mapping(node, Record) + +def is_python(node): + return _is_yaml_mapping(node, Python) + +def is_menuitem(node): + return isinstance(node, Menuitem) \ + or _is_yaml_mapping(node, Menuitem) + +def is_function(node): + return isinstance(node, Function) \ + or _is_yaml_mapping(node, Function) + +def is_report(node): + return isinstance(node, Report) + +def is_workflow(node): + return isinstance(node, Workflow) + +def is_act_window(node): + return isinstance(node, ActWindow) + +def is_delete(node): + return isinstance(node, Delete) + +def is_context(node): + return isinstance(node, Context) + +def is_url(node): + return isinstance(node, Url) + +def is_eval(node): + return isinstance(node, Eval) + +def is_ir_set(node): + return _is_yaml_mapping(node, IrSet) + + +class TestReport(object): + def __init__(self): + self._report = {} + + def record(self, success, severity): + """Records the result of an assertion for the failed/success count. + Returns success.""" + + if severity in self._report: + self._report[severity][success] += 1 + else: + self._report[severity] = {success: 1, not success: 0} + return success + + def __str__(self): + res = [] + res.append('\nAssertions report:\nLevel\tsuccess\tfailure') + success = failure = 0 + for severity in self._report: + res.append("%s\t%s\t%s" % (severity, self._report[severity][True], self._report[severity][False])) + success += self._report[severity][True] + failure += self._report[severity][False] + res.append("total\t%s\t%s" % (success, failure)) + res.append("end of report (%s assertion(s) checked)" % success + failure) + return "\n".join(res) + + +class YamlInterpreter(object): + def __init__(self, cr, module, id_map, mode, filename, noupdate=False): + self.cr = cr + self.module = module + self.id_map = id_map + self.mode = mode + self.filename = filename + self.assert_report = TestReport() + self.noupdate = noupdate + self.logger = logging.getLogger(logger_channel) + self.pool = pooler.get_pool(cr.dbname) + self.uid = 1 + self.context = {} + + def _ref(self): + return lambda xml_id: self.get_id(xml_id) + + def get_model(self, model_name): + model = self.pool.get(model_name) + assert model, "The model %s does not exist." % (model_name,) + return model + + def validate_xml_id(self, xml_id): + id = xml_id + if '.' in xml_id: + module, id = xml_id.split('.', 1) + assert '.' not in id, "The ID reference '%s' must contains maximum one dot.\n" \ + "It is used to refer to other modules ID, in the form: module.record_id" \ + % (xml_id,) + if module != self.module: + module_count = self.pool.get('ir.module.module').search_count(self.cr, self.uid, \ + ['&', ('name', '=', module), ('state', 'in', ['installed'])]) + assert module_count == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,) + if len(id) > 64: # TODO where does 64 come from (DB is 128)? should be a constant or loaded form DB + self.logger.log(logging.ERROR, 'id: %s is to long (max: 64)', id) + + def get_id(self, xml_id): + if isinstance(xml_id, types.IntType): + id = xml_id + elif xml_id in self.id_map: + id = self.id_map[xml_id] + else: + if '.' in xml_id: + module, 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) + 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 + return id + + def get_context(self, node, locals_dict): + context = self.context.copy() + if node.context: + context.update(eval(node.context, locals_dict)) + return context + + def isnoupdate(self, node): + return self.noupdate or node.noupdate or False + + 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.' % (severity,)) + return + + def _get_assertion_id(self, assertion): + if assertion.id: + ids = [self.get_id(assertion.id)] + elif assertion.search: + q = eval(assertion.search, {'ref': self._ref}) + ids = self.pool.get(assertion.model).search(self.cr, self.uid, q, context=assertion.context) + if not ids: + raise YamlImportException('Nothing to assert: you must give either an id or a search criteria.') + return ids + + def process_assert(self, node): + assertion, expressions = node.items()[0] + + if self.isnoupdate(assertion) and self.mode != 'init': + return + model = self.get_model(assertion.model) + ids = self._get_assertion_id(assertion) + if assertion.count and len(ids) != assertion.count: + msg = 'assertion "%s" failed!\n' \ + ' Incorrect search count:\n' \ + ' expected count: %d\n' \ + ' obtained count: %d\n' + args = (assertion.string, assertion.count, len(ids)) + self._log_assert_failure(assertion.severity, msg, *args) + else: + local_context = {'ref': self._ref, '_ref': self._ref} # added '_ref' so that record['ref'] is possible + context = self.get_context(assertion, local_context) + for id in ids: + record = model.browse(self.cr, self.uid, id, context) + for test in expressions.get('test', ''): + success = eval(test, local_context, record) + if not success: + msg = 'Assertion "%s" FAILED\ntest: %s\n' + args = (assertion.string, test) + self._log_assert_failure(assertion.severity, msg, *args) + return + else: # all tests were successful for this assertion tag (no break) + self.assert_report.record(True, assertion.severity) + + 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) + 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 = {} + for field_name, expression in fields.items(): + field_value = self._eval_field(model, field_name, expression) + record_dict[field_name] = field_value + return record_dict + + def _eval_field(self, model, field_name, expression): + column = model._columns[field_name] + if 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)] + else: # scalar field + if isinstance(expression, Eval): + value = eval(expression.expression) + else: + value = expression + # raise YamlImportException('Unsupported column "%s" or value %s:%s' % (field_name, type(expression), expression)) + return value + + def process_context(self, node): + self.context = node.__dict__ + if 'uid' in node.__dict__: + self.uid = self.get_id(node.__dict__['uid']) + + def process_python(self, node): + def log(msg, *args): + self.logger.log(logging.TEST, msg, *args) + python, statements = node.items()[0] + model = self.get_model(python.model) + statements = statements.replace("\r\n", "\n") + code_context = {'self': model, 'cr': self.cr, 'uid': self.uid, 'log': log, 'context': self.context} + try: + code = compile(statements, self.filename, 'exec') + eval(code, code_context) + except AssertionError, e: + self._log_assert_failure(python.severity, 'Assertion "%s" FAILED in Python code.', python.name) + return + except Exception, e: + raise YamlImportAbortion(e) + else: + self.assert_report.record(True, python.severity) + + def process_workflow(self, node): + 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: + if not values: + raise YamlImportException('You must define a child node if you do not give a ref.') + if not len(values) == 1: + raise YamlImportException('Only one child node is accepted (%d given).' % len(values)) + value = values[0] + 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 = {'ref': self._ref, '_ref': self._ref} + local_context['obj'] = lambda x: value_model.browse(self.cr, self.uid, x, context=self.context) + local_context.update(self.id_map) + id = eval(value['eval'], local_context) + + if workflow.uid is not None: + uid = workflow.uid + else: + uid = self.uid + wf_service = netsvc.LocalService("workflow") + wf_service.trg_validate(uid, model, id, workflow.action, self.cr) + + def process_function(self, node): + function, values = node.items()[0] + if self.isnoupdate(function) and self.mode != 'init': + return + local_context = {'ref': self._ref, '_ref': self._ref} + context = self.get_context(function, local_context) + args = [] + if function.eval: + args = eval(function.eval, local_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 = {'ref': self._ref, '_ref': self._ref} + 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'], local_context) + if id != None: + args.append(id) + model = self.get_model(function.model) + method = function.name + getattr(model, method)(self.cr, self.uid, *args) + + def _set_group_values(self, node, values): + if node.groups: + group_names = node.groups.split(',') + groups_value = [] + for group in group_names: + if group.startswith('-'): + group_id = self.get_id(group[1:]) + groups_value.append((3, group_id)) + else: + group_id = self.get_id(group) + groups_value.append((4, group_id)) + values['groups_id'] = groups_value + + def process_menuitem(self, node): + self.validate_xml_id(node.id) + + if not node.parent: + parent_id = False + self.cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (node.name,)) + res = self.cr.fetchone() + values = {'parent_id': parent_id, 'name': node.name} + else: + parent_id = self.get_id(node.parent) + values = {'parent_id': parent_id} + if node.name: + values['name'] = node.name + try: + res = [ self.get_id(node.id) ] + except: # which exception ? + res = None + + if node.action: + action_type = node.type or 'act_window' + icons = { + "act_window": 'STOCK_NEW', + "report.xml": 'STOCK_PASTE', + "wizard": 'STOCK_EXECUTE', + "url": 'STOCK_JUMP_TO', + } + values['icon'] = icons.get(action_type, 'STOCK_NEW') + if action_type == 'act_window': + action_id = self.get_id(node.action) + self.cr.execute('select view_type,view_mode,name,view_id,target from ir_act_window where id=%s', (action_id,)) + ir_act_window_result = self.cr.fetchone() + assert ir_act_window_result, "No window action defined for this id %s !\n" \ + "Verify that this is a window action or add a type argument." % (node.action,) + action_type, action_mode, action_name, view_id, target = ir_act_window_result + if view_id: + self.cr.execute('SELECT type FROM ir_ui_view WHERE id=%s', (view_id,)) + # TODO guess why action_mode is ir_act_window.view_mode above and ir_ui_view.type here + action_mode = self.cr.fetchone() + self.cr.execute('SELECT view_mode FROM ir_act_window_view WHERE act_window_id=%s ORDER BY sequence LIMIT 1', (action_id,)) + if self.cr.rowcount: + action_mode = self.cr.fetchone() + if action_type == 'tree': + values['icon'] = 'STOCK_INDENT' + elif action_mode and action_mode.startswith('tree'): + values['icon'] = 'STOCK_JUSTIFY_FILL' + elif action_mode and action_mode.startswith('graph'): + values['icon'] = 'terp-graph' + elif action_mode and action_mode.startswith('calendar'): + values['icon'] = 'terp-calendar' + if target == 'new': + values['icon'] = 'STOCK_EXECUTE' + if not values.get('name', False): + values['name'] = action_name + elif action_type == 'wizard': + action_id = self.get_id(node.action) + self.cr.execute('select name from ir_act_wizard where id=%s', (action_id,)) + ir_act_wizard_result = self.cr.fetchone() + if (not values.get('name', False)) and ir_act_wizard_result: + values['name'] = ir_act_wizard_result[0] + else: + raise YamlImportException("Unsupported type '%s' in menuitem tag." % action_type) + if node.sequence: + values['sequence'] = node.sequence + if node.icon: + values['icon'] = node.icon + + self._set_group_values(node, values) + + pid = self.pool.get('ir.model.data')._update(self.cr, self.uid, \ + 'ir.ui.menu', self.module, values, node.id, mode=self.mode, \ + noupdate=self.isnoupdate(node), res_id=res and res[0] or False) + + if node.id and parent_id: + self.id_map[node.id] = int(parent_id) + + if node.action and pid: + action_type = node.type or 'act_window' + action_id = self.get_id(node.action) + action = "ir.actions.%s,%d" % (action_type, action_id) + self.pool.get('ir.model.data').ir_set(self.cr, self.uid, 'action', \ + 'tree_but_open', 'Menuitem', [('ir.ui.menu', int(parent_id))], action, True, True, xml_id=node.id) + + def process_act_window(self, node): + self.validate_xml_id(node.id) + view_id = False + if node.view: + view_id = self.get_id(node.view) + context = eval(node.context, {'ref': self._ref, '_ref': self._ref}) + + values = { + 'name': node.name, + 'type': type or 'ir.actions.act_window', + 'view_id': view_id, + 'domain': node.domain, + 'context': context, + 'res_model': node.res_model, + 'src_model': node.src_model, + 'view_type': node.view_type or 'form', + 'view_mode': node.view_mode or 'tree,form', + 'usage': node.usage, + 'limit': node.limit, + 'auto_refresh': node.auto_refresh, + } + + self._set_group_values(node, values) + + if node.target: + values['target'] = node.target + id = self.pool.get('ir.model.data')._update(self.cr, self.uid, \ + 'ir.actions.act_window', self.module, values, node.id, mode=self.mode) + self.id_map[node.id] = int(id) + + if node.src_model: + keyword = 'client_action_relate' + value = 'ir.actions.act_window,%s' % id + replace = node.replace or True + self.pool.get('ir.model.data').ir_set(self.cr, self.uid, 'action', keyword, \ + node.id, [node.src_model], value, replace=replace, noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id) + # 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)) + if len(node.id): + try: + ids.append(self.get_id(node.id)) + except: + pass + 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) + + def process_url(self, node): + self.validate_xml_id(node.id) + + res = {'name': node.name, 'url': node.url, 'target': node.target} + + id = self.pool.get('ir.model.data')._update(self.cr, self.uid, \ + "ir.actions.url", self.module, res, node.id, mode=self.mode) + self.id_map[node.id] = int(id) + # ir_set + if (not node.menu or eval(node.menu)) and id: + keyword = node.keyword or 'client_action_multi' + value = 'ir.actions.url,%s' % id + replace = node.replace or True + self.pool.get('ir.model.data').ir_set(self.cr, self.uid, 'action', \ + keyword, node.url, ["ir.actions.url"], value, replace=replace, \ + noupdate=self.isnoupdate(node), isobject=True, xml_id=node.id) + + def process_ir_set(self, node): + if not self.mode == 'init': + return False + _, fields = node.items()[0] + res = {} + for fieldname, expression in fields.items(): + if isinstance(expression, Eval): + value = eval(expression.expression, {'ref': self._ref, '_ref': self._ref}) + else: + value = expression + res[fieldname] = value + self.pool.get('ir.model.data').ir_set(self.cr, self.uid, res['key'], res['key2'], \ + res['name'], res['models'], res['value'], replace=res.get('replace',True), \ + isobject=res.get('isobject', False), meta=res.get('meta',None)) + + def process_report(self, node): + values = {} + for dest, f in (('name','string'), ('model','model'), ('report_name','name')): + values[dest] = getattr(node, f) + assert values[dest], "Attribute %s of report is empty !" % (f,) + for field,dest in (('rml','report_rml'),('xml','report_xml'),('xsl','report_xsl'),('attachment','attachment'),('attachment_use','attachment_use')): + if getattr(node, field): + values[dest] = getattr(node, field) + if node.auto: + values['auto'] = eval(node.auto) + if node.sxw: + sxw_content = misc.file_open(node.sxw).read() + values['report_sxw_content'] = sxw_content + if node.header: + values['header'] = eval(node.header) + values['multi'] = node.multi and eval(node.multi) + xml_id = node.id + self.validate_xml_id(xml_id) + + self._set_group_values(node, values) + + id = self.pool.get('ir.model.data')._update(self.cr, self.uid, "ir.actions.report.xml", \ + self.module, values, xml_id, noupdate=self.isnoupdate(node), mode=self.mode) + self.id_map[xml_id] = int(id) + + if not node.menu or eval(node.menu): + keyword = node.keyword or 'client_print_multi' + value = 'ir.actions.report.xml,%s' % id + replace = node.replace or True + self.pool.get('ir.model.data').ir_set(self.cr, self.uid, 'action', \ + keyword, values['name'], [values['model']], value, replace=replace, isobject=True, xml_id=xml_id) + + def process(self, yaml_string): + """Processes a Yaml string. Custom tags are interpreted by 'process_' instance methods.""" + + is_preceded_by_comment = False + for node in yaml.load(yaml_string): + is_preceded_by_comment = self._log(node, is_preceded_by_comment) + try: + self._process_node(node) + except YamlImportException, e: + self.logger.log(logging.ERROR, e) + except YamlImportAbortion, e: + self.logger.log(logging.ERROR, e) + return + + def _process_node(self, node): + if is_comment(node): + self.process_comment(node) + elif is_assert(node): + self.process_assert(node) + elif is_record(node): + self.process_record(node) + elif is_python(node): + self.process_python(node) + elif is_menuitem(node): + self.process_menuitem(node) + elif is_delete(node): + self.process_delete(node) + elif is_url(node): + self.process_url(node) + elif is_context(node): + self.process_context(node) + elif is_ir_set(node): + self.process_ir_set(node) + elif is_act_window(node): + self.process_act_window(node) + elif is_report(node): + self.process_report(node) + elif is_workflow(node): + if isinstance(node, types.DictionaryType): + self.process_workflow(node) + else: + self.process_workflow({node: []}) + elif is_function(node): + if isinstance(node, types.DictionaryType): + self.process_function(node) + else: + self.process_function({node: []}) + else: + raise YamlImportException("Can not process YAML block: %s" % node) + + def _log(self, node, is_preceded_by_comment): + if is_comment(node): + is_preceded_by_comment = True + self.logger.log(logging.TEST, node) + elif not is_preceded_by_comment: + if isinstance(node, types.DictionaryType): + msg = "Creating %s\n with %s" + args = node.items()[0] + self.logger.log(logging.TEST, msg, *args) + else: + self.logger.log(logging.TEST, node) + else: + is_preceded_by_comment = False + return is_preceded_by_comment + +def yaml_import(cr, module, yamlfile, idref=None, mode='init', noupdate=False, report=None): + if idref is None: + idref = {} + yaml_string = yamlfile.read() + yaml_interpreter = YamlInterpreter(cr, module, idref, mode, filename=yamlfile.name, noupdate=noupdate) + yaml_interpreter.process(yaml_string) + +# keeps convention of convert.py +convert_yaml_import = yaml_import + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: