diff --git a/addons/account/tests/test_fiscal_position.py b/addons/account/tests/test_fiscal_position.py index e3c933ea6e02e..10f2c9ed07242 100644 --- a/addons/account/tests/test_fiscal_position.py +++ b/addons/account/tests/test_fiscal_position.py @@ -1,5 +1,10 @@ +import logging from odoo.tests import common + +_logger = logging.getLogger(__name__) + + class TestFiscalPosition(common.TransactionCase): """Tests for fiscal positions in auto apply (account.fiscal.position). If a partner has a vat number, the fiscal positions with "vat_required=True" @@ -62,10 +67,21 @@ def setUp(self): def test_10_fp_country(self): def assert_fp(partner, expected_pos, message): - self.assertEquals( - self.fp.get_fiscal_position(partner.id), - expected_pos.id, - message) + partner_pos_id = self.fp.get_fiscal_position(partner.id) + # If we know assert will raise an error, tell names of + # actual fiscal positions: + if partner_pos_id != expected_pos.id: + partner_pos = self.fp.browse([partner_pos_id]) + _logger.error( + "Partner %s in country %s and state %s with zip %s" + " has fp %s, but expected fp is %s", + partner.name, + partner.country_id.name, + partner.state_id.name, + partner.zip, + partner_pos.name, + expected_pos.name) + self.assertEquals(partner_pos_id, expected_pos.id, message) george, jc, ben, alberto = self.george, self.jc, self.ben, self.alberto @@ -120,10 +136,17 @@ def assert_fp(partner, expected_pos, message): assert_fp(george, self.fr_b2b_zip100, "FR-B2B with zip range should have precedence") # States - self.fr_b2b_state = self.fr_b2b.copy(dict(state_ids=[(4, self.state_fr.id)], sequence=70)) + self.fr_b2b_state = self.fr_b2b.copy(dict( + state_ids=[(4, self.state_fr.id)], + name="EU-VAT-FR-B2B-state", + sequence=70)) george.state_id = self.state_fr + self.assertEquals( + george.state_id, self.state_fr, "Setting state failed") assert_fp(george, self.fr_b2b_zip100, "FR-B2B with zip should have precedence over states") george.zip = 0 + self.assertEquals( + george.state_id, self.state_fr, "State has been reset somehow") assert_fp(george, self.fr_b2b_state, "FR-B2B with states should have precedence") # Dedicated position has max precedence diff --git a/odoo/addons/base/ir/ir_ui_menu.py b/odoo/addons/base/ir/ir_ui_menu.py index 26c56de43297c..5b0760232e967 100644 --- a/odoo/addons/base/ir/ir_ui_menu.py +++ b/odoo/addons/base/ir/ir_ui_menu.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - import base64 +import logging import operator import re from odoo import api, fields, models, tools, _ -from odoo.exceptions import ValidationError +from odoo.exceptions import ValidationError, MissingError from odoo.http import request from odoo.modules import get_module_resource from odoo.tools.safe_eval import safe_eval @@ -15,6 +15,9 @@ NUMBER_PARENS = re.compile(r"\(([0-9]+)\)") +_logger = logging.getLogger(__name__) # pylint: disable=invalid-name + + class IrUiMenu(models.Model): _name = 'ir.ui.menu' _order = "sequence,id" @@ -264,6 +267,10 @@ def load_menus(self, debug): :return: the menu root :rtype: dict('children': menu_nodes) """ + if not tools.config['test_enable']: + # as tests can run during initialization of the database, we + # can't do our check here because the menu isn't initialized yet + self._check_menu_corruption() fields = ['name', 'sequence', 'parent_id', 'action', 'web_icon', 'web_icon_data'] menu_roots = self.get_user_roots() menu_roots_data = menu_roots.read(fields) if menu_roots else [] @@ -301,3 +308,31 @@ def load_menus(self, debug): menu_item.setdefault('children', []).sort(key=operator.itemgetter('sequence')) return menu_root + + def _check_menu_corruption(self): + """Raise error when menu hierarchy has become corrupted.""" + # pylint: disable=invalid-name + STATEMENT = \ + "SELECT COUNT(*) FROM ir_ui_menu" \ + " WHERE NOT parent_id IS NULL" \ + " AND (parent_left is null or parent_right is null)" + self.env.cr.execute(STATEMENT) + count = self.env.cr.fetchone()[0] + if count > 0: + STATEMENT_EXTENDED = \ + "SELECT mn.id, mn.parent_id, mn.name, dt.name, dt.module" \ + " FROM ir_ui_menu mn" \ + " LEFT OUTER JOIN ir_model_data dt" \ + " ON dt.model = 'ir.ui.menu' AND dt.res_id = mn.id" \ + " WHERE NOT parent_id IS NULL" \ + " AND (parent_left is null or parent_right is null)" + self.env.cr.execute(STATEMENT_EXTENDED) + for record in self.env.cr.fetchall(): + _logger.error( + "Menu %s with id %d and parent_id %d is missing link" + " to parent_left and/or parent_right.\n" + "Menu added from %s.%s.", + record[2], record[0], record[1], + record[4] or '?', record[3] or '?') + raise MissingError( + "Menu's have been corrupted. Regenerate parent hierarchy.") diff --git a/odoo/api.py b/odoo/api.py index 413a6b8a2a5d8..163641236e335 100644 --- a/odoo/api.py +++ b/odoo/api.py @@ -46,6 +46,7 @@ ] import logging +import traceback from collections import defaultdict, Mapping from contextlib import contextmanager from inspect import currentframe, getargspec @@ -673,12 +674,20 @@ def call_kw_model(method, self, args, kwargs): return downgrade(method, result, recs, args, kwargs) def call_kw_multi(method, self, args, kwargs): - ids, args = args[0], args[1:] - context, args, kwargs = split_context(method, args, kwargs) - recs = self.with_context(context or {}).browse(ids) - _logger.debug("call %s.%s(%s)", recs, method.__name__, Params(args, kwargs)) - result = method(recs, *args, **kwargs) - return downgrade(method, result, recs, args, kwargs) + try: + ids, args = args[0], args[1:] + context, args, kwargs = split_context(method, args, kwargs) + recs = self.with_context(context or {}).browse(ids) + _logger.debug("call %s.%s(%s)", recs, method.__name__, Params(args, kwargs)) + result = method(recs, *args, **kwargs) + return downgrade(method, result, recs, args, kwargs) + except: + _logger.error(traceback.format_exc()) + _logger.error( + "Unexpected error calling %s, on %s with args=%s and kwargs=%s." % + (method, self, args, kwargs) + ) + raise def call_kw(model, name, args, kwargs): """ Invoke the given method ``name`` on the recordset ``model``. """ diff --git a/odoo/fields.py b/odoo/fields.py index 9a11d8e55ab97..f7a964181c477 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -541,9 +541,17 @@ def _setup_related_full(self, model): # determine the chain of fields, and make sure they are all set up target = model for name in self.related: - field = target._fields[name] - field.setup_full(target) - target = target[name] + try: + field = target._fields[name] + field.setup_full(target) + target = target[name] + except: + _logger.error( + "Problem setting up related field %s for model %s" % + (name, self.model_name) + ) + raise + self.related_field = field diff --git a/odoo/http.py b/odoo/http.py index 5a8f644895657..27f97564ce7bb 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -1343,7 +1343,14 @@ def load_addons(self): manifest['addons_path'] = addons_path _logger.debug("Loading %s", module) if 'odoo.addons' in sys.modules: - m = __import__('odoo.addons.' + module) + try: + m = __import__('odoo.addons.' + module) + except: + _logger.error(traceback.format_exc()) + _logger.error( + "Could not load module %s" % module + ) + m = None else: m = None addons_module[module] = m diff --git a/odoo/models.py b/odoo/models.py index de5fb780cda7e..20f3f3928ba83 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3569,6 +3569,7 @@ def write(self, vals): if unknown: _logger.warning("%s.write() with unknown fields: %s", self._name, ', '.join(sorted(unknown))) + _logger.info("Unknown fields: %s", str(vals)) protected_fields = map(self._fields.get, new_vals) with self.env.protecting(protected_fields, self): @@ -3741,6 +3742,7 @@ def _write(self, vals): if unknown_fields: _logger.warning('No such field(s) in model %s: %s.', self._name, ', '.join(unknown_fields)) + _logger.info("Unknown fields: %s", str(vals)) # check Python constraints self._validate_fields(vals) @@ -3856,6 +3858,7 @@ def create(self, vals): if unknown: _logger.warning("%s.create() includes unknown fields: %s", self._name, ', '.join(sorted(unknown))) + _logger.info("Unknown fields: %s", str(vals)) # create record with old-style fields record = self.browse(self._create(old_vals)) diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index a151759e7418a..aa1119460f0a1 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -144,7 +144,13 @@ def _load_data(cr, module_name, idref, mode, kind): if pre_init: getattr(py_module, pre_init)(cr) - model_names = registry.load(cr, package) + try: + model_names = registry.load(cr, package) + except: + _logger.error( + "Error loading module %s" % package + ) + raise loaded_modules.append(package.name) if needs_update: diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index 60db60cdd50f4..282fa1bd39ff6 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -265,7 +265,21 @@ def load(self, cr, module): model_names = [] for cls in models.MetaModel.module_to_models.get(module.name, []): # models register themselves in self.models - model = cls._build_model(self, cr) + try: + model = cls._build_model(self, cr) + except: + # determine name for model to give clear error message: + # (code adapted from what is done in _build_model method) + parents = getattr(cls, '_inherit', []) + parents = [parents] if isinstance(parents, basestring) \ + else (parents or []) + model_name = ( + cls._name or + (len(parents) == 1 and parents[0]) or + cls.__name__ + ) + _logger.error("Error building model %s" % model_name) + raise model_names.append(model._name) return self.descendants(model_names, '_inherit', '_inherits') @@ -297,7 +311,13 @@ def setup_models(self, cr, partial=False): model._setup_base(partial) for model in models: - model._setup_fields(partial) + try: + model._setup_fields(partial) + except: + _logger.error( + "Error setting up fields for model %s" % model._name + ) + raise for model in models: model._setup_complete() diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index 3025472e0d05e..4bbec442842dc 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -1298,6 +1298,8 @@ def to_sql(self): q2 = stack.pop() stack.append('(%s %s %s)' % (q1, ops[leaf.leaf], q2,)) + if len(stack) <> 1: + _logger.error("Stack is now %s" % stack) assert len(stack) == 1 query = stack[0] joins = ' AND '.join(self.joins)