From f873421eb50a3919d4ce1efaa8344131a9ac7406 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Fri, 9 Jan 2009 07:59:28 +0100 Subject: [PATCH 01/46] bugfix bzr revid: fp@tinyerp.com-20090109065928-w2a6ygqy5qr3guem --- bin/report/report_sxw.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py index e202a9a275b..c4f10212b5a 100644 --- a/bin/report/report_sxw.py +++ b/bin/report/report_sxw.py @@ -21,7 +21,7 @@ ############################################################################## from interface import report_rml -import cStringIO +import StringIO import base64 import copy import ir @@ -618,7 +618,7 @@ class report_sxw(report_rml): if report_type in ['sxw','odt'] and report_xml: context['parents'] = sxw_parents - sxw_io = cStringIO.StringIO(report_xml.report_sxw_content) + sxw_io = StringIO.StringIO(report_xml.report_sxw_content) sxw_z = zipfile.ZipFile(sxw_io, mode='r') rml = sxw_z.read('content.xml') meta = sxw_z.read('meta.xml') From eab8eb27cb4f1500463cfd7e4205297212bcc0cf Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 10:12:01 +0100 Subject: [PATCH 02/46] [FIX] bug with exception formating bzr revid: christophe@tinyerp.com-20090109091201-58kfjqi23awrjbtz --- bin/netsvc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/netsvc.py b/bin/netsvc.py index a1dc8b06968..b0e5035e29f 100644 --- a/bin/netsvc.py +++ b/bin/netsvc.py @@ -227,7 +227,7 @@ class OpenERPDispatcher: self.log('result', result) return result except Exception, e: - self.log('exception', e) + self.log('exception', tools.exception_to_unicode(e)) tb = sys.exc_info() tb_s = "".join(traceback.format_exception(*tb)) if tools.config['debug_mode']: From f4c1cc32bacfce01fbafa1fe47fef925b7d33b4d Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 10:15:55 +0100 Subject: [PATCH 03/46] [FIX] avoid a crash when the migration server is not accessible bzr revid: christophe@tinyerp.com-20090109091555-qipdi2hyrxlqpquc --- bin/service/web_services.py | 12 +++++------- bin/tools/maintenance.py | 7 ++++++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bin/service/web_services.py b/bin/service/web_services.py index ef0dedc551e..489879559e1 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -301,8 +301,6 @@ class db(netsvc.Service): return True db() -class MigrationException(Exception): pass - class common(netsvc.Service): def __init__(self,name="common"): netsvc.Service.__init__(self,name) @@ -384,12 +382,12 @@ GNU Public Licence. security.check_super(password) l = netsvc.Logger() try: - from tools.maintenance import remote_contract - rc = remote_contract(contract_id, contract_password) + import tools.maintenance as tm + rc = tm.remote_contract(contract_id, contract_password) if not rc.id: - raise MigrationException('This contract does not exist or is not active') + raise tm.RemoteContractException('This contract does not exist or is not active') if rc.status != 'full': - raise MigrationException('Can not get updates for a partial contract') + raise tm.RemoteContractException('Can not get updates for a partial contract') l.notifyChannel('migration', netsvc.LOG_INFO, 'starting migration with contract %s' % (rc.name,)) @@ -412,7 +410,7 @@ GNU Public Licence. zip.close() return True - except MigrationException, e: + except tm.RemoteContractException, e: self.abortResponse(1, 'Migration Error', 'warning', str(e)) except Exception, e: import traceback diff --git a/bin/tools/maintenance.py b/bin/tools/maintenance.py index 959d9f7bf3c..aa3e6b50ad9 100644 --- a/bin/tools/maintenance.py +++ b/bin/tools/maintenance.py @@ -22,6 +22,8 @@ import xmlrpclib +class RemoteContractException(Exception): pass + class remote_contract(object): def __init__(self, contract_id, contract_password, modules=None): self.__server = 'http://localhost:8069/xmlrpc/' @@ -30,7 +32,10 @@ class remote_contract(object): self.__login = "admin" rpc = xmlrpclib.ServerProxy(self.__server + 'common') - self.__userid = rpc.login(self.__db, self.__login, self.__password) + try: + self.__userid = rpc.login(self.__db, self.__login, self.__password) + except: + raise RemoteContractException("Unable to contact the migration server") self.__rpc = xmlrpclib.ServerProxy(self.__server + 'object') From 6618009a60b44f38c9eb24e8ae98388079700f16 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 11:02:29 +0100 Subject: [PATCH 04/46] [IMP] locate the migration server elsewhere than localhost bzr revid: christophe@tinyerp.com-20090109100229-7dkbk41stji21lba --- bin/tools/maintenance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/tools/maintenance.py b/bin/tools/maintenance.py index aa3e6b50ad9..e23e9c8f7ad 100644 --- a/bin/tools/maintenance.py +++ b/bin/tools/maintenance.py @@ -26,8 +26,8 @@ class RemoteContractException(Exception): pass class remote_contract(object): def __init__(self, contract_id, contract_password, modules=None): - self.__server = 'http://localhost:8069/xmlrpc/' - self.__db = "trunk" + self.__server = 'http://192.168.0.127:8072/xmlrpc/' + self.__db = "maintenance" self.__password = "admin" self.__login = "admin" From 9089f665bd7309f53a250acfd860f39b0b09cbbc Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 9 Jan 2009 11:07:59 +0100 Subject: [PATCH 05/46] [FIX] Encoding lp bug: https://launchpad.net/bugs/315378 fixed bzr revid: stephane@tinyerp.com-20090109100759-hq2o03u54ftvrxmh --- bin/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 8caef262f03..d21a95710ef 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -812,7 +812,7 @@ class orm_template(object): elif node.nodeType==node.ELEMENT_NODE and node.localName in ('form', 'tree'): result = self.view_header_get(cr, user, False, node.localName, context) if result: - node.setAttribute('string', result.decode('utf-8')) + node.setAttribute('string', result) elif node.nodeType==node.ELEMENT_NODE and node.localName == 'calendar': for additional_field in ('date_start', 'date_delay', 'date_stop', 'color'): From 25cbe167502c675b8a43881de7ec46bc8bbdff98 Mon Sep 17 00:00:00 2001 From: Olivier Laurent Date: Fri, 9 Jan 2009 12:23:33 +0100 Subject: [PATCH 06/46] Undefined variable 'Logger' bzr revid: olt@tinyerp.com-20090109112333-owqkojit9e9rqhk7 --- bin/service/web_services.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/service/web_services.py b/bin/service/web_services.py index 489879559e1..590680c6606 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -295,8 +295,7 @@ class db(netsvc.Service): except Exception, e: import traceback tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback)) - logger = Logger() - logger.notifyChannel('web-services', LOG_ERROR, tb_s) + l.notifyChannel('web-services', netsvc.LOG_ERROR, tb_s) raise return True db() From 383c212c97bd7221fb9b2f4e1411c77a4f72945e Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 12:35:25 +0100 Subject: [PATCH 07/46] [FIX] fix (some) cStringIO problems [IMP] remove debug code bzr revid: christophe@tinyerp.com-20090109113525-5iq0uryv35ho4nwr --- bin/addons/__init__.py | 10 +++------- bin/tools/misc.py | 5 +++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 1b72281830b..bdcdf91c1a0 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -41,7 +41,8 @@ import release import re import base64 from zipfile import PyZipFile, ZIP_DEFLATED -import cStringIO +from cStringIO import StringIO + logger = netsvc.Logger() @@ -198,7 +199,7 @@ def get_module_as_zip(modulename, b64enc=True, src=True): if os.path.isfile(ap + '.zip'): val = file(ap + '.zip', 'rb').read() else: - archname = cStringIO.StringIO('wb') + archname = StringIO() archive = PyZipFile(archname, "w", ZIP_DEFLATED) archive.writepy(ap) _zippy(archive, ap, src=src) @@ -206,11 +207,6 @@ def get_module_as_zip(modulename, b64enc=True, src=True): val = archname.getvalue() archname.close() - ### debug - f = file('/tmp/mod.zip', 'wb') - f.write(val) - f.close() - if b64enc: val = base64.encodestring(val) return val diff --git a/bin/tools/misc.py b/bin/tools/misc.py index c42c77a056e..060fc11c160 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -224,10 +224,11 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False): else: zipname = tail if zipfile.is_zipfile(head+'.zip'): - import cStringIO + from cStringIO import StringIO zfile = zipfile.ZipFile(head+'.zip') try: - fo = cStringIO.StringIO(zfile.read(os.path.join( + fo = StringIO() + fo.write(zfile.read(os.path.join( os.path.basename(head), zipname).replace( os.sep, '/'))) From 80a7a66742505981a4c806fefc0f4013da890825 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 9 Jan 2009 13:18:20 +0100 Subject: [PATCH 08/46] [FIX] StringIO bzr revid: stephane@tinyerp.com-20090109121820-vlxvuadud95x9jox --- .../base/module/wizard/wizard_module_import.py | 5 +++-- bin/report/print_xml.py | 5 +++-- bin/report/render/rml2pdf/trml2pdf.py | 17 +++++++++-------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/bin/addons/base/module/wizard/wizard_module_import.py b/bin/addons/base/module/wizard/wizard_module_import.py index e686fa856e2..a2c653471e2 100644 --- a/bin/addons/base/module/wizard/wizard_module_import.py +++ b/bin/addons/base/module/wizard/wizard_module_import.py @@ -28,7 +28,7 @@ import os import tools import zipfile -import cStringIO +from StringIO import StringIO import base64 finish_form =''' @@ -55,7 +55,8 @@ class move_module_wizard(wizard.interface): module_data = data['form']['module_file'] val =base64.decodestring(module_data) - fp = cStringIO.StringIO(val) + fp = StringIO() + fp.write(val) fdata = zipfile.ZipFile(fp, 'r') fname = fdata.namelist()[0] module_name = os.path.split(fname)[0] diff --git a/bin/report/print_xml.py b/bin/report/print_xml.py index 972beb7252e..cceecd14c33 100644 --- a/bin/report/print_xml.py +++ b/bin/report/print_xml.py @@ -163,9 +163,10 @@ class document(object): ext = fname.split('.')[-1].lower() if ext in ('jpg','jpeg', 'png'): import base64 - import cStringIO + from StringIO import StringIO dt = base64.decodestring(datas['datas']) - fp = cStringIO.StringIO(dt) + fp = StringIO() + fp.write(dt) i = str(len(self.bin_datas)) self.bin_datas[i] = fp diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index ddd014b7bfa..91108f12f50 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -40,7 +40,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys -import cStringIO +from StringIO import StringIO import xml.dom.minidom import copy @@ -351,26 +351,26 @@ class _rml_canvas(object): import urllib from reportlab.lib.utils import ImageReader + s = StringIO() if not node.hasAttribute('file'): if node.hasAttribute('name'): image_data = self.images[node.getAttribute('name')] - s = cStringIO.StringIO(image_data) + s.write(image_data) else: import base64 image_data = base64.decodestring(node.firstChild.nodeValue) if not image_data: return False - s = cStringIO.StringIO(image_data) + s.write(image_data) else: if node.getAttribute('file') in self.images: - s = cStringIO.StringIO(self.images[node.getAttribute('file')]) + s.write(self.images[node.getAttribute('file')]) else: try: u = urllib.urlopen(str(node.getAttribute('file'))) - s = cStringIO.StringIO(u.read()) except: u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb') - s = cStringIO.StringIO(u.read()) + s.write(u.read()) img = ImageReader(s) (sx,sy) = img.getSize() @@ -639,7 +639,8 @@ class _rml_flowable(object): else: import base64 image_data = base64.decodestring(node.firstChild.nodeValue) - image = cStringIO.StringIO(image_data) + image = StringIO() + image.write(image_data) return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height']))) else: return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height']))) @@ -794,7 +795,7 @@ def parseString(data, fout=None, images={}, path='.',title=None): fp.close() return fout else: - fp = cStringIO.StringIO() + fp = StringIO() r.render(fp) return fp.getvalue() From 90e04b8632de7d64bba8c43217088fad7ced8ca9 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 13:57:00 +0100 Subject: [PATCH 09/46] [FIX] handle the case when the base module is a zipmodule bzr revid: christophe@tinyerp.com-20090109125700-atfc112c5wmfvy3v --- bin/tools/misc.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 060fc11c160..983b0bc568b 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -44,7 +44,7 @@ from itertools import izip def init_db(cr): import addons f = addons.get_module_resource('base', 'base.sql') - for line in file(f).read().split(';'): + for line in file_open(f).read().split(';'): if (len(line)>0) and (not line.isspace()): cr.execute(line) cr.commit() @@ -55,12 +55,8 @@ def init_db(cr): if not mod_path: continue info = False - if os.path.isfile(terp_file) and not os.path.isfile(mod_path+'.zip'): - info = eval(file(terp_file).read()) - elif zipfile.is_zipfile(mod_path+'.zip'): - zfile = zipfile.ZipFile(mod_path+'.zip') - i = os.path.splitext(i)[0] - info = eval(zfile.read(os.path.join(i, '__terp__.py'))) + if os.path.isfile(terp_file) or os.path.isfile(mod_path+'.zip'): + info = eval(file_open(terp_file).read()) if info: categs = info.get('category', 'Uncategorized').split('/') p_id = None From e700cb62757b8af23d00077388d636ece0e36b8d Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Fri, 9 Jan 2009 14:48:17 +0100 Subject: [PATCH 10/46] modifs bzr revid: fp@tinyerp.com-20090109134817-3qp4riqpeto23bx0 --- addons/stock/stock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 26b581c18e1..e5b66648a26 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -367,6 +367,7 @@ class stock_picking(osv.osv): res[pick]['max_date'] = dt2 return res + _columns = { 'name': fields.char('Reference', size=64, required=True, select=True), 'origin': fields.char('Origin Reference', size=64), From 35cba4cd42475b8b2da8107fa3ee2305d729a80d Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 14:59:21 +0100 Subject: [PATCH 11/46] [FIX] migration: when the module is a symlink, just delete the symlink bzr revid: christophe@tinyerp.com-20090109135921-gg3zas4mcijmrfc7 --- bin/service/web_services.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/service/web_services.py b/bin/service/web_services.py index 590680c6606..abb19fd1a52 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -398,7 +398,10 @@ GNU Public Licence. mp = addons.get_module_path(module) if mp: if os.path.isdir(mp): - rmtree(os.path.realpath(mp)) + if os.path.islink(mp): + os.unlink(mp) + else: + rmtree(mp) else: os.unlink(mp + '.zip') From 97149e497fe7d6b1549c172c457a35810af0674e Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 9 Jan 2009 15:20:08 +0100 Subject: [PATCH 12/46] [IMP] Add three languages, Danish/Greek/Indonesian bzr revid: stephane@tinyerp.com-20090109142008-rjzqqicwjxk4mv4u --- bin/tools/misc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 983b0bc568b..38915ea8a69 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -718,7 +718,9 @@ def get_languages(): 'bs_BS': u'Bosnian / bosanski jezik', 'ca_ES': u'Catalan / Català', 'cs_CZ': u'Czech / Čeština', + 'da_DK': u'Danish / Dansk', 'de_DE': u'German / Deutsch', + 'el_EL': u'Greek / Ελληνικά', 'en_CA': u'English (CA)', 'en_EN': u'English (default)', 'en_GB': u'English (UK)', @@ -731,6 +733,7 @@ def get_languages(): 'fr_FR': u'French / Français', 'hr_HR': u'Croatian / hrvatski jezik', 'hu_HU': u'Hungarian / Magyar', + 'id_ID': u'Indonesian / Bahasa Indonesia', 'it_IT': u'Italian / Italiano', 'lt_LT': u'Lithuanian / Lietuvių kalba', 'nl_NL': u'Dutch / Nederlands', From a416f08255619642cc564e8d3c9fd955bb5258d4 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 15:28:49 +0100 Subject: [PATCH 13/46] [FIX] bzr revid: christophe@tinyerp.com-20090109142849-ee06p9d5b73yygly --- bin/tools/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 983b0bc568b..0246c83cc31 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -227,7 +227,7 @@ def file_open(name, mode="r", subdir='addons', pathinfo=False): fo.write(zfile.read(os.path.join( os.path.basename(head), zipname).replace( os.sep, '/'))) - + fo.seek(0) if pathinfo: return fo, name return fo From 5fc9c91289f5c83eab03b7e896a716eb813c834c Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 16:54:38 +0100 Subject: [PATCH 14/46] [FIX] migration scripts: works now with zip modules bzr revid: christophe@tinyerp.com-20090109155438-ayp2m5nt4e6kcajp --- bin/addons/__init__.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index bdcdf91c1a0..a75f30f1cf6 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -461,21 +461,33 @@ class MigrationManager(object): name, ext = os.path.splitext(os.path.basename(pyfile)) if ext.lower() != '.py': continue - fp = tools.file_open(opj(modulename, pyfile)) - mod = None + mod = fp = fp2 = None try: - mod = imp.load_source(name, pyfile, fp) - logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s"' % mergedict({'name': mod.__name__},strfmt)) - mod.migrate(self.cr, pkg.installed_version) - except ImportError: - logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)-migration file %(file)s' % mergedict({'file': opj(modulename,pyfile)}, strfmt)) - raise - except AttributeError: - logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)-migration file must have a "migrate(cr, installed_version)" function' % strfmt) - except: - raise - fp.close() - del mod + fp = tools.file_open(opj(modulename, pyfile)) + + # imp.load_source need a real file object, so we create + # on from the file-like object we get from file_open + fp2 = os.tmpfile() + fp2.write(fp.read()) + fp2.seek(0) + try: + mod = imp.load_source(name, pyfile, fp2) + logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s"' % mergedict({'name': mod.__name__},strfmt)) + mod.migrate(self.cr, pkg.installed_version) + except ImportError: + logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': opj(modulename,pyfile)}, strfmt)) + raise + except AttributeError: + logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt) + except: + raise + finally: + if fp: + fp.close() + if fp2: + fp2.close() + if mod: + del mod def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): From 6a5bb6904fdb5518753f5f6dbafbef5236257d6c Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 9 Jan 2009 17:25:35 +0100 Subject: [PATCH 15/46] [FIX] fix the verification of view with relaxng to work with base as a zip module bzr revid: christophe@tinyerp.com-20090109162535-n79h7d457jib1hzx --- bin/addons/base/ir/ir_ui_view.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index c2340b404e9..236f03f26a9 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -30,7 +30,8 @@ def _check_xml(self, cr, uid, ids, context={}): for view in self.browse(cr, uid, ids, context): eview = etree.fromstring(view.arch.encode('utf8')) frng = tools.file_open(os.path.join('base','rng','view.rng')) - relaxng = etree.RelaxNG(file=frng) + relaxng_doc = etree.parse(frng) + relaxng = etree.RelaxNG(relaxng_doc) if not relaxng.validate(eview): logger = netsvc.Logger() logger.notifyChannel('init', netsvc.LOG_ERROR, 'The view does not fit the required schema !') From e6c2039ec40eaeb7b5a4fdd7982cd15179a74907 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Sun, 11 Jan 2009 19:01:06 +0100 Subject: [PATCH 16/46] [IMP] sort translations in the output translation file. Allow future patches to po files to be more realist bzr revid: christophe@taupe-20090111180106-uw6uvw44yggpfw2e --- bin/tools/translate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index b7a59538c9e..a25a8de0242 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -335,7 +335,7 @@ def trans_generate(lang, modules, dbname=None): _to_translate = [] def push_translation(module, type, name, id, source): - tuple = (module, type, name, id, source) + tuple = (module, source, name, id, type) if source and tuple not in _to_translate: _to_translate.append(tuple) @@ -486,8 +486,9 @@ def trans_generate(lang, modules, dbname=None): out = [["module","type","name","res_id","src","value"]] # header + _to_translate.sort() # translate strings marked as to be translated - for module, type, name, id, source in _to_translate: + for module, source, name, id, type in _to_translate: trans = trans_obj._get_source(cr, uid, name, type, lang, source) out.append([module, type, name, id, source, encode(trans) or '']) From 9aa2c5fb5c248c40887d664860379dc30bc78fe4 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Mon, 12 Jan 2009 10:40:20 +0100 Subject: [PATCH 17/46] [FIX] delete the connection pool to template1 when finished [IMP] sql_db.db_connect does not take the 'serialize' parameter anymore lp bug: https://launchpad.net/bugs/314973 fixed bzr revid: christophe@tinyerp.com-20090112094020-6l1hdgp5oflengl6 --- bin/service/web_services.py | 60 +++++++++++++++++++++---------------- bin/sql_db.py | 3 +- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/bin/service/web_services.py b/bin/service/web_services.py index abb19fd1a52..7b4b5c35156 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -68,12 +68,14 @@ class db(netsvc.Service): self.actions[id] = {'clean': False} - db = sql_db.db_connect('template1', serialize=1) + db = sql_db.db_connect('template1') cr = db.cursor() - cr.autocommit(True) - time.sleep(0.2) - cr.execute('CREATE DATABASE ' + db_name + ' ENCODING \'unicode\'') - cr.close() + try: + cr.autocommit(True) + cr.execute('CREATE DATABASE ' + db_name + ' ENCODING \'unicode\'') + finally: + cr.close() + sql_db.close_db('template1') class DBInitialize(object): def __call__(self, serv, id, db_name, demo, lang, user_password='admin'): try: @@ -146,7 +148,7 @@ class db(netsvc.Service): sql_db.close_db(db_name) logger = netsvc.Logger() - db = sql_db.db_connect('template1', serialize=1) + db = sql_db.db_connect('template1') cr = db.cursor() cr.autocommit(True) try: @@ -161,6 +163,7 @@ class db(netsvc.Service): 'DROP DB: %s' % (db_name)) finally: cr.close() + sql_db.close_db('template1') return True def dump(self, password, db_name): @@ -197,11 +200,14 @@ class db(netsvc.Service): 'RESTORE DB: %s already exists' % (db_name,)) raise Exception, "Database already exists" - db = sql_db.db_connect('template1', serialize=1) + db = sql_db.db_connect('template1') cr = db.cursor() cr.autocommit(True) - cr.execute('CREATE DATABASE ' + db_name + ' ENCODING \'unicode\'') - cr.close() + try: + cr.execute('CREATE DATABASE ' + db_name + ' ENCODING \'unicode\'') + finally: + cr.close() + sql_db.close_db('template1') cmd = ['pg_restore'] if tools.config['db_user']: @@ -240,24 +246,28 @@ class db(netsvc.Service): def list(self): db = sql_db.db_connect('template1') + cr = db.cursor() try: - cr = db.cursor() - db_user = tools.config["db_user"] - if not db_user and os.name == 'posix': - import pwd - db_user = pwd.getpwuid(os.getuid())[0] - if not db_user: - cr.execute("select decode(usename, 'escape') from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],)) - res = cr.fetchone() - db_user = res and str(res[0]) - if db_user: - cr.execute("select decode(datname, 'escape') from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in ('template0', 'template1', 'postgres')", (db_user,)) - else: - cr.execute("select decode(datname, 'escape') from pg_database where datname not in('template0', 'template1','postgres')") - res = [str(name) for (name,) in cr.fetchall()] + try: + cr = db.cursor() + db_user = tools.config["db_user"] + if not db_user and os.name == 'posix': + import pwd + db_user = pwd.getpwuid(os.getuid())[0] + if not db_user: + cr.execute("select decode(usename, 'escape') from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],)) + res = cr.fetchone() + db_user = res and str(res[0]) + if db_user: + cr.execute("select decode(datname, 'escape') from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in ('template0', 'template1', 'postgres')", (db_user,)) + else: + cr.execute("select decode(datname, 'escape') from pg_database where datname not in('template0', 'template1','postgres')") + res = [str(name) for (name,) in cr.fetchall()] + except: + res = [] + finally: cr.close() - except: - res = [] + sql_db.close_db('template1') res.sort() return res diff --git a/bin/sql_db.py b/bin/sql_db.py index 49094a438f8..1dbdbbc3611 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -221,9 +221,10 @@ class PoolManager(object): logger = netsvc.Logger() logger.notifyChannel('dbpool', netsvc.LOG_INFO, 'Closing all connections to %s' % (db_name,)) cls._pools[db_name].closeall() + print "closeall done" del cls._pools[db_name] -def db_connect(db_name, serialize=0): +def db_connect(db_name): return PoolManager.get(db_name) def close_db(db_name): From 9ba9153187d2b5f02ed5a2ee6c77dd27cbb72bf9 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Mon, 12 Jan 2009 13:01:09 +0100 Subject: [PATCH 18/46] [FIX] et_EE instead of et_ET lp bug: https://launchpad.net/bugs/316338 fixed bzr revid: stephane@tinyerp.com-20090112120109-1cqagacbepu9sw99 --- bin/tools/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 40ef7a68717..3efac8998e6 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -727,7 +727,7 @@ def get_languages(): 'en_US': u'English (US)', 'es_AR': u'Spanish (AR) / Español (AR)', 'es_ES': u'Spanish / Español', - 'et_ET': u'Estonian / Eesti keel', + 'et_EE': u'Estonian / Eesti keel', 'fr_BE': u'French (BE) / Français (BE)', 'fr_CH': u'French (CH) / Français (CH)', 'fr_FR': u'French / Français', From b92b5795172dbb8e4661afa7acf9ff196e7a72c3 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Mon, 12 Jan 2009 13:33:13 +0100 Subject: [PATCH 19/46] [FIX] rename et_ET.po to et_EE.po bzr revid: stephane@tinyerp.com-20090112123313-bw8s6y16bvovvp4y --- bin/addons/base/i18n/{et_ET.po => et_EE.po} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bin/addons/base/i18n/{et_ET.po => et_EE.po} (100%) diff --git a/bin/addons/base/i18n/et_ET.po b/bin/addons/base/i18n/et_EE.po similarity index 100% rename from bin/addons/base/i18n/et_ET.po rename to bin/addons/base/i18n/et_EE.po From 976adfa6652d70e7fee99766c3a915689b80a5bd Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 13 Jan 2009 02:01:07 +0100 Subject: [PATCH 20/46] [IMP] Remove print statement bzr revid: stephane@tinyerp.com-20090113010107-wdev3dd4qftoafj3 --- bin/sql_db.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/sql_db.py b/bin/sql_db.py index 1dbdbbc3611..3628213f307 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -221,7 +221,6 @@ class PoolManager(object): logger = netsvc.Logger() logger.notifyChannel('dbpool', netsvc.LOG_INFO, 'Closing all connections to %s' % (db_name,)) cls._pools[db_name].closeall() - print "closeall done" del cls._pools[db_name] def db_connect(db_name): From 179a924635943eaca4e52100b21df4d8aa7306bd Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 13 Jan 2009 02:08:16 +0100 Subject: [PATCH 21/46] [FIX] Call the browse method with a value different of False lp bug: https://launchpad.net/bugs/316484 fixed bzr revid: stephane@tinyerp.com-20090113010816-anjtjt26oatk59xs --- bin/addons/base/ir/ir_ui_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index 236f03f26a9..1c89e3f9ebd 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -74,7 +74,7 @@ class view(osv.osv): ] def create(self, cr, uid, vals, context={}): - if 'inherit_id' in vals: + if 'inherit_id' in vals and vals['inherit_id']: obj=self.browse(cr,uid,vals['inherit_id']) child=self.pool.get(vals['model']) error="Inherited view model [%s] and \ From 0ef9e21e6d6a6843c5a9016019b064c578f446b1 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 13 Jan 2009 02:08:46 +0100 Subject: [PATCH 22/46] [FIX] The tree tag has the string attribute and not title bzr revid: stephane@tinyerp.com-20090113010846-2u398dpmjvhj72dv --- bin/addons/base/ir/ir_ui_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index 1c89e3f9ebd..0af9a32cbb8 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -65,7 +65,7 @@ class view(osv.osv): 'field_parent': fields.char('Childs Field',size=64), } _defaults = { - 'arch': lambda *a: '\n\n\t\n', + 'arch': lambda *a: '\n\n\t\n', 'priority': lambda *a: 16 } _order = "priority" From 121bbced1e18c3e54aecddaf2c11bcc49b7153d0 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 13 Jan 2009 03:22:26 +0100 Subject: [PATCH 23/46] [FIX] Use the field name instead of the field label lp bug: https://launchpad.net/bugs/314529 fixed bzr revid: stephane@tinyerp.com-20090113022226-m8rrce0e82d7lr8o --- bin/report/custom.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/report/custom.py b/bin/report/custom.py index 0823457ddce..f7ff8996875 100644 --- a/bin/report/custom.py +++ b/bin/report/custom.py @@ -158,8 +158,13 @@ class report_custom(report_int): row = [] cond = [] for i in range(4): - if f['field_child'+str(i)]: - row.append(f['field_child'+str(i)][1]) + field_child = f['field_child'+str(i)] + if field_child: + row.append( + service.execute(cr.dbname, uid, + 'ir.model.fields', 'read', [field_child[0]], + ['name'], context=context)[0]['name'] + ) if f['fc'+str(i)+'_operande']: fct_name = 'id' cond_op = f['fc'+str(i)+'_op'] From 892eeea5ae2c7be313c718a88830ac9ec948a9ef Mon Sep 17 00:00:00 2001 From: Olivier Laurent Date: Wed, 14 Jan 2009 08:59:02 +0100 Subject: [PATCH 24/46] Fixed a potential IndexError if variable 'dir' equals '' bzr revid: olt@tinyerp.com-20090114075902-tz5sbtlkcsrg56qc --- bin/addons/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index a75f30f1cf6..bf0dc39e279 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -154,8 +154,9 @@ def get_module_filetree(module, dir='.'): return False dir = os.path.normpath(dir) - if dir == '.': dir = '' - if dir.startswith('..') or dir[0] == '/': + if dir == '.': + dir = '' + if dir.startswith('..') or (dir and dir[0] == '/'): raise Exception('Cannot access file outside the module') if not os.path.isdir(path): From 9acb5b8ca4a06360610026f016b47e510ccc83f9 Mon Sep 17 00:00:00 2001 From: Olivier Laurent Date: Wed, 14 Jan 2009 09:10:41 +0100 Subject: [PATCH 25/46] Fixed a bug when 'dir' equals ''. Filename's first character was truncated. bzr revid: olt@tinyerp.com-20090114081041-ft5vynfi58078vj6 --- bin/addons/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index bf0dc39e279..980125a41e6 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -170,7 +170,8 @@ def get_module_filetree(module, dir='.'): for f in files: if not f.startswith(dir): continue - f = f[len(dir)+int(not dir.endswith('/')):] + if dir: + f = f[len(dir)+int(not dir.endswith('/')):] lst = f.split(os.sep) current = tree while len(lst) != 1: From b01f0410471a41cc459494e6fe06477bf4a85bf7 Mon Sep 17 00:00:00 2001 From: Olivier Laurent Date: Wed, 14 Jan 2009 10:12:23 +0100 Subject: [PATCH 26/46] pep8 bzr revid: olt@tinyerp.com-20090114091223-lbuvca2agsqehdbp --- bin/addons/__init__.py | 107 ++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 49 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 980125a41e6..1c6618a824e 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -56,6 +56,7 @@ if ad != _ad: # Modules already loaded loaded = [] + class Graph(dict): def addNode(self, name, deps): @@ -79,6 +80,7 @@ class Graph(dict): yield module level += 1 + class Singleton(object): def __new__(cls, name, graph): @@ -90,6 +92,7 @@ class Singleton(object): graph[name] = inst return inst + class Node(Singleton): def __init__(self, name, graph): @@ -107,7 +110,7 @@ class Node(Singleton): for attr in ('init', 'update', 'demo'): if hasattr(self, attr): setattr(node, attr, True) - self.childs.sort(lambda x,y: cmp(x.name, y.name)) + self.childs.sort(lambda x, y: cmp(x.name, y.name)) def hasChild(self, name): return Node(name, self.graph) in self.childs or \ @@ -135,9 +138,9 @@ class Node(Singleton): s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1)) return s + def get_module_path(module): - """Return the path of the given module. - """ + """Return the path of the given module.""" if os.path.exists(opj(ad, module)) or os.path.exists(opj(ad, '%s.zip' % module)): return opj(ad, module) @@ -148,11 +151,12 @@ def get_module_path(module): logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: module not found' % (module,)) return False + def get_module_filetree(module, dir='.'): path = get_module_path(module) if not path: return False - + dir = os.path.normpath(dir) if dir == '.': dir = '' @@ -165,11 +169,12 @@ def get_module_filetree(module, dir='.'): files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()] else: files = tools.osutil.listdir(path, True) - + tree = {} for f in files: if not f.startswith(dir): continue + if dir: f = f[len(dir)+int(not dir.endswith('/')):] lst = f.split(os.sep) @@ -177,14 +182,14 @@ def get_module_filetree(module, dir='.'): while len(lst) != 1: current = current.setdefault(lst.pop(0), {}) current[lst.pop(0)] = None - + return tree def get_module_as_zip(modulename, b64enc=True, src=True): - + RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I) - + def _zippy(archive, path, src=True): path = os.path.abspath(path) base = os.path.basename(path) @@ -192,12 +197,12 @@ def get_module_as_zip(modulename, b64enc=True, src=True): bf = os.path.basename(f) if not RE_exclude.search(bf) and (src or bf == '__terp__.py' or not path.endswith('.py')): archive.write(os.path.join(path, f), os.path.join(base, f)) - + ap = get_module_path(str(modulename)) if not ap: raise Exception('Unable to find path for module %s' % modulename) - - ap = ap.encode('utf8') + + ap = ap.encode('utf8') if os.path.isfile(ap + '.zip'): val = file(ap + '.zip', 'rb').read() else: @@ -225,6 +230,7 @@ def get_module_resource(module, *args): a = get_module_path(module) return a and opj(a, *args) or False + def get_modules(): """Returns the list of module names """ @@ -242,9 +248,10 @@ def get_modules(): return list(set(listdir(ad) + listdir(_ad))) + def create_graph(module_list, force=None): if not force: - force=[] + force = [] graph = Graph() packages = [] @@ -256,7 +263,8 @@ def create_graph(module_list, force=None): except IOError: continue terp_file = get_module_resource(module, '__terp__.py') - if not terp_file: continue + if not terp_file: + continue if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'): try: info = eval(tools.file_open(terp_file).read()) @@ -265,14 +273,14 @@ def create_graph(module_list, force=None): raise if info.get('installable', True): packages.append((module, info.get('depends', []), info)) - + dependencies = dict([(p, deps) for p, deps, data in packages]) current, later = Set([p for p, dep, data in packages]), Set() while packages and current > later: package, deps, data = packages[0] # if all dependencies of 'package' are already in the graph, add 'package' in the graph - if reduce(lambda x,y: x and y in graph, deps, True): + if reduce(lambda x, y: x and y in graph, deps, True): if not package in current: packages.pop(0) continue @@ -288,15 +296,15 @@ def create_graph(module_list, force=None): later.add(package) packages.append((package, deps, data)) packages.pop(0) - + for package in later: unmet_deps = filter(lambda p: p not in graph, dependencies[package]) logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps))) return graph + def init_module_objects(cr, module_name, obj_list): - pool = pooler.get_pool(cr.dbname) logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name) todo = [] for obj in obj_list: @@ -311,6 +319,7 @@ def init_module_objects(cr, module_name, obj_list): t[1](cr, *t[2]) cr.commit() + def register_class(m): """ Register module named m, if not already registered @@ -351,8 +360,8 @@ class MigrationManager(object): This class manage the migration of modules Migrations files must be python files containing a "migrate(cr, installed_version)" function. Theses files must respect a directory tree structure: A 'migrations' folder which containt a - folder by version. Version can be 'module' version or 'server.module' version (in this case, - the files will only be processed by this version of the server). Python file names must start + folder by version. Version can be 'module' version or 'server.module' version (in this case, + the files will only be processed by this version of the server). Python file names must start by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation Example: @@ -368,7 +377,7 @@ class MigrationManager(object): | `-- post-clean-data.py `-- foo.py # not processed - This similar structure is generated by the maintenance module with the migrations files get by + This similar structure is generated by the maintenance module with the migrations files get by the maintenance contract """ @@ -393,7 +402,6 @@ class MigrationManager(object): self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {} self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {} - def migrate_module(self, pkg, stage): assert stage in ('pre', 'post') stageformat = {'pre': '[>%s]', @@ -402,7 +410,7 @@ class MigrationManager(object): if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'): return - + def convert_version(version): if version.startswith(release.major_version) and version != release.major_version: return version # the version number already containt the server version @@ -413,7 +421,7 @@ class MigrationManager(object): return [d for d in tree if tree[d] is not None] versions = list(set( - __get_dir(self.migrations[pkg.name]['module']) + + __get_dir(self.migrations[pkg.name]['module']) + __get_dir(self.migrations[pkg.name]['maintenance']) )) versions.sort(key=lambda k: parse_version(convert_version(k))) @@ -424,7 +432,7 @@ class MigrationManager(object): """ m = self.migrations[pkg.name] lst = [] - + mapping = {'module': {'module': pkg.name, 'rootdir': opj('migrations')}, 'maintenance': {'module': 'base', 'rootdir': opj('maintenance', 'migrations', pkg.name)}, } @@ -439,7 +447,7 @@ class MigrationManager(object): lst.append((mapping[x]['module'], opj(mapping[x]['rootdir'], version, f))) return lst - def mergedict(a,b): + def mergedict(a, b): a = a.copy() a.update(b) return a @@ -448,7 +456,7 @@ class MigrationManager(object): parsed_installed_version = parse_version(pkg.installed_version or '') current_version = parse_version(convert_version(pkg.data.get('version', '0'))) - + versions = _get_migration_versions(pkg) for version in versions: @@ -458,7 +466,7 @@ class MigrationManager(object): 'stage': stage, 'version': stageformat[stage] % version, } - + for modulename, pyfile in _get_migration_files(pkg, version, stage): name, ext = os.path.splitext(os.path.basename(pyfile)) if ext.lower() != '.py': @@ -466,18 +474,18 @@ class MigrationManager(object): mod = fp = fp2 = None try: fp = tools.file_open(opj(modulename, pyfile)) - - # imp.load_source need a real file object, so we create + + # imp.load_source need a real file object, so we create # on from the file-like object we get from file_open fp2 = os.tmpfile() fp2.write(fp.read()) fp2.seek(0) try: mod = imp.load_source(name, pyfile, fp2) - logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s"' % mergedict({'name': mod.__name__},strfmt)) + logger.notifyChannel('migration', netsvc.LOG_INFO, 'module %(addon)s: Running migration %(version)s %(name)s"' % mergedict({'name': mod.__name__}, strfmt)) mod.migrate(self.cr, pkg.installed_version) except ImportError: - logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': opj(modulename,pyfile)}, strfmt)) + logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % mergedict({'file': opj(modulename, pyfile)}, strfmt)) raise except AttributeError: logger.notifyChannel('migration', netsvc.LOG_ERROR, 'module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt) @@ -490,12 +498,12 @@ class MigrationManager(object): fp2.close() if mod: del mod - + def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): # **kwargs is passed directly to convert_xml_import if not status: - status={} + status = {} status = status.copy() package_todo = [] @@ -519,14 +527,14 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): for package in graph: for k, v in additional_data[package.name].items(): setattr(package, k, v) - + migrations = MigrationManager(cr, graph) check_rules = False modobj = None for package in graph: - status['progress'] = (float(statusi)+0.1)/len(graph) + status['progress'] = (float(statusi)+0.1) / len(graph) m = package.name mid = package.id @@ -543,14 +551,14 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): modobj.check(cr, 1, [mid]) idref = {} - status['progress'] = (float(statusi)+0.4)/len(graph) + status['progress'] = (float(statusi)+0.4) / len(graph) if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): check_rules = True init_module_objects(cr, m, modules) for kind in ('init', 'update'): for filename in package.data.get('%s_xml' % kind, []): mode = 'update' - if hasattr(package, 'init') or package.state=='to install': + if hasattr(package, 'init') or package.state == 'to install': mode = 'init' logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename)) name, ext = os.path.splitext(filename) @@ -567,7 +575,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): tools.convert_xml_import(cr, m, fp, idref, mode=mode, **kwargs) fp.close() if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'): - status['progress'] = (float(statusi)+0.75)/len(graph) + 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)) @@ -584,7 +592,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): #cr.execute("update ir_module_module set state='installed', latest_version=%s where id=%s", (ver, mid,)) # Set new modules and dependencies - modobj.write(cr, 1, [mid], {'state':'installed', 'latest_version':ver}) + modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver}) cr.commit() # Update translations for all installed languages @@ -593,12 +601,12 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): cr.commit() migrations.migrate_module(package, 'post') - statusi+=1 + statusi += 1 if perform_checks and check_rules: cr.execute("""select model,name from ir_model where id not in (select model_id from ir_model_access)""") - for (model,name) in cr.fetchall(): - logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model,name)) + for (model, name) in cr.fetchall(): + logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name)) cr.execute('select model from ir_model where state=%s', ('manual',)) @@ -608,9 +616,10 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs): pool.get('ir.model.data')._process_end(cr, 1, package_todo) cr.commit() + def load_modules(db, force_demo=False, status=None, update_module=False): if not status: - status={} + status = {} cr = db.cursor() force = [] @@ -632,20 +641,20 @@ def load_modules(db, force_demo=False, status=None, update_module=False): ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)]) if ids: modobj.button_install(cr, 1, ids) - + mods = [k for k in tools.config['update'] if tools.config['update'][k]] if mods: - ids = modobj.search(cr, 1, ['&',('state', '=', 'installed'), ('name', 'in', mods)]) + ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)]) if ids: modobj.button_upgrade(cr, 1, ids) - + cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base')) cr.execute("select name from ir_module_module where state in ('installed', 'to install', 'to upgrade')") else: cr.execute("select name from ir_module_module where state in ('installed', 'to upgrade')") module_list = [name for (name,) in cr.fetchall()] graph = create_graph(module_list, force) - + # the 'base' module has already been updated base = graph['base'] base.state = 'installed' @@ -658,7 +667,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): logger.notifyChannel('init', netsvc.LOG_INFO, report) for kind in ('init', 'demo', 'update'): - tools.config[kind]={} + tools.config[kind] = {} cr.commit() if update_module: @@ -666,7 +675,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): for mod_id, mod_name in cr.fetchall(): pool = pooler.get_pool(cr.dbname) cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,)) - for rmod,rid in cr.fetchall(): + for rmod, rid in cr.fetchall(): uid = 1 pool.get(rmod).unlink(cr, uid, [rid]) cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,)) From 58ac90e77466f5ca7f4608d9617f99b5ae67d9b9 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Wed, 14 Jan 2009 12:17:10 +0100 Subject: [PATCH 27/46] [FIX] Use the header field from the ir.actions.report.xml to print the header in the rml report lp bug: https://launchpad.net/bugs/316184 fixed bzr revid: stephane@tinyerp.com-20090114111710-rags1eq0p4n0kb0h --- bin/report/report_sxw.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bin/report/report_sxw.py b/bin/report/report_sxw.py index c4f10212b5a..3e10bed3c77 100644 --- a/bin/report/report_sxw.py +++ b/bin/report/report_sxw.py @@ -598,6 +598,7 @@ class report_sxw(report_rml): report_xml = None title='' attach = False + want_header = self.header if report_xml_ids: report_xml = ir_actions_report_xml_obj.browse(cr, uid, report_xml_ids[0], context=context) @@ -605,6 +606,7 @@ class report_sxw(report_rml): attach = report_xml.attachment rml = report_xml.report_rml_content report_type = report_xml.report_type + want_header = report_xml.header else: ir_menu_report_obj = pool.get('ir.ui.menu') report_menu_ids = ir_menu_report_obj.search(cr, uid, @@ -657,14 +659,14 @@ class report_sxw(report_rml): pe.childNodes[0].data=data['model'] meta = rml_dom_meta.documentElement.toxml('utf-8') - rml2 = rml_parser._parse(rml_dom, objs, data, header=self.header) + rml2 = rml_parser._parse(rml_dom, objs, data, header=want_header) sxw_z = zipfile.ZipFile(sxw_io, mode='a') sxw_z.writestr('content.xml', "" + \ rml2) sxw_z.writestr('meta.xml', "" + \ meta) - if self.header: + if want_header: #Add corporate header/footer if report_type=='odt': rml = tools.file_open('custom/corporate_odt_header.xml').read() @@ -676,7 +678,7 @@ class report_sxw(report_rml): objs = self.getObjects(cr, uid, ids, context) rml_parser.preprocess(objs, data, ids) rml_dom = xml.dom.minidom.parseString(rml) - rml2 = rml_parser._parse(rml_dom, objs, data, header=self.header) + rml2 = rml_parser._parse(rml_dom, objs, data, header=want_header) sxw_z.writestr('styles.xml',"" + \ rml2) sxw_z.close() @@ -690,7 +692,7 @@ class report_sxw(report_rml): objs = self.getObjects(cr, uid, ids, context) rml_parser.preprocess(objs, data, ids) rml_dom = xml.dom.minidom.parseString(rml) - rml2 = rml_parser._parse(rml_dom, objs, data, header=self.header) + rml2 = rml_parser._parse(rml_dom, objs, data, header=want_header) if rml_parser.logo: logo = base64.decodestring(rml_parser.logo) From f386b602a27e34fa10b7a8b7a78d963cd7ceb855 Mon Sep 17 00:00:00 2001 From: Olivier Laurent Date: Thu, 15 Jan 2009 11:12:10 +0100 Subject: [PATCH 28/46] Added a new configuration option: 'csv_internal_sep' (equals ',' by default) Some old import files used the pipe ('|') as a separator in records. It has been changed to a comma but this causes problems with old import files when some records have a comma that is not a separator. bzr revid: olt@tinyerp.com-20090115101210-czdbqqeptbj8o1so --- bin/osv/orm.py | 4 +-- bin/tools/config.py | 73 +++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index d21a95710ef..3dcc510db27 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -481,7 +481,7 @@ class orm_template(object): if line[i]: if fields_def[field[len(prefix)][:-3]]['type']=='many2many': res_id = [] - for word in line[i].split(','): + for word in line[i].split(config.get('csv_internal_sep')): if '.' in word: module, xml_id = word.rsplit('.', 1) else: @@ -551,7 +551,7 @@ class orm_template(object): res = [] if line[i]: relation = fields_def[field[len(prefix)]]['relation'] - for word in line[i].split(','): + for word in line[i].split(config.get('csv_internal_sep')): res2 = self.pool.get(relation).name_search(cr, uid, word, [], operator='=') res3 = (res2 and res2[0][0]) or False diff --git a/bin/tools/config.py b/bin/tools/config.py index e06424a005e..c3a7bacd1ac 100644 --- a/bin/tools/config.py +++ b/bin/tools/config.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- ############################################################################## # -# OpenERP, Open Source Management Solution +# OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved # $Id$ # @@ -60,6 +60,7 @@ class configmanager(object): 'language': None, 'pg_path': None, 'admin_passwd': 'admin', + 'csv_internal_sep': ',', 'addons_path': None, 'root_path': None, 'debug_mode': False, @@ -76,22 +77,22 @@ class configmanager(object): 'syslog' : False, 'log_level': logging.INFO, 'assert_exit_level': logging.WARNING, # level above which a failed assert will be raise - 'cache_timeout': 100000, + 'cache_timeout': 100000, } hasSSL = check_ssl() loglevels = dict([(getattr(netsvc, 'LOG_%s' % x), getattr(logging, x)) - for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'DEBUG_RPC', 'NOTSET')]) + for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'DEBUG_RPC', 'NOTSET')]) version = "%s %s" % (release.description, release.version) parser = optparse.OptionParser(version=version) - + parser.add_option("-c", "--config", dest="config", help="specify alternate config file") - parser.add_option("-s", "--save", action="store_true", dest="save", default=False, + parser.add_option("-s", "--save", action="store_true", dest="save", default=False, help="save configuration to ~/.openerp_serverrc") parser.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored") - + parser.add_option("-n", "--interface", dest="interface", help="specify the TCP IP address") parser.add_option("-p", "--port", dest="port", help="specify the TCP port", type="int") parser.add_option("--net_interface", dest="netinterface", help="specify the TCP IP address for netrpc") @@ -99,37 +100,37 @@ class configmanager(object): parser.add_option("--no-netrpc", dest="netrpc", action="store_false", default=True, help="disable netrpc") parser.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false", default=True, help="disable xmlrpc") parser.add_option("-i", "--init", dest="init", help="init a module (use \"all\" for all modules)") - parser.add_option("--without-demo", dest="without_demo", + parser.add_option("--without-demo", dest="without_demo", help="load demo data for a module (use \"all\" for all modules)", default=False) - parser.add_option("-u", "--update", dest="update", + parser.add_option("-u", "--update", dest="update", help="update a module (use \"all\" for all modules)") - parser.add_option("--cache-timeout", dest="cache_timeout", + parser.add_option("--cache-timeout", dest="cache_timeout", help="set the timeout for the cache system", default=100000, type="int") - + # stops the server from launching after initialization - parser.add_option("--stop-after-init", action="store_true", dest="stop_after_init", default=False, + parser.add_option("--stop-after-init", action="store_true", dest="stop_after_init", default=False, help="stop the server after it initializes") 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=loglevels.keys(), + parser.add_option("--assert-exit-level", dest='assert_exit_level', type="choice", choices=loglevels.keys(), help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (loglevels.keys(),)) if hasSSL: group = optparse.OptionGroup(parser, "SSL Configuration") - group.add_option("-S", "--secure", dest="secure", action="store_true", + group.add_option("-S", "--secure", dest="secure", action="store_true", help="launch server over https instead of http", default=False) group.add_option("--cert-file", dest="secure_cert_file", - default="server.cert", + default="server.cert", help="specify the certificate file for the SSL connection") - group.add_option("--pkey-file", dest="secure_pkey_file", + group.add_option("--pkey-file", dest="secure_pkey_file", default="server.pkey", help="specify the private key file for the SSL connection") parser.add_option_group(group) - + # Logging Group group = optparse.OptionGroup(parser, "Logging Configuration") group.add_option("--logfile", dest="logfile", help="file where the server log will be stored") group.add_option("--syslog", action="store_true", dest="syslog", default=False, help="Send the log to the syslog server") - group.add_option('--log-level', dest='log_level', type='choice', choices=loglevels.keys(), + group.add_option('--log-level', dest='log_level', type='choice', choices=loglevels.keys(), help='specify the level of the logging. Accepted values: ' + str(loglevels.keys())) parser.add_option_group(group) @@ -144,17 +145,17 @@ class configmanager(object): group.add_option('--smtp-password', dest='smtp_password', default='', help='specify the SMTP password for sending email') group.add_option('--price_accuracy', dest='price_accuracy', default='2', help='specify the price accuracy') parser.add_option_group(group) - + group = optparse.OptionGroup(parser, "Database related options") group.add_option("-d", "--database", dest="db_name", help="specify the database name") group.add_option("-r", "--db_user", dest="db_user", help="specify the database user name") - group.add_option("-w", "--db_password", dest="db_password", help="specify the database password") - group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path") - group.add_option("--db_host", dest="db_host", help="specify the database host") - group.add_option("--db_port", dest="db_port", help="specify the database port", type="int") - group.add_option("--db_maxconn", dest="db_maxconn", default='64', + group.add_option("-w", "--db_password", dest="db_password", help="specify the database password") + group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path") + group.add_option("--db_host", dest="db_host", help="specify the database host") + group.add_option("--db_port", dest="db_port", help="specify the database port", type="int") + group.add_option("--db_maxconn", dest="db_maxconn", default='64', help="specify the the maximum number of physical connections to posgresql") - group.add_option("-P", "--import-partial", dest="import_partial", + group.add_option("-P", "--import-partial", dest="import_partial", help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.", default=False) parser.add_option_group(group) @@ -164,16 +165,16 @@ class configmanager(object): "Option '-l' is mandatory in case of importation" ) - group.add_option('-l', "--language", dest="language", + group.add_option('-l', "--language", dest="language", help="specify the language of the translation file. Use it with --i18n-export or --i18n-import") - group.add_option("--i18n-export", dest="translate_out", + group.add_option("--i18n-export", dest="translate_out", help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit") - group.add_option("--i18n-import", dest="translate_in", + group.add_option("--i18n-import", dest="translate_in", help="import a CSV or a PO file with translations and exit. The '-l' option is required.") - group.add_option("--modules", dest="translate_modules", + group.add_option("--modules", dest="translate_modules", help="specify modules to export. Use in combination with --i18n-export") - group.add_option("--addons-path", dest="addons_path", - help="specify an alternative addons path.", + group.add_option("--addons-path", dest="addons_path", + help="specify an alternative addons path.", action="callback", callback=self._check_addons_path, nargs=1, type="string") parser.add_option_group(group) @@ -194,7 +195,7 @@ class configmanager(object): self.rcfile = fname or opt.config or os.environ.get('OPENERP_SERVER') or rcfilepath self.load() - + # Verify that we want to log or not, if not the output will go to stdout if self.options['logfile'] in ('None', 'False'): @@ -204,8 +205,8 @@ class configmanager(object): self.options['pidfile'] = False keys = ['interface', 'port', 'db_name', 'db_user', 'db_password', 'db_host', - 'db_port', 'logfile', 'pidfile', 'smtp_port', 'cache_timeout', - 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', + 'db_port', 'logfile', 'pidfile', 'smtp_port', 'cache_timeout', + 'email_from', 'smtp_server', 'smtp_user', 'smtp_password', 'price_accuracy', 'netinterface', 'netport', 'db_maxconn', 'import_partial', 'addons_path'] if hasSSL: @@ -215,7 +216,7 @@ class configmanager(object): if getattr(opt, arg): self.options[arg] = getattr(opt, arg) - keys = ['language', 'translate_out', 'translate_in', 'debug_mode', + keys = ['language', 'translate_out', 'translate_in', 'debug_mode', 'stop_after_init', 'without_demo', 'netrpc', 'xmlrpc', 'syslog'] if hasSSL: @@ -229,7 +230,7 @@ class configmanager(object): if opt.log_level: self.options['log_level'] = loglevels[opt.log_level] - + if not self.options['root_path'] or self.options['root_path']=='None': self.options['root_path'] = os.path.abspath(os.path.dirname(sys.argv[0])) if not self.options['addons_path'] or self.options['addons_path']=='None': @@ -241,7 +242,7 @@ class configmanager(object): self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all'] self.options['translate_modules'].sort() - + if opt.pg_path: self.options['pg_path'] = opt.pg_path From 3f53a2fd6a3cd254f6683de09bd4294c3e3e154c Mon Sep 17 00:00:00 2001 From: qdp Date: Thu, 15 Jan 2009 11:33:40 +0100 Subject: [PATCH 29/46] [IMP] improved the fr chart template bzr revid: qdp@tinyerp.com-20090115103340-08xab1dom3860m1y --- addons/l10n_fr/taxes.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/l10n_fr/taxes.xml b/addons/l10n_fr/taxes.xml index c18a687373b..e98aba353ed 100644 --- a/addons/l10n_fr/taxes.xml +++ b/addons/l10n_fr/taxes.xml @@ -359,7 +359,7 @@ France PCMN - + From a2a55cbe3162420cfd3035123d3bbdec6c264325 Mon Sep 17 00:00:00 2001 From: qdp Date: Thu, 15 Jan 2009 11:36:15 +0100 Subject: [PATCH 30/46] [IMP] improved to get higher score on pylint bzr revid: qdp@tinyerp.com-20090115103615-8qunbjw23fajwhxp --- .../base_module_quality/base_module_quality.py | 18 ++++++++++++------ .../base_module_quality/terp_test/terp_test.py | 9 +++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/addons/base_module_quality/base_module_quality.py b/addons/base_module_quality/base_module_quality.py index 0477e201cbb..b50ad8d20b0 100644 --- a/addons/base_module_quality/base_module_quality.py +++ b/addons/base_module_quality/base_module_quality.py @@ -23,6 +23,7 @@ import pooler import os import osv from tools import config +from tools.translate import _ class abstract_quality_check(object): ''' @@ -52,24 +53,28 @@ class abstract_quality_check(object): #Used to provide more details if necessary. self.result_details = "" - #This bool defines if the test can be run only if the module is installed. + #This bool defines if the test can be run only if the module + #is installed. #True => the module have to be installed. #False => the module can be uninstalled. self.bool_installed_only = True - #This variable is use to make result of test should have more weight (Some tests are more critical than others) + #This variable is used to give result of test more weight, + #because some tests are more critical than others. self.ponderation = 1.0 #Specify test got an error on module self.error = False - #The tests have to subscribe itselfs in this list, that contains all the test that have to be performed. + #The tests have to subscribe itselfs in this list, that contains + #all the test that have to be performed. self.tests = [] - self.list_folders = os.listdir(config['addons_path']+'/base_module_quality/') + self.list_folders = os.listdir(config['addons_path'] + + '/base_module_quality/') for item in self.list_folders: self.item = item path = config['addons_path']+'/base_module_quality/'+item - if os.path.exists(path+'/'+item+'.py') and item not in ['report', 'wizard', 'security']: + if os.path.exists(path + '/' + item + '.py') and item not in ['report', 'wizard', 'security']: item2 = 'base_module_quality.' + item +'.' + item x = __import__(item2) x2 = getattr(x, item) @@ -87,7 +92,8 @@ class abstract_quality_check(object): def get_objects(self, cr, uid, module): # This function returns all object of the given module.. pool = pooler.get_pool(cr.dbname) - ids2 = pool.get('ir.model.data').search(cr, uid, [('module','=', module), ('model','=','ir.model')]) + ids2 = pool.get('ir.model.data').search(cr, uid, + [('module', '=', module), ('model', '=', 'ir.model')]) model_list = [] model_data = pool.get('ir.model.data').browse(cr, uid, ids2) for model in model_data: diff --git a/addons/base_module_quality/terp_test/terp_test.py b/addons/base_module_quality/terp_test/terp_test.py index cf9977c89b7..da64ca404c2 100644 --- a/addons/base_module_quality/terp_test/terp_test.py +++ b/addons/base_module_quality/terp_test/terp_test.py @@ -22,6 +22,7 @@ import os import tools +from tools.translate import _ from base_module_quality import base_module_quality import pooler @@ -30,9 +31,6 @@ import re class quality_test(base_module_quality.abstract_quality_check): def __init__(self): -# ''' -# This test checks the quality of __terp__.py file in the selected module. -# ''' super(quality_test, self).__init__() self.name = _("Terp Test") self.note = _("This test checks if the module satisfies the current coding standard used by OpenERP.") @@ -94,16 +92,15 @@ class quality_test(base_module_quality.abstract_quality_check): feel_bad_factor += 1 else: if not res[key]: - feel_bad_factor += 1 + feel_bad_factor += 1 - if key == 'installable' and not res[key]: # installable tag is provided and True + if key == 'installable' and not res[key]: # installable tag is provided and False feel_bad_factor +=1 else: feel_bad_factor += 1 score = round((feel_good_factor) / float(feel_good_factor + feel_bad_factor),2) -# self.result += "__terp__.py : "+ str(self.score) + "/10\n" return [_('__terp__.py file'), score] From 4d8b255cf10ffa04795d4be9609362f961fc7d32 Mon Sep 17 00:00:00 2001 From: "mra (Open ERP)" Date: Thu, 15 Jan 2009 16:00:37 +0530 Subject: [PATCH 31/46] changes quality module bzr revid: mra@tinyerp.com-20090115103037-xz9tqmgcqzftae7m --- .../base_module_quality_wizard.xml | 4 +- addons/base_module_quality/wizard/__init__.py | 1 + .../wizard/quality_save_report.py | 56 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 addons/base_module_quality/wizard/quality_save_report.py diff --git a/addons/base_module_quality/base_module_quality_wizard.xml b/addons/base_module_quality/base_module_quality_wizard.xml index 467e7e7dc24..21986b49959 100644 --- a/addons/base_module_quality/base_module_quality_wizard.xml +++ b/addons/base_module_quality/base_module_quality_wizard.xml @@ -2,6 +2,7 @@ + Results of Quality Checks @@ -45,7 +46,8 @@ - +