From 329bfb0d18a37fa1f1dcb5c222e72cb8345f00c3 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 16:59:25 +0200 Subject: [PATCH 01/75] tools: Initial version of test_reports utility With this utility module, YAML tests (or others) can quickly check a (pdf) report. bzr revid: p_christ@hol.gr-20101116145925-z4lshbefogbwz7r9 --- bin/tools/test_reports.py | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 bin/tools/test_reports.py diff --git a/bin/tools/test_reports.py b/bin/tools/test_reports.py new file mode 100644 index 00000000000..b6d0ec6b714 --- /dev/null +++ b/bin/tools/test_reports.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2010 OpenERP s.a. (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +""" Helper functions for reports testing. + + Please /do not/ import this file by default, but only explicitly call it + through the code of yaml tests. +""" + +import netsvc +import tools +import logging +from subprocess import Popen, PIPE +import os +import tempfile + +def try_report(cr, uid, rname, ids, data=None, context=None): + """ Try to render a report with contents of ids + + This function should also check for common pitfalls of reports. + """ + log = logging.getLogger('tools.test_reports') + if data is None: + data = {} + if context is None: + context = {} + if rname.startswith('report.'): + rname_s = rname[7:] + else: + rname_s = rname + log.debug("Trying %s.create(%r)", rname, ids) + res = netsvc.LocalService(rname).create(cr, uid, ids, data, context) + if not isinstance(res, tuple): + raise RuntimeError("Result of %s.create() should be a (data,format) tuple, now it is a %s" % \ + (rname, type(res))) + (res_data, res_format) = res + + if not res_data: + raise ValueError("Report %s produced an empty result!" % rname) + + if tools.config['test_report_directory']: + file(os.path.join(tools.config['test_report_directory'], rname+ '.'+res_format), 'wb+').write(res_data) + + log.debug("Have a %s report for %s, will examine it", res_format, rname) + if res_format == 'pdf': + if res_data[:5] != '%PDF-': + raise ValueError("Report %s produced a non-pdf header, %r" % (rname, res_data[:10])) + + res_text = False + try: + fd, rfname = tempfile.mkstemp(suffix=res_format) + os.write(fd, res_data) + os.close(fd) + + fp = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', rfname, '-'], shell=False, stdout=PIPE).stdout + res_text = tools.ustr(fp.read()) + os.unlink(rfname) + except Exception: + log.warning("Cannot extract report's text:", exc_info=True) + + if res_text is not False: + for line in res_text.split('\n'): + if ('[[' in line) or ('[ [' in line): + log.error("Report %s may have bad expression near: \"%s\".", rname, line[80:]) + # TODO more checks, what else can be a sign of a faulty report? + elif res_format == 'foobar': + # TODO + pass + else: + log.warning("Report %s produced a \"%s\" chunk, cannot examine it", rname, res_format) + + return True +#eof From 63d562de395face054ae4f55c576782a2795e6f4 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 17:00:25 +0200 Subject: [PATCH 02/75] ORM: fix bug at orm.copy_data(), would reset context[lang] Thanks to Dr. Gassauer that made me revise this code. bzr revid: p_christ@hol.gr-20101116150025-ssyiix1c72t04fbk --- 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 496d569a29c..ef2ca8e171a 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -4096,7 +4096,7 @@ class orm(orm_template): else: default['state'] = self._defaults['state'] - context_wo_lang = context + context_wo_lang = context.copy() if 'lang' in context: del context_wo_lang['lang'] data = self.read(cr, uid, [id], context=context_wo_lang)[0] From 4187facd9f033bc4b9e4eda8f81dcc6cc2bb3784 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 17:00:47 +0200 Subject: [PATCH 03/75] ORM: Better message when copying non-existing records by: jvo@openerp.com In both cases, we end up with an exception. But it is better to know which model+id has failed. bzr revid: p_christ@hol.gr-20101116150047-xtyz8nrt6465ug2k --- bin/osv/orm.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index ef2ca8e171a..0d37f5afc15 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -4099,7 +4099,11 @@ class orm(orm_template): context_wo_lang = context.copy() if 'lang' in context: del context_wo_lang['lang'] - data = self.read(cr, uid, [id], context=context_wo_lang)[0] + data = self.read(cr, uid, [id,], context=context_wo_lang) + if data: + data = data[0] + else: + raise IndexError( _("Record #%d of %s not found, cannot copy!") %( id, self._name)) fields = self.fields_get(cr, uid, context=context) for f in fields: From 08ef1a95f1d60df8fb777b9022b17fc6091b274e Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 17:04:35 +0200 Subject: [PATCH 04/75] ORM: deprecate check_recursion() in favour of _check_recursion() bzr revid: p_christ@hol.gr-20101116150435-jfmjah8ookf11x3y --- bin/osv/orm.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 0d37f5afc15..d7b406c0f3d 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -4214,6 +4214,12 @@ class orm(orm_template): return cr.fetchone()[0] == len(ids) def check_recursion(self, cr, uid, ids, parent=None): + warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \ + self._name, DeprecationWarning, stacklevel=3) + assert ' ' not in (parent or '') + return self._check_recursion(cr, uid, ids, parent) + + def _check_recursion(self, cr, uid, ids, parent=None): """ Verifies that there is no loop in a hierarchical structure of records, by following the parent relationship using the **parent** field until a loop From 8ce5d2bc20fb631a3df359c667c344d4c7aafa92 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 17:05:37 +0200 Subject: [PATCH 05/75] tools/misc: prevent icon names from being translated They are identifier strings, not human-readable. bzr revid: p_christ@hol.gr-20101116150537-s7tyw7d6y0r1dl6c --- bin/tools/misc.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 90b4c135214..79121eb89c9 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -1125,7 +1125,7 @@ def debug(what): netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what) -icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', +__icons_list = ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER', 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE', 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO', @@ -1161,7 +1161,11 @@ icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_ 'terp-gdu-smart-failing','terp-go-week','terp-gtk-select-all','terp-locked','terp-mail-forward', 'terp-mail-message-new','terp-mail-replied','terp-rating-rated','terp-stage','terp-stock_format-scientific', 'terp-dolar_ok!','terp-idea','terp-stock_format-default','terp-mail-','terp-mail_delete' -]) +] + +def icons(*a, **kw): + global __icons_list + return [(x, x) for x in __icons_list ] def extract_zip_file(zip_file, outdirectory): zf = zipfile.ZipFile(zip_file, 'r') From e34769a59d517e4206225e9e37c225b61b9c63c6 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 17:06:07 +0200 Subject: [PATCH 06/75] module wizards: improve over trivial rpc errors bzr revid: p_christ@hol.gr-20101116150607-qh03n3w225j6xh8v --- bin/addons/base/module/wizard/base_language_install.py | 4 +++- bin/addons/base/module/wizard/base_update_translations.py | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/addons/base/module/wizard/base_language_install.py b/bin/addons/base/module/wizard/base_language_install.py index 9cdae41ad2b..f7b469f85ed 100644 --- a/bin/addons/base/module/wizard/base_language_install.py +++ b/bin/addons/base/module/wizard/base_language_install.py @@ -38,7 +38,9 @@ class base_language_install(osv.osv_memory): 'state': 'init', 'overwrite': False } - def lang_install(self, cr, uid, ids, context): + def lang_install(self, cr, uid, ids, context=None): + if context is None: + context = {} language_obj = self.browse(cr, uid, ids)[0] lang = language_obj.lang if lang: diff --git a/bin/addons/base/module/wizard/base_update_translations.py b/bin/addons/base/module/wizard/base_update_translations.py index b413e9d7093..6b329cf1212 100644 --- a/bin/addons/base/module/wizard/base_update_translations.py +++ b/bin/addons/base/module/wizard/base_update_translations.py @@ -35,7 +35,7 @@ class base_update_translations(osv.osv_memory): lang_obj=pooler.get_pool(cr.dbname).get('res.lang') ids=lang_obj.search(cr, uid, [('code', '=', lang_code)]) if not ids: - raise osv.except_osv(_('No language with code "%s" exists') % lang_code) + raise osv.except_osv(_('Error!'), _('No language with code "%s" exists') % lang_code) lang = lang_obj.browse(cr, uid, ids[0]) return lang.name def act_cancel(self, cr, uid, ids, context=None): @@ -51,12 +51,14 @@ class base_update_translations(osv.osv_memory): return {'type': 'ir.actions.act_window_close'} def default_get(self, cr, uid, fields, context=None): + if context is None: + context = {} res = super(base_update_translations, self).default_get(cr, uid, fields, context=context) if context.get('active_model') != "res.lang": return res - record_id = context and context.get('active_id', False) or False + record_id = context.get('active_id', False) or False if record_id: lang = self.pool.get('res.lang').browse(cr, uid, record_id).code res.update(lang=lang) From 3e32b3406019c85a8f84f9f2402b35aac85818e3 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 17:07:53 +0200 Subject: [PATCH 07/75] base_data: fix English typo bzr revid: p_christ@hol.gr-20101116150753-k60y3sfqn40goppr --- bin/addons/base/base_data.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/base_data.xml b/bin/addons/base/base_data.xml index b907f8d19d6..acdd45fba4f 100644 --- a/bin/addons/base/base_data.xml +++ b/bin/addons/base/base_data.xml @@ -989,7 +989,7 @@ Zambia zm - + Zaire zr From cfdd34d511aeda2b8937a19aaeb4948c16d76665 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 18:20:25 +0200 Subject: [PATCH 08/75] web_services: implement os timestamp query. The os user and sys elapsed times are very useful for performance metrics, they can tell us how much did the openerp server try for some operation. Conflicts: bin/service/web_services.py bzr revid: p_christ@hol.gr-20101116162025-go5lx4qs658pt7t5 --- 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 df42d8736ce..7054be9597e 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -385,7 +385,7 @@ class common(_ObjectService): 'login_message','get_stats', 'check_connectivity', 'list_http_services']: pass - elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel']: + elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time']: passwd = params[0] params = params[1:] security.check_super(passwd) @@ -567,6 +567,9 @@ GNU Public Licence. def exp_check_connectivity(self): return bool(sql_db.db_connect('template1')) + + def exp_get_os_time(self): + return os.times() common() From 89c40dec2f4933790c82c130e2a47bb46396937a Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 18:35:10 +0200 Subject: [PATCH 09/75] translate: fix scanning the ir.models for translations The loop would only consider models that have >0 records registered in ir.model.data. This is merge of commits 3988ebf4dc130, 070e7d6ac717de and adapt to trunk. bzr revid: p_christ@hol.gr-20101116163510-yemzlcy0f0yk893l --- bin/tools/translate.py | 49 ++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index c75153cdde8..686f9677b43 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -499,13 +499,21 @@ def trans_generate(lang, modules, dbname=None): query = 'SELECT name, model, res_id, module' \ ' FROM ir_model_data' + + query_models = """SELECT m.id, m.model, imd.module + FROM ir_model AS m, ir_model_data AS imd + WHERE m.id = imd.res_id AND imd.model = 'ir.model' """ + if 'all_installed' in modules: query += ' WHERE module IN ( SELECT name FROM ir_module_module WHERE state = \'installed\') ' + query_models += " AND imd.module in ( SELECT name FROM ir_module_module WHERE state = 'installed') " query_param = None if 'all' not in modules: query += ' WHERE module IN %s' + query_models += ' AND imd.module in %s' query_param = (tuple(modules),) query += ' ORDER BY module, model, name' + query_models += ' ORDER BY module, model' cr.execute(query, query_param) @@ -637,20 +645,7 @@ def trans_generate(lang, modules, dbname=None): except (IOError, etree.XMLSyntaxError): logging.getLogger("i18n").exception("couldn't export translation for report %s %s %s", name, report_type, fname) - model_obj = pool.get(model) - def push_constraint_msg(module, term_type, model, msg): - # Check presence of __call__ directly instead of using - # callable() because it will be deprecated as of Python 3.0 - if not hasattr(msg, '__call__'): - push_translation(module, term_type, model, 0, encode(msg)) - - for constraint in model_obj._constraints: - push_constraint_msg(module, 'constraint', model, constraint[1]) - - for constraint in model_obj._sql_constraints: - push_constraint_msg(module, 'sql_constraint', model, constraint[2]) - - for field_name,field_def in model_obj._columns.items(): + for field_name,field_def in obj._table._columns.items(): if field_def.translate: name = model + "," + field_name try: @@ -659,6 +654,32 @@ def trans_generate(lang, modules, dbname=None): trad = '' push_translation(module, 'model', name, xml_name, encode(trad)) + # End of data for ir.model.data query results + + cr.execute(query_models, query_param) + + def push_constraint_msg(module, term_type, model, msg): + # Check presence of __call__ directly instead of using + # callable() because it will be deprecated as of Python 3.0 + if not hasattr(msg, '__call__'): + push_translation(module, term_type, model, 0, encode(msg)) + + for (model_id, model, module) in cr.fetchall(): + module = encode(module) + model = encode(model) + + model_obj = pool.get(model) + + if not model_obj: + logging.getLogger("i18n").error("Unable to find object %r", model) + continue + + for constraint in getattr(model_obj, '_constraints', []): + push_constraint_msg(module, 'constraint', model, constraint[1]) + + for constraint in getattr(model_obj, '_sql_constraints', []): + push_constraint_msg(module, 'sql_constraint', model, constraint[2]) + # parse source code for _() calls def get_module_from_path(path, mod_paths=None): if not mod_paths: From 406e8aaba6d3d4ca52df2456ef79b45b70c19565 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 18:35:36 +0200 Subject: [PATCH 10/75] ORM: do import warnings, needed for _check_recursion() earlier patch bzr revid: p_christ@hol.gr-20101116163536-jmfxkzagrq5fcrx8 --- bin/osv/orm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index d7b406c0f3d..5cf0e08a02e 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -41,6 +41,7 @@ import calendar import copy import datetime import logging +import warnings import operator import pickle import re From 1f380f050417c247e40f2f29d14d6c1fbd80360e Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Wed, 17 Nov 2010 13:52:32 +0200 Subject: [PATCH 11/75] web-services: new "get_sqlcount" call Useful for automated statistics, returns the global SQL counter of queries. It does not affect any counters, and will print a warning if debug_sql is not enabled (counter would be zero, then). bzr revid: p_christ@hol.gr-20101117115232-olq747j4vmauqgy8 --- bin/service/web_services.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/service/web_services.py b/bin/service/web_services.py index 7054be9597e..0af15691bea 100644 --- a/bin/service/web_services.py +++ b/bin/service/web_services.py @@ -36,6 +36,7 @@ import release import sql_db import tools import locale +import logging from cStringIO import StringIO class db(netsvc.ExportService): @@ -385,7 +386,7 @@ class common(_ObjectService): 'login_message','get_stats', 'check_connectivity', 'list_http_services']: pass - elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time']: + elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time', 'get_sqlcount']: passwd = params[0] params = params[1:] security.check_super(passwd) @@ -571,6 +572,12 @@ GNU Public Licence. def exp_get_os_time(self): return os.times() + def exp_get_sqlcount(self): + logger = logging.getLogger('db.cursor') + if not logger.isEnabledFor(logging.DEBUG_SQL): + logger.warning("Counters of SQL will not be reliable unless DEBUG_SQL is set at the server's config.") + return sql_db.sql_counter + common() class objects_proxy(netsvc.ExportService): From c8f52b8c98775e98657600bfc3a559960c500be9 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Thu, 18 Nov 2010 18:47:21 +0200 Subject: [PATCH 12/75] ORM, ir_*: convert standard constraint messages to callables Also, fix way that callable constraints are passed with the context so that they can have translatable strings inside (but not on the results of the callable). Conflicts: bin/addons/base/ir/ir_model.py bin/addons/base/module/module.py bin/tools/translate.py bzr revid: p_christ@hol.gr-20101118164721-19rgx43d3p96b2lf --- bin/addons/base/ir/ir_actions.py | 6 +++++- bin/addons/base/ir/ir_model.py | 9 +++++++-- bin/addons/base/ir/ir_ui_menu.py | 7 ++++++- bin/addons/base/module/module.py | 9 +++++++-- bin/osv/orm.py | 11 ++++++++--- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 84419d25b51..807d0438b82 100644 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -151,8 +151,12 @@ class act_window(osv.osv): if action.src_model and not self.pool.get(action.src_model): return False return True + + def _invalid_model_msg(self, cr, uid, ids, context=None): + return _('Invalid model name in the action definition.') + _constraints = [ - (_check_model, 'Invalid model name in the action definition.', ['res_model','src_model']) + (_check_model, _invalid_model_msg, ['res_model','src_model']) ] def _views_get_fnc(self, cr, uid, ids, name, arg, context={}): diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index 3f3700b0e45..5f9e3347b74 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -88,8 +88,10 @@ class ir_model(osv.osv): return False return True + def _model_name_msg(self, cr, uid, ids, context=None): + return _('The Object name must start with x_ and not contain any special character !') _constraints = [ - (_check_model_name, 'The Object name must start with x_ and not contain any special character !', ['model']), + (_check_model_name, _model_name_msg, ['model']), ] # overridden to allow searching both on model name (model field) @@ -181,8 +183,11 @@ class ir_model_fields(osv.osv): 'selectable': lambda *a: 1, } _order = "id" + def _size_gt_zero_msg(self, cr, user, ids, context=None): + return _('Size of the field can never be less than 1 !') + _sql_constraints = [ - ('size_gt_zero', 'CHECK (size>0)', 'Size of the field can never be less than 1 !'), + ('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ), ] def unlink(self, cr, user, ids, context=None): for field in self.browse(cr, user, ids, context): diff --git a/bin/addons/base/ir/ir_ui_menu.py b/bin/addons/base/ir/ir_ui_menu.py index f79493b4813..81dd5bef4d7 100644 --- a/bin/addons/base/ir/ir_ui_menu.py +++ b/bin/addons/base/ir/ir_ui_menu.py @@ -22,6 +22,7 @@ from osv import fields, osv import re import tools +from tools.translate import _ def one_in(setA, setB): """Check the presence of an element of setA in setB @@ -285,8 +286,12 @@ class ir_ui_menu(osv.osv): ('ir.actions.server', 'ir.actions.server'), ]), } + + def _rec_message(self, cr, uid, ids, context=None): + return _('Error ! You can not create recursive Menu.') + _constraints = [ - (_check_recursion, 'Error ! You can not create recursive Menu.', ['parent_id']) + (_check_recursion, _rec_message , ['parent_id']) ] _defaults = { 'icon' : lambda *a: 'STOCK_OPEN', diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index 81985332157..f392ee51326 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -187,9 +187,14 @@ class module(osv.osv): } _order = 'name' + def _name_uniq_msg(self, cr, uid, ids, context=None): + return _('The name of the module must be unique !') + def _certificate_uniq_msg(self, cr, uid, ids, context=None): + return _('The certificate ID of the module must be unique !') + _sql_constraints = [ - ('name_uniq', 'unique (name)', 'The name of the module must be unique !'), - ('certificate_uniq', 'unique (certificate)', 'The certificate ID of the module must be unique !') + ('name_uniq', 'UNIQUE (name)',_name_uniq_msg ), + ('certificate_uniq', 'UNIQUE (certificate)',_certificate_uniq_msg ) ] def unlink(self, cr, uid, ids, context=None): diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 5cf0e08a02e..e168146b595 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1053,9 +1053,14 @@ class orm_template(object): # Check presence of __call__ directly instead of using # callable() because it will be deprecated as of Python 3.0 if hasattr(msg, '__call__'): - txt_msg, params = msg(self, cr, uid, ids) - tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg - translated_msg = tmp_msg % params + tmp_msg = msg(self, cr, uid, ids, context=context) + # Why translate something that has been generated dynamically? + # tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg + if isinstance(tmp_msg, tuple): + tmp_msg, params = tmp_msg + translated_msg = tmp_msg % params + else: + translated_msg = tmp_msg else: translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=msg) or msg error_msgs.append( From d0dee320199662492c3779deef06c412238eec73 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Thu, 18 Nov 2010 20:46:38 +0200 Subject: [PATCH 13/75] osv: Improve the translation logic for the osv.check wrapper Done after bug [655783], the osv layer is too early to call the _(). Also, we cannot trust the cursor to perform a further SQL query on it, since it carries the IntegrityError. bzr revid: p_christ@hol.gr-20101118184638-2yrfpvx9har48h1g --- bin/osv/osv.py | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 11db4096a9a..9b1bb3ad86f 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -32,7 +32,7 @@ import traceback import logging from psycopg2 import IntegrityError, errorcodes from tools.func import wraps -from tools.translate import _ +from tools.translate import translate module_list = [] module_class_list = {} @@ -51,6 +51,46 @@ class osv_pool(netsvc.Service): def check(f): @wraps(f) def wrapper(self, dbname, *args, **kwargs): + """ Wraps around OSV functions and normalises a few exceptions + """ + + def tr(src, ttype): + # We try to do the same as the _(), but without the frame + # inspection, since we aready are wrapping an osv function + # trans_obj = self.get('ir.translation') cannot work yet :( + ctx = {} + if not kwargs: + if args and isinstance(args[-1], dict): + ctx = args[-1] + else: + ctx = {} + elif isinstance(kwargs, dict): + ctx = kwargs.get('context', {}) + else: + ctx = {} + + uid = 1 + if args and isinstance(args[0], (long, int)): + uid = args[0] + + lang = ctx and ctx.get('lang', False) + if not lang: + return src + + # We open a *new* cursor here, one reason is that failed SQL + # queries (as in IntegrityError) will invalidate the current one. + cr = False + try: + cr = pooler.get_db_only(dbname).cursor() + #return trans_obj._get_source( name=?) + return translate(cr, name=False, source_type=ttype, + lang=lang, source=src) + finally: + if cr: cr.close() + + def _(src): + return tr(src, 'code') + try: if not pooler.get_pool(dbname)._ready: raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.') @@ -64,7 +104,8 @@ class osv_pool(netsvc.Service): except IntegrityError, inst: for key in self._sql_error.keys(): if key in inst[0]: - self.abortResponse(1, _('Constraint Error'), 'warning', _(self._sql_error[key])) + self.abortResponse(1, _('Constraint Error'), 'warning', + tr(self._sql_error[key], 'sql_constraint')) if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION): msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set') self.logger.debug("IntegrityError", exc_info=True) From 41c9098ca994a3712bcdd0e6dc61805ed946b02d Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Thu, 18 Nov 2010 20:46:43 +0200 Subject: [PATCH 14/75] OSV: Fix callable _sql_constraint messages This fixes the support for callable sql_constraint messages, which have always been wrong. It respects the (cr, uid, ids, context) arguments and will fallback to Postgres's error if it cannot call. Note that sql constraints are not properly explained when base_module_record or audittrail are installed. bzr revid: p_christ@hol.gr-20101118184643-8tnmj83b09kuvf1j --- bin/osv/orm.py | 2 ++ bin/osv/osv.py | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index e168146b595..f0678d91903 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -980,6 +980,8 @@ class orm_template(object): for key in self.pool._sql_error.keys(): if key in e[0]: msg = self.pool._sql_error[key] + if hasattr(msg, '__call__'): + msg = msg(cr, uid, [res_id,], context=context) break return (-1, res, 'Line ' + str(counter) +' : ' + msg, '') if isinstance(e, osv.orm.except_orm): diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 9b1bb3ad86f..fc72069b22f 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -74,17 +74,44 @@ class osv_pool(netsvc.Service): uid = args[0] lang = ctx and ctx.get('lang', False) - if not lang: + if not (lang or hasattr(src, '__call__')): return src # We open a *new* cursor here, one reason is that failed SQL # queries (as in IntegrityError) will invalidate the current one. cr = False + + if hasattr(src, '__call__'): + # callable. We need to find the right parameters to call + # the orm._sql_message(self, cr, uid, ids, context) function, + # or we skip.. + # our signature is f(osv_pool, dbname [,uid, obj, method, args]) + try: + if args and len(args) > 1: + obj = self.get(args[1]) + if len(args) > 3 and isinstance(args[3], (long, int, list)): + ids = args[3] + else: + ids = [] + cr = pooler.get_db_only(dbname).cursor() + return src(obj, cr, uid, ids, context=(ctx or {})) + except Exception, e: + pass + finally: + if cr: cr.close() + + return False # so that the original SQL error will + # be returned, it is the best we have. + try: cr = pooler.get_db_only(dbname).cursor() #return trans_obj._get_source( name=?) - return translate(cr, name=False, source_type=ttype, + res = translate(cr, name=False, source_type=ttype, lang=lang, source=src) + if res: + return res + else: + return src finally: if cr: cr.close() @@ -105,7 +132,7 @@ class osv_pool(netsvc.Service): for key in self._sql_error.keys(): if key in inst[0]: self.abortResponse(1, _('Constraint Error'), 'warning', - tr(self._sql_error[key], 'sql_constraint')) + tr(self._sql_error[key], 'sql_constraint') or inst[0]) if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION): msg = _('The operation cannot be completed, probably due to the following:\n- deletion: you may be trying to delete a record while other records still reference it\n- creation/update: a mandatory field is not correctly set') self.logger.debug("IntegrityError", exc_info=True) From ac2c09908a50ae8c7b57157466effd35ec23ab9e Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Thu, 18 Nov 2010 20:48:40 +0200 Subject: [PATCH 15/75] OSV: a lint check fix bzr revid: p_christ@hol.gr-20101118184840-08li9h2ebfzdtxpy --- bin/osv/osv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/osv/osv.py b/bin/osv/osv.py index fc72069b22f..6f43739b35a 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -95,7 +95,7 @@ class osv_pool(netsvc.Service): ids = [] cr = pooler.get_db_only(dbname).cursor() return src(obj, cr, uid, ids, context=(ctx or {})) - except Exception, e: + except Exception: pass finally: if cr: cr.close() From 5d09bce9205785f8eaacef041a76218f3b415b3e Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 23 Nov 2010 13:56:38 +0100 Subject: [PATCH 16/75] [IMP] orm: proper test for column names in check_recursion() bzr revid: odo@openerp.com-20101123125638-dhd95d6a6xg912ls --- bin/osv/orm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index e168146b595..55ba80624ea 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -4222,7 +4222,8 @@ class orm(orm_template): def check_recursion(self, cr, uid, ids, parent=None): warnings.warn("You are using deprecated %s.check_recursion(). Please use the '_check_recursion()' instead!" % \ self._name, DeprecationWarning, stacklevel=3) - assert ' ' not in (parent or '') + assert parent is None or parent in self._columns or parent in self._inherit_fields,\ + "The 'parent' parameter passed to check_recursion() must be None or a valid field name" return self._check_recursion(cr, uid, ids, parent) def _check_recursion(self, cr, uid, ids, parent=None): From 091a3eba2b361f719a8218acf36875013f9a8dd3 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:49:32 +0200 Subject: [PATCH 17/75] websrv_lib: fix do_OPTIONS for m$ webdav Since we have moved all code into the websrv_lib, we must also support this non-standard extension in our do_OPTIONS(). It has been noted that some Win XP machines wouldn't connect to our WebDAV unless we set that header. bzr revid: p_christ@hol.gr-20101123134932-65wmh8bntuwt4jvc --- bin/service/websrv_lib.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index 8565ab6ccaa..e9b1b770c63 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -191,6 +191,12 @@ class HttpOptions: self.send_response(200) self.send_header("Content-Length", 0) + if 'Microsoft' in self.headers.get('User-Agent', ''): + self.send_header('MS-Author-Via', 'DAV') + # Microsoft's webdav lib ass-umes that the server would + # be a FrontPage(tm) one, unless we send a non-standard + # header that we are not an elephant. + # http://www.ibm.com/developerworks/rational/library/2089.html for key, value in opts.items(): if isinstance(value, basestring): From 063ac95428c587518a40f2ab20f9c35e35cff932 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:50:01 +0200 Subject: [PATCH 18/75] base.module.upgrade: have default context arg. bzr revid: p_christ@hol.gr-20101123135001-gosz6v48duf3t467 --- bin/addons/base/module/wizard/base_module_upgrade.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/addons/base/module/wizard/base_module_upgrade.py b/bin/addons/base/module/wizard/base_module_upgrade.py index 4db4028bdb0..f018ded2fe2 100644 --- a/bin/addons/base/module/wizard/base_module_upgrade.py +++ b/bin/addons/base/module/wizard/base_module_upgrade.py @@ -20,6 +20,7 @@ ############################################################################## import pooler +import wizard from osv import osv, fields class base_module_upgrade(osv.osv_memory): @@ -80,7 +81,7 @@ class base_module_upgrade(osv.osv_memory): res = mod_obj.read(cr, uid, ids, ['name','state'], context) return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))} - def upgrade_module(self, cr, uid, ids, context): + def upgrade_module(self, cr, uid, ids, context=None): pool = pooler.get_pool(cr.dbname) mod_obj = self.pool.get('ir.module.module') data_obj = self.pool.get('ir.model.data') From f4c0dfd0f5bfb0bafb5ed4698321eb693932267b Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:50:03 +0200 Subject: [PATCH 19/75] addons/base: bump the module version bzr revid: p_christ@hol.gr-20101123135003-krickjbds9qnbnhq --- bin/addons/base/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/__openerp__.py b/bin/addons/base/__openerp__.py index 492c397754d..5c0d833b6ce 100644 --- a/bin/addons/base/__openerp__.py +++ b/bin/addons/base/__openerp__.py @@ -23,7 +23,7 @@ { 'name': 'Base', - 'version': '1.2', + 'version': '1.3', 'category': 'Generic Modules/Base', 'description': """The kernel of OpenERP, needed for all installation.""", 'author': 'OpenERP SA', From 1aa8aad95e5cd349d5149d8cf3ce7c3460aaba56 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:52:24 +0200 Subject: [PATCH 20/75] ir_actions: Cleanup code, use pythonic logging. Conflicts: bin/addons/base/ir/ir_actions.py bzr revid: p_christ@hol.gr-20101123135224-190hp303ipjncml8 --- bin/addons/base/ir/ir_actions.py | 49 ++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 807d0438b82..b7d62c169f7 100644 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -26,6 +26,7 @@ import time from tools.config import config from tools.translate import _ import netsvc +import logging import re import copy import os @@ -144,7 +145,7 @@ class act_window(osv.osv): _table = 'ir_act_window' _sequence = 'ir_actions_id_seq' - def _check_model(self, cr, uid, ids, context={}): + def _check_model(self, cr, uid, ids, context=None): for action in self.browse(cr, uid, ids, context): if not self.pool.get(action.res_model): return False @@ -159,7 +160,7 @@ class act_window(osv.osv): (_check_model, _invalid_model_msg, ['res_model','src_model']) ] - def _views_get_fnc(self, cr, uid, ids, name, arg, context={}): + def _views_get_fnc(self, cr, uid, ids, name, arg, context=None): res={} for act in self.browse(cr, uid, ids): res[act.id]=[(view.view_id.id, view.view_mode) for view in act.view_ids] @@ -175,30 +176,34 @@ class act_window(osv.osv): res[act.id].append((False, t)) return res - def _search_view(self, cr, uid, ids, name, arg, context={}): + def _search_view(self, cr, uid, ids, name, arg, context=None): res = {} def encode(s): if isinstance(s, unicode): return s.encode('utf8') return s - for act in self.browse(cr, uid, ids): + for act in self.browse(cr, uid, ids, context=context): fields_from_fields_get = self.pool.get(act.res_model).fields_get(cr, uid, context=context) search_view_id = False if act.search_view_id: 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)]) + res_view = self.pool.get('ir.ui.view').search(cr, uid, + [('model','=',act.res_model),('type','=','search'), + ('inherit_id','=',False)], context=context) 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) + 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) else: def process_child(node, new_node, doc): for child in node.childNodes: - if child.localName=='field' and child.hasAttribute('select') and child.getAttribute('select')=='1': + if child.localName=='field' and child.hasAttribute('select') \ + and child.getAttribute('select')=='1': if child.childNodes: fld = doc.createElement('field') for attr in child.attributes.keys(): @@ -222,7 +227,7 @@ class act_window(osv.osv): res[act.id] = str(form_arch) return res - def _get_help_status(self, cr, uid, ids, name, arg, context={}): + def _get_help_status(self, cr, uid, ids, name, arg, context=None): activate_tips = self.pool.get('res.users').browse(cr, uid, uid).menu_tips return dict([(id, activate_tips) for id in ids]) @@ -340,7 +345,7 @@ class act_url(osv.osv): } act_url() -def model_get(self, cr, uid, context={}): +def model_get(self, cr, uid, context=None): wkf_pool = self.pool.get('workflow') ids = wkf_pool.search(cr, uid, []) osvs = wkf_pool.read(cr, uid, ids, ['osv']) @@ -385,7 +390,7 @@ server_object_lines() # class actions_server(osv.osv): - def _select_signals(self, cr, uid, context={}): + def _select_signals(self, cr, uid, context=None): cr.execute("SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t \ WHERE w.id = a.wkf_id AND t.act_from = a.id OR t.act_to = a.id AND t.signal!='' \ AND t.signal NOT IN (null, NULL)") @@ -397,13 +402,13 @@ class actions_server(osv.osv): res.append(line) return res - def _select_objects(self, cr, uid, context={}): + def _select_objects(self, cr, uid, context=None): model_pool = self.pool.get('ir.model') ids = model_pool.search(cr, uid, [('name','not ilike','.')]) res = model_pool.read(cr, uid, ids, ['model', 'name']) return [(r['model'], r['name']) for r in res] + [('','')] - def change_object(self, cr, uid, ids, copy_object, state, context={}): + def change_object(self, cr, uid, ids, copy_object, state, context=None): if state == 'object_copy': model_pool = self.pool.get('ir.model') model = copy_object.split(',')[0] @@ -474,7 +479,7 @@ class actions_server(osv.osv): } def get_email(self, cr, uid, action, context): - logger = netsvc.Logger() + logger = logging.getLogger('Workflow') obj_pool = self.pool.get(action.model_id.model) id = context.get('active_id') obj = obj_pool.browse(cr, uid, id) @@ -490,12 +495,12 @@ class actions_server(osv.osv): try: obj = getattr(obj, field) except Exception,e : - logger.notifyChannel('Workflow', netsvc.LOG_ERROR, 'Failed to parse : %s' % (field)) + logger.exception('Failed to parse: %s',field) return obj def get_mobile(self, cr, uid, action, context): - logger = netsvc.Logger() + logger = logging.getLogger('Workflow') obj_pool = self.pool.get(action.model_id.model) id = context.get('active_id') obj = obj_pool.browse(cr, uid, id) @@ -511,14 +516,13 @@ class actions_server(osv.osv): try: obj = getattr(obj, field) except Exception,e : - logger.notifyChannel('Workflow', netsvc.LOG_ERROR, 'Failed to parse : %s' % (field)) + logger.exception('Failed to parse: %s', field) return obj def merge_message(self, cr, uid, keystr, action, context=None): if context is None: context = {} - logger = netsvc.Logger() def merge(match): obj_pool = self.pool.get(action.model_id.model) id = context.get('active_id') @@ -548,7 +552,7 @@ class actions_server(osv.osv): # FIXME: refactor all the eval() calls in run()! def run(self, cr, uid, ids, context=None): - logger = netsvc.Logger() + logger = logging.getLogger(self._name) if context is None: context = {} for action in self.browse(cr, uid, ids, context): @@ -596,18 +600,19 @@ class actions_server(osv.osv): pass if not address: - logger.notifyChannel('email', netsvc.LOG_INFO, 'Partner Email address not Specified!') + logger.info('Partner Email address not Specified!') continue if not user: + logger.info('Email-From address not Specified at server!') raise osv.except_osv(_('Error'), _("Please specify server option --email-from !")) subject = self.merge_message(cr, uid, action.subject, action, context) body = self.merge_message(cr, uid, action.message, action, context) if tools.email_send(user, [address], subject, body, debug=False, subtype='html') == True: - logger.notifyChannel('email', netsvc.LOG_INFO, 'Email successfully send to : %s' % (address)) + logger.info('Email successfully send to: %s', address) else: - logger.notifyChannel('email', netsvc.LOG_ERROR, 'Failed to send email to : %s' % (address)) + logger.warning('Failed to send email to: %s', address) if action.state == 'trigger': wf_service = netsvc.LocalService("workflow") @@ -621,7 +626,7 @@ class actions_server(osv.osv): #TODO: set the user and password from the system # for the sms gateway user / password # USE smsclient module from extra-addons - logger.notifyChannel('sms', netsvc.LOG_ERROR, 'SMS Facility has not been implemented yet. Use smsclient module!') + logger.warning('SMS Facility has not been implemented yet. Use smsclient module!') if action.state == 'other': res = [] From ddb331f9442faa1cd8670e7267af46bce846be72 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:55:06 +0200 Subject: [PATCH 21/75] res_users: When deleting a group with users, have a more detailed exc. bzr revid: p_christ@hol.gr-20101123135506-qr5aumaw8bwoedfd --- bin/addons/base/res/res_user.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py index 6f9cdad2518..d5c9f3cc697 100644 --- a/bin/addons/base/res/res_user.py +++ b/bin/addons/base/res/res_user.py @@ -512,9 +512,24 @@ class groups2(osv.osv): ##FIXME: Is there a reason to inherit this object ? } def unlink(self, cr, uid, ids, context=None): + group_users = [] for record in self.read(cr, uid, ids, ['users'], context=context): if record['users']: + group_users.extend(record['users']) + + if group_users: + group_names = [] + try: + for rec in self.pool.get('res.users').read(cr, uid, group_users, ['name'], context=context): + group_names.append(rec['name']) + if len(group_names) >=5: + group_names = group_names[:5] + group_names += '...' + except Exception: raise osv.except_osv(_('Warning !'), _('Make sure you have no users linked with the group(s)!')) + raise osv.except_osv(_('Warning !'), + _('Group(s) cannot be deleted, because user(s) %s participate in them!') % \ + ', '.join(group_names)) return super(groups2, self).unlink(cr, uid, ids, context=context) groups2() From 6e13dcb8a2d71dcdfbbb0d686f4c2dc2a48ada33 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:56:54 +0200 Subject: [PATCH 22/75] orm: Return numeric uid at perm_read() when no access to res.user If a res.user belongs to a company_id that the current user has no access to, perm_read() wouldn't be able to return results (but exception). Now, just skip the uid->login resolution and return the numeric value instead. bzr revid: p_christ@hol.gr-20101123135654-bslxfv93p10216d3 --- bin/osv/orm.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index f0678d91903..c4d5ad1de09 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -3206,9 +3206,11 @@ class orm(orm_template): for r in res: for key in r: r[key] = r[key] or False - if details and key in ('write_uid', 'create_uid'): - if r[key]: + if details and key in ('write_uid', 'create_uid') and r[key]: + try: r[key] = self.pool.get('res.users').name_get(cr, user, [r[key]])[0] + except Exception: + pass # Leave the numeric uid there r['xmlid'] = ("%(module)s.%(name)s" % r) if r['name'] else False del r['name'], r['module'] if uniq: From 962a4cce702bc69924cd09702690b8df6bfd6ef0 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:57:28 +0200 Subject: [PATCH 23/75] orm: revert e940ece64169da4f, read() must return empty list! bzr revid: p_christ@hol.gr-20101123135728-rqoec2b92roln4up --- bin/osv/orm.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index c4d5ad1de09..a0473c14e8d 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -3055,21 +3055,6 @@ class orm(orm_template): else: res = map(lambda x: {'id': x}, ids) -# if not res: -# res = map(lambda x: {'id': x}, ids) -# for record in res: -# for f in fields_to_read: -# field_val = False -# if f in self._columns.keys(): -# ftype = self._columns[f]._type -# elif f in self._inherit_fields.keys(): -# ftype = self._inherit_fields[f][2]._type -# else: -# continue -# if ftype in ('one2many', 'many2many'): -# field_val = [] -# record.update({f:field_val}) - for f in fields_pre: if f == self.CONCURRENCY_CHECK_FIELD: continue From 6cd5fec0e295d7b25f6ee66ae4b7ab8bddf25306 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:57:42 +0200 Subject: [PATCH 24/75] sql_db: Avoid using inspect.stack(), use failsafe alternative. Inspecting the full stack just to learn the file:line info for debugging purposes is both expensive and error-prone. Write an alternative function in tools/func.py that will just go N frames back and try to get the info. bzr revid: p_christ@hol.gr-20101123135742-2jp75f3uxyg3a6b6 --- bin/sql_db.py | 6 +++--- bin/tools/func.py | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/bin/sql_db.py b/bin/sql_db.py index 6c2c17beeb3..cdc3d0de4b3 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -51,11 +51,11 @@ psycopg2.extensions.register_type(psycopg2.extensions.new_type((700, 701, 1700,) import tools -from tools.func import wraps +from tools.func import wraps, frame_codeinfo from datetime import datetime as mdt from datetime import timedelta import threading -from inspect import stack +from inspect import currentframe import re re_from = re.compile('.* from "?([a-zA-Z_0-9]+)"? .*$'); @@ -93,7 +93,7 @@ class Cursor(object): self._obj = self._cnx.cursor(cursor_factory=psycopg1cursor) self.__closed = False # real initialisation value self.autocommit(False) - self.__caller = tuple(stack()[2][1:3]) + self.__caller = frame_codeinfo(currentframe(),2) def __del__(self): if not self.__closed: diff --git a/bin/tools/func.py b/bin/tools/func.py index 8cedf6715fd..4150b3e688b 100644 --- a/bin/tools/func.py +++ b/bin/tools/func.py @@ -93,3 +93,24 @@ def synchronized(lock_attr='_lock'): return decorator + +from inspect import getsourcefile + +def frame_codeinfo(fframe, back=0): + """ Return a (filename, line) pair for a previous frame . + @return (filename, lineno) where lineno is either int or string=='' + """ + + try: + if not fframe: + return ("", '') + for i in range(back): + fframe = fframe.f_back + try: + fname = getsourcefile(fframe) + except TypeError: + fname = '' + lineno = fframe.f_lineno or '' + return (fname, lineno) + except Exception: + return ("", '') From ba47847b20f719016cb1f23348bc002dadbb304c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:57:48 +0200 Subject: [PATCH 25/75] ORM: moved columns should carry the old name, not the table When a column changes type in module upgrades, we have code to rename the column. The new name shall be based on the old column name, not the name of the table. bzr revid: p_christ@hol.gr-20101123135748-z8rtmy36c8u972xb --- 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 a0473c14e8d..4dba5902739 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -2604,7 +2604,7 @@ class orm(orm_template): if not ok: i = 0 while True: - newname = self._table + '_moved' + str(i) + newname = k + '_moved' + str(i) cr.execute("SELECT count(1) FROM pg_class c,pg_attribute a " \ "WHERE c.relname=%s " \ "AND a.attname=%s " \ From 81a1a310fc5e65cc9cc1db7fd773b6e48cfaa81c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:57:48 +0200 Subject: [PATCH 26/75] addons-init: better messages when an ir.model is missing Suppress the msgs "no access rules", but add an explicit message that the model is not there. bzr revid: p_christ@hol.gr-20101123135748-y6t35206fy4tq44c --- bin/addons/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 0b5c6dc09c5..9fedd3bbdf4 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -857,7 +857,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""") for (model, name) in cr.fetchall(): model_obj = pool.get(model) - if not isinstance(model_obj, osv.osv.osv_memory): + if model_obj and not isinstance(model_obj, osv.osv.osv_memory): logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name)) # Temporary warning while we remove access rights on osv_memory objects, as they have @@ -873,6 +873,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False): obj = pool.get(model) if obj: obj._check_removed_columns(cr, log=True) + else: + logger.warning("Model %s is referenced but not present in the orm pool!", model) if report.get_report(): logger.notifyChannel('init', netsvc.LOG_INFO, report) From c67f1746f4f79dc0e4215325b2a15c2d990bf7c3 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:58:35 +0200 Subject: [PATCH 27/75] translate: port to pythonic logging, filename in a message It is good to know which .po file we are currently loading. Warning: some messages moved from the 'db' logger to the 'i18n' one. bzr revid: p_christ@hol.gr-20101123135835-k6d6r7sgif5zhb75 --- bin/tools/translate.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index 686f9677b43..f32e6bfb0e7 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -27,6 +27,7 @@ import itertools import locale import os import re +import logging import tarfile import tempfile from os.path import join @@ -220,11 +221,11 @@ def unquote(str): # class to handle po files class TinyPoFile(object): def __init__(self, buffer): - self.logger = netsvc.Logger() + self.logger = logging.getLogger('i18n') self.buffer = buffer def warn(self, msg): - self.logger.notifyChannel("i18n", netsvc.LOG_WARNING, msg) + self.logger.warning(msg) def __iter__(self): self.buffer.seek(0) @@ -483,7 +484,7 @@ def in_modules(object_name, modules): return module in modules def trans_generate(lang, modules, dbname=None): - logger = netsvc.Logger() + logger = logging.getLogger('i18n') if not dbname: dbname=tools.config['db_name'] if not modules: @@ -534,12 +535,12 @@ def trans_generate(lang, modules, dbname=None): xml_name = "%s.%s" % (module, encode(xml_name)) if not pool.get(model): - logger.notifyChannel("db", netsvc.LOG_ERROR, "Unable to find object %r" % (model,)) + logger.error("Unable to find object %r", model) continue exists = pool.get(model).exists(cr, uid, res_id) if not exists: - logger.notifyChannel("db", netsvc.LOG_WARNING, "Unable to find object %r with id %d" % (model, res_id)) + logger.warning("Unable to find object %r with id %d", model, res_id) continue obj = pool.get(model).browse(cr, uid, res_id) @@ -566,7 +567,7 @@ def trans_generate(lang, modules, dbname=None): # export fields if not result.has_key('fields'): - logger.notifyChannel("db",netsvc.LOG_WARNING,"res has no fields: %r" % result) + logger.warning("res has no fields: %r", result) continue for field_name, field_def in result['fields'].iteritems(): res_name = name + ',' + field_name @@ -595,7 +596,7 @@ def trans_generate(lang, modules, dbname=None): try: field_name = encode(obj.name) except AttributeError, exc: - logger.notifyChannel("db", netsvc.LOG_ERROR, "name error in %s: %s" % (xml_name,str(exc))) + logger.error("name error in %s: %s", xml_name, str(exc)) continue objmodel = pool.get(obj.model) if not objmodel or not field_name in objmodel._columns: @@ -643,7 +644,7 @@ def trans_generate(lang, modules, dbname=None): for t in parse_func(d.iter()): push_translation(module, report_type, name, 0, t) except (IOError, etree.XMLSyntaxError): - logging.getLogger("i18n").exception("couldn't export translation for report %s %s %s", name, report_type, fname) + logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname) for field_name,field_def in obj._table._columns.items(): if field_def.translate: @@ -772,22 +773,23 @@ def trans_generate(lang, modules, dbname=None): return out def trans_load(db_name, filename, lang, strict=False, verbose=True, context={}): - logger = netsvc.Logger() + logger = logging.getLogger('i18n') try: fileobj = open(filename,'r') + logger.info("loading %s", filename) fileformat = os.path.splitext(filename)[-1][1:].lower() r = trans_load_data(db_name, fileobj, fileformat, lang, strict=strict, verbose=verbose, context=context) fileobj.close() return r except IOError: if verbose: - logger.notifyChannel("i18n", netsvc.LOG_ERROR, "couldn't read translation file %s" % (filename,)) + logger.error("couldn't read translation file %s", filename) return None def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name=None, verbose=True, context={}): - logger = netsvc.Logger() + logger = logging.getLogger('i18n') if verbose: - logger.notifyChannel("i18n", netsvc.LOG_INFO, 'loading translation file for language %s' % (lang)) + logger.info('loading translation file for language %s', lang) pool = pooler.get_pool(db_name) lang_obj = pool.get('res.lang') trans_obj = pool.get('ir.translation') @@ -811,7 +813,7 @@ def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name= if fail: lc = locale.getdefaultlocale()[0] msg = 'Unable to get information for locale %s. Information from the default locale (%s) have been used.' - logger.notifyChannel('i18n', netsvc.LOG_WARNING, msg % (lang, lc)) + logger.warning(msg, lang, lc) if not lang_name: lang_name = tools.get_languages().get(lang, lang) @@ -850,6 +852,7 @@ def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name= reader = TinyPoFile(fileobj) f = ['type', 'name', 'res_id', 'src', 'value'] else: + logger.error('Bad file format: %s', fileformat) raise Exception(_('Bad file format')) # read the rest of the file @@ -928,11 +931,10 @@ def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name= cr.commit() cr.close() if verbose: - logger.notifyChannel("i18n", netsvc.LOG_INFO, - "translation file loaded succesfully") + logger.info("translation file loaded succesfully") except IOError: filename = '[lang: %s][format: %s]' % (iso_lang or 'new', fileformat) - logger.notifyChannel("i18n", netsvc.LOG_ERROR, "couldn't read translation file %s" % (filename,)) + logger.exception("couldn't read translation file %s", filename) def get_locales(lang=None): if lang is None: From 7d3afa7c0c90bd5d312db0cc977cafa6bd82e73c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:58:37 +0200 Subject: [PATCH 28/75] sql_db: Better exceptions. A closed cursor should behave the same as a closed connection, ie. OperationalError. Also instrument a function we don't expect to be called. bzr revid: p_christ@hol.gr-20101123135837-vkn3ph0e4bz9urf0 --- bin/sql_db.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/sql_db.py b/bin/sql_db.py index cdc3d0de4b3..89f5cb01c27 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -28,6 +28,7 @@ from psycopg2.psycopg1 import cursor as psycopg1cursor from psycopg2.pool import PoolError import psycopg2.extensions +import warnings psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) @@ -71,7 +72,7 @@ class Cursor(object): @wraps(f) def wrapper(self, *args, **kwargs): if self.__closed: - raise psycopg2.ProgrammingError('Unable to use the cursor after having closed it') + raise psycopg2.OperationalError('Unable to use the cursor after having closed it') return f(self, *args, **kwargs) return wrapper @@ -337,10 +338,12 @@ class Connection(object): def __nonzero__(self): """Check if connection is possible""" try: + warnings.warn("You use an expensive function to test a connection.", + DeprecationWarning, stacklevel=1) cr = self.cursor() cr.close() return True - except: + except Exception: return False From e5bc5503d71cdb7e3aa96f7590fdbbe5ca96dd3d Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:58:44 +0200 Subject: [PATCH 29/75] setup: The ssl certs should *not* be distributed. The meaning of any SSL certificate (even self-signed) is that it uniquely identifies the server. So, if we have a generic cert distributed with our packaging, we break that. We could not even generate a cert at the "build" stage of our server, because that would be included in the packages. If anybody needs to run OpenERP with SSL, they will need to generate the certificate at the target server, possibly using ssl-cert.cfg as a sample. Also, the "ssl" directory under bin/ would confuse some pythonic code that had tried to "import ssl" (eg. urllib.py). bzr revid: p_christ@hol.gr-20101123135844-nr8k78qrmlyn19xb --- bin/server.cert | 14 -------------- bin/server.pkey | 15 --------------- setup.py | 8 ++------ bin/ssl/cert.cfg => ssl-cert.cfg | 8 ++++---- 4 files changed, 6 insertions(+), 39 deletions(-) delete mode 100644 bin/server.cert delete mode 100644 bin/server.pkey rename bin/ssl/cert.cfg => ssl-cert.cfg (96%) diff --git a/bin/server.cert b/bin/server.cert deleted file mode 100644 index de04f1ca33d..00000000000 --- a/bin/server.cert +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICNDCCAZ0CAQEwDQYJKoZIhvcNAQEEBQAweTEQMA4GA1UEChMHVGlueUVSUDEM -MAoGA1UECxMDRVJQMRkwFwYJKoZIhvcNAQkBFgpmcEB0aW55LmJlMRAwDgYDVQQH -EwdXYWxoYWluMQswCQYDVQQIEwJCVzELMAkGA1UEBhMCQkUxEDAOBgNVBAMTB1Rp -bnlFUlAwHhcNMDYwNTI0MDgzODUxWhcNMDcwNTI0MDgzODUxWjBMMQswCQYDVQQG -EwJCRTELMAkGA1UECBMCQlcxEDAOBgNVBAoTB1RpbnlFUlAxDDAKBgNVBAsTA0VS -UDEQMA4GA1UEAxMHVGlueUVSUDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA -xzIDlU2PrczPsgXtxCskYxuwMPgNCNSCBfWsUZ9nJzlZfRAEXEq4LxaTPIgkzkIF -82bmJLgFz6/CyCFid4mkBLQBj30Opp2Vco39WRncNKHKxbk+/wZpZtQ0bSpvf+F4 -MBqCLldYIqsoyenombVCb8X62IUu0ENF1wR22owvyKcCAwEAATANBgkqhkiG9w0B -AQQFAAOBgQB2yUqJ3gbQ8I6rcmaVJlcLDHfC5w1Jr1cUzcJevOPh3wygSZYYoUoe -yeYlzEag/DpPSHyRiJJVOKdiwU0yfmZPhfDNtDiBr47bz8qzIsYq5VeMmSeXrq/f -AA3iI4xE8YFzJHWtiBCqqyUok+j9pVad7iV7+UVIePHZLEkGGWIjDA== ------END CERTIFICATE----- diff --git a/bin/server.pkey b/bin/server.pkey deleted file mode 100644 index 1b1b7d63942..00000000000 --- a/bin/server.pkey +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDHMgOVTY+tzM+yBe3EKyRjG7Aw+A0I1IIF9axRn2cnOVl9EARc -SrgvFpM8iCTOQgXzZuYkuAXPr8LIIWJ3iaQEtAGPfQ6mnZVyjf1ZGdw0ocrFuT7/ -Bmlm1DRtKm9/4XgwGoIuV1giqyjJ6eiZtUJvxfrYhS7QQ0XXBHbajC/IpwIDAQAB -AoGAVwAxMHS/3FkoHckZICT3r5HYUosEpmaqo4+5w6yrkSYrP8RPI0A/UdG6XSXZ -bXzIvJakzkTRxPQvTtnF+A/V4rF9hxwB8cGXSywv5eDGmZ91qIsxY7Sv99VqSKNH -dNr9aZHloTvI51e/oramIJ/O3A+TbAS5i+u1DJC2IIFJcAECQQD8iRPTlPIqzjYD -Lg7KYGvwW9TE4ONAhC86kJbzV5o3amlV5duJgnkl/mNlfN1ihA7f3Gx9dfCjfRKp -V1rcjtCBAkEAye2aMw2v1m+MEqcPxyTUzVf5Y8BIXWbk15T43czXec9YclZSOBCX -Dgv4a3Fk+yxQUE0cZUH0U4FJq6mTgpuFJwJASFqZ9KATNlJ4xTZ4BGHV6zrUXkg0 -tDJrObNdnID37XKulW7TFLXuMgWNwvEgmO5POLJ13whglubp5tzhapn8gQJAJz9Z -U0b7wFAaB54VAP31ppvMy0iaSB0xqX05CdNAplpYtJB2lpMS6RYGiMuXdwJb8d+q -/ztcg8aDTSw+kYoszQJBAPBrt694VkGT1k9Be6e5wyVDrE05bkHhFxPk/HMeWMDX -sZqHPs9vVaLBqu/uU84FdwRMOV71RG90g6eUEl7HWsg= ------END RSA PRIVATE KEY----- diff --git a/setup.py b/setup.py index c1b348503a5..894c7f7c11e 100755 --- a/setup.py +++ b/setup.py @@ -103,9 +103,7 @@ def data_files(): files.append((root, [join(root, name) for name in names])) #for root, _, names in os.walk('pixmaps'): # files.append((root, [join(root, name) for name in names])) - files.append(('.', [join('bin', 'import_xml.rng'), - join('bin', 'server.pkey'), - join('bin', 'server.cert')])) + files.append(('.', [join('bin', 'import_xml.rng'),])) else: man_directory = join('share', 'man') files.append((join(man_directory, 'man1'), ['man/openerp-server.1'])) @@ -120,9 +118,7 @@ def data_files(): openerp_site_packages = join(get_python_lib(prefix=''), 'openerp-server') - files.append((openerp_site_packages, [join('bin', 'import_xml.rng'), - join('bin', 'server.pkey'), - join('bin', 'server.cert')])) + files.append((openerp_site_packages, [join('bin', 'import_xml.rng'),])) if sys.version_info[0:2] == (2,5): files.append((openerp_site_packages, [ join('python25-compat','BaseHTTPServer.py'), diff --git a/bin/ssl/cert.cfg b/ssl-cert.cfg similarity index 96% rename from bin/ssl/cert.cfg rename to ssl-cert.cfg index 8cab1eeb4ca..08ca7b5757e 100644 --- a/bin/ssl/cert.cfg +++ b/ssl-cert.cfg @@ -3,19 +3,19 @@ # DN options # The organization of the subject. -organization = "Acme inc." +organization = "Some organization." # The organizational unit of the subject. -unit = "dept." +unit = "ERP dept." # The locality of the subject. # locality = # The state of the certificate owner. -state = "Attiki" +state = "State" # The country of the subject. Two letter code. -country = GR +country = BE # The common name of the certificate owner. cn = "Some company" From fe35e2517b2639fc494454dbc293763be290ad78 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:58:49 +0200 Subject: [PATCH 30/75] Move the client scripts into tools/ Just a cleanup of the root dir. bzr revid: p_christ@hol.gr-20101123135849-z6bojjykt7r0dgx1 --- change-loglevel.sh => tools/change-loglevel.sh | 0 get-srvstats.sh => tools/get-srvstats.sh | 0 list-services.sh => tools/list-services.sh | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename change-loglevel.sh => tools/change-loglevel.sh (100%) rename get-srvstats.sh => tools/get-srvstats.sh (100%) rename list-services.sh => tools/list-services.sh (100%) diff --git a/change-loglevel.sh b/tools/change-loglevel.sh similarity index 100% rename from change-loglevel.sh rename to tools/change-loglevel.sh diff --git a/get-srvstats.sh b/tools/get-srvstats.sh similarity index 100% rename from get-srvstats.sh rename to tools/get-srvstats.sh diff --git a/list-services.sh b/tools/list-services.sh similarity index 100% rename from list-services.sh rename to tools/list-services.sh From 8e6c11a16c0d962d2e3f0bc1db63a6a77919ad46 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:58:56 +0200 Subject: [PATCH 31/75] ir.module: use pythonic logging, addons api bzr revid: p_christ@hol.gr-20101123135856-0px463nz9xei8ho4 --- bin/addons/base/module/module.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index f392ee51326..36c98deafc3 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -27,7 +27,6 @@ import urllib import zipimport import addons -import netsvc import pooler import release import tools @@ -447,7 +446,7 @@ class module(osv.osv): self.write(cr, uid, [id], {'category_id': p_id}) def update_translations(self, cr, uid, ids, filter_lang=None, context={}): - logger = netsvc.Logger() + logger = logging.getLogger('i18n') if not filter_lang: pool = pooler.get_pool(cr.dbname) lang_obj = pool.get('res.lang') @@ -467,13 +466,18 @@ class module(osv.osv): if len(lang) > 5: raise osv.except_osv(_('Error'), _('You Can Not Load Translation For language Due To Invalid Language/Country Code')) iso_lang = tools.get_iso_codes(lang) - f = os.path.join(modpath, 'i18n', iso_lang + '.po') - if not os.path.exists(f) and iso_lang.find('_') != -1: - f = os.path.join(modpath, 'i18n', iso_lang.split('_')[0] + '.po') + f = addons.get_module_resource(mod.name, 'i18n', iso_lang + '.po') + # Implementation notice: we must first search for the full name of + # the language derivative, like "en_UK", and then the generic, + # like "en". + if (not os.path.exists(f)) and '_' in iso_lang: + f = addons.get_module_resource(mod.name, 'i18n', iso_lang.split('_')[0] + '.po') iso_lang = iso_lang.split('_')[0] if os.path.exists(f): - logger.notifyChannel("i18n", netsvc.LOG_INFO, 'module %s: loading translation file for language %s' % (mod.name, iso_lang)) + logger.info('module %s: loading translation file for language %s', mod.name, iso_lang) tools.trans_load(cr.dbname, f, lang, verbose=False, context=context) + else: + logger.warning('module %s: no translation for language %s', mod.name, iso_lang) def check(self, cr, uid, ids, context=None): logger = logging.getLogger('init') From 7ce517908686e0107499360ce05e45399637e2f8 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:58:58 +0200 Subject: [PATCH 32/75] translate: don't complain about fuzzy strings. They are part of the format and ought to be silently discarded. bzr revid: p_christ@hol.gr-20101123135858-zkp7hjcn4slv3r57 --- bin/tools/translate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index f32e6bfb0e7..5fcae3dd01a 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -317,7 +317,9 @@ class TinyPoFile(object): self.first = False if name is None: - self.warn('Missing "#:" formated comment for the following source:\n\t%s' % (source,)) + if not fuzzy: + self.warn('Missing "#:" formated comment at line %d for the following source:\n\t%s', + self.cur_line(), source[:30]) return self.next() return type, name, res_id, source, trad From 53c306cfe019249a0409a47fe416825f54439663 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 15:58:58 +0200 Subject: [PATCH 33/75] websrv_lib: allow "noconnection" object to carry the socket name Needed for WebDAV, which wants to know the server's endpoint address. bzr revid: p_christ@hol.gr-20101123135858-qycopdfqsll1fy38 --- bin/service/websrv_lib.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index e9b1b770c63..58bc6508245 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -136,15 +136,25 @@ class HTTPDir: return self.path return False -class noconnection: +class noconnection(object): """ a class to use instead of the real connection """ + def __init__(self, realsocket=None): + self.__hidden_socket = realsocket + def makefile(self, mode, bufsize): return None def close(self): pass + def getsockname(self): + """ We need to return info about the real socket that is used for the request + """ + if not self.__hidden_socket: + raise AttributeError("No-connection class cannot tell real socket") + return self.__hidden_socket.getsockname() + class dummyconn: def shutdown(self, tru): pass @@ -396,7 +406,7 @@ class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler): npath = '/' + npath if not self.in_handlers.has_key(p): - self.in_handlers[p] = vdir.handler(noconnection(),self.client_address,self.server) + self.in_handlers[p] = vdir.handler(noconnection(self.request),self.client_address,self.server) if vdir.auth_provider: vdir.auth_provider.setupAuth(self, self.in_handlers[p]) hnd = self.in_handlers[p] From 2f2ed1d13809310f25bd258146fa5a7b1f04081f Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:09:03 +0200 Subject: [PATCH 34/75] rml2pdf: fix logging of exceptions. exception() cannot be called without a string, yet it is even better to demote those logs to warnings. Conflicts: bin/report/render/rml2pdf/utils.py bzr revid: p_christ@hol.gr-20101123150903-zgiyob943ivcftkq --- bin/report/render/rml2pdf/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/report/render/rml2pdf/utils.py b/bin/report/render/rml2pdf/utils.py index 947526fc61b..076c8405118 100644 --- a/bin/report/render/rml2pdf/utils.py +++ b/bin/report/render/rml2pdf/utils.py @@ -66,7 +66,7 @@ def _child_get(node, self=None, tagname=None): except GeneratorExit: continue except Exception, e: - logging.getLogger('report').exception(e) + logging.getLogger('report').warning('rml_except: "%s"',n.get('rml_except',''), exc_info=True) continue if n.get('rml_tag'): try: @@ -78,7 +78,7 @@ def _child_get(node, self=None, tagname=None): except GeneratorExit: yield n except Exception, e: - logging.getLogger('report').exception(e) + logging.getLogger('report').warning('rml_tag: "%s"',n.get('rml_tag',''), exc_info=True) yield n else: yield n @@ -89,7 +89,7 @@ def _child_get(node, self=None, tagname=None): except GeneratorExit: continue except Exception, e: - logging.getLogger('report').exception(e) + logging.getLogger('report').warning('rml_except: "%s"',n.get('rml_except',''), exc_info=True) continue if self and self.localcontext and n.get('rml_tag'): try: @@ -102,7 +102,7 @@ def _child_get(node, self=None, tagname=None): except GeneratorExit: pass except Exception, e: - logging.getLogger('report').exception(e) + logging.getLogger('report').warning('rml_tag: "%s"',n.get('rml_tag',''), exc_info=True) pass if (tagname is None) or (n.tag==tagname): yield n From d1347d0b0d62c13375232d1defd4b2cff6d87acd Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:09:45 +0200 Subject: [PATCH 35/75] Fix rml2pdf custom ttf fonts. The attempt to put checks in that code had broken it. bzr revid: p_christ@hol.gr-20101123150945-012xrvhz3ya2watl --- bin/report/render/rml2pdf/customfonts.py | 59 ++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/bin/report/render/rml2pdf/customfonts.py b/bin/report/render/rml2pdf/customfonts.py index 7041541cd55..96ccc759ce1 100644 --- a/bin/report/render/rml2pdf/customfonts.py +++ b/bin/report/render/rml2pdf/customfonts.py @@ -22,6 +22,13 @@ from reportlab import rl_config import os +"""This module allows the mapping of some system-available TTF fonts to +the reportlab engine. + +This file could be customized per distro (although most Linux/Unix ones) +should have the same filenames, only need the code below). +""" + CustomTTFonts = [ ('Helvetica',"DejaVu Sans", "DejaVuSans.ttf", 'normal'), ('Helvetica',"DejaVu Sans Bold", "DejaVuSans-Bold.ttf", 'bold'), ('Helvetica',"DejaVu Sans Oblique", "DejaVuSans-Oblique.ttf", 'italic'), @@ -43,23 +50,49 @@ CustomTTFonts = [ ('Helvetica',"DejaVu Sans", "DejaVuSans.ttf", 'normal'), ('Courier',"FreeMono Oblique", "FreeMonoOblique.ttf", 'italic'), ('Courier',"FreeMono BoldOblique", "FreeMonoBoldOblique.ttf", 'bolditalic'),] -def SearchFontPath(font_file): +__foundFonts = [] + +def FindCustomFonts(): + """Fill the __foundFonts list with those filenames, whose fonts + can be found in the reportlab ttf font path. + + This process needs only be done once per loading of this module, + it is cached. But, if the system admin adds some font in the + meanwhile, the server must be restarted eventually. + """ + dirpath = [] + global __foundFonts for dirname in rl_config.TTFSearchPath: - for root, dirs, files in os.walk(os.path.abspath(dirname)): - for file_name in files: - filename = os.path.join(root, file_name) - extension = os.path.splitext(filename)[1] - if extension.lower() in ['.ttf']: - if file_name==font_file: - return True - return False + abp = os.path.abspath(dirname) + if os.path.isdir(abp): + dirpath.append(abp) + + for k, (name, font, fname, mode) in enumerate(CustomTTFonts): + if fname in __foundFonts: + continue + for d in dirpath: + if os.path.exists(os.path.join(d, fname)): + print "found font %s in %s" % (fname, d) + __foundFonts.append(fname) + break + + # print "Found fonts:", __foundFonts + def SetCustomFonts(rmldoc): + """ Map some font names to the corresponding TTF fonts + + The ttf font may not even have the same name, as in + Times -> Liberation Serif. + This function is called once per report, so it should + avoid system-wide processing (cache it, instead). + """ + if not len(__foundFonts): + FindCustomFonts() + global __foundFonts for name, font, fname, mode in CustomTTFonts: - if SearchFontPath(fname): - rmldoc.setTTFontMapping(name, font,filename, mode) + if fname in __foundFonts: + rmldoc.setTTFontMapping(name, font, fname, mode) return True - - #eof From 27ae6f3522474b2093f4cb4a8a046d67cdb04ffa Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:11:10 +0200 Subject: [PATCH 36/75] report/render: fixes from xrg branch This should log the rendering exceptions for reports. Also fix am error at custom fonts, suppress a message. Conflicts: bin/report/render/rml2pdf/utils.py bzr revid: p_christ@hol.gr-20101123151110-bckon1ji7hul20mp --- bin/report/render/rml.py | 2 +- bin/report/render/rml2pdf/customfonts.py | 4 ++-- bin/report/render/rml2pdf/utils.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/report/render/rml.py b/bin/report/render/rml.py index c30549b4bb0..0bc78f95555 100644 --- a/bin/report/render/rml.py +++ b/bin/report/render/rml.py @@ -55,7 +55,7 @@ class rml2txt(render.render): def __init__(self, rml, localcontext= None, datas={}): super(rml2txt, self).__init__(datas) self.rml = rml - self.localcontext = localcontext + self.localcontext = localcontext self.output_type = 'txt' def _render(self): diff --git a/bin/report/render/rml2pdf/customfonts.py b/bin/report/render/rml2pdf/customfonts.py index 96ccc759ce1..11c35749d4c 100644 --- a/bin/report/render/rml2pdf/customfonts.py +++ b/bin/report/render/rml2pdf/customfonts.py @@ -72,7 +72,7 @@ def FindCustomFonts(): continue for d in dirpath: if os.path.exists(os.path.join(d, fname)): - print "found font %s in %s" % (fname, d) + # print "found font %s in %s" % (fname, d) __foundFonts.append(fname) break @@ -87,9 +87,9 @@ def SetCustomFonts(rmldoc): This function is called once per report, so it should avoid system-wide processing (cache it, instead). """ + global __foundFonts if not len(__foundFonts): FindCustomFonts() - global __foundFonts for name, font, fname, mode in CustomTTFonts: if fname in __foundFonts: rmldoc.setTTFontMapping(name, font, fname, mode) diff --git a/bin/report/render/rml2pdf/utils.py b/bin/report/render/rml2pdf/utils.py index 076c8405118..e607aabeb20 100644 --- a/bin/report/render/rml2pdf/utils.py +++ b/bin/report/render/rml2pdf/utils.py @@ -38,6 +38,7 @@ import copy import locale + import logging import re import reportlab From 03b4e87ccaa474025481e523ecf9659b7a1aa9a2 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:11:24 +0200 Subject: [PATCH 37/75] report customFonts: have debugging logs for fonts mapped. Since this is platform-dependant, it is good to be able to debug if that algorithm worked and located our TTF fonts. bzr revid: p_christ@hol.gr-20101123151124-wk5r5fac00kpu3f4 --- bin/report/render/rml2pdf/customfonts.py | 4 +++- bin/report/render/rml2pdf/trml2pdf.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/report/render/rml2pdf/customfonts.py b/bin/report/render/rml2pdf/customfonts.py index 11c35749d4c..ad97fec756b 100644 --- a/bin/report/render/rml2pdf/customfonts.py +++ b/bin/report/render/rml2pdf/customfonts.py @@ -21,6 +21,7 @@ from reportlab import rl_config import os +import logging """This module allows the mapping of some system-available TTF fonts to the reportlab engine. @@ -61,6 +62,7 @@ def FindCustomFonts(): meanwhile, the server must be restarted eventually. """ dirpath = [] + log = logging.getLogger('report.fonts') global __foundFonts for dirname in rl_config.TTFSearchPath: abp = os.path.abspath(dirname) @@ -72,7 +74,7 @@ def FindCustomFonts(): continue for d in dirpath: if os.path.exists(os.path.join(d, fname)): - # print "found font %s in %s" % (fname, d) + log.debug("Found font %s in %s as %s", fname, d, name) __foundFonts.append(fname) break diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index e9969fa4e52..bfb53fa2a16 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -31,6 +31,7 @@ import cStringIO import utils import color import os +import logging from lxml import etree import base64 from reportlab.platypus.doctemplate import ActionFlowable @@ -909,7 +910,11 @@ def parseNode(rml, localcontext = {},fout=None, images={}, path='.',title=None): try: from customfonts import SetCustomFonts SetCustomFonts(r) + except ImportError: + # means there is no custom fonts mapping in this system. + pass except Exception: + logging.getLogger('report').warning('Cannot set font mapping', exc_info=True) pass fp = cStringIO.StringIO() r.render(fp) From 1a5e3cf073a706957881e1e3a6022c0d74e9f188 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:11:31 +0200 Subject: [PATCH 38/75] pdf customfonts: allow absolute paths in registered fonts bzr revid: p_christ@hol.gr-20101123151131-v0v1qfkiajh0cw73 --- bin/report/render/rml2pdf/customfonts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/report/render/rml2pdf/customfonts.py b/bin/report/render/rml2pdf/customfonts.py index ad97fec756b..e4f15aa8d8b 100644 --- a/bin/report/render/rml2pdf/customfonts.py +++ b/bin/report/render/rml2pdf/customfonts.py @@ -93,7 +93,7 @@ def SetCustomFonts(rmldoc): if not len(__foundFonts): FindCustomFonts() for name, font, fname, mode in CustomTTFonts: - if fname in __foundFonts: + if os.path.isabs(fname) or fname in __foundFonts: rmldoc.setTTFontMapping(name, font, fname, mode) return True From 792c8eba1935785f38ce0e80be8382c7b24e39f8 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:14:44 +0200 Subject: [PATCH 39/75] trml2pdf: cleaner import of StringIO bzr revid: p_christ@hol.gr-20101123151444-sbnfj0fu1nkl2pkr --- bin/report/render/rml2pdf/trml2pdf.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index bfb53fa2a16..cfbf21e33c8 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -21,13 +21,11 @@ import sys -from StringIO import StringIO import copy import reportlab import re from reportlab.pdfgen import canvas from reportlab import platypus -import cStringIO import utils import color import os @@ -38,6 +36,12 @@ from reportlab.platypus.doctemplate import ActionFlowable from tools.safe_eval import safe_eval as eval from reportlab.lib.units import inch,cm,mm +try: + from cStringIO import StringIO + _hush_pyflakes = [ StringIO ] +except ImportError: + from StringIO import StringIO + encoding = 'utf-8' class NumberedCanvas(canvas.Canvas): @@ -223,7 +227,7 @@ class _rml_doc(object): addMapping(name, 1, 0, name) #bold addMapping(name, 1, 1, name) #italic and bold - def setTTFontMapping(self,face, fontname,filename, mode='all'): + def setTTFontMapping(self,face, fontname, filename, mode='all'): from reportlab.lib.fonts import addMapping from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont @@ -412,7 +416,7 @@ class _rml_canvas(object): if not node.get('file') : if node.get('name'): image_data = self.images[node.get('name')] - s = cStringIO.StringIO(image_data) + s = StringIO(image_data) else: if self.localcontext: res = utils._regex.findall(node.text) @@ -423,19 +427,19 @@ class _rml_canvas(object): if node.text: image_data = base64.decodestring(node.text) if image_data: - s = cStringIO.StringIO(image_data) + s = StringIO(image_data) else: return False else: if node.get('file') in self.images: - s = cStringIO.StringIO(self.images[node.get('file')]) + s = StringIO(self.images[node.get('file')]) else: try: u = urllib.urlopen(str(node.get('file'))) - s = cStringIO.StringIO(u.read()) + s = StringIO(u.read()) except Exception: u = file(os.path.join(self.path,str(node.get('file'))), 'rb') - s = cStringIO.StringIO(u.read()) + s = StringIO(u.read()) img = ImageReader(s) (sx,sy) = img.getSize() @@ -738,7 +742,7 @@ class _rml_flowable(object): node.text = newtext image_data = base64.decodestring(node.text) if not image_data: return False - image = cStringIO.StringIO(image_data) + image = StringIO(image_data) return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height']))) else: return platypus.Image(node.get('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height']))) @@ -916,7 +920,7 @@ def parseNode(rml, localcontext = {},fout=None, images={}, path='.',title=None): except Exception: logging.getLogger('report').warning('Cannot set font mapping', exc_info=True) pass - fp = cStringIO.StringIO() + fp = StringIO() r.render(fp) return fp.getvalue() @@ -937,7 +941,7 @@ def parseString(rml, localcontext = {},fout=None, images={}, path='.',title=None fp.close() return fout else: - fp = cStringIO.StringIO() + fp = StringIO() r.render(fp) return fp.getvalue() From 1a0a2535a2ff6052da73e7d042ffbbc852817ccd Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:17:40 +0200 Subject: [PATCH 40/75] http_server: allow requests to "see" the protocol of the server Needed for WebDAV. Conflicts: bin/service/http_server.py bzr revid: p_christ@hol.gr-20101123151740-ovx7jg8yp3070qqe --- bin/service/http_server.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/service/http_server.py b/bin/service/http_server.py index a06334d51e5..f40e1df5ba8 100644 --- a/bin/service/http_server.py +++ b/bin/service/http_server.py @@ -67,7 +67,7 @@ class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer) _send_traceback_header = False i = 0 - def __init__(self, addr, requestHandler, + def __init__(self, addr, requestHandler, proto='http', logRequests=True, allow_none=False, encoding=None, bind_and_activate=True): self.logRequests = logRequests @@ -75,6 +75,7 @@ class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer) HTTPServer.__init__(self, addr, requestHandler) self.numThreads = 0 + self.proto = proto self.__threadno = 0 # [Bug #1222790] If possible, set close-on-exec flag; if a @@ -143,7 +144,7 @@ class BaseHttpDaemon(threading.Thread, netsvc.Server): self.__interface = interface try: - self.server = ThreadedHTTPServer((interface, port), handler) + self.server = ThreadedHTTPServer((interface, port), handler, proto=self._RealProto) self.server.vdirs = [] self.server.logRequests = True self.server.timeout = self._busywait_timeout @@ -178,7 +179,7 @@ class BaseHttpDaemon(threading.Thread, netsvc.Server): def stats(self): res = "%sd: " % self._RealProto + ((self.running and "running") or "stopped") if self.server: - res += ", %d threads" % (self.server.numThreads,) + res += ", %d threads" % (self.server.numThreads,) return res def append_svc(self, service): From 6e893f1e9e607b3fcf392ae9cbbb4f7c696d4bd3 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:19:30 +0200 Subject: [PATCH 41/75] rml2pdf: use ustr() instead of unicode() When strings are 8-bit utf8-encoded, unicode will break with the usual: UnicodeDecodeError: 'ascii' codec can't decode byte 0xce in position .. message. ustr() will have better luck in those cases. Conflicts: bin/report/render/rml2pdf/utils.py bzr revid: p_christ@hol.gr-20101123151930-8r9q9gg7r902i2nv --- bin/report/render/rml2pdf/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/report/render/rml2pdf/utils.py b/bin/report/render/rml2pdf/utils.py index e607aabeb20..6f8616da96d 100644 --- a/bin/report/render/rml2pdf/utils.py +++ b/bin/report/render/rml2pdf/utils.py @@ -45,6 +45,7 @@ import reportlab import tools from tools.safe_eval import safe_eval as eval +from tools import ustr _regex = re.compile('\[\[(.+?)\]\]') @@ -131,11 +132,11 @@ def _process_text(self, txt): if isinstance(txt, basestring): result += str2xml(txt) elif (txt is not None) and (txt is not False): - result += unicode(txt) + result += ustr(txt) return result def text_get(node): - return ''.join([unicode(n.text) for n in node]) + return ''.join([ustr(n.text) for n in node]) units = [ (re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch), From 3e3270264fb49739144e4fefd6a55182ea9900cc Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:20:02 +0200 Subject: [PATCH 42/75] trml2pdf: Fix using of custom fonts in reports That snippet of code practically meant "don't use anything but the standard fonts (by name) in the report". It obviously wanted to prevent the rml2pdf engine from crashing at a non-existent font. Well, if the report specifies a font that cannot be mapped by the system, it should preferably raise an exception (and ask us to fix the report), not silently ignore the font. Case 1: the internationalized reports, where font name is used to select a Unicode-capable font. Case 2: l10n_ch, where the OCR-B font had to be used (perhaps legal req.) bzr revid: p_christ@hol.gr-20101123152002-es404ul29rsohzqd --- bin/report/render/rml2pdf/trml2pdf.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index cfbf21e33c8..1280f8e612b 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -485,11 +485,7 @@ class _rml_canvas(object): self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'})) def setFont(self, node): - from reportlab.pdfbase import pdfmetrics fname = node.get('name') - #TODO : other fonts should be supported - if fname not in pdfmetrics.standardFonts: - fname = self.canvas._fontname return self.canvas.setFont(fname, utils.unit_get(node.get('size'))) def render(self, node): From b4e5c513112036bb6d6eee7e16eda547ccda80eb Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:20:29 +0200 Subject: [PATCH 43/75] res_user: fix default args, close db cursors. Conflicts: bin/addons/base/res/res_user.py bzr revid: p_christ@hol.gr-20101123152029-c823isjqrpml7dva --- bin/addons/base/res/res_user.py | 74 +++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py index d5c9f3cc697..1a939798934 100644 --- a/bin/addons/base/res/res_user.py +++ b/bin/addons/base/res/res_user.py @@ -46,7 +46,7 @@ class groups(osv.osv): ('name_uniq', 'unique (name)', 'The name of the group must be unique !') ] - def copy(self, cr, uid, id, default=None, context={}): + def copy(self, cr, uid, id, default=None, context=None): group_name = self.read(cr, uid, [id], ['name'])[0]['name'] default.update({'name': _('%s (copy)')%group_name}) return super(groups, self).copy(cr, uid, id, default, context) @@ -83,14 +83,14 @@ class groups(osv.osv): groups() -def _lang_get(self, cr, uid, context={}): +def _lang_get(self, cr, uid, context=None): obj = self.pool.get('res.lang') ids = obj.search(cr, uid, [('translatable','=',True)]) - res = obj.read(cr, uid, ids, ['code', 'name'], context) + res = obj.read(cr, uid, ids, ['code', 'name'], context=context) res = [(r['code'], r['name']) for r in res] return res -def _tz_get(self,cr,uid, context={}): +def _tz_get(self,cr,uid, context=None): return [(x, x) for x in pytz.all_timezones] class users(osv.osv): @@ -373,7 +373,7 @@ class users(osv.osv): ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit) return self.name_get(cr, user, ids) - def copy(self, cr, uid, id, default=None, context={}): + def copy(self, cr, uid, id, default=None, context=None): user2copy = self.read(cr, uid, [id], ['login','name'])[0] if default is None: default = {} @@ -395,10 +395,10 @@ class users(osv.osv): result[k[8:]] = res or False return result - def action_get(self, cr, uid, context={}): + def action_get(self, cr, uid, context=None): dataobj = self.pool.get('ir.model.data') data_id = dataobj._get_id(cr, 1, 'base', 'action_res_users_my') - return dataobj.browse(cr, uid, data_id, context).res_id + return dataobj.browse(cr, uid, data_id, context=context).res_id def login(self, db, login, password): @@ -408,12 +408,19 @@ class users(osv.osv): cr.execute('select id from res_users where login=%s and password=%s and active', (tools.ustr(login), tools.ustr(password))) res = cr.fetchone() result = False - if res: - cr.execute("update res_users set date=%s where id=%s", (time.strftime('%Y-%m-%d %H:%M:%S'),res[0])) - cr.commit() - result = res[0] - cr.close() - return result + try: + cr.execute('SELECT id FROM res_users WHERE login=%s AND password=%s AND active', + (tools.ustr(login), tools.ustr(password))) + res = cr.fetchone() + if res: + cr.execute("UPDATE res_users SET date=%s WHERE id=%s", (time.strftime('%Y-%m-%d %H:%M:%S'),res[0])) + cr.commit() + return res[0] + else: + return False + finally: + cr.close() + def check_super(self, passwd): if passwd == tools.config['admin_passwd']: return True @@ -427,29 +434,34 @@ class users(osv.osv): if (cached_pass is not None) and cached_pass == passwd: return True cr = pooler.get_db(db).cursor() - cr.execute('select count(1) from res_users where id=%s and password=%s and active=%s', (int(uid), passwd, True)) - res = cr.fetchone()[0] - cr.close() - if not bool(res): - raise security.ExceptionNoTb('AccessDenied') - if res: - if self._uid_cache.has_key(db): - ulist = self._uid_cache[db] - ulist[uid] = passwd - else: - self._uid_cache[db] = {uid:passwd} - return bool(res) + try: + cr.execute('SELECT COUNT(1) FROM res_users WHERE id=%s AND password=%s AND active=%s', + (int(uid), passwd, True)) + res = cr.fetchone()[0] + if not bool(res): + raise security.ExceptionNoTb('AccessDenied') + if res: + if self._uid_cache.has_key(db): + ulist = self._uid_cache[db] + ulist[uid] = passwd + else: + self._uid_cache[db] = {uid:passwd} + return bool(res) + finally: + cr.close() def access(self, db, uid, passwd, sec_level, ids): if not passwd: return False cr = pooler.get_db(db).cursor() - cr.execute('select id from res_users where id=%s and password=%s', (uid, passwd)) - res = cr.fetchone() - cr.close() - if not res: - raise security.ExceptionNoTb('Bad username or password') - return res[0] + try: + cr.execute('SELECT id FROM res_users WHERE id=%s AND password=%s', (uid, passwd)) + res = cr.fetchone() + if not res: + raise security.ExceptionNoTb('Bad username or password') + return res[0] + finally: + cr.close() users() From d4379792fc53002b51f54b8a105ff420f4e4bef4 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:22:24 +0200 Subject: [PATCH 44/75] tools/misc: port the exec_command_pipe() to the subprocess module. The os.popen2 is deprecated since Python 2.6. It also failed to properly escape the command line arguments. Conflicts: bin/tools/misc.py bzr revid: p_christ@hol.gr-20101123152224-d52414w2tscw6bvo --- bin/tools/misc.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 79121eb89c9..381ffb9677f 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -25,6 +25,7 @@ Miscelleanous tools used by OpenERP. """ import inspect +import subprocess import logging import os import re @@ -141,27 +142,24 @@ def exec_pg_command(name, *args): if not prog: raise Exception('Couldn\'t find %s' % name) args2 = (os.path.basename(prog),) + args - return os.spawnv(os.P_WAIT, prog, args2) + + return subprocess.call(args2, executable=prog) def exec_pg_command_pipe(name, *args): prog = find_pg_tool(name) if not prog: raise Exception('Couldn\'t find %s' % name) - if os.name == "nt": - cmd = '"' + prog + '" ' + ' '.join(args) - else: - cmd = prog + ' ' + ' '.join(args) - return os.popen2(cmd, 'b') + pop = subprocess.Popen(args, executable=prog, shell=True, bufsize= -1, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) + return (pop.stdin, pop.stdout) def exec_command_pipe(name, *args): prog = find_in_path(name) if not prog: raise Exception('Couldn\'t find %s' % name) - if os.name == "nt": - cmd = '"'+prog+'" '+' '.join(args) - else: - cmd = prog+' '+' '.join(args) - return os.popen2(cmd, 'b') + pop = subprocess.Popen(args, executable=prog, shell=True, bufsize= -1, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) + return (pop.stdin, pop.stdout) #---------------------------------------------------------- # File paths From e037e96f6db49f670a99ddd2c76a2516923d018c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:22:34 +0200 Subject: [PATCH 45/75] module: fix usage of new get_module_resource() It will return False, now, if the file is missing. No need to perform os.path.exists(). bzr revid: p_christ@hol.gr-20101123152234-4iptp8yzboq3gicz --- bin/addons/base/module/module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index 36c98deafc3..22e47ee8301 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -470,10 +470,10 @@ class module(osv.osv): # Implementation notice: we must first search for the full name of # the language derivative, like "en_UK", and then the generic, # like "en". - if (not os.path.exists(f)) and '_' in iso_lang: + if (not f) and '_' in iso_lang: f = addons.get_module_resource(mod.name, 'i18n', iso_lang.split('_')[0] + '.po') iso_lang = iso_lang.split('_')[0] - if os.path.exists(f): + if f: logger.info('module %s: loading translation file for language %s', mod.name, iso_lang) tools.trans_load(cr.dbname, f, lang, verbose=False, context=context) else: From 49dc2ec5ecb630eb60b8d7191273f64837f8e104 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:25:51 +0200 Subject: [PATCH 46/75] ir.ui.menu: Fix default args, no need for lambda in _defaults. bzr revid: p_christ@hol.gr-20101123152551-dr7dov1fip2r7jet --- bin/addons/base/ir/ir_ui_menu.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bin/addons/base/ir/ir_ui_menu.py b/bin/addons/base/ir/ir_ui_menu.py index 81dd5bef4d7..078d8948326 100644 --- a/bin/addons/base/ir/ir_ui_menu.py +++ b/bin/addons/base/ir/ir_ui_menu.py @@ -68,8 +68,10 @@ class ir_ui_menu(osv.osv): # radical but this doesn't frequently happen self._cache = {} - def create_shortcut(self, cr, uid, values, context={}): + def create_shortcut(self, cr, uid, values, context=None): dataobj = self.pool.get('ir.model.data') + if context is None: + context = {} new_context = context.copy() for key in context: if key.startswith('default_'): @@ -294,9 +296,9 @@ class ir_ui_menu(osv.osv): (_check_recursion, _rec_message , ['parent_id']) ] _defaults = { - 'icon' : lambda *a: 'STOCK_OPEN', - 'icon_pict': lambda *a: ('stock', ('STOCK_OPEN','ICON_SIZE_MENU')), - 'sequence' : lambda *a: 10, + 'icon' : 'STOCK_OPEN', + 'icon_pict': ('stock', ('STOCK_OPEN','ICON_SIZE_MENU')), + 'sequence' : 10, } _order = "sequence,id" ir_ui_menu() From 4db6f0e5c1f293a8598dc76f44ecf3ca2922c626 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:27:40 +0200 Subject: [PATCH 47/75] translate: fix default arguments after last patch bzr revid: p_christ@hol.gr-20101123152740-pn9mbptpdvw0sz98 --- bin/addons/base/module/module.py | 2 +- bin/tools/translate.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index 22e47ee8301..f05b2c382d2 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -445,7 +445,7 @@ class module(osv.osv): categs = categs[1:] self.write(cr, uid, [id], {'category_id': p_id}) - def update_translations(self, cr, uid, ids, filter_lang=None, context={}): + def update_translations(self, cr, uid, ids, filter_lang=None, context=None): logger = logging.getLogger('i18n') if not filter_lang: pool = pooler.get_pool(cr.dbname) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index 5fcae3dd01a..97bac00a359 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -774,7 +774,7 @@ def trans_generate(lang, modules, dbname=None): cr.close() return out -def trans_load(db_name, filename, lang, strict=False, verbose=True, context={}): +def trans_load(db_name, filename, lang, strict=False, verbose=True, context=None): logger = logging.getLogger('i18n') try: fileobj = open(filename,'r') @@ -788,10 +788,12 @@ def trans_load(db_name, filename, lang, strict=False, verbose=True, context={}): logger.error("couldn't read translation file %s", filename) return None -def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name=None, verbose=True, context={}): +def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name=None, verbose=True, context=None): logger = logging.getLogger('i18n') if verbose: logger.info('loading translation file for language %s', lang) + if context is None: + context = {} pool = pooler.get_pool(db_name) lang_obj = pool.get('res.lang') trans_obj = pool.get('ir.translation') From f9f0d929e78726f0e178d5452425f53dc26c6438 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:31:59 +0200 Subject: [PATCH 48/75] ir.attachment: mark 'type' and 'company_id' with change_defaults Other fields of attachments (like the directories, in document) should have their default properties depending on these. bzr revid: p_christ@hol.gr-20101123153159-wpus8bxls1q03ks9 --- bin/addons/base/ir/ir_attachment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/addons/base/ir/ir_attachment.py b/bin/addons/base/ir/ir_attachment.py index fb621ea58fc..b536ae19655 100644 --- a/bin/addons/base/ir/ir_attachment.py +++ b/bin/addons/base/ir/ir_attachment.py @@ -114,11 +114,11 @@ class ir_attachment(osv.osv): 'url': fields.char('Url', size=512, oldname="link"), 'type': fields.selection( [ ('url','URL'), ('binary','Binary'), ], - 'Type', help="Binary File or external URL", required=True), + 'Type', help="Binary File or external URL", required=True, change_default=True), 'create_date': fields.datetime('Date Created', readonly=True), 'create_uid': fields.many2one('res.users', 'Owner', readonly=True), - 'company_id': fields.many2one('res.company', 'Company'), + 'company_id': fields.many2one('res.company', 'Company', change_default=True), } _defaults = { From 3a220a02141b54faa8dcb789f18c488781291b2d Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:32:23 +0200 Subject: [PATCH 49/75] res.user: cleanup defaults, use 1 sql query per login (from 3) bzr revid: p_christ@hol.gr-20101123153223-2u34e4eszt5aflv1 --- bin/addons/base/res/res_user.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py index 1a939798934..8c18206342e 100644 --- a/bin/addons/base/res/res_user.py +++ b/bin/addons/base/res/res_user.py @@ -315,9 +315,9 @@ class users(osv.osv): return result _defaults = { - 'password' : lambda *a : '', - 'context_lang': lambda *args: 'en_US', - 'active' : lambda *a: True, + 'password' : '', + 'context_lang': 'en_US', + 'active' : True, 'menu_id': _get_menu, 'company_id': _get_company, 'company_ids': _get_companies, @@ -405,16 +405,12 @@ class users(osv.osv): if not password: return False cr = pooler.get_db(db).cursor() - cr.execute('select id from res_users where login=%s and password=%s and active', (tools.ustr(login), tools.ustr(password))) - res = cr.fetchone() - result = False try: - cr.execute('SELECT id FROM res_users WHERE login=%s AND password=%s AND active', + cr.execute('UPDATE res_users SET date=now() WHERE login=%s AND password=%s AND active RETURNING id', (tools.ustr(login), tools.ustr(password))) res = cr.fetchone() + cr.commit() if res: - cr.execute("UPDATE res_users SET date=%s WHERE id=%s", (time.strftime('%Y-%m-%d %H:%M:%S'),res[0])) - cr.commit() return res[0] else: return False From 652be11b5795a03d95e8a60730161200c3cc6f65 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:32:35 +0200 Subject: [PATCH 50/75] rml2pdf: better message if a font is not available. Since 91422704d965268f, specifying a font that is not registered with pdfmetrics will raise an exception. Now, improve that exception to help the user understand what has gone wrong. Note: rather than hiding the fact that some font is missing, the admin should see this error and try to either fix the report (to use a known font), or register more fonts with the customfonts.py mechanism. bzr revid: p_christ@hol.gr-20101123153235-c1yri33ptaydb5eo --- bin/report/render/rml2pdf/trml2pdf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index 1280f8e612b..4e85ac2b0db 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -486,7 +486,10 @@ class _rml_canvas(object): def setFont(self, node): fname = node.get('name') - return self.canvas.setFont(fname, utils.unit_get(node.get('size'))) + try: + return self.canvas.setFont(fname, utils.unit_get(node.get('size'))) + except KeyError, e: + raise KeyError('Font "%s" is not registered in the engine' % fname) def render(self, node): tags = { From 186b9190f68b0ec535ab9531a872ea7d9bd585d0 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:33:25 +0200 Subject: [PATCH 51/75] tools.convert, misc: use pythonic logging Conflicts: bin/tools/convert.py bin/tools/misc.py bzr revid: p_christ@hol.gr-20101123153325-wicnjxt98lp03cpf --- bin/tools/convert.py | 24 +++++++++++------------- bin/tools/misc.py | 4 +++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 14823984ca3..1d050a832a4 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -269,7 +269,7 @@ form: module.record_id""" % (xml_id,) assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,) if len(id) > 64: - self.logger.notifyChannel('init', netsvc.LOG_ERROR, 'id: %s is to long (max: 64)'% (id,)) + self.logger.error('id: %s is to long (max: 64)', id) def _tag_delete(self, cr, rec, data_node=None): d_model = rec.get("model",'') @@ -562,7 +562,7 @@ form: module.record_id""" % (xml_id,) pid = res[0] else: # the menuitem does't exist but we are in branch (not a leaf) - self.logger.notifyChannel("init", netsvc.LOG_WARNING, 'Warning no ID for submenu %s of menu %s !' % (menu_elem, str(m_l))) + self.logger.warning('Warning no ID for submenu %s of menu %s !', menu_elem, str(m_l)) pid = self.pool.get('ir.ui.menu').create(cr, self.uid, {'parent_id' : pid, 'name' : menu_elem}) values['parent_id'] = pid else: @@ -691,7 +691,7 @@ form: module.record_id""" % (xml_id,) ' expected count: %d\n' \ ' obtained count: %d\n' \ % (rec_string, count, len(ids)) - self.logger.notifyChannel('init', severity, msg) + self.logger.log(severity, msg) sevval = getattr(logging, severity.upper()) if sevval >= config['assert_exit_level']: # TODO: define a dedicated exception @@ -723,7 +723,7 @@ form: module.record_id""" % (xml_id,) ' expected value: %r\n' \ ' obtained value: %r\n' \ % (rec_string, etree.tostring(test), expected_value, expression_value) - self.logger.notifyChannel('init', severity, msg) + self.logger.log(severity, msg) sevval = getattr(logging, severity.upper()) if sevval >= config['assert_exit_level']: # TODO: define a dedicated exception @@ -838,11 +838,11 @@ form: module.record_id""" % (xml_id,) def parse(self, de): if not de.tag in ['terp', 'openerp']: - self.logger.notifyChannel("init", netsvc.LOG_ERROR, "Mismatch xml format" ) + self.logger.error("Mismatch xml format") raise Exception( "Mismatch xml format: only terp or openerp as root tag" ) if de.tag == 'terp': - self.logger.notifyChannel("init", netsvc.LOG_WARNING, "The tag is deprecated, use ") + self.logger.warning("The tag is deprecated, use ") for n in de.findall('./data'): for rec in n: @@ -860,7 +860,7 @@ form: module.record_id""" % (xml_id,) def __init__(self, cr, module, idref, mode, report=None, noupdate=False): - self.logger = netsvc.Logger() + self.logger = logging.getLogger('init') self.mode = mode self.module = module self.cr = cr @@ -893,13 +893,14 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', encoding: utf-8''' if not idref: idref={} + logger = logging.getLogger('init') model = ('.'.join(fname.split('.')[:-1]).split('-'))[0] #remove folder path from model head, model = os.path.split(model) pool = pooler.get_pool(cr.dbname) - input = cStringIO.StringIO(csvcontent) + input = cStringIO.StringIO(csvcontent) #FIXME reader = csv.reader(input, quotechar='"', delimiter=',') fields = reader.next() fname_partial = "" @@ -917,9 +918,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', reader.next() if not (mode == 'init' or 'id' in fields): - logger = netsvc.Logger() - logger.notifyChannel("init", netsvc.LOG_ERROR, - "Import specification does not contain 'id' and we are in init mode, Cannot continue.") + logger.error("Import specification does not contain 'id' and we are in init mode, Cannot continue.") return uid = 1 @@ -930,8 +929,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', try: datas.append(map(lambda x: misc.ustr(x), line)) except: - logger = netsvc.Logger() - logger.notifyChannel("init", netsvc.LOG_ERROR, "Cannot import the line: %s" % line) + logger.error("Cannot import the line: %s", line) pool.get(model).import_data(cr, uid, fields, datas,mode, module, noupdate, filename=fname_partial) if config.get('import_partial'): data = pickle.load(file(config.get('import_partial'))) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index 381ffb9677f..e1128db9cea 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -1112,6 +1112,8 @@ def debug(what): --log-level=debug """ + warnings.warn("The tools.debug() method is deprecated, please use logging.", + DeprecationWarning, stacklevel=2) from inspect import stack from pprint import pformat st = stack()[1] @@ -1120,7 +1122,7 @@ def debug(what): what = pformat(what) if param != what: what = "%s = %s" % (param, what) - netsvc.Logger().notifyChannel(st[3], netsvc.LOG_DEBUG, what) + logging.getLogger(st[3]).debug(what) __icons_list = ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', From 1fed78619fa71675f3ae902abbafe65526a04f7c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:35:58 +0200 Subject: [PATCH 52/75] websrv_lib: abort request on partial headers. While searching for the KTHML bug, noticed that partial http requests would falsely be honoured. Instead, we must check if the full headers (including the blank line) have been parsed before we dispatch the request to the virtual handler class. Conflicts: bin/service/websrv_lib.py bzr revid: p_christ@hol.gr-20101123153558-oh44mjkhwjaebwow --- bin/service/websrv_lib.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index 58bc6508245..5b17191b493 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -255,6 +255,12 @@ class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler): fore.raw_requestline = "%s %s %s\n" % (self.command, path, self.version) if not fore.parse_request(): # An error code has been sent, just exit return + if fore.headers.status: + self.log_error("Parse error at headers: %s", fore.headers.status) + self.close_connection = 1 + self.send_error(400,"Parse error at HTTP headers") + return + self.request_version = fore.request_version if auth_provider and auth_provider.realm: try: @@ -413,8 +419,14 @@ class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler): hnd.rfile = self.rfile hnd.wfile = self.wfile self.rlpath = self.raw_requestline - self._handle_one_foreign(hnd,npath, vdir.auth_provider) - # print "Handled, closing = ", self.close_connection + try: + self._handle_one_foreign(hnd,npath, vdir.auth_provider) + except IOError, e: + if e.errno == errno.EPIPE: + self.log_message("Could not complete request %s," \ + "client closed connection", self.rlpath.rstrip()) + else: + raise return # if no match: self.send_error(404, "Path not found: %s" % self.path) From 04c05faf9b5469afdbc0d446246a39d860fc5345 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:39:36 +0200 Subject: [PATCH 53/75] yaml-import: fix working with deleted data At some cases, ir.model.data is left with orphan entries, so ensure that the message below is used (rather than "bool has no get()" exception). Conflicts: bin/tools/yaml_import.py bzr revid: p_christ@hol.gr-20101123153936-0cc4t4e2xwat70q1 --- bin/tools/yaml_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py index 90fedbd0330..5a2cfd54758 100644 --- a/bin/tools/yaml_import.py +++ b/bin/tools/yaml_import.py @@ -175,7 +175,7 @@ class YamlInterpreter(object): module = self.module checked_xml_id = xml_id ir_id = self.pool.get('ir.model.data')._get_id(self.cr, self.uid, module, checked_xml_id) - obj = self.pool.get('ir.model.data').read(self.cr, self.uid, ir_id, ['res_id']) + obj = self.pool.get('ir.model.data').read(self.cr, self.uid, ir_id, ['res_id']) or {} id = int(obj['res_id']) self.id_map[xml_id] = id return id From a62f0bede48c62c3ecb89e16cb0e7449302c6621 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:40:32 +0200 Subject: [PATCH 54/75] ir.model: mutable argument fix bzr revid: p_christ@hol.gr-20101123154032-tuoe1aiy9wpnjp2y --- bin/addons/base/ir/ir_model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index 5f9e3347b74..19ef96c1e17 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -75,10 +75,12 @@ class ir_model(osv.osv): fnct_search=_search_osv_memory, help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)") } + _defaults = { 'model': lambda *a: 'x_', - 'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base', + 'state': lambda self,cr,uid,ctx=None: (ctx and ctx.get('manual',False)) and 'manual' or 'base', } + def _check_model_name(self, cr, uid, ids): for model in self.browse(cr, uid, ids): if model.state=='manual': From c5b99753eac61a300899430d0b0a7c234f92b7b6 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:42:12 +0200 Subject: [PATCH 55/75] ir.attachment: revise the access control code, let it be overriden There was a fundamental problem when a user, through the 'document' module would try to write into his personal folder; the res.users model won't be writable by normal users. So, have all this code in a _single_ function, which will be redefined by document. Also follow the v6 API for access control, don't just lookup at ir.model.access. Conflicts: bin/addons/base/ir/ir_attachment.py bzr revid: p_christ@hol.gr-20101123154212-pphsnyp2rsmbjf72 --- bin/addons/base/ir/ir_attachment.py | 32 ++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/bin/addons/base/ir/ir_attachment.py b/bin/addons/base/ir/ir_attachment.py index b536ae19655..0811e3b563b 100644 --- a/bin/addons/base/ir/ir_attachment.py +++ b/bin/addons/base/ir/ir_attachment.py @@ -24,16 +24,29 @@ from osv.orm import except_orm import tools class ir_attachment(osv.osv): - def check(self, cr, uid, ids, mode, context=None): + def check(self, cr, uid, ids, mode, context=None, values=None): + """Restricts the access to an ir.attachment, according to referred model + In the 'document' module, it is overriden to relax this hard rule, since + more complex ones apply there. + """ if not ids: return ima = self.pool.get('ir.model.access') - if isinstance(ids, (int, long)): - ids = [ids] - cr.execute('select distinct res_model from ir_attachment where id IN %s', (tuple(ids),)) - for obj in cr.fetchall(): - if obj[0]: - ima.check(cr, uid, obj[0], mode, context=context) + res_ids = {} + if ids: + if isinstance(ids, (int, long)): + ids = [ids] + cr.execute('SELECT DISTINCT res_model, res_id FROM ir_attachment WHERE id = ANY (%s)', (ids,), self._debug) + for rmod, rid in cr.fetchall(): + if not (rmod and rid): + continue + res_ids.setdefault(rmod,[]).append(rid) + if values: + if 'res_model' in values and 'res_id' in values: + res_ids.setdefault(values['res_model'],[]).append(values['res_id']) + + for model, mids in res_ids.items(): + self.pool.get(model).check_access_rule(cr, uid, mids, mode, context=context) def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): @@ -64,7 +77,7 @@ class ir_attachment(osv.osv): return super(ir_attachment, self).read(cr, uid, ids, fields_to_read, context, load) def write(self, cr, uid, ids, vals, context=None): - self.check(cr, uid, ids, 'write', context=context) + self.check(cr, uid, ids, 'write', context=context, values=vals) return super(ir_attachment, self).write(cr, uid, ids, vals, context) def copy(self, cr, uid, id, default=None, context=None): @@ -76,8 +89,7 @@ class ir_attachment(osv.osv): return super(ir_attachment, self).unlink(cr, uid, ids, context) def create(self, cr, uid, values, context=None): - if 'res_model' in values and values['res_model'] != '': - self.pool.get('ir.model.access').check(cr, uid, values['res_model'], 'create', context=context) + self.check(cr, uid, [], mode='create', context=context, values=values) return super(ir_attachment, self).create(cr, uid, values, context) def action_get(self, cr, uid, context=None): From 09099f1ed8a7f775ca1d9a7f5618c2eb3901e42f Mon Sep 17 00:00:00 2001 From: Ferdinand Gassauer Date: Tue, 23 Nov 2010 17:42:55 +0200 Subject: [PATCH 56/75] module: sorted Features returns alphabetically sorted Menu,Views,Reports to make these lists readable for humans. bzr revid: p_christ@hol.gr-20101123154255-4iazp4ec7wisn2sq --- bin/addons/base/module/module.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index f05b2c382d2..19a7962370c 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -98,9 +98,9 @@ class module(osv.osv): for m in mlist: mnames[m.name] = m.id res[m.id] = { - 'menus_by_module':'', - 'reports_by_module':'', - 'views_by_module': '' + 'menus_by_module':[], + 'reports_by_module':[], + 'views_by_module': [] } view_id = model_data_obj.search(cr,uid,[('module','in', mnames.keys()), ('model','in',('ir.ui.view','ir.actions.report.xml','ir.ui.menu'))]) @@ -109,26 +109,18 @@ class module(osv.osv): try: key = data_id['model'] if key=='ir.ui.view': - try: - v = view_obj.browse(cr,uid,data_id.res_id) - aa = v.inherit_id and '* INHERIT ' or '' - res[mnames[data_id.module]]['views_by_module'] += aa + v.name + ' ('+v.type+')\n' - except Exception: - self.__logger.debug( - 'Unknown error while browsing ir.ui.view[%s]', - data_id.res_id, exc_info=True) + v = view_obj.browse(cr,uid,data_id.res_id) + aa = v.inherit_id and '* INHERIT ' or '' + res[mnames[data_id.module]]['views_by_module'].append(aa + v.name + '('+v.type+')') elif key=='ir.actions.report.xml': - res[mnames[data_id.module]]['reports_by_module'] += report_obj.browse(cr,uid,data_id.res_id).name + '\n' + res[mnames[data_id.module]]['reports_by_module'].append(report_obj.browse(cr,uid,data_id.res_id).name) elif key=='ir.ui.menu': - try: - m = menu_obj.browse(cr,uid,data_id.res_id) - res[mnames[data_id.module]]['menus_by_module'] += m.complete_name + '\n' - except Exception: - self.__logger.debug( - 'Unknown error while browsing ir.ui.menu[%s]', - data_id.res_id, exc_info=True) - except KeyError: + res[mnames[data_id.module]]['menus_by_module'].append(menu_obj.browse(cr,uid,data_id.res_id).complete_name) + except KeyError, e: pass + for key, value in res.iteritems() : + for k, v in res[key].iteritems() : + res[key][k] = "\n".join(sorted(v)) return res _columns = { From 0c1061e4deb5a9c9963ede2ea8bc2b185ff66af8 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:43:06 +0200 Subject: [PATCH 57/75] module: improve over previous commit, fix mutable args Conflicts: bin/addons/base/module/module.py bzr revid: p_christ@hol.gr-20101123154306-j88k617evep9napa --- bin/addons/base/module/module.py | 39 ++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index 19a7962370c..1dc507da2f3 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -40,7 +40,7 @@ class module_category(osv.osv): _name = "ir.module.category" _description = "Module Category" - def _module_nbr(self,cr,uid, ids, prop, unknow_none,context): + def _module_nbr(self,cr,uid, ids, prop, unknow_none, context): cr.execute('SELECT category_id, COUNT(*) \ FROM ir_module_module \ WHERE category_id IN %(ids)s \ @@ -81,13 +81,13 @@ class module(osv.osv): 'module %s', name, exc_info=True) return info - def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context={}): + def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None): res = dict.fromkeys(ids, '') for m in self.browse(cr, uid, ids): res[m.id] = self.get_module_info(m.name).get('version', '') return res - def _get_views(self, cr, uid, ids, field_name=None, arg=None, context={}): + def _get_views(self, cr, uid, ids, field_name=None, arg=None, context=None): res = {} model_data_obj = self.pool.get('ir.model.data') view_obj = self.pool.get('ir.ui.view') @@ -107,7 +107,7 @@ class module(osv.osv): for data_id in model_data_obj.browse(cr,uid,view_id,context): # We use try except, because views or menus may not exist try: - key = data_id['model'] + key = data_id.model if key=='ir.ui.view': v = view_obj.browse(cr,uid,data_id.res_id) aa = v.inherit_id and '* INHERIT ' or '' @@ -117,8 +117,15 @@ class module(osv.osv): elif key=='ir.ui.menu': res[mnames[data_id.module]]['menus_by_module'].append(menu_obj.browse(cr,uid,data_id.res_id).complete_name) except KeyError, e: + self.__logger.warning( + 'Data not found for reference %s[%s:%s.%s]', data_id.model, + data_id.res_id, data_id.model, data_id.name, exc_info=True) pass - for key, value in res.iteritems() : + except Exception, e: + self.__logger.warning('Unknown error while browsing %s[%s]', + data_id.model, data_id.res_id, exc_info=True) + pass + for key, value in res.iteritems(): for k, v in res[key].iteritems() : res[key][k] = "\n".join(sorted(v)) return res @@ -171,9 +178,9 @@ class module(osv.osv): } _defaults = { - 'state': lambda *a: 'uninstalled', - 'demo': lambda *a: False, - 'license': lambda *a: 'AGPL-3', + 'state': 'uninstalled', + 'demo': False, + 'license': 'AGPL-3', 'web': False, } _order = 'name' @@ -262,14 +269,14 @@ class module(osv.osv): demo = demo or mdemo return demo - def button_install(self, cr, uid, ids, context={}): + def button_install(self, cr, uid, ids, context=None): return self.state_update(cr, uid, ids, 'to install', ['uninstalled'], context) - def button_install_cancel(self, cr, uid, ids, context={}): + def button_install_cancel(self, cr, uid, ids, context=None): self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False}) return True - def button_uninstall(self, cr, uid, ids, context={}): + def button_uninstall(self, cr, uid, ids, context=None): for module in self.browse(cr, uid, ids): cr.execute('''select m.state,m.name from @@ -285,7 +292,7 @@ class module(osv.osv): self.write(cr, uid, ids, {'state': 'to remove'}) return True - def button_uninstall_cancel(self, cr, uid, ids, context={}): + def button_uninstall_cancel(self, cr, uid, ids, context=None): self.write(cr, uid, ids, {'state': 'installed'}) return True @@ -321,7 +328,7 @@ class module(osv.osv): self.button_install(cr, uid, to_install, context=context) return True - def button_upgrade_cancel(self, cr, uid, ids, context={}): + def button_upgrade_cancel(self, cr, uid, ids, context=None): self.write(cr, uid, ids, {'state': 'installed'}) return True def button_update_translations(self, cr, uid, ids, context=None): @@ -414,7 +421,9 @@ class module(osv.osv): zimp.load_module(mod.name) return res - def _update_dependencies(self, cr, uid, id, depends=[]): + def _update_dependencies(self, cr, uid, id, depends=None): + if depends is None: + depends = [] for d in depends: cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)', (id, d)) @@ -538,7 +547,7 @@ class module_dependency(osv.osv): _name = "ir.module.module.dependency" _description = "Module dependency" - def _state(self, cr, uid, ids, name, args, context={}): + def _state(self, cr, uid, ids, name, args, context=None): result = {} mod_obj = self.pool.get('ir.module.module') for md in self.browse(cr, uid, ids): From ec2c32e2fe760190e4405acac6d44d3a33c396e2 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:43:35 +0200 Subject: [PATCH 58/75] ir.model: simplify some defaults bzr revid: p_christ@hol.gr-20101123154335-tl2jm6litwx3ysro --- bin/addons/base/ir/ir_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index 19ef96c1e17..e8d5e1063b4 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -388,8 +388,8 @@ class ir_model_data(osv.osv): _defaults = { 'date_init': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'date_update': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), - 'noupdate': lambda *a: False, - 'module': lambda *a: '' + 'noupdate': False, + 'module': '' } _sql_constraints = [ ('module_name_uniq', 'unique(name, module)', 'You cannot have multiple records with the same id for the same module !'), From c2c264467f296274f9e6ae0a5ffe1a90caf3826c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:44:08 +0200 Subject: [PATCH 59/75] fields: conform some default arguments to the coding standards Side-effects may appear, let us test. bzr revid: p_christ@hol.gr-20101123154408-qlkittdb7ogrqq2s --- bin/osv/fields.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/osv/fields.py b/bin/osv/fields.py index a5839381f27..135296779b1 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -341,8 +341,10 @@ class many2one(_column): return result def get(self, cr, obj, ids, name, user=None, context=None, values=None): - context = context or {} - values = values or {} + if context is None: + context = {} + if values is None: + values = {} res = {} for r in values: @@ -454,12 +456,12 @@ class one2many(_column): raise _('Not Implemented') def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None): - if not context: + if context is None: context = {} if self._context: context = context.copy() context.update(self._context) - if not values: + if values is None: values = {} res = {} @@ -740,9 +742,9 @@ class function(_column): return self._fnct_search(obj, cr, uid, obj, name, args, context=context) def get(self, cr, obj, ids, name, user=None, context=None, values=None): - if not context: + if context is None: context = {} - if not values: + if values is None: values = {} res = {} if self._method: From a2ee48478a0a1e1d7f30bd7bceee20a064ac851b Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:44:48 +0200 Subject: [PATCH 60/75] websrv_lib: add quirk for Darwin 9.x.x webdav clients. They will report "HTTP/1.0" to us, while they support (and need) the authorisation features of HTTP/1.1 . This makes WebDAV work for some Mac OSX versions. bzr revid: p_christ@hol.gr-20101123154448-0z7s5o77256cj1tu --- bin/service/websrv_lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index 5b17191b493..fd5bedc5b26 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -266,8 +266,8 @@ class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler): try: self.sec_realms[auth_provider.realm].checkRequest(fore,path) except AuthRequiredExc,ae: - if self.request_version != 'HTTP/1.1': - self.log_error("Cannot require auth at %s",self.request_version) + if self.request_version != 'HTTP/1.1' and ('Darwin/9.' not in fore.headers.get('User-Agent', '')): + self.log_error("Cannot require auth at %s", self.request_version) self.send_error(403) return self._get_ignore_body(fore) # consume any body that came, not loose sync with input From 7165010fd64329ea4fb500b3641a361aacdf9649 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 17:58:20 +0200 Subject: [PATCH 61/75] ir.translate: revert some pg84 argument from the cherry-picks bzr revid: p_christ@hol.gr-20101123155820-ltqymky8482k8l9l --- bin/addons/base/ir/ir_attachment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_attachment.py b/bin/addons/base/ir/ir_attachment.py index 0811e3b563b..eeaef655643 100644 --- a/bin/addons/base/ir/ir_attachment.py +++ b/bin/addons/base/ir/ir_attachment.py @@ -36,7 +36,7 @@ class ir_attachment(osv.osv): if ids: if isinstance(ids, (int, long)): ids = [ids] - cr.execute('SELECT DISTINCT res_model, res_id FROM ir_attachment WHERE id = ANY (%s)', (ids,), self._debug) + cr.execute('SELECT DISTINCT res_model, res_id FROM ir_attachment WHERE id = ANY (%s)', (ids,)) for rmod, rid in cr.fetchall(): if not (rmod and rid): continue From 597a8714703e9eb493ffc8d4fa19f004a9bb86fa Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:08:24 +0200 Subject: [PATCH 62/75] misc: import warnings, needed for earlier patch bzr revid: p_christ@hol.gr-20101123180824-sk28lwc298v89rr4 --- bin/tools/misc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/tools/misc.py b/bin/tools/misc.py index e1128db9cea..7a416052d21 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -34,6 +34,7 @@ import socket import sys import threading import time +import warnings import zipfile from datetime import datetime from email.MIMEText import MIMEText From 5d53dcc70079ca3041cc731bac44e9baf9a289f4 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:33:57 +0200 Subject: [PATCH 63/75] websrv_lib: update copyright and remove bogus note The reference to the "ganeti" project has always been totally arbitrary and false. It is true that some parts of "websrv_lib" have been based on Python's BaseHTTPServer and SimpleHTTPServer classes, but not more than that. Overriding and perhaps resembling to parent classes is the most common thing in OO programming. But never copied or knew about any "ganeti" project whatsoever. (actually, the code mentioned at the url admits itself that it copies the BaseHTTPServer, a common denominator) Git history to your help, all the code evolution is clearly recorded. bzr revid: p_christ@hol.gr-20101123183357-fjroqywizql2qdkk --- bin/service/websrv_lib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index fd5bedc5b26..c89bcee7fa2 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright P. Christeas 2008,2009 -# A part of the code comes from the ganeti project: http://www.mail-archive.com/ganeti-devel@googlegroups.com/msg00713.html# +# Copyright P. Christeas 2008-2010 # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential From 12f4f6232cf2b9bf2475844cdcdb12599bab952c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Wed, 24 Nov 2010 11:48:53 +0200 Subject: [PATCH 64/75] sql_db: only do frame inspection when debugging This affects the "Cursor not closed explicitly..." message. The message is at a "warning" level, but the frame inspection added some expensive overhead to each cursor open. So, avoid that unless we are at debug mode. bzr revid: p_christ@hol.gr-20101124094853-0ljr6nebkyya0eui --- bin/sql_db.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bin/sql_db.py b/bin/sql_db.py index 89f5cb01c27..95d1f7d24be 100644 --- a/bin/sql_db.py +++ b/bin/sql_db.py @@ -94,7 +94,10 @@ class Cursor(object): self._obj = self._cnx.cursor(cursor_factory=psycopg1cursor) self.__closed = False # real initialisation value self.autocommit(False) - self.__caller = frame_codeinfo(currentframe(),2) + if self.sql_log: + self.__caller = frame_codeinfo(currentframe(),2) + else: + self.__caller = False def __del__(self): if not self.__closed: @@ -103,9 +106,12 @@ class Cursor(object): # but the database connection is not put back into the connection # pool, preventing some operation on the database like dropping it. # This can also lead to a server overload. - msg = "Cursor not closed explicitly\n" \ - "Cursor was created at %s:%s" - self.__logger.warn(msg, *self.__caller) + msg = "Cursor not closed explicitly\n" + if self.__caller: + msg += "Cursor was created at %s:%s" % self.__caller + else: + msg += "Please enable sql debugging to trace the caller." + self.__logger.warn(msg) self._close(True) @check From 458d7c7dea7a6e3fd53fae9c8f460326fe4e4ff3 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Nov 2010 17:38:40 +0100 Subject: [PATCH 65/75] [FIX] addons.__init__: fixed mismatching logger API usage bzr revid: odo@openerp.com-20101124163840-eyctlqtgjfq9h1u0 --- bin/addons/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 9fedd3bbdf4..8bc280b73ad 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -874,7 +874,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if obj: obj._check_removed_columns(cr, log=True) else: - logger.warning("Model %s is referenced but not present in the orm pool!", model) + logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model) if report.get_report(): logger.notifyChannel('init', netsvc.LOG_INFO, report) From 276a54b0af93d1fdd45aeeb4e051b0d7704d560d Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Nov 2010 17:40:41 +0100 Subject: [PATCH 66/75] [FIX] osv.osv: code cleanup after merging previous patches bzr revid: odo@openerp.com-20101124164041-760b4zoidv0omu6c --- bin/osv/osv.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bin/osv/osv.py b/bin/osv/osv.py index 6f43739b35a..e7b84351a23 100644 --- a/bin/osv/osv.py +++ b/bin/osv/osv.py @@ -62,18 +62,14 @@ class osv_pool(netsvc.Service): if not kwargs: if args and isinstance(args[-1], dict): ctx = args[-1] - else: - ctx = {} elif isinstance(kwargs, dict): ctx = kwargs.get('context', {}) - else: - ctx = {} uid = 1 if args and isinstance(args[0], (long, int)): uid = args[0] - lang = ctx and ctx.get('lang', False) + lang = ctx and ctx.get('lang') if not (lang or hasattr(src, '__call__')): return src @@ -105,7 +101,6 @@ class osv_pool(netsvc.Service): try: cr = pooler.get_db_only(dbname).cursor() - #return trans_obj._get_source( name=?) res = translate(cr, name=False, source_type=ttype, lang=lang, source=src) if res: From c34d2ae6d4cdc3f4995751c5254257b7277940b5 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Nov 2010 17:41:05 +0100 Subject: [PATCH 67/75] [FIX] res.user: code cleanup after merging previous patches bzr revid: odo@openerp.com-20101124164105-xsd2nkit09g29gjl --- bin/addons/base/res/res_user.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py index 8c18206342e..b1f95b778dd 100644 --- a/bin/addons/base/res/res_user.py +++ b/bin/addons/base/res/res_user.py @@ -524,20 +524,15 @@ class groups2(osv.osv): ##FIXME: Is there a reason to inherit this object ? for record in self.read(cr, uid, ids, ['users'], context=context): if record['users']: group_users.extend(record['users']) - + if group_users: - group_names = [] - try: - for rec in self.pool.get('res.users').read(cr, uid, group_users, ['name'], context=context): - group_names.append(rec['name']) - if len(group_names) >=5: - group_names = group_names[:5] - group_names += '...' - except Exception: - raise osv.except_osv(_('Warning !'), _('Make sure you have no users linked with the group(s)!')) + user_names = [user.name for user in self.pool.get('res.users').browse(cr, uid, group_users, context=context)] + if len(user_names) >= 5: + user_names = user_names[:5] + user_names += '...' raise osv.except_osv(_('Warning !'), - _('Group(s) cannot be deleted, because user(s) %s participate in them!') % \ - ', '.join(group_names)) + _('Group(s) cannot be deleted, because some user(s) still belong to them: %s !') % \ + ', '.join(user_names)) return super(groups2, self).unlink(cr, uid, ids, context=context) groups2() From 03f310034619ef65b3597dd4ef6531b677ef090c Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Nov 2010 17:41:35 +0100 Subject: [PATCH 68/75] [FIX] ir_actions: minor typo bzr revid: odo@openerp.com-20101124164135-6qemb2nof0uzx2f2 --- bin/addons/base/ir/ir_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index b7d62c169f7..875510c70f7 100644 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -610,7 +610,7 @@ class actions_server(osv.osv): body = self.merge_message(cr, uid, action.message, action, context) if tools.email_send(user, [address], subject, body, debug=False, subtype='html') == True: - logger.info('Email successfully send to: %s', address) + logger.info('Email successfully sent to: %s', address) else: logger.warning('Failed to send email to: %s', address) From 475667d3842401a9c0b4f3d50012f51b929af552 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Nov 2010 18:28:19 +0100 Subject: [PATCH 69/75] [FIX] orm.import_data: also translate SQL constraints error messages that are not callable bzr revid: odo@openerp.com-20101124172819-33ahiq3aacgwt8f2 --- bin/osv/orm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 051bf9ea98e..757f05b321a 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -982,6 +982,8 @@ class orm_template(object): msg = self.pool._sql_error[key] if hasattr(msg, '__call__'): msg = msg(cr, uid, [res_id,], context=context) + else: + msg = _(msg) break return (-1, res, 'Line ' + str(counter) +' : ' + msg, '') if isinstance(e, osv.orm.except_orm): From 7505e7d822ce640de84d6bbc2a90f62ee3fa92ec Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Nov 2010 18:30:28 +0100 Subject: [PATCH 70/75] [FIX] websrv_lib: Darwin webdav workaround bzr revid: odo@openerp.com-20101124173028-iumdj77clfhsnsuj --- bin/service/websrv_lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index c89bcee7fa2..13ba6cd6e95 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -265,6 +265,8 @@ class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler): try: self.sec_realms[auth_provider.realm].checkRequest(fore,path) except AuthRequiredExc,ae: + # Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the + # authorisation features of HTTP/1.1 if self.request_version != 'HTTP/1.1' and ('Darwin/9.' not in fore.headers.get('User-Agent', '')): self.log_error("Cannot require auth at %s", self.request_version) self.send_error(403) From 0fa9b158681a88bc348790e6f78540f6cba7547e Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Nov 2010 18:32:22 +0100 Subject: [PATCH 71/75] [IMP] yaml_import: improved resolution of XML IDs bzr revid: odo@openerp.com-20101124173222-177ra54gh0t5z0uy --- bin/tools/yaml_import.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py index 5a2cfd54758..0098e4e2117 100644 --- a/bin/tools/yaml_import.py +++ b/bin/tools/yaml_import.py @@ -174,9 +174,7 @@ class YamlInterpreter(object): else: module = self.module checked_xml_id = xml_id - ir_id = self.pool.get('ir.model.data')._get_id(self.cr, self.uid, module, checked_xml_id) - obj = self.pool.get('ir.model.data').read(self.cr, self.uid, ir_id, ['res_id']) or {} - id = int(obj['res_id']) + _, id = self.pool.get('ir.model.data').get_object_reference(self.cr, self.uid, module, checked_xml_id) self.id_map[xml_id] = id return id From 30cb4b88915b52d48a8aa1cab37f7291fd69016a Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 24 Nov 2010 19:41:08 +0100 Subject: [PATCH 72/75] [IMP] orm.orm: cleaned up comments after previous patches bzr revid: odo@openerp.com-20101124184108-iw1up4jomysfoh85 --- bin/osv/orm.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 757f05b321a..d8dc906187c 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1058,8 +1058,6 @@ class orm_template(object): # callable() because it will be deprecated as of Python 3.0 if hasattr(msg, '__call__'): tmp_msg = msg(self, cr, uid, ids, context=context) - # Why translate something that has been generated dynamically? - # tmp_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, source=txt_msg) or txt_msg if isinstance(tmp_msg, tuple): tmp_msg, params = tmp_msg translated_msg = tmp_msg % params From ad490bdcf54d66755be074d57c35c0bebdacc5b7 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 25 Nov 2010 14:58:00 +0100 Subject: [PATCH 73/75] [IMP] orm.write: ignore unknown fields, more consistent with rest of API bzr revid: odo@openerp.com-20101125135800-dzad2x864bn8p1ah --- 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 d8dc906187c..82e74074f7f 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -3349,7 +3349,7 @@ class orm(orm_template): fobj = None if field in self._columns: fobj = self._columns[field] - else: + elif field in self._inherit_fields: fobj = self._inherit_fields[field][2] if not fobj: continue From ebdd7f7f40656ef2b2abfc513d7e5da7813062b6 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 25 Nov 2010 15:11:57 +0100 Subject: [PATCH 74/75] [REVERT] use move instead of delete+add, revert revid:p_christ@hol.gr-20101123135849-z6bojjykt7r0dgx1use bzr revid: odo@openerp.com-20101125141157-3gbjfqf0iwt1uqh8 --- tools/change-loglevel.sh => change-loglevel.sh | 0 tools/get-srvstats.sh => get-srvstats.sh | 0 tools/list-services.sh => list-services.sh | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tools/change-loglevel.sh => change-loglevel.sh (100%) rename tools/get-srvstats.sh => get-srvstats.sh (100%) rename tools/list-services.sh => list-services.sh (100%) diff --git a/tools/change-loglevel.sh b/change-loglevel.sh similarity index 100% rename from tools/change-loglevel.sh rename to change-loglevel.sh diff --git a/tools/get-srvstats.sh b/get-srvstats.sh similarity index 100% rename from tools/get-srvstats.sh rename to get-srvstats.sh diff --git a/tools/list-services.sh b/list-services.sh similarity index 100% rename from tools/list-services.sh rename to list-services.sh From 897ba61b4b12110e574f86fd414220ae53328761 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 25 Nov 2010 15:13:24 +0100 Subject: [PATCH 75/75] [IMP] tools: moved files properly after reverting incorrect commit See revid:p_christ@hol.gr-20101123135849-z6bojjykt7r0dgx1use bzr revid: odo@openerp.com-20101125141324-u7eevky9ckcrzj73 --- change-loglevel.sh => tools/change-loglevel.sh | 0 get-srvstats.sh => tools/get-srvstats.sh | 0 list-services.sh => tools/list-services.sh | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename change-loglevel.sh => tools/change-loglevel.sh (100%) rename get-srvstats.sh => tools/get-srvstats.sh (100%) rename list-services.sh => tools/list-services.sh (100%) diff --git a/change-loglevel.sh b/tools/change-loglevel.sh similarity index 100% rename from change-loglevel.sh rename to tools/change-loglevel.sh diff --git a/get-srvstats.sh b/tools/get-srvstats.sh similarity index 100% rename from get-srvstats.sh rename to tools/get-srvstats.sh diff --git a/list-services.sh b/tools/list-services.sh similarity index 100% rename from list-services.sh rename to tools/list-services.sh