From fb492d98baec3ac8ebc06930801ae6736b109c79 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 24 Apr 2013 15:09:07 +0200 Subject: [PATCH] [ADD] behavior and tests for default & ancestors, fallback handling in read_combined bzr revid: xmo@openerp.com-20130424130907-6d6lkrkrgqv0hsfq --- openerp/addons/base/ir/ir_ui_view.py | 71 ++++++++++++++++++++-------- openerp/osv/orm.py | 65 +++++++++++++------------ openerp/tests/test_views.py | 29 +++++++----- 3 files changed, 101 insertions(+), 64 deletions(-) diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 259405288d9..c91245930b0 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -29,6 +29,7 @@ from openerp import tools from openerp.osv import fields,osv from openerp.tools import graph, SKIPPED_ELEMENT_TYPES from openerp.tools.safe_eval import safe_eval as eval +from openerp.tools.translate import _ from openerp.tools.view_validation import valid_view _logger = logging.getLogger(__name__) @@ -51,6 +52,9 @@ class view_custom(osv.osv): class view(osv.osv): _name = 'ir.ui.view' + class NoViewError(Exception): pass + class NoDefaultError(NoViewError): pass + def _type_field(self, cr, uid, ids, name, args, context=None): result = {} for record in self.browse(cr, uid, ids, context): @@ -166,7 +170,7 @@ class view(osv.osv): cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)') def read_combined(self, cr, uid, view_id, view_type, model, - fields=None, context=None): + fields=None, fallback=None, context=None): """ Utility function stringing together all method calls necessary to get a full, final view: @@ -174,8 +178,7 @@ class view(osv.osv): * Gets the default view if no view_id is provided * Gets the top of the view tree if a sub-view is requested * Applies all inherited archs on the root view - * Applies post-processing both generic (ir.ui.view) and specific - (ir.ui.view.$type) + * Applies post-processing * Returns the view with all requested fields .. note:: ``arch`` is always added to the fields list even if not @@ -183,29 +186,47 @@ class view(osv.osv): If no view is available (no view_id or invalid view_id provided, or no view stored for (model, view_type)) a view record will be fetched - from the ``defaults`` mapping. + from the ``defaults`` mapping? + + :param fallback: a mapping of {view_type: view_dict}, if no view can + be found (read) will be used to provide a default + before post-processing + :type fallback: mapping """ - if not view_id: - view_id = self.default_view(cr, uid, model, view_type, context=context) - root_id = self.root_ancestor(cr, uid, view_id, context=context) + if context is None: context = {} + try: + if not view_id: + view_id = self.default_view(cr, uid, model, view_type, context=context) + root_id = self.root_ancestor(cr, uid, view_id, context=context) - if fields and 'arch' not in fields: - fields = list(itertools.chain(['arch'], fields)) + if fields and 'arch' not in fields: + fields = list(itertools.chain(['arch'], fields)) - [view] = self.read(cr, uid, [root_id], fields=fields, context=context) + [view] = self.read(cr, uid, [root_id], fields=fields, context=context) - arch_tree = etree.fromstring( - view['arch'].encode('utf-8') if isinstance(view['arch'], unicode) - else view['arch']) - descendants = self.iter( - cr, uid, view['id'], model, exclude_base=True, context=context) - arch = self.apply_inherited_archs( - cr, uid, arch_tree, descendants, - model, view['id'], context=context) + arch_tree = etree.fromstring( + view['arch'].encode('utf-8') if isinstance(view['arch'], unicode) + else view['arch']) + descendants = self.iter( + cr, uid, view['id'], model, exclude_base=True, context=context) + arch = self.apply_inherited_archs( + cr, uid, arch_tree, descendants, + model, view['id'], context=context) + + if view['model'] != model: + context = dict(context, base_model_name=view['model']) + except self.NoViewError: + # defaultdict is "empty" until first __getattr__ + if fallback is None: raise + view = fallback[view_type] + arch = view['arch'] + if isinstance(arch, basestring): + arch = etree.fromstring( + arch.encode('utf-8') if isinstance(arch, unicode) else arch) # TODO: post-processing - return dict(view, arch=arch)#etree.tostring(arch, 'utf-8')) + return dict(view, arch=etree.tostring(arch, encoding='utf-8')) def locate_node(self, arch, spec): """ Locate a node in a source (parent) architecture. @@ -308,11 +329,16 @@ class view(osv.osv): :return: id of the default view for the (model, view_type) pair :rtype: int """ - return self.search(cr, uid, [ + ids = self.search(cr, uid, [ ['model', '=', model], ['type', '=', view_type], ['inherit_id', '=', False], - ], limit=1, order='priority', context=context)[0] + ], limit=1, order='priority', context=context) + if not ids: + raise self.NoDefaultError( + _("No default view of type %s for model %s") % ( + view_type, model)) + return ids[0] def root_ancestor(self, cr, uid, view_id, context=None): """ @@ -325,6 +351,9 @@ class view(osv.osv): :return: id of the root view for the tree """ view = self.browse(cr, uid, view_id, context=context) + if not view.exists(): + raise self.NoViewError( + _("No view for id %s, root ancestor not available") % view_id) # Search for a root (i.e. without any parent) view. while view.inherit_id: diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 1794a6646fc..ec161ad8573 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -631,6 +631,27 @@ class MetaModel(type): if not self._custom: self.module_to_models.setdefault(self._module, []).append(self) +class FallbackViewMapping(collections.defaultdict): + def __init__(self, cr, uid, model, context=None): + super(FallbackViewMapping, self).__init__() + self.cr = cr + self.uid = uid + self.model = model + self.context = context + + def __missing__(self, view_type): + try: + arch = getattr(self.model, '_get_default_%s_view' % view_type)( + self.cr, self.uid, self.context) + except AttributeError: + raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) + return { + 'id': 0, + 'type': view_type, + 'name': 'default', + 'field_parent': False, + 'arch': arch, + } # Definition of log access columns, automatically added to models if # self._log_access is True @@ -2054,8 +2075,6 @@ class BaseModel(object): context = {} View = self.pool['ir.ui.view'] - result = {'type': view_type, 'model': self._name} - view_ref = context.get(view_type + '_view_ref') if view_ref and not view_id and '.' in view_ref: @@ -2068,37 +2087,23 @@ class BaseModel(object): root_view = View.read_combined( cr, user, view_id, view_type, self._name, fields=[ 'id', 'name', 'field_parent', 'type', 'model', 'arch' - ], context=context) - if root_view: - result.update( - arch=root_view['arch'], - type=root_view['type'], - view_id=root_view['id'], - name=root_view['name'], - field_parent=root_view['field_parent'] or False) - else: - # otherwise, build some kind of default view - try: - view = getattr(self, '_get_default_%s_view' % view_type)( - cr, user, context) - except AttributeError: - # what happens here, graph case? - raise except_orm(_('Invalid Architecture!'), _("There is no view of type '%s' defined for the structure!") % view_type) + ], fallback=FallbackViewMapping(cr, user, self, context=context), + context=context) - result.update( - arch=view, - name='default', - field_parent=False, - view_id=0) + result = { + 'model': self._name, + 'arch': root_view['arch'], + 'type': root_view['type'], + 'view_id': root_view['id'], + 'name': root_view['name'], + 'field_parent': root_view['field_parent'] or False + } - parent_view_model = root_view['model'] if root_view else None - if parent_view_model != self._name: - ctx = context.copy() - ctx['base_model_name'] = parent_view_model - else: - ctx = context + ctx = context + if root_view.get('model') != self._name: + ctx = dict(context, base_model_name=root_view.get('model')) xarch, xfields = self.__view_look_dom_arch( - cr, user, result['arch'], result['view_id'], context=ctx) + cr, user, etree.fromstring(result['arch']), result['view_id'], context=ctx) result['arch'] = xarch result['fields'] = xfields diff --git a/openerp/tests/test_views.py b/openerp/tests/test_views.py index 6a555ed089b..bedb3e78b19 100644 --- a/openerp/tests/test_views.py +++ b/openerp/tests/test_views.py @@ -184,12 +184,9 @@ class TestViewInheritance(common.TransactionCase): self.cr, self.uid, view_id=self.ids['B1']) self.assertEqual(root_id, self.ids['B']) - @unittest2.skip("What should the behavior be when no ancestor is found " - "because view_id is invalid?") def test_no_root_ancestor(self): - root = self.View.root_ancestor( - self.cr, self.uid, view_id=12345678) - self.assertFalse(root) + with self.assertRaises(self.View.NoViewError): + self.View.root_ancestor(self.cr, self.uid, view_id=12345678) def test_default_view(self): default = self.View.default_view( @@ -200,16 +197,22 @@ class TestViewInheritance(common.TransactionCase): self.cr, self.uid, model=self.model, view_type='tree') self.assertEqual(default_tree, self.ids['C']) - @unittest2.skip("What should the behavior be when no default is found " - "because model does not exist or no view for model?") def test_no_default_view(self): - default = self.View.default_view( - self.cr, self.uid, model='does.not.exist', view_type='form') - self.assertFalse(default) + with self.assertRaises(self.View.NoDefaultError): + self.View.default_view( + self.cr, self.uid, model='does.not.exist', view_type='form') - default = self.View.default_view( - self.cr, self.uid, model=self.model, view_type='graph') - self.assertFalse(default) + with self.assertRaises(self.View.NoDefaultError): + self.View.default_view( + self.cr, self.uid, model=self.model, view_type='graph') + + @unittest2.skip("Not tested") + def test_apply_inherited_archs(self): + self.fail() + + @unittest2.skip("Not tested") + def test_apply_inheritance_specs(self): + self.fail() class TestViewCombined(common.TransactionCase): """