diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 999431788f8..e13602d6a77 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -110,7 +110,7 @@ class report_xml(osv.osv): kwargs = {} new_report = report_sxw('report.'+r['report_name'], r['model'], opj('addons',r['report_rml'] or '/'), header=r['header'], register=False, **kwargs) - elif r['report_xsl']: + elif r['report_xsl'] and r['report_xml']: new_report = report_rml('report.'+r['report_name'], r['model'], opj('addons',r['report_xml']), r['report_xsl'] and opj('addons',r['report_xsl']), register=False) diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index 79154a35f3b..1b5f5d5d6a4 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -404,8 +404,10 @@ class ir_mail_server(osv.osv): # The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters. from_rfc2822 = extract_rfc2822_addresses(smtp_from) - assert len(from_rfc2822) == 1, "Malformed 'Return-Path' or 'From' address - it may only contain plain ASCII characters" - smtp_from = from_rfc2822[0] + assert from_rfc2822, ("Malformed 'Return-Path' or 'From' address: %r - " + "It should contain one valid plain ASCII email") % smtp_from + # use last extracted email, to support rarities like 'Support@MyComp ' + smtp_from = from_rfc2822[-1] email_to = message['To'] email_cc = message['Cc'] email_bcc = message['Bcc'] diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 9699545ddf5..f46ac13af42 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -21,6 +21,7 @@ import logging from lxml import etree +from operator import itemgetter import os from openerp import tools @@ -127,10 +128,14 @@ class view(osv.osv): try: fvg = self.pool[view.model].fields_view_get(cr, uid, view_id=view.id, view_type=view.type, context=context) return fvg['arch'] - except: + except Exception: return False def _check_xml(self, cr, uid, ids, context=None): + if context is None: + context = {} + context['check_view_ids'] = ids + for view in self.browse(cr, uid, ids, context): # Sanity check: the view should not break anything upon rendering! view_arch_utf8 = self._check_render_view(cr, uid, view, context=context) @@ -183,13 +188,15 @@ class view(osv.osv): :rtype: list of tuples :return: [(view_arch,view_id), ...] """ + user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id) if self.pool._init: # Module init currently in progress, only consider views from modules whose code was already loaded + check_view_ids = context and context.get('check_view_ids') or (0,) query = """SELECT v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) - WHERE v.inherit_id=%s AND v.model=%s AND (md.module IS NULL or md.module in %s) + WHERE v.inherit_id=%s AND v.model=%s AND (md.module in %s OR v.id in %s) ORDER BY priority""" - query_params = (view_id, model, tuple(self.pool._init_modules)) + query_params = (view_id, model, tuple(self.pool._init_modules), tuple(check_view_ids)) else: # Modules fully loaded, consider all views query = """SELECT v.id FROM ir_ui_view v @@ -282,6 +289,22 @@ class view(osv.osv): 'blank_nodes': blank_nodes, 'node_parent_field': _Model_Field,} + def _validate_custom_views(self, cr, uid, model): + """Validate architecture of custom views (= without xml id) for a given model. + This method is called at the end of registry update. + """ + cr.execute("""SELECT max(v.id) + FROM ir_ui_view v + LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) + WHERE md.module IS NULL + AND v.model = %s + GROUP BY coalesce(v.inherit_id, v.id) + """, (model,)) + + ids = map(itemgetter(0), cr.fetchall()) + return self._check_xml(cr, uid, ids) + + class view_sc(osv.osv): _name = 'ir.ui.view_sc' _columns = { diff --git a/openerp/addons/base/tests/__init__.py b/openerp/addons/base/tests/__init__.py index 4c6a1e444f5..0306839b5d3 100644 --- a/openerp/addons/base/tests/__init__.py +++ b/openerp/addons/base/tests/__init__.py @@ -7,6 +7,7 @@ import test_menu import test_res_config import test_res_lang import test_search +import test_views checks = [ test_base, @@ -18,4 +19,5 @@ checks = [ test_res_config, test_res_lang, test_search, + test_views, ] diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py new file mode 100644 index 00000000000..992ef0886da --- /dev/null +++ b/openerp/addons/base/tests/test_views.py @@ -0,0 +1,105 @@ +from functools import partial +import unittest2 + +import openerp.tests.common as common +from openerp.osv.orm import except_orm +from openerp.tools import mute_logger + +class test_views(common.TransactionCase): + + @mute_logger('openerp.osv.orm', 'openerp.addons.base.ir.ir_ui_view') + def test_00_init_check_views(self): + Views = self.registry('ir.ui.view') + + self.assertTrue(Views.pool._init) + + error_msg = "Invalid XML for View Architecture" + # test arch check is call for views without xmlid during registry initialization + with self.assertRaisesRegexp(except_orm, error_msg): + Views.create(self.cr, self.uid, { + 'name': 'Test View #1', + 'model': 'ir.ui.view', + 'arch': """ + + + + """, + }) + + # same for inherited views + with self.assertRaisesRegexp(except_orm, error_msg): + # Views.pudb = True + Views.create(self.cr, self.uid, { + 'name': 'Test View #2', + 'model': 'ir.ui.view', + 'inherit_id': self.browse_ref('base.view_view_tree').id, + 'arch': """ + + + + """, + }) + + def _insert_view(self, **kw): + """Insert view into database via a query to passtrough validation""" + kw.pop('id', None) + + keys = sorted(kw.keys()) + fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys) + params = ','.join('%%(%s)s' % (k,) for k in keys) + + query = 'INSERT INTO ir_ui_view(%s) VALUES(%s) RETURNING id' % (fields, params) + self.cr.execute(query, kw) + return self.cr.fetchone()[0] + + def test_10_validate_custom_views(self): + Views = self.registry('ir.ui.view') + model = 'ir.actions.act_url' + + validate = partial(Views._validate_custom_views, self.cr, self.uid, model) + + # validation of a single view + vid = self._insert_view(**{ + 'name': 'base view', + 'model': model, + 'priority': 1, + 'arch': """ + + + + """, + }) + self.assertTrue(validate()) # single view + + # validation of a inherited view + self._insert_view(**{ + 'name': 'inherited view', + 'model': model, + 'priority': 1, + 'inherit_id': vid, + 'arch': """ + + + + """, + }) + self.assertTrue(validate()) # inherited view + + # validation of a bad inherited view + self._insert_view(**{ + 'name': 'bad inherited view', + 'model': model, + 'priority': 2, + 'inherit_id': vid, + 'arch': """ + + + + """, + }) + with mute_logger('openerp.osv.orm', 'openerp.addons.base.ir.ir_ui_view'): + self.assertFalse(validate()) # bad inherited view + + +if __name__ == '__main__': + unittest2.main() diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 814c1dac5d9..390ac77b2ed 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -400,12 +400,22 @@ def load_modules(db, force_demo=False, status=None, update_module=False): _logger.info('Reloading registry once more after uninstalling modules') return openerp.modules.registry.RegistryManager.new(cr.dbname, force_demo, status, update_module) + # STEP 7: verify custom views on every model + if update_module: + Views = registry['ir.ui.view'] + custom_view_test = True + for model in registry.models.keys(): + if not Views._validate_custom_views(cr, SUPERUSER_ID, model): + custom_view_test = False + _logger.error('invalid custom view(s) for model %s', model) + report.record_result(custom_view_test) + if report.failures: _logger.error('At least one test failed when loading the modules.') else: _logger.info('Modules loaded.') - # STEP 7: call _register_hook on every model + # STEP 8: call _register_hook on every model for model in registry.models.values(): model._register_hook(cr) diff --git a/openerp/modules/migration.py b/openerp/modules/migration.py index 09595336eaa..f199771ba61 100644 --- a/openerp/modules/migration.py +++ b/openerp/modules/migration.py @@ -89,7 +89,7 @@ class MigrationManager(object): 'post': '[%s>]', } - if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'): + if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade') or pkg.installed_version is None: return def convert_version(version): diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2fd31a8b80d..83346cae8b3 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2191,7 +2191,7 @@ class BaseModel(object): are applied """ - sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name) + sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name, context=context) for (view_arch, view_id) in sql_inherit: source = apply_inheritance_specs(source, view_arch, view_id) source = apply_view_inheritance(cr, user, source, view_id) diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index 3c51fa5f3b5..4a055950c46 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -832,8 +832,11 @@ def trans_generate(lang, modules, cr): if module: src_file = open(fabsolutepath, 'r') try: - for lineno, message, comments in extract.extract(extract_method, src_file, - keywords=extract_keywords): + for extracted in extract.extract(extract_method, src_file, + keywords=extract_keywords): + # Babel 0.9.6 yields lineno, message, comments + # Babel 1.3 yields lineno, message, comments, context + lineno, message, comments = extracted[:3] push_translation(module, trans_type, display_path, lineno, encode(message), comments + extra_comments) except Exception: