[IMP] move stuff around, try building flatter top-down API with read_combined as utility 'do all the things' hook
bzr revid: xmo@openerp.com-20130424095114-4nw0nfuxzx4n1ltv
This commit is contained in:
parent
dfc1800305
commit
8715746672
|
@ -165,34 +165,47 @@ class view(osv.osv):
|
|||
if not cr.fetchone():
|
||||
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, fields=None, model=None, context=None):
|
||||
def read_combined(self, cr, uid, view_id, view_type, model,
|
||||
fields=None, context=None):
|
||||
"""
|
||||
Reads the specified view and returns it after applying all inheriting
|
||||
views upon it (essentially reads the "currently final" view)
|
||||
Utility function stringing together all method calls necessary to get
|
||||
a full, final view:
|
||||
|
||||
Returns `False` if the provided id is not a valid view, or if `False`
|
||||
is provided as an id.
|
||||
* 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)
|
||||
* Returns the view with all requested fields
|
||||
|
||||
The `arch` of the view is always read (regardless of its presence in
|
||||
`fields`), and is returned as an lxml tree
|
||||
.. note:: ``arch`` is always added to the fields list even if not
|
||||
requested (similar to ``id``)
|
||||
|
||||
:param list(str) fields: same as in BaseModel.read()
|
||||
:param str model:
|
||||
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.
|
||||
"""
|
||||
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 not view_id: return False
|
||||
views = self.read(cr, uid, [view_id], fields=fields, context=context)
|
||||
if not views: return False
|
||||
|
||||
base_arch = views[0]['arch']
|
||||
base_arch = base_arch.encode('utf-8') if isinstance(base_arch, unicode) else base_arch
|
||||
return dict(views[0], arch=reduce(
|
||||
lambda current_arch, descendant: self.apply_inheritance_specs(
|
||||
cr, uid, model, view_id, current_arch, *descendant, context=context),
|
||||
self.iter(cr, uid, view_id, model, exclude_base=True, context=context),
|
||||
etree.fromstring(base_arch)
|
||||
))
|
||||
[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)
|
||||
|
||||
# TODO: post-processing
|
||||
|
||||
return dict(view, arch=arch)#etree.tostring(arch, 'utf-8'))
|
||||
|
||||
def locate_node(self, arch, spec):
|
||||
""" Locate a node in a source (parent) architecture.
|
||||
|
@ -262,18 +275,18 @@ class view(osv.osv):
|
|||
if not (view.groups_id and user_groups.isdisjoint(view.groups_id))]
|
||||
|
||||
def iter(self, cr, uid, view_id, model, exclude_base=False, context=None):
|
||||
""" iterates on all of `view_id`'s descendants tree depth-first.
|
||||
""" iterates on all of ``view_id``'s descendants tree depth-first.
|
||||
|
||||
If `exclude_base` is `False`, also yields `view_id` itself. It is
|
||||
`False` by default to match the behavior of etree's Element.iter.
|
||||
If ``exclude_base`` is ``False``, also yields ``view_id`` itself. It is
|
||||
``False`` by default to match the behavior of etree's Element.iter.
|
||||
|
||||
:param int view_id: database id of the root view
|
||||
:param str model: name of the view's related model (for filtering)
|
||||
:param boolean exclude_base: whether `view_id` should be excluded from the iteration
|
||||
:param context:
|
||||
:return: iterator of (database_id, arch_field) pairs for all
|
||||
descendants of `view_id` (including `view_id` itself if
|
||||
`exclude_base` is `False`, the default)
|
||||
:param bool exclude_base: whether ``view_id`` should be excluded
|
||||
from the iteration
|
||||
:return: iterator of (database_id, arch_string) pairs for all
|
||||
descendants of ``view_id`` (including ``view_id`` itself if
|
||||
``exclude_base`` is ``False``, the default)
|
||||
"""
|
||||
if not exclude_base:
|
||||
base = self.browse(cr, uid, view_id, context=context)
|
||||
|
@ -286,36 +299,32 @@ class view(osv.osv):
|
|||
cr, uid, id, model, exclude_base=True, context=None):
|
||||
yield info
|
||||
|
||||
def get_root_ancestor(self, cr, uid, view_id=None,
|
||||
model=None, view_type=None, context=None):
|
||||
def default_view(self, cr, uid, model, view_type, context=None):
|
||||
""" Fetches the default view for the provided (model, view_type) pair:
|
||||
view with no parent (inherit_id=Fase) with the lowest priority.
|
||||
|
||||
:param str model:
|
||||
:param int view_type:
|
||||
:return: id of the default view for the (model, view_type) pair
|
||||
:rtype: int
|
||||
"""
|
||||
Fetches the id of the root of the view tree specified by the id or
|
||||
(type, model) parameters.
|
||||
return self.search(cr, uid, [
|
||||
['model', '=', model],
|
||||
['type', '=', view_type],
|
||||
['inherit_id', '=', False],
|
||||
], limit=1, order='priority', context=context)[0]
|
||||
|
||||
def root_ancestor(self, cr, uid, view_id, context=None):
|
||||
"""
|
||||
Fetches the id of the root of the view tree of which view_id is part
|
||||
|
||||
If view_id is specified, view_type and model aren't needed (and the
|
||||
other way around)
|
||||
|
||||
:param view_id: id of view to search the root ancestor of
|
||||
:param str model: model to use the view for
|
||||
:param str view_type: expected view type
|
||||
:return: id of the root view for the tree
|
||||
"""
|
||||
assert view_id or (model and view_type),\
|
||||
"caller must provide either a view_id or a model and a view_type"\
|
||||
" to be able to fetch a root view"
|
||||
|
||||
if not view_id:
|
||||
ids = self.search(cr, uid, [
|
||||
['model', '=', model],
|
||||
['type', '=', view_type],
|
||||
['inherit_id', '=', False],
|
||||
], limit=1, order='priority', context=context)[:1]
|
||||
if not ids: return False
|
||||
[view_id] = ids
|
||||
|
||||
view = self.browse(cr, uid, view_id, context=context)
|
||||
if not view.exists():
|
||||
return False
|
||||
|
||||
# Search for a root (i.e. without any parent) view.
|
||||
while view.inherit_id:
|
||||
|
@ -323,6 +332,25 @@ class view(osv.osv):
|
|||
|
||||
return view.id
|
||||
|
||||
def apply_inherited_archs(self, cr, uid, source, descendants,
|
||||
model, source_view_id, context=None):
|
||||
""" Applies descendants to the ``source`` view, returns the result of
|
||||
the application.
|
||||
|
||||
:param Element source: source arch to apply descendant on
|
||||
:param descendants: iterable of (id, arch_string) pairs of all
|
||||
descendants in the view tree, depth-first,
|
||||
excluding the base view
|
||||
:type descendants: iter((int, str))
|
||||
:return: new architecture etree produced by applying all descendants
|
||||
on ``source``
|
||||
:rtype: Element
|
||||
"""
|
||||
return reduce(
|
||||
lambda current_arch, descendant: self.apply_inheritance_specs(
|
||||
cr, uid, model, source_view_id, current_arch,
|
||||
*descendant, context=context),
|
||||
descendants, source)
|
||||
|
||||
def raise_view_error(self, cr, uid, model, error_msg, view_id, child_view_id, context=None):
|
||||
view, child_view = self.browse(cr, uid, [view_id, child_view_id], context)
|
||||
|
@ -337,11 +365,11 @@ class view(osv.osv):
|
|||
describing where and what changes to apply to some parent
|
||||
architecture) given by an inheriting view.
|
||||
|
||||
:param source: a parent architecture to modify
|
||||
:param Element source: a parent architecture to modify
|
||||
:param descendant_id: the database id of the descendant
|
||||
:param specs_arch: a modifying architecture in an inheriting view
|
||||
:return: a modified source where the specs are applied
|
||||
|
||||
:rtype: Element
|
||||
"""
|
||||
if isinstance(specs_arch, unicode):
|
||||
specs_arch = specs_arch.encode('utf-8')
|
||||
|
|
|
@ -2065,11 +2065,10 @@ class BaseModel(object):
|
|||
if view_ref_res:
|
||||
view_id = view_ref_res[0]
|
||||
|
||||
root_view_id = View.get_root_ancestor(
|
||||
cr, user, view_id, self._name, view_type, context=context)
|
||||
root_view = View.read_combined(cr, user, root_view_id, fields=[
|
||||
'id', 'name', 'field_parent', 'type', 'model', 'arch'
|
||||
], model=self._name, context=context)
|
||||
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'],
|
||||
|
|
|
@ -2,6 +2,7 @@ from lxml import etree as ET
|
|||
from lxml.builder import E
|
||||
|
||||
from . import common
|
||||
import unittest2
|
||||
|
||||
Field = E.field
|
||||
|
||||
|
@ -96,14 +97,14 @@ class TestNodeLocator(common.BaseCase):
|
|||
self.assertIsNone(node)
|
||||
|
||||
class TestViewInheritance(common.TransactionCase):
|
||||
def view_for(self, name):
|
||||
return ET.tostring(ET.Element('form', string=name))
|
||||
def arch_for(self, name, view_type='form'):
|
||||
return ET.tostring(ET.Element(view_type, string=name))
|
||||
|
||||
def makeView(self, name, parent=None):
|
||||
def makeView(self, name, parent=None, arch=None):
|
||||
view_id = self.View.create(self.cr, self.uid, {
|
||||
'model': self.model,
|
||||
'name': name,
|
||||
'arch': self.view_for(name),
|
||||
'arch': arch or self.arch_for(name),
|
||||
'inherit_id': parent,
|
||||
})
|
||||
self.ids[name] = view_id
|
||||
|
@ -128,15 +129,20 @@ class TestViewInheritance(common.TransactionCase):
|
|||
a22 = self.makeView("A22", a2)
|
||||
self.makeView("A221", a22)
|
||||
|
||||
b = self.makeView('B', arch=self.arch_for("B", 'tree'))
|
||||
self.makeView('B1', b, arch=self.arch_for("B1", 'tree'))
|
||||
c = self.makeView('C', arch=self.arch_for("C", 'tree'))
|
||||
self.View.write(self.cr, self.uid, c, {'priority': 1})
|
||||
|
||||
def tearDown(self):
|
||||
self.View.pool._init = self._init
|
||||
super(TestViewInheritance, self).tearDown()
|
||||
|
||||
def test_get_children(self):
|
||||
def test_get_inheriting_views_arch(self):
|
||||
self.assertEqual(self.View.get_inheriting_views_arch(
|
||||
self.cr, self.uid, self.ids['A'], self.model), [
|
||||
(self.view_for('A1'), self.ids['A1']),
|
||||
(self.view_for('A2'), self.ids['A2']),
|
||||
(self.arch_for('A1'), self.ids['A1']),
|
||||
(self.arch_for('A2'), self.ids['A2']),
|
||||
])
|
||||
|
||||
self.assertEqual(self.View.get_inheriting_views_arch(
|
||||
|
@ -145,49 +151,69 @@ class TestViewInheritance(common.TransactionCase):
|
|||
|
||||
self.assertEqual(self.View.get_inheriting_views_arch(
|
||||
self.cr, self.uid, self.ids['A11'], self.model),
|
||||
[(self.view_for('A111'), self.ids['A111'])])
|
||||
[(self.arch_for('A111'), self.ids['A111'])])
|
||||
|
||||
def test_iterate_descendents(self):
|
||||
def test_iter(self):
|
||||
descendents = list(self.View.iter(self.cr, self.uid, self.ids['A1'], self.model))
|
||||
self.assertEqual(descendents, [
|
||||
(self.ids[name], self.view_for(name))
|
||||
(self.ids[name], self.arch_for(name))
|
||||
for name in ['A1', 'A11', 'A111', 'A12']
|
||||
])
|
||||
descendents = list(self.View.iter(
|
||||
self.cr, self.uid, self.ids['A2'], self.model, exclude_base=True))
|
||||
self.assertEqual(descendents, [
|
||||
(self.ids[name], self.view_for(name))
|
||||
(self.ids[name], self.arch_for(name))
|
||||
for name in ['A21', 'A22', 'A221']
|
||||
])
|
||||
|
||||
def test_find_root(self):
|
||||
def test_root_ancestor(self):
|
||||
A_id = self.ids['A']
|
||||
root_id = self.View.get_root_ancestor(self.cr, self.uid, view_id=A_id)
|
||||
root_id = self.View.root_ancestor(self.cr, self.uid, view_id=A_id)
|
||||
self.assertEqual(root_id, A_id,
|
||||
"when given a root view, operation should be id")
|
||||
|
||||
root_id = self.View.get_root_ancestor(
|
||||
root_id = self.View.root_ancestor(
|
||||
self.cr, self.uid, view_id=self.ids['A11'])
|
||||
self.assertEqual(root_id, A_id)
|
||||
|
||||
root_id = self.View.get_root_ancestor(
|
||||
root_id = self.View.root_ancestor(
|
||||
self.cr, self.uid, view_id=self.ids['A221'])
|
||||
self.assertEqual(root_id, A_id)
|
||||
|
||||
# search by model
|
||||
root_id = self.View.get_root_ancestor(
|
||||
self.cr, self.uid, model=self.model, view_type='form')
|
||||
self.assertEqual(root_id, A_id)
|
||||
root_id = self.View.root_ancestor(
|
||||
self.cr, self.uid, view_id=self.ids['B1'])
|
||||
self.assertEqual(root_id, self.ids['B'])
|
||||
|
||||
def test_no_root(self):
|
||||
root = self.View.get_root_ancestor(
|
||||
self.cr, self.uid, model='does.not.exist', view_type='form')
|
||||
self.assertFalse(root)
|
||||
|
||||
root = self.View.get_root_ancestor(
|
||||
self.cr, self.uid, model=self.model, view_type='tree')
|
||||
self.assertFalse(root)
|
||||
|
||||
root = self.View.get_root_ancestor(
|
||||
@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)
|
||||
|
||||
def test_default_view(self):
|
||||
default = self.View.default_view(
|
||||
self.cr, self.uid, model=self.model, view_type='form')
|
||||
self.assertEqual(default, self.ids['A'])
|
||||
|
||||
default_tree = self.View.default_view(
|
||||
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)
|
||||
|
||||
default = self.View.default_view(
|
||||
self.cr, self.uid, model=self.model, view_type='graph')
|
||||
self.assertFalse(default)
|
||||
|
||||
class TestViewCombined(common.TransactionCase):
|
||||
"""
|
||||
Test fallback operations of View.read_combined:
|
||||
* defaults mapping
|
||||
* ?
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue