Merge pull request #228 from xmo-odoo/extended-view-inheritance
Extended view inheritance
This commit is contained in:
commit
05fe3ca7e3
|
@ -141,8 +141,23 @@ class view(osv.osv):
|
||||||
'model_ids': fields.one2many('ir.model.data', 'res_id', domain=[('model','=','ir.ui.view')], auto_join=True),
|
'model_ids': fields.one2many('ir.model.data', 'res_id', domain=[('model','=','ir.ui.view')], auto_join=True),
|
||||||
'create_date': fields.datetime('Create Date', readonly=True),
|
'create_date': fields.datetime('Create Date', readonly=True),
|
||||||
'write_date': fields.datetime('Last Modification Date', readonly=True),
|
'write_date': fields.datetime('Last Modification Date', readonly=True),
|
||||||
|
|
||||||
|
'mode': fields.selection(
|
||||||
|
[('primary', "Base view"), ('extension', "Extension View")],
|
||||||
|
string="View inheritance mode", required=True,
|
||||||
|
help="""Only applies if this view inherits from an other one (inherit_id is not False/Null).
|
||||||
|
|
||||||
|
* if extension (default), if this view is requested the closest primary view
|
||||||
|
is looked up (via inherit_id), then all views inheriting from it with this
|
||||||
|
view's model are applied
|
||||||
|
* if primary, the closest primary view is fully resolved (even if it uses a
|
||||||
|
different model than this one), then this view's inheritance specs
|
||||||
|
(<xpath/>) are applied, and the result is used as if it were this view's
|
||||||
|
actual arch.
|
||||||
|
"""),
|
||||||
}
|
}
|
||||||
_defaults = {
|
_defaults = {
|
||||||
|
'mode': 'primary',
|
||||||
'priority': 16,
|
'priority': 16,
|
||||||
}
|
}
|
||||||
_order = "priority,name"
|
_order = "priority,name"
|
||||||
|
@ -191,8 +206,14 @@ class view(osv.osv):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
('inheritance_mode',
|
||||||
|
"CHECK (mode != 'extension' OR inherit_id IS NOT NULL)",
|
||||||
|
"Invalid inheritance mode: if the mode is 'extension', the view must"
|
||||||
|
" extend an other view"),
|
||||||
|
]
|
||||||
_constraints = [
|
_constraints = [
|
||||||
(_check_xml, 'Invalid view definition', ['arch'])
|
(_check_xml, 'Invalid view definition', ['arch']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def _auto_init(self, cr, context=None):
|
def _auto_init(self, cr, context=None):
|
||||||
|
@ -201,6 +222,12 @@ class view(osv.osv):
|
||||||
if not cr.fetchone():
|
if not cr.fetchone():
|
||||||
cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)')
|
cr.execute('CREATE INDEX ir_ui_view_model_type_inherit_id ON ir_ui_view (model, inherit_id)')
|
||||||
|
|
||||||
|
def _compute_defaults(self, cr, uid, values, context=None):
|
||||||
|
if 'inherit_id' in values:
|
||||||
|
values.setdefault(
|
||||||
|
'mode', 'extension' if values['inherit_id'] else 'primary')
|
||||||
|
return values
|
||||||
|
|
||||||
def create(self, cr, uid, values, context=None):
|
def create(self, cr, uid, values, context=None):
|
||||||
if 'type' not in values:
|
if 'type' not in values:
|
||||||
if values.get('inherit_id'):
|
if values.get('inherit_id'):
|
||||||
|
@ -209,10 +236,13 @@ class view(osv.osv):
|
||||||
values['type'] = etree.fromstring(values['arch']).tag
|
values['type'] = etree.fromstring(values['arch']).tag
|
||||||
|
|
||||||
if not values.get('name'):
|
if not values.get('name'):
|
||||||
values['name'] = "%s %s" % (values['model'], values['type'])
|
values['name'] = "%s %s" % (values.get('model'), values['type'])
|
||||||
|
|
||||||
self.read_template.clear_cache(self)
|
self.read_template.clear_cache(self)
|
||||||
return super(view, self).create(cr, uid, values, context)
|
return super(view, self).create(
|
||||||
|
cr, uid,
|
||||||
|
self._compute_defaults(cr, uid, values, context=context),
|
||||||
|
context=context)
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
def write(self, cr, uid, ids, vals, context=None):
|
||||||
if not isinstance(ids, (list, tuple)):
|
if not isinstance(ids, (list, tuple)):
|
||||||
|
@ -227,7 +257,10 @@ class view(osv.osv):
|
||||||
self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids)
|
self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids)
|
||||||
|
|
||||||
self.read_template.clear_cache(self)
|
self.read_template.clear_cache(self)
|
||||||
ret = super(view, self).write(cr, uid, ids, vals, context)
|
ret = super(view, self).write(
|
||||||
|
cr, uid, ids,
|
||||||
|
self._compute_defaults(cr, uid, vals, context=context),
|
||||||
|
context)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def copy(self, cr, uid, id, default=None, context=None):
|
def copy(self, cr, uid, id, default=None, context=None):
|
||||||
|
@ -241,7 +274,7 @@ class view(osv.osv):
|
||||||
# default view selection
|
# default view selection
|
||||||
def default_view(self, cr, uid, model, view_type, context=None):
|
def default_view(self, cr, uid, model, view_type, context=None):
|
||||||
""" Fetches the default view for the provided (model, view_type) pair:
|
""" Fetches the default view for the provided (model, view_type) pair:
|
||||||
view with no parent (inherit_id=Fase) with the lowest priority.
|
primary view with the lowest priority.
|
||||||
|
|
||||||
:param str model:
|
:param str model:
|
||||||
:param int view_type:
|
:param int view_type:
|
||||||
|
@ -251,7 +284,7 @@ class view(osv.osv):
|
||||||
domain = [
|
domain = [
|
||||||
['model', '=', model],
|
['model', '=', model],
|
||||||
['type', '=', view_type],
|
['type', '=', view_type],
|
||||||
['inherit_id', '=', False],
|
['mode', '=', 'primary'],
|
||||||
]
|
]
|
||||||
ids = self.search(cr, uid, domain, limit=1, context=context)
|
ids = self.search(cr, uid, domain, limit=1, context=context)
|
||||||
if not ids:
|
if not ids:
|
||||||
|
@ -278,15 +311,18 @@ class view(osv.osv):
|
||||||
user = self.pool['res.users'].browse(cr, 1, uid, context=context)
|
user = self.pool['res.users'].browse(cr, 1, uid, context=context)
|
||||||
user_groups = frozenset(user.groups_id or ())
|
user_groups = frozenset(user.groups_id or ())
|
||||||
|
|
||||||
check_view_ids = context and context.get('check_view_ids') or (0,)
|
conditions = [
|
||||||
conditions = [['inherit_id', '=', view_id], ['model', '=', model]]
|
['inherit_id', '=', view_id],
|
||||||
|
['model', '=', model],
|
||||||
|
['mode', '=', 'extension'],
|
||||||
|
]
|
||||||
if self.pool._init:
|
if self.pool._init:
|
||||||
# Module init currently in progress, only consider views from
|
# Module init currently in progress, only consider views from
|
||||||
# modules whose code is already loaded
|
# modules whose code is already loaded
|
||||||
conditions.extend([
|
conditions.extend([
|
||||||
'|',
|
'|',
|
||||||
['model_ids.module', 'in', tuple(self.pool._init_modules)],
|
['model_ids.module', 'in', tuple(self.pool._init_modules)],
|
||||||
['id', 'in', check_view_ids],
|
['id', 'in', context and context.get('check_view_ids') or (0,)],
|
||||||
])
|
])
|
||||||
view_ids = self.search(cr, uid, conditions, context=context)
|
view_ids = self.search(cr, uid, conditions, context=context)
|
||||||
|
|
||||||
|
@ -442,7 +478,7 @@ class view(osv.osv):
|
||||||
if context is None: context = {}
|
if context is None: context = {}
|
||||||
if root_id is None:
|
if root_id is None:
|
||||||
root_id = source_id
|
root_id = source_id
|
||||||
sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, uid, source_id, model, context=context)
|
sql_inherit = self.pool['ir.ui.view'].get_inheriting_views_arch(cr, uid, source_id, model, context=context)
|
||||||
for (specs, view_id) in sql_inherit:
|
for (specs, view_id) in sql_inherit:
|
||||||
specs_tree = etree.fromstring(specs.encode('utf-8'))
|
specs_tree = etree.fromstring(specs.encode('utf-8'))
|
||||||
if context.get('inherit_branding'):
|
if context.get('inherit_branding'):
|
||||||
|
@ -465,7 +501,7 @@ class view(osv.osv):
|
||||||
|
|
||||||
# if view_id is not a root view, climb back to the top.
|
# if view_id is not a root view, climb back to the top.
|
||||||
base = v = self.browse(cr, uid, view_id, context=context)
|
base = v = self.browse(cr, uid, view_id, context=context)
|
||||||
while v.inherit_id:
|
while v.mode != 'primary':
|
||||||
v = v.inherit_id
|
v = v.inherit_id
|
||||||
root_id = v.id
|
root_id = v.id
|
||||||
|
|
||||||
|
@ -475,7 +511,16 @@ class view(osv.osv):
|
||||||
|
|
||||||
# read the view arch
|
# read the view arch
|
||||||
[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'))
|
view_arch = etree.fromstring(view['arch'].encode('utf-8'))
|
||||||
|
if not v.inherit_id:
|
||||||
|
arch_tree = view_arch
|
||||||
|
else:
|
||||||
|
parent_view = self.read_combined(
|
||||||
|
cr, uid, v.inherit_id.id, fields=fields, context=context)
|
||||||
|
arch_tree = etree.fromstring(parent_view['arch'])
|
||||||
|
self.apply_inheritance_specs(
|
||||||
|
cr, uid, arch_tree, view_arch, parent_view['id'], context=context)
|
||||||
|
|
||||||
|
|
||||||
if context.get('inherit_branding'):
|
if context.get('inherit_branding'):
|
||||||
arch_tree.attrib.update({
|
arch_tree.attrib.update({
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
import itertools
|
||||||
|
|
||||||
import unittest2
|
import unittest2
|
||||||
|
|
||||||
from lxml import etree as ET
|
from lxml import etree as ET
|
||||||
from lxml.builder import E
|
from lxml.builder import E
|
||||||
|
|
||||||
|
from psycopg2 import IntegrityError
|
||||||
|
|
||||||
from openerp.tests import common
|
from openerp.tests import common
|
||||||
|
import openerp.tools
|
||||||
|
|
||||||
Field = E.field
|
Field = E.field
|
||||||
|
|
||||||
|
@ -14,9 +18,15 @@ class ViewCase(common.TransactionCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ViewCase, self).setUp()
|
super(ViewCase, self).setUp()
|
||||||
self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual)
|
self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual)
|
||||||
|
self.Views = self.registry('ir.ui.view')
|
||||||
|
|
||||||
|
def browse(self, id, context=None):
|
||||||
|
return self.Views.browse(self.cr, self.uid, id, context=context)
|
||||||
|
def create(self, value, context=None):
|
||||||
|
return self.Views.create(self.cr, self.uid, value, context=context)
|
||||||
|
|
||||||
def assertTreesEqual(self, n1, n2, msg=None):
|
def assertTreesEqual(self, n1, n2, msg=None):
|
||||||
self.assertEqual(n1.tag, n2.tag)
|
self.assertEqual(n1.tag, n2.tag, msg)
|
||||||
self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg)
|
self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg)
|
||||||
self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg)
|
self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg)
|
||||||
|
|
||||||
|
@ -24,8 +34,8 @@ class ViewCase(common.TransactionCase):
|
||||||
# equality (!?!?!?!)
|
# equality (!?!?!?!)
|
||||||
self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
|
self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
|
||||||
|
|
||||||
for c1, c2 in zip(n1, n2):
|
for c1, c2 in itertools.izip_longest(n1, n2):
|
||||||
self.assertTreesEqual(c1, c2, msg)
|
self.assertEqual(c1, c2, msg)
|
||||||
|
|
||||||
|
|
||||||
class TestNodeLocator(common.TransactionCase):
|
class TestNodeLocator(common.TransactionCase):
|
||||||
|
@ -374,13 +384,6 @@ class TestApplyInheritedArchs(ViewCase):
|
||||||
""" Applies a sequence of modificator archs to a base view
|
""" Applies a sequence of modificator archs to a base view
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class TestViewCombined(ViewCase):
|
|
||||||
"""
|
|
||||||
Test fallback operations of View.read_combined:
|
|
||||||
* defaults mapping
|
|
||||||
* ?
|
|
||||||
"""
|
|
||||||
|
|
||||||
class TestNoModel(ViewCase):
|
class TestNoModel(ViewCase):
|
||||||
def test_create_view_nomodel(self):
|
def test_create_view_nomodel(self):
|
||||||
View = self.registry('ir.ui.view')
|
View = self.registry('ir.ui.view')
|
||||||
|
@ -628,6 +631,7 @@ class test_views(ViewCase):
|
||||||
def _insert_view(self, **kw):
|
def _insert_view(self, **kw):
|
||||||
"""Insert view into database via a query to passtrough validation"""
|
"""Insert view into database via a query to passtrough validation"""
|
||||||
kw.pop('id', None)
|
kw.pop('id', None)
|
||||||
|
kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary')
|
||||||
|
|
||||||
keys = sorted(kw.keys())
|
keys = sorted(kw.keys())
|
||||||
fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys)
|
fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys)
|
||||||
|
@ -805,6 +809,273 @@ class test_views(ViewCase):
|
||||||
string="Replacement title", version="7.0"
|
string="Replacement title", version="7.0"
|
||||||
))
|
))
|
||||||
|
|
||||||
|
class ViewModeField(ViewCase):
|
||||||
|
"""
|
||||||
|
This should probably, eventually, be folded back into other test case
|
||||||
|
classes, integrating the test (or not) of the mode field to regular cases
|
||||||
|
"""
|
||||||
|
|
||||||
|
def testModeImplicitValue(self):
|
||||||
|
""" mode is auto-generated from inherit_id:
|
||||||
|
* inherit_id -> mode=extension
|
||||||
|
* not inherit_id -> mode=primary
|
||||||
|
"""
|
||||||
|
view = self.browse(self.create({
|
||||||
|
'inherit_id': None,
|
||||||
|
'arch': '<qweb/>'
|
||||||
|
}))
|
||||||
|
self.assertEqual(view.mode, 'primary')
|
||||||
|
|
||||||
|
view2 = self.browse(self.create({
|
||||||
|
'inherit_id': view.id,
|
||||||
|
'arch': '<qweb/>'
|
||||||
|
}))
|
||||||
|
self.assertEqual(view2.mode, 'extension')
|
||||||
|
|
||||||
|
@openerp.tools.mute_logger('openerp.sql_db')
|
||||||
|
def testModeExplicit(self):
|
||||||
|
view = self.browse(self.create({
|
||||||
|
'inherit_id': None,
|
||||||
|
'arch': '<qweb/>'
|
||||||
|
}))
|
||||||
|
view2 = self.browse(self.create({
|
||||||
|
'inherit_id': view.id,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<qweb/>'
|
||||||
|
}))
|
||||||
|
self.assertEqual(view.mode, 'primary')
|
||||||
|
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
self.create({
|
||||||
|
'inherit_id': None,
|
||||||
|
'mode': 'extension',
|
||||||
|
'arch': '<qweb/>'
|
||||||
|
})
|
||||||
|
|
||||||
|
@openerp.tools.mute_logger('openerp.sql_db')
|
||||||
|
def testPurePrimaryToExtension(self):
|
||||||
|
"""
|
||||||
|
A primary view with inherit_id=None can't be converted to extension
|
||||||
|
"""
|
||||||
|
view_pure_primary = self.browse(self.create({
|
||||||
|
'inherit_id': None,
|
||||||
|
'arch': '<qweb/>'
|
||||||
|
}))
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
view_pure_primary.write({'mode': 'extension'})
|
||||||
|
|
||||||
|
def testInheritPrimaryToExtension(self):
|
||||||
|
"""
|
||||||
|
A primary view with an inherit_id can be converted to extension
|
||||||
|
"""
|
||||||
|
base = self.create({'inherit_id': None, 'arch': '<qweb/>'})
|
||||||
|
view = self.browse(self.create({
|
||||||
|
'inherit_id': base,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<qweb/>'
|
||||||
|
}))
|
||||||
|
|
||||||
|
view.write({'mode': 'extension'})
|
||||||
|
|
||||||
|
def testDefaultExtensionToPrimary(self):
|
||||||
|
"""
|
||||||
|
An extension view can be converted to primary
|
||||||
|
"""
|
||||||
|
base = self.create({'inherit_id': None, 'arch': '<qweb/>'})
|
||||||
|
view = self.browse(self.create({
|
||||||
|
'inherit_id': base,
|
||||||
|
'arch': '<qweb/>'
|
||||||
|
}))
|
||||||
|
|
||||||
|
view.write({'mode': 'primary'})
|
||||||
|
|
||||||
|
class TestDefaultView(ViewCase):
|
||||||
|
def testDefaultViewBase(self):
|
||||||
|
self.create({
|
||||||
|
'inherit_id': False,
|
||||||
|
'priority': 10,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<qweb/>',
|
||||||
|
})
|
||||||
|
v2 = self.create({
|
||||||
|
'inherit_id': False,
|
||||||
|
'priority': 1,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<qweb/>',
|
||||||
|
})
|
||||||
|
|
||||||
|
default = self.Views.default_view(self.cr, self.uid, False, 'qweb')
|
||||||
|
self.assertEqual(
|
||||||
|
default, v2,
|
||||||
|
"default_view should get the view with the lowest priority for "
|
||||||
|
"a (model, view_type) pair"
|
||||||
|
)
|
||||||
|
|
||||||
|
def testDefaultViewPrimary(self):
|
||||||
|
v1 = self.create({
|
||||||
|
'inherit_id': False,
|
||||||
|
'priority': 10,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<qweb/>',
|
||||||
|
})
|
||||||
|
self.create({
|
||||||
|
'inherit_id': False,
|
||||||
|
'priority': 5,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<qweb/>',
|
||||||
|
})
|
||||||
|
v3 = self.create({
|
||||||
|
'inherit_id': v1,
|
||||||
|
'priority': 1,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<qweb/>',
|
||||||
|
})
|
||||||
|
|
||||||
|
default = self.Views.default_view(self.cr, self.uid, False, 'qweb')
|
||||||
|
self.assertEqual(
|
||||||
|
default, v3,
|
||||||
|
"default_view should get the view with the lowest priority for "
|
||||||
|
"a (model, view_type) pair in all the primary tables"
|
||||||
|
)
|
||||||
|
|
||||||
|
class TestViewCombined(ViewCase):
|
||||||
|
"""
|
||||||
|
* When asked for a view, instead of looking for the closest parent with
|
||||||
|
inherit_id=False look for mode=primary
|
||||||
|
* If root.inherit_id, resolve the arch for root.inherit_id (?using which
|
||||||
|
model?), then apply root's inheritance specs to it
|
||||||
|
* Apply inheriting views on top
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestViewCombined, self).setUp()
|
||||||
|
|
||||||
|
self.a1 = self.create({
|
||||||
|
'model': 'a',
|
||||||
|
'arch': '<qweb><a1/></qweb>'
|
||||||
|
})
|
||||||
|
self.a2 = self.create({
|
||||||
|
'model': 'a',
|
||||||
|
'inherit_id': self.a1,
|
||||||
|
'priority': 5,
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><a2/></xpath>'
|
||||||
|
})
|
||||||
|
self.a3 = self.create({
|
||||||
|
'model': 'a',
|
||||||
|
'inherit_id': self.a1,
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><a3/></xpath>'
|
||||||
|
})
|
||||||
|
# mode=primary should be an inheritance boundary in both direction,
|
||||||
|
# even within a model it should not extend the parent
|
||||||
|
self.a4 = self.create({
|
||||||
|
'model': 'a',
|
||||||
|
'inherit_id': self.a1,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><a4/></xpath>',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.b1 = self.create({
|
||||||
|
'model': 'b',
|
||||||
|
'inherit_id': self.a3,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><b1/></xpath>'
|
||||||
|
})
|
||||||
|
self.b2 = self.create({
|
||||||
|
'model': 'b',
|
||||||
|
'inherit_id': self.b1,
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><b2/></xpath>'
|
||||||
|
})
|
||||||
|
|
||||||
|
self.c1 = self.create({
|
||||||
|
'model': 'c',
|
||||||
|
'inherit_id': self.a1,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><c1/></xpath>'
|
||||||
|
})
|
||||||
|
self.c2 = self.create({
|
||||||
|
'model': 'c',
|
||||||
|
'inherit_id': self.c1,
|
||||||
|
'priority': 5,
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><c2/></xpath>'
|
||||||
|
})
|
||||||
|
self.c3 = self.create({
|
||||||
|
'model': 'c',
|
||||||
|
'inherit_id': self.c2,
|
||||||
|
'priority': 10,
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><c3/></xpath>'
|
||||||
|
})
|
||||||
|
|
||||||
|
self.d1 = self.create({
|
||||||
|
'model': 'd',
|
||||||
|
'inherit_id': self.b1,
|
||||||
|
'mode': 'primary',
|
||||||
|
'arch': '<xpath expr="//a1" position="after"><d1/></xpath>'
|
||||||
|
})
|
||||||
|
|
||||||
|
def read_combined(self, id):
|
||||||
|
return self.Views.read_combined(
|
||||||
|
self.cr, self.uid,
|
||||||
|
id, ['arch'],
|
||||||
|
context={'check_view_ids': self.Views.search(self.cr, self.uid, [])}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_basic_read(self):
|
||||||
|
arch = self.read_combined(self.a1)['arch']
|
||||||
|
self.assertEqual(
|
||||||
|
ET.fromstring(arch),
|
||||||
|
E.qweb(
|
||||||
|
E.a1(),
|
||||||
|
E.a3(),
|
||||||
|
E.a2(),
|
||||||
|
), arch)
|
||||||
|
|
||||||
|
def test_read_from_child(self):
|
||||||
|
arch = self.read_combined(self.a3)['arch']
|
||||||
|
self.assertEqual(
|
||||||
|
ET.fromstring(arch),
|
||||||
|
E.qweb(
|
||||||
|
E.a1(),
|
||||||
|
E.a3(),
|
||||||
|
E.a2(),
|
||||||
|
), arch)
|
||||||
|
|
||||||
|
def test_read_from_child_primary(self):
|
||||||
|
arch = self.read_combined(self.a4)['arch']
|
||||||
|
self.assertEqual(
|
||||||
|
ET.fromstring(arch),
|
||||||
|
E.qweb(
|
||||||
|
E.a1(),
|
||||||
|
E.a4(),
|
||||||
|
E.a3(),
|
||||||
|
E.a2(),
|
||||||
|
), arch)
|
||||||
|
|
||||||
|
def test_cross_model_simple(self):
|
||||||
|
arch = self.read_combined(self.c2)['arch']
|
||||||
|
self.assertEqual(
|
||||||
|
ET.fromstring(arch),
|
||||||
|
E.qweb(
|
||||||
|
E.a1(),
|
||||||
|
E.c3(),
|
||||||
|
E.c2(),
|
||||||
|
E.c1(),
|
||||||
|
E.a3(),
|
||||||
|
E.a2(),
|
||||||
|
), arch)
|
||||||
|
|
||||||
|
def test_cross_model_double(self):
|
||||||
|
arch = self.read_combined(self.d1)['arch']
|
||||||
|
self.assertEqual(
|
||||||
|
ET.fromstring(arch),
|
||||||
|
E.qweb(
|
||||||
|
E.a1(),
|
||||||
|
E.d1(),
|
||||||
|
E.b2(),
|
||||||
|
E.b1(),
|
||||||
|
E.a3(),
|
||||||
|
E.a2(),
|
||||||
|
), arch)
|
||||||
|
|
||||||
class TestXPathExtentions(common.BaseCase):
|
class TestXPathExtentions(common.BaseCase):
|
||||||
def test_hasclass(self):
|
def test_hasclass(self):
|
||||||
tree = E.node(
|
tree = E.node(
|
||||||
|
|
|
@ -217,7 +217,14 @@
|
||||||
<rng:optional><rng:attribute name="priority"/></rng:optional>
|
<rng:optional><rng:attribute name="priority"/></rng:optional>
|
||||||
<rng:choice>
|
<rng:choice>
|
||||||
<rng:group>
|
<rng:group>
|
||||||
<rng:optional><rng:attribute name="inherit_id"/></rng:optional>
|
<rng:optional>
|
||||||
|
<rng:attribute name="inherit_id"/>
|
||||||
|
<rng:optional>
|
||||||
|
<rng:attribute name="primary">
|
||||||
|
<rng:value>True</rng:value>
|
||||||
|
</rng:attribute>
|
||||||
|
</rng:optional>
|
||||||
|
</rng:optional>
|
||||||
<rng:optional><rng:attribute name="inherit_option_id"/></rng:optional>
|
<rng:optional><rng:attribute name="inherit_option_id"/></rng:optional>
|
||||||
<rng:optional><rng:attribute name="groups"/></rng:optional>
|
<rng:optional><rng:attribute name="groups"/></rng:optional>
|
||||||
</rng:group>
|
</rng:group>
|
||||||
|
|
|
@ -729,7 +729,6 @@ class BaseModel(object):
|
||||||
_all_columns = {}
|
_all_columns = {}
|
||||||
|
|
||||||
_table = None
|
_table = None
|
||||||
_invalids = set()
|
|
||||||
_log_create = False
|
_log_create = False
|
||||||
_sql_constraints = []
|
_sql_constraints = []
|
||||||
_protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
|
_protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
|
||||||
|
@ -1543,9 +1542,6 @@ class BaseModel(object):
|
||||||
|
|
||||||
yield dbid, xid, converted, dict(extras, record=stream.index)
|
yield dbid, xid, converted, dict(extras, record=stream.index)
|
||||||
|
|
||||||
def get_invalid_fields(self, cr, uid):
|
|
||||||
return list(self._invalids)
|
|
||||||
|
|
||||||
def _validate(self, cr, uid, ids, context=None):
|
def _validate(self, cr, uid, ids, context=None):
|
||||||
context = context or {}
|
context = context or {}
|
||||||
lng = context.get('lang')
|
lng = context.get('lang')
|
||||||
|
@ -1566,12 +1562,9 @@ class BaseModel(object):
|
||||||
# Check presence of __call__ directly instead of using
|
# Check presence of __call__ directly instead of using
|
||||||
# callable() because it will be deprecated as of Python 3.0
|
# callable() because it will be deprecated as of Python 3.0
|
||||||
if hasattr(msg, '__call__'):
|
if hasattr(msg, '__call__'):
|
||||||
tmp_msg = msg(self, cr, uid, ids, context=context)
|
translated_msg = msg(self, cr, uid, ids, context=context)
|
||||||
if isinstance(tmp_msg, tuple):
|
if isinstance(translated_msg, tuple):
|
||||||
tmp_msg, params = tmp_msg
|
translated_msg = translated_msg[0] % translated_msg[1]
|
||||||
translated_msg = tmp_msg % params
|
|
||||||
else:
|
|
||||||
translated_msg = tmp_msg
|
|
||||||
else:
|
else:
|
||||||
translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, msg)
|
translated_msg = trans._get_source(cr, uid, self._name, 'constraint', lng, msg)
|
||||||
if extra_error:
|
if extra_error:
|
||||||
|
@ -1579,11 +1572,8 @@ class BaseModel(object):
|
||||||
error_msgs.append(
|
error_msgs.append(
|
||||||
_("The field(s) `%s` failed against a constraint: %s") % (', '.join(fields), translated_msg)
|
_("The field(s) `%s` failed against a constraint: %s") % (', '.join(fields), translated_msg)
|
||||||
)
|
)
|
||||||
self._invalids.update(fields)
|
|
||||||
if error_msgs:
|
if error_msgs:
|
||||||
raise except_orm('ValidateError', '\n'.join(error_msgs))
|
raise except_orm('ValidateError', '\n'.join(error_msgs))
|
||||||
else:
|
|
||||||
self._invalids.clear()
|
|
||||||
|
|
||||||
def default_get(self, cr, uid, fields_list, context=None):
|
def default_get(self, cr, uid, fields_list, context=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -898,6 +898,8 @@ form: module.record_id""" % (xml_id,)
|
||||||
record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]"))
|
record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]"))
|
||||||
if el.attrib.pop('page', None) == 'True':
|
if el.attrib.pop('page', None) == 'True':
|
||||||
record.append(Field(name="page", eval="True"))
|
record.append(Field(name="page", eval="True"))
|
||||||
|
if el.get('primary') == 'True':
|
||||||
|
record.append(Field('primary', name='mode'))
|
||||||
|
|
||||||
return self._tag_record(cr, record, data_node)
|
return self._tag_record(cr, record, data_node)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue