[ADD] behavior and tests for default & ancestors, fallback handling in read_combined

bzr revid: xmo@openerp.com-20130424130907-6d6lkrkrgqv0hsfq
This commit is contained in:
Xavier Morel 2013-04-24 15:09:07 +02:00
parent 8715746672
commit fb492d98ba
3 changed files with 101 additions and 64 deletions

View File

@ -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:

View File

@ -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

View File

@ -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):
"""