2013-04-25 09:37:39 +00:00
|
|
|
# -*- encoding: utf-8 -*-
|
2013-09-12 18:29:26 +00:00
|
|
|
from functools import partial
|
2014-05-27 09:46:58 +00:00
|
|
|
import itertools
|
2013-09-12 14:10:18 +00:00
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
import unittest2
|
|
|
|
|
2013-09-10 13:26:38 +00:00
|
|
|
from lxml import etree as ET
|
2013-06-25 06:00:41 +00:00
|
|
|
from lxml.builder import E
|
2013-04-22 09:10:14 +00:00
|
|
|
|
2014-05-27 10:21:52 +00:00
|
|
|
from psycopg2 import IntegrityError
|
|
|
|
|
2017-04-26 17:36:10 +00:00
|
|
|
from openerp.osv.orm import modifiers_tests
|
2017-03-09 14:42:00 +00:00
|
|
|
from openerp.exceptions import ValidationError
|
2013-04-24 15:43:10 +00:00
|
|
|
from openerp.tests import common
|
2014-05-27 10:21:52 +00:00
|
|
|
import openerp.tools
|
2013-04-22 09:10:14 +00:00
|
|
|
|
|
|
|
Field = E.field
|
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
class ViewCase(common.TransactionCase):
|
|
|
|
def setUp(self):
|
|
|
|
super(ViewCase, self).setUp()
|
|
|
|
self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual)
|
2014-05-27 09:44:55 +00:00
|
|
|
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)
|
2014-02-25 09:28:42 +00:00
|
|
|
|
2014-05-27 09:49:26 +00:00
|
|
|
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, [])}
|
|
|
|
)
|
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
def assertTreesEqual(self, n1, n2, msg=None):
|
2014-05-27 09:46:58 +00:00
|
|
|
self.assertEqual(n1.tag, n2.tag, msg)
|
2014-02-25 09:28:42 +00:00
|
|
|
self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg)
|
|
|
|
self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg)
|
|
|
|
|
|
|
|
# Because lxml uses ordereddicts in which order is important to
|
|
|
|
# equality (!?!?!?!)
|
|
|
|
self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
|
|
|
|
|
2014-05-27 09:46:58 +00:00
|
|
|
for c1, c2 in itertools.izip_longest(n1, n2):
|
|
|
|
self.assertEqual(c1, c2, msg)
|
2014-02-25 09:28:42 +00:00
|
|
|
|
|
|
|
|
2014-04-08 11:49:36 +00:00
|
|
|
class TestNodeLocator(common.TransactionCase):
|
2013-04-22 09:10:14 +00:00
|
|
|
"""
|
|
|
|
The node locator returns None when it can not find a node, and the first
|
|
|
|
match when it finds something (no jquery-style node sets)
|
|
|
|
"""
|
|
|
|
def setUp(self):
|
|
|
|
super(TestNodeLocator, self).setUp()
|
|
|
|
self.Views = self.registry('ir.ui.view')
|
|
|
|
|
|
|
|
def test_no_match_xpath(self):
|
|
|
|
"""
|
|
|
|
xpath simply uses the provided @expr pattern to find a node
|
|
|
|
"""
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(E.foo(), E.bar(), E.baz()),
|
|
|
|
E.xpath(expr="//qux"))
|
|
|
|
self.assertIsNone(node)
|
|
|
|
|
|
|
|
def test_match_xpath(self):
|
|
|
|
bar = E.bar()
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(E.foo(), bar, E.baz()),
|
|
|
|
E.xpath(expr="//bar"))
|
|
|
|
self.assertIs(node, bar)
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_match_field(self):
|
|
|
|
"""
|
|
|
|
A field spec will match by @name against all fields of the view
|
|
|
|
"""
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(E.foo(), E.bar(), E.baz()),
|
|
|
|
Field(name="qux"))
|
|
|
|
self.assertIsNone(node)
|
|
|
|
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(Field(name="foo"), Field(name="bar"), Field(name="baz")),
|
|
|
|
Field(name="qux"))
|
|
|
|
self.assertIsNone(node)
|
|
|
|
|
|
|
|
def test_match_field(self):
|
|
|
|
bar = Field(name="bar")
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(Field(name="foo"), bar, Field(name="baz")),
|
|
|
|
Field(name="bar"))
|
|
|
|
self.assertIs(node, bar)
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_match_other(self):
|
|
|
|
"""
|
|
|
|
Non-xpath non-fields are matched by node name first
|
|
|
|
"""
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(E.foo(), E.bar(), E.baz()),
|
|
|
|
E.qux())
|
|
|
|
self.assertIsNone(node)
|
|
|
|
|
|
|
|
def test_match_other(self):
|
|
|
|
bar = E.bar()
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(E.foo(), bar, E.baz()),
|
|
|
|
E.bar())
|
|
|
|
self.assertIs(bar, node)
|
|
|
|
|
|
|
|
def test_attribute_mismatch(self):
|
|
|
|
"""
|
|
|
|
Non-xpath non-field are filtered by matching attributes on spec and
|
|
|
|
matched nodes
|
|
|
|
"""
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(E.foo(attr='1'), E.bar(attr='2'), E.baz(attr='3')),
|
|
|
|
E.bar(attr='5'))
|
|
|
|
self.assertIsNone(node)
|
|
|
|
|
|
|
|
def test_attribute_filter(self):
|
|
|
|
match = E.bar(attr='2')
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(E.bar(attr='1'), match, E.root(E.bar(attr='3'))),
|
|
|
|
E.bar(attr='2'))
|
|
|
|
self.assertIs(node, match)
|
|
|
|
|
|
|
|
def test_version_mismatch(self):
|
|
|
|
"""
|
|
|
|
A @version on the spec will be matched against the view's version
|
|
|
|
"""
|
|
|
|
node = self.Views.locate_node(
|
|
|
|
E.root(E.foo(attr='1'), version='4'),
|
|
|
|
E.foo(attr='1', version='3'))
|
|
|
|
self.assertIsNone(node)
|
2013-04-22 12:28:12 +00:00
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
class TestViewInheritance(ViewCase):
|
2013-06-25 06:00:41 +00:00
|
|
|
def arch_for(self, name, view_type='form', parent=None):
|
|
|
|
""" Generates a trivial view of the specified ``view_type``.
|
|
|
|
|
|
|
|
The generated view is empty but ``name`` is set as its root's ``@string``.
|
|
|
|
|
|
|
|
If ``parent`` is not falsy, generates an extension view (instead of
|
|
|
|
a root view) replacing the parent's ``@string`` by ``name``
|
|
|
|
|
|
|
|
:param str name: ``@string`` value for the view root
|
|
|
|
:param str view_type:
|
|
|
|
:param bool parent:
|
|
|
|
:return: generated arch
|
|
|
|
:rtype: str
|
|
|
|
"""
|
|
|
|
if not parent:
|
|
|
|
element = E(view_type, string=name)
|
|
|
|
else:
|
|
|
|
element = E(view_type,
|
|
|
|
E.attribute(name, name='string'),
|
|
|
|
position='attributes'
|
|
|
|
)
|
|
|
|
return ET.tostring(element)
|
2013-04-22 12:28:12 +00:00
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
def makeView(self, name, parent=None, arch=None):
|
2013-06-25 06:00:41 +00:00
|
|
|
""" Generates a basic ir.ui.view with the provided name, parent and arch.
|
|
|
|
|
|
|
|
If no parent is provided, the view is top-level.
|
|
|
|
|
|
|
|
If no arch is provided, generates one by calling :meth:`~.arch_for`.
|
|
|
|
|
|
|
|
:param str name:
|
|
|
|
:param int parent: id of the parent view, if any
|
|
|
|
:param str arch:
|
|
|
|
:returns: the created view's id.
|
|
|
|
:rtype: int
|
|
|
|
"""
|
|
|
|
view_id = self.View.create(self.cr, self.uid, {
|
2013-04-22 12:28:12 +00:00
|
|
|
'model': self.model,
|
|
|
|
'name': name,
|
2013-06-25 06:00:41 +00:00
|
|
|
'arch': arch or self.arch_for(name, parent=parent),
|
2013-04-23 13:25:23 +00:00
|
|
|
'inherit_id': parent,
|
2014-01-30 15:43:54 +00:00
|
|
|
'priority': 5, # higher than default views
|
2013-06-25 06:00:41 +00:00
|
|
|
})
|
2013-04-22 12:28:12 +00:00
|
|
|
self.ids[name] = view_id
|
|
|
|
return view_id
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super(TestViewInheritance, self).setUp()
|
|
|
|
|
2014-01-30 15:43:54 +00:00
|
|
|
self.model = 'ir.ui.view.custom'
|
2013-04-22 12:28:12 +00:00
|
|
|
self.View = self.registry('ir.ui.view')
|
2013-04-22 14:01:16 +00:00
|
|
|
self._init = self.View.pool._init
|
|
|
|
self.View.pool._init = False
|
2013-04-22 12:28:12 +00:00
|
|
|
self.ids = {}
|
|
|
|
|
|
|
|
a = self.makeView("A")
|
|
|
|
a1 = self.makeView("A1", a)
|
|
|
|
a11 = self.makeView("A11", a1)
|
|
|
|
self.makeView("A111", a11)
|
|
|
|
self.makeView("A12", a1)
|
|
|
|
a2 = self.makeView("A2", a)
|
|
|
|
self.makeView("A21", a2)
|
|
|
|
a22 = self.makeView("A22", a2)
|
|
|
|
self.makeView("A221", a22)
|
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
b = self.makeView('B', arch=self.arch_for("B", 'tree'))
|
2013-06-25 06:00:41 +00:00
|
|
|
self.makeView('B1', b, arch=self.arch_for("B1", 'tree', parent=b))
|
2013-04-24 09:51:14 +00:00
|
|
|
c = self.makeView('C', arch=self.arch_for("C", 'tree'))
|
|
|
|
self.View.write(self.cr, self.uid, c, {'priority': 1})
|
|
|
|
|
2013-04-22 14:01:16 +00:00
|
|
|
def tearDown(self):
|
|
|
|
self.View.pool._init = self._init
|
|
|
|
super(TestViewInheritance, self).tearDown()
|
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
def test_get_inheriting_views_arch(self):
|
2013-04-22 12:28:12 +00:00
|
|
|
self.assertEqual(self.View.get_inheriting_views_arch(
|
2013-07-01 14:53:34 +00:00
|
|
|
self.cr, self.uid, self.ids['A'], self.model), [
|
2013-06-25 06:00:41 +00:00
|
|
|
(self.arch_for('A1', parent=True), self.ids['A1']),
|
|
|
|
(self.arch_for('A2', parent=True), self.ids['A2']),
|
2013-04-22 12:28:12 +00:00
|
|
|
])
|
|
|
|
|
|
|
|
self.assertEqual(self.View.get_inheriting_views_arch(
|
2013-07-01 14:53:34 +00:00
|
|
|
self.cr, self.uid, self.ids['A21'], self.model),
|
2013-04-22 12:28:12 +00:00
|
|
|
[])
|
|
|
|
|
|
|
|
self.assertEqual(self.View.get_inheriting_views_arch(
|
2013-07-01 14:53:34 +00:00
|
|
|
self.cr, self.uid, self.ids['A11'], self.model),
|
2013-06-25 06:00:41 +00:00
|
|
|
[(self.arch_for('A111', parent=True), self.ids['A111'])])
|
2013-04-22 12:50:00 +00:00
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
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(
|
2013-04-23 13:36:17 +00:00
|
|
|
self.cr, self.uid, model=self.model, view_type='tree')
|
2013-04-24 09:51:14 +00:00
|
|
|
self.assertEqual(default_tree, self.ids['C'])
|
2013-04-23 13:36:17 +00:00
|
|
|
|
2013-04-24 09:51:14 +00:00
|
|
|
def test_no_default_view(self):
|
2013-07-01 11:41:29 +00:00
|
|
|
self.assertFalse(
|
2013-04-24 13:09:07 +00:00
|
|
|
self.View.default_view(
|
2013-07-01 11:41:29 +00:00
|
|
|
self.cr, self.uid, model='does.not.exist', view_type='form'))
|
2013-04-24 09:51:14 +00:00
|
|
|
|
2013-07-01 11:41:29 +00:00
|
|
|
self.assertFalse(
|
2013-04-24 13:09:07 +00:00
|
|
|
self.View.default_view(
|
2013-07-01 11:41:29 +00:00
|
|
|
self.cr, self.uid, model=self.model, view_type='graph'))
|
2013-04-24 13:09:07 +00:00
|
|
|
|
2017-03-09 14:42:00 +00:00
|
|
|
def test_no_recursion(self):
|
|
|
|
r1 = self.makeView('R1')
|
|
|
|
with self.assertRaises(ValidationError), self.cr.savepoint():
|
|
|
|
self.View.write(self.cr, self.uid, r1, {'inherit_id': r1})
|
|
|
|
|
|
|
|
r2 = self.makeView('R2', r1)
|
|
|
|
r3 = self.makeView('R3', r2)
|
|
|
|
with self.assertRaises(ValidationError), self.cr.savepoint():
|
|
|
|
self.View.write(self.cr, self.uid, r2, {'inherit_id': r3})
|
|
|
|
|
|
|
|
with self.assertRaises(ValidationError), self.cr.savepoint():
|
|
|
|
self.View.write(self.cr, self.uid, r1, {'inherit_id': r3})
|
|
|
|
|
|
|
|
with self.assertRaises(ValidationError), self.cr.savepoint():
|
|
|
|
self.View.write(self.cr, self.uid, r1, {
|
|
|
|
'inherit_id': r1,
|
|
|
|
'arch': self.arch_for('itself', parent=True),
|
|
|
|
})
|
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
class TestApplyInheritanceSpecs(ViewCase):
|
2013-06-25 08:48:57 +00:00
|
|
|
""" Applies a sequence of inheritance specification nodes to a base
|
|
|
|
architecture. IO state parameters (cr, uid, model, context) are used for
|
|
|
|
error reporting
|
2013-04-24 13:09:07 +00:00
|
|
|
|
2013-06-25 08:48:57 +00:00
|
|
|
The base architecture is altered in-place.
|
|
|
|
"""
|
|
|
|
def setUp(self):
|
|
|
|
super(TestApplyInheritanceSpecs, self).setUp()
|
|
|
|
self.View = self.registry('ir.ui.view')
|
|
|
|
self.base_arch = E.form(
|
|
|
|
Field(name="target"),
|
|
|
|
string="Title")
|
|
|
|
|
|
|
|
def test_replace(self):
|
2013-07-01 10:51:07 +00:00
|
|
|
spec = Field(
|
2013-06-25 08:48:57 +00:00
|
|
|
Field(name="replacement"),
|
2013-07-01 10:51:07 +00:00
|
|
|
name="target", position="replace")
|
2013-06-25 08:48:57 +00:00
|
|
|
|
2013-07-01 10:51:07 +00:00
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
spec, None)
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
self.assertEqual(
|
2014-02-25 09:28:42 +00:00
|
|
|
self.base_arch,
|
|
|
|
E.form(Field(name="replacement"), string="Title"))
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
def test_delete(self):
|
2013-07-01 10:51:07 +00:00
|
|
|
spec = Field(name="target", position="replace")
|
2013-06-25 08:48:57 +00:00
|
|
|
|
2013-07-01 10:51:07 +00:00
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
spec, None)
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
self.assertEqual(
|
2014-02-25 09:28:42 +00:00
|
|
|
self.base_arch,
|
|
|
|
E.form(string="Title"))
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
def test_insert_after(self):
|
2013-07-01 10:51:07 +00:00
|
|
|
spec = Field(
|
2013-06-25 08:48:57 +00:00
|
|
|
Field(name="inserted"),
|
2013-07-01 10:51:07 +00:00
|
|
|
name="target", position="after")
|
2013-06-25 08:48:57 +00:00
|
|
|
|
2013-07-01 10:51:07 +00:00
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
spec, None)
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
self.assertEqual(
|
2014-02-25 09:28:42 +00:00
|
|
|
self.base_arch,
|
|
|
|
E.form(
|
2013-06-25 08:48:57 +00:00
|
|
|
Field(name="target"),
|
|
|
|
Field(name="inserted"),
|
|
|
|
string="Title"
|
2014-02-25 09:28:42 +00:00
|
|
|
))
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
def test_insert_before(self):
|
2013-07-01 10:51:07 +00:00
|
|
|
spec = Field(
|
2013-06-25 08:48:57 +00:00
|
|
|
Field(name="inserted"),
|
2013-07-01 10:51:07 +00:00
|
|
|
name="target", position="before")
|
2013-06-25 08:48:57 +00:00
|
|
|
|
2013-07-01 10:51:07 +00:00
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
spec, None)
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
self.assertEqual(
|
2014-02-25 09:28:42 +00:00
|
|
|
self.base_arch,
|
|
|
|
E.form(
|
2013-06-25 08:48:57 +00:00
|
|
|
Field(name="inserted"),
|
|
|
|
Field(name="target"),
|
2014-02-25 09:28:42 +00:00
|
|
|
string="Title"))
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
def test_insert_inside(self):
|
2013-07-01 10:51:07 +00:00
|
|
|
default = Field(Field(name="inserted"), name="target")
|
|
|
|
spec = Field(Field(name="inserted 2"), name="target", position='inside')
|
|
|
|
|
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
default, None)
|
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
spec, None)
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
self.assertEqual(
|
2014-02-25 09:28:42 +00:00
|
|
|
self.base_arch,
|
|
|
|
E.form(
|
2013-06-25 08:48:57 +00:00
|
|
|
Field(
|
|
|
|
Field(name="inserted"),
|
|
|
|
Field(name="inserted 2"),
|
|
|
|
name="target"),
|
2014-02-25 09:28:42 +00:00
|
|
|
string="Title"))
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
def test_unpack_data(self):
|
2013-07-01 10:51:07 +00:00
|
|
|
spec = E.data(
|
2013-06-25 08:48:57 +00:00
|
|
|
Field(Field(name="inserted 0"), name="target"),
|
|
|
|
Field(Field(name="inserted 1"), name="target"),
|
|
|
|
Field(Field(name="inserted 2"), name="target"),
|
|
|
|
Field(Field(name="inserted 3"), name="target"),
|
2013-07-01 10:51:07 +00:00
|
|
|
)
|
2013-06-25 08:48:57 +00:00
|
|
|
|
2013-07-01 10:51:07 +00:00
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
spec, None)
|
2013-06-25 08:48:57 +00:00
|
|
|
|
|
|
|
self.assertEqual(
|
2014-02-25 09:28:42 +00:00
|
|
|
self.base_arch,
|
|
|
|
E.form(
|
2013-06-25 08:48:57 +00:00
|
|
|
Field(
|
|
|
|
Field(name="inserted 0"),
|
|
|
|
Field(name="inserted 1"),
|
|
|
|
Field(name="inserted 2"),
|
|
|
|
Field(name="inserted 3"),
|
|
|
|
name="target"),
|
2014-02-25 09:28:42 +00:00
|
|
|
string="Title"))
|
2013-06-25 08:48:57 +00:00
|
|
|
|
2014-07-06 14:44:26 +00:00
|
|
|
@openerp.tools.mute_logger('openerp.addons.base.ir.ir_ui_view')
|
2013-06-25 10:28:00 +00:00
|
|
|
def test_invalid_position(self):
|
2013-07-01 10:51:07 +00:00
|
|
|
spec = Field(
|
2013-06-25 10:28:00 +00:00
|
|
|
Field(name="whoops"),
|
2013-07-01 10:51:07 +00:00
|
|
|
name="target", position="serious_series")
|
2013-06-25 10:28:00 +00:00
|
|
|
|
|
|
|
with self.assertRaises(AttributeError):
|
2013-07-01 10:51:07 +00:00
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
spec, None)
|
2013-06-25 10:28:00 +00:00
|
|
|
|
2014-07-06 14:44:26 +00:00
|
|
|
@openerp.tools.mute_logger('openerp.addons.base.ir.ir_ui_view')
|
2013-06-25 10:28:00 +00:00
|
|
|
def test_incorrect_version(self):
|
|
|
|
# Version ignored on //field elements, so use something else
|
|
|
|
arch = E.form(E.element(foo="42"))
|
2013-07-01 10:51:07 +00:00
|
|
|
spec = E.element(
|
2013-06-25 10:28:00 +00:00
|
|
|
Field(name="placeholder"),
|
2013-07-01 10:51:07 +00:00
|
|
|
foo="42", version="7.0")
|
2013-06-25 10:28:00 +00:00
|
|
|
|
|
|
|
with self.assertRaises(AttributeError):
|
2013-07-01 10:51:07 +00:00
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
arch,
|
|
|
|
spec, None)
|
2013-06-25 10:28:00 +00:00
|
|
|
|
2014-07-06 14:44:26 +00:00
|
|
|
@openerp.tools.mute_logger('openerp.addons.base.ir.ir_ui_view')
|
2013-06-25 10:28:00 +00:00
|
|
|
def test_target_not_found(self):
|
2013-07-01 10:51:07 +00:00
|
|
|
spec = Field(name="targut")
|
2013-06-25 10:28:00 +00:00
|
|
|
|
|
|
|
with self.assertRaises(AttributeError):
|
2013-07-01 10:51:07 +00:00
|
|
|
self.View.apply_inheritance_specs(self.cr, self.uid,
|
|
|
|
self.base_arch,
|
|
|
|
spec, None)
|
2013-06-25 08:48:57 +00:00
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
class TestApplyInheritedArchs(ViewCase):
|
2013-06-25 08:48:57 +00:00
|
|
|
""" Applies a sequence of modificator archs to a base view
|
|
|
|
"""
|
2013-04-24 09:51:14 +00:00
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
class TestNoModel(ViewCase):
|
2013-04-25 09:37:39 +00:00
|
|
|
def test_create_view_nomodel(self):
|
|
|
|
View = self.registry('ir.ui.view')
|
|
|
|
view_id = View.create(self.cr, self.uid, {
|
|
|
|
'name': 'dummy',
|
2014-01-30 15:43:54 +00:00
|
|
|
'arch': '<template name="foo"/>',
|
|
|
|
'inherit_id': False,
|
|
|
|
'type': 'qweb',
|
2013-04-25 09:37:39 +00:00
|
|
|
})
|
2013-04-25 10:02:47 +00:00
|
|
|
fields = ['name', 'arch', 'type', 'priority', 'inherit_id', 'model']
|
|
|
|
[view] = View.read(self.cr, self.uid, [view_id], fields)
|
2013-04-25 09:37:39 +00:00
|
|
|
self.assertEqual(view, {
|
|
|
|
'id': view_id,
|
|
|
|
'name': 'dummy',
|
2014-01-30 15:43:54 +00:00
|
|
|
'arch': '<template name="foo"/>',
|
|
|
|
'type': 'qweb',
|
2013-04-25 09:37:39 +00:00
|
|
|
'priority': 16,
|
|
|
|
'inherit_id': False,
|
2013-04-26 14:51:13 +00:00
|
|
|
'model': False,
|
2013-04-25 09:37:39 +00:00
|
|
|
})
|
|
|
|
|
2014-01-30 15:43:54 +00:00
|
|
|
text_para = E.p("", {'class': 'legalese'})
|
2013-04-25 09:37:39 +00:00
|
|
|
arch = E.body(
|
|
|
|
E.div(
|
|
|
|
E.h1("Title"),
|
|
|
|
id="header"),
|
|
|
|
E.p("Welcome!"),
|
|
|
|
E.div(
|
|
|
|
E.hr(),
|
2014-01-30 15:43:54 +00:00
|
|
|
text_para,
|
2013-04-25 09:37:39 +00:00
|
|
|
id="footer"),
|
|
|
|
{'class': "index"},)
|
|
|
|
|
2014-01-30 15:43:54 +00:00
|
|
|
def test_qweb_translation(self):
|
2013-04-25 09:37:39 +00:00
|
|
|
"""
|
|
|
|
Test if translations work correctly without a model
|
|
|
|
"""
|
|
|
|
View = self.registry('ir.ui.view')
|
|
|
|
self.registry('res.lang').load_lang(self.cr, self.uid, 'fr_FR')
|
2014-01-30 15:43:54 +00:00
|
|
|
orig_text = "Copyright copyrighter"
|
|
|
|
translated_text = u"Copyrighter, tous droits réservés"
|
|
|
|
self.text_para.text = orig_text
|
2013-04-25 09:37:39 +00:00
|
|
|
self.registry('ir.translation').create(self.cr, self.uid, {
|
2014-01-30 15:43:54 +00:00
|
|
|
'name': 'website',
|
2013-04-25 09:37:39 +00:00
|
|
|
'type': 'view',
|
|
|
|
'lang': 'fr_FR',
|
2014-01-30 15:43:54 +00:00
|
|
|
'src': orig_text,
|
|
|
|
'value': translated_text,
|
2013-04-25 09:37:39 +00:00
|
|
|
})
|
2014-01-30 15:43:54 +00:00
|
|
|
sarch = View.translate_qweb(self.cr, self.uid, None, self.arch, 'fr_FR')
|
2014-02-25 09:28:42 +00:00
|
|
|
|
2014-01-30 15:43:54 +00:00
|
|
|
self.text_para.text = translated_text
|
2014-02-25 09:28:42 +00:00
|
|
|
self.assertEqual(sarch, self.arch)
|
2013-04-25 09:37:39 +00:00
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
class TestTemplating(ViewCase):
|
2014-02-13 16:45:46 +00:00
|
|
|
def setUp(self):
|
|
|
|
import openerp.modules
|
|
|
|
super(TestTemplating, self).setUp()
|
2015-06-08 07:44:46 +00:00
|
|
|
self._pool = openerp.modules.registry.RegistryManager.get(common.get_db_name())
|
2014-02-13 16:45:46 +00:00
|
|
|
self._init = self._pool._init
|
|
|
|
# fuck off
|
|
|
|
self._pool._init = False
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
self._pool._init = self._init
|
|
|
|
super(TestTemplating, self).tearDown()
|
|
|
|
|
|
|
|
def test_branding_inherit(self):
|
|
|
|
Views = self.registry('ir.ui.view')
|
|
|
|
id = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "Base view",
|
|
|
|
'type': 'qweb',
|
|
|
|
'arch': """<root>
|
|
|
|
<item order="1"/>
|
|
|
|
</root>
|
|
|
|
"""
|
|
|
|
})
|
|
|
|
id2 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "Extension",
|
|
|
|
'type': 'qweb',
|
|
|
|
'inherit_id': id,
|
|
|
|
'arch': """<xpath expr="//item" position="before">
|
|
|
|
<item order="2"/>
|
|
|
|
</xpath>
|
|
|
|
"""
|
|
|
|
})
|
|
|
|
|
|
|
|
arch_string = Views.read_combined(
|
|
|
|
self.cr, self.uid, id, fields=['arch'],
|
|
|
|
context={'inherit_branding': True})['arch']
|
|
|
|
|
|
|
|
arch = ET.fromstring(arch_string)
|
|
|
|
Views.distribute_branding(arch)
|
|
|
|
|
|
|
|
[initial] = arch.xpath('//item[@order=1]')
|
|
|
|
self.assertEqual(
|
|
|
|
str(id),
|
|
|
|
initial.get('data-oe-id'),
|
|
|
|
"initial should come from the root view")
|
|
|
|
self.assertEqual(
|
|
|
|
'/root[1]/item[1]',
|
|
|
|
initial.get('data-oe-xpath'),
|
|
|
|
"initial's xpath should be within the root view only")
|
|
|
|
|
|
|
|
[second] = arch.xpath('//item[@order=2]')
|
|
|
|
self.assertEqual(
|
|
|
|
str(id2),
|
|
|
|
second.get('data-oe-id'),
|
|
|
|
"second should come from the extension view")
|
2013-10-02 13:38:20 +00:00
|
|
|
|
2014-02-24 17:03:01 +00:00
|
|
|
def test_branding_distribute_inner(self):
|
|
|
|
""" Checks that the branding is correctly distributed within a view
|
|
|
|
extension
|
|
|
|
"""
|
|
|
|
Views = self.registry('ir.ui.view')
|
|
|
|
id = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "Base view",
|
|
|
|
'type': 'qweb',
|
|
|
|
'arch': """<root>
|
|
|
|
<item order="1"/>
|
|
|
|
</root>"""
|
|
|
|
})
|
|
|
|
id2 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "Extension",
|
|
|
|
'type': 'qweb',
|
|
|
|
'inherit_id': id,
|
|
|
|
'arch': """<xpath expr="//item" position="before">
|
|
|
|
<item order="2">
|
|
|
|
<content t-att-href="foo">bar</content>
|
|
|
|
</item>
|
|
|
|
</xpath>"""
|
|
|
|
})
|
|
|
|
|
|
|
|
arch_string = Views.read_combined(
|
|
|
|
self.cr, self.uid, id, fields=['arch'],
|
|
|
|
context={'inherit_branding': True})['arch']
|
|
|
|
|
|
|
|
arch = ET.fromstring(arch_string)
|
|
|
|
Views.distribute_branding(arch)
|
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
self.assertEqual(
|
2014-02-24 17:03:01 +00:00
|
|
|
arch,
|
|
|
|
E.root(
|
|
|
|
E.item(
|
|
|
|
E.content("bar", {
|
|
|
|
't-att-href': "foo",
|
|
|
|
'data-oe-model': 'ir.ui.view',
|
|
|
|
'data-oe-id': str(id2),
|
|
|
|
'data-oe-field': 'arch',
|
|
|
|
'data-oe-xpath': '/xpath/item/content[1]',
|
2014-08-20 16:23:12 +00:00
|
|
|
'data-oe-source-id': str(id)
|
2014-02-24 17:03:01 +00:00
|
|
|
}), {
|
|
|
|
'order': '2',
|
|
|
|
}),
|
|
|
|
E.item({
|
|
|
|
'order': '1',
|
|
|
|
'data-oe-model': 'ir.ui.view',
|
|
|
|
'data-oe-id': str(id),
|
|
|
|
'data-oe-field': 'arch',
|
|
|
|
'data-oe-xpath': '/root[1]/item[1]'
|
|
|
|
})
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
def test_esc_no_branding(self):
|
2014-02-25 11:00:46 +00:00
|
|
|
Views = self.registry('ir.ui.view')
|
|
|
|
id = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "Base View",
|
|
|
|
'type': 'qweb',
|
|
|
|
'arch': """<root>
|
|
|
|
<item><span t-esc="foo"/></item>
|
|
|
|
</root>""",
|
|
|
|
})
|
|
|
|
|
|
|
|
arch_string = Views.read_combined(
|
|
|
|
self.cr, self.uid, id, fields=['arch'],
|
|
|
|
context={'inherit_branding': True})['arch']
|
|
|
|
arch = ET.fromstring(arch_string)
|
|
|
|
Views.distribute_branding(arch)
|
|
|
|
|
|
|
|
self.assertEqual(arch, E.root(E.item(E.span({'t-esc': "foo"}))))
|
2014-02-24 17:03:01 +00:00
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
def test_ignore_unbrand(self):
|
2014-02-25 11:47:06 +00:00
|
|
|
Views = self.registry('ir.ui.view')
|
|
|
|
id = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "Base view",
|
|
|
|
'type': 'qweb',
|
|
|
|
'arch': """<root>
|
|
|
|
<item order="1" t-ignore="true">
|
|
|
|
<t t-esc="foo"/>
|
|
|
|
</item>
|
|
|
|
</root>"""
|
|
|
|
})
|
|
|
|
id2 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "Extension",
|
|
|
|
'type': 'qweb',
|
|
|
|
'inherit_id': id,
|
|
|
|
'arch': """<xpath expr="//item[@order='1']" position="inside">
|
|
|
|
<item order="2">
|
|
|
|
<content t-att-href="foo">bar</content>
|
|
|
|
</item>
|
|
|
|
</xpath>"""
|
|
|
|
})
|
|
|
|
|
|
|
|
arch_string = Views.read_combined(
|
|
|
|
self.cr, self.uid, id, fields=['arch'],
|
|
|
|
context={'inherit_branding': True})['arch']
|
|
|
|
|
|
|
|
arch = ET.fromstring(arch_string)
|
|
|
|
Views.distribute_branding(arch)
|
|
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
arch,
|
|
|
|
E.root(
|
|
|
|
E.item(
|
|
|
|
{'t-ignore': 'true', 'order': '1'},
|
|
|
|
E.t({'t-esc': 'foo'}),
|
|
|
|
E.item(
|
2014-08-20 16:23:12 +00:00
|
|
|
{'order': '2'},
|
2014-02-25 11:47:06 +00:00
|
|
|
E.content(
|
|
|
|
{'t-att-href': 'foo'},
|
|
|
|
"bar")
|
|
|
|
)
|
|
|
|
)
|
|
|
|
),
|
|
|
|
"t-ignore should apply to injected sub-view branding, not just to"
|
|
|
|
" the main view's"
|
|
|
|
)
|
2014-02-24 17:03:01 +00:00
|
|
|
|
2014-02-25 09:28:42 +00:00
|
|
|
class test_views(ViewCase):
|
2013-09-12 14:10:18 +00:00
|
|
|
|
2013-12-12 12:06:00 +00:00
|
|
|
def test_nonexistent_attribute_removal(self):
|
2013-11-08 17:57:50 +00:00
|
|
|
Views = self.registry('ir.ui.view')
|
|
|
|
Views.create(self.cr, self.uid, {
|
|
|
|
'name': 'Test View',
|
|
|
|
'model': 'ir.ui.view',
|
|
|
|
'inherit_id': self.browse_ref('base.view_view_tree').id,
|
|
|
|
'arch': """<?xml version="1.0"?>
|
|
|
|
<xpath expr="//field[@name='name']" position="attributes">
|
|
|
|
<attribute name="non_existing_attribute"></attribute>
|
|
|
|
</xpath>
|
|
|
|
""",
|
|
|
|
})
|
|
|
|
|
2013-09-12 18:29:26 +00:00
|
|
|
def _insert_view(self, **kw):
|
|
|
|
"""Insert view into database via a query to passtrough validation"""
|
|
|
|
kw.pop('id', None)
|
2014-05-27 09:43:08 +00:00
|
|
|
kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary')
|
2014-08-31 14:56:44 +00:00
|
|
|
kw.setdefault('active', True)
|
2013-09-12 18:29:26 +00:00
|
|
|
|
|
|
|
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]
|
|
|
|
|
2013-12-12 12:06:00 +00:00
|
|
|
def test_custom_view_validation(self):
|
2013-09-12 18:29:26 +00:00
|
|
|
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
|
2013-10-07 09:33:28 +00:00
|
|
|
vid = self._insert_view(
|
|
|
|
name='base view',
|
|
|
|
model=model,
|
|
|
|
priority=1,
|
|
|
|
arch="""<?xml version="1.0"?>
|
2013-09-12 18:29:26 +00:00
|
|
|
<tree string="view">
|
|
|
|
<field name="url"/>
|
|
|
|
</tree>
|
|
|
|
""",
|
2013-10-07 09:33:28 +00:00
|
|
|
)
|
2013-09-12 18:29:26 +00:00
|
|
|
self.assertTrue(validate()) # single view
|
|
|
|
|
|
|
|
# validation of a inherited view
|
2013-10-07 09:33:28 +00:00
|
|
|
self._insert_view(
|
|
|
|
name='inherited view',
|
|
|
|
model=model,
|
|
|
|
priority=1,
|
|
|
|
inherit_id=vid,
|
|
|
|
arch="""<?xml version="1.0"?>
|
2013-09-12 18:29:26 +00:00
|
|
|
<xpath expr="//field[@name='url']" position="before">
|
|
|
|
<field name="name"/>
|
|
|
|
</xpath>
|
|
|
|
""",
|
2013-10-07 09:33:28 +00:00
|
|
|
)
|
2013-09-12 18:29:26 +00:00
|
|
|
self.assertTrue(validate()) # inherited view
|
|
|
|
|
2015-03-04 11:49:06 +00:00
|
|
|
# validation of a second inherited view (depending on 1st)
|
|
|
|
self._insert_view(
|
|
|
|
name='inherited view 2',
|
|
|
|
model=model,
|
|
|
|
priority=5,
|
|
|
|
inherit_id=vid,
|
2015-03-04 12:38:36 +00:00
|
|
|
arch="""<?xml version="1.0"?>
|
2015-03-04 11:49:06 +00:00
|
|
|
<xpath expr="//field[@name='name']" position="after">
|
|
|
|
<field name="target"/>
|
|
|
|
</xpath>
|
|
|
|
""",
|
|
|
|
)
|
|
|
|
self.assertTrue(validate()) # inherited view
|
|
|
|
|
2013-12-12 12:06:00 +00:00
|
|
|
def test_view_inheritance(self):
|
|
|
|
Views = self.registry('ir.ui.view')
|
|
|
|
|
|
|
|
v1 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "bob",
|
|
|
|
'model': 'ir.ui.view',
|
|
|
|
'arch': """
|
|
|
|
<form string="Base title" version="7.0">
|
|
|
|
<separator string="separator" colspan="4"/>
|
|
|
|
<footer>
|
|
|
|
<button name="action_next" type="object" string="Next button"/>
|
|
|
|
or
|
|
|
|
<button string="Skip" special="cancel" />
|
|
|
|
</footer>
|
|
|
|
</form>
|
|
|
|
"""
|
|
|
|
})
|
|
|
|
v2 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "edmund",
|
|
|
|
'model': 'ir.ui.view',
|
|
|
|
'inherit_id': v1,
|
|
|
|
'arch': """
|
|
|
|
<data>
|
|
|
|
<form position="attributes" version="7.0">
|
|
|
|
<attribute name="string">Replacement title</attribute>
|
|
|
|
</form>
|
|
|
|
<footer position="replace">
|
|
|
|
<footer>
|
|
|
|
<button name="action_next" type="object" string="New button"/>
|
|
|
|
</footer>
|
|
|
|
</footer>
|
|
|
|
<separator string="separator" position="replace">
|
|
|
|
<p>Replacement data</p>
|
|
|
|
</separator>
|
|
|
|
</data>
|
|
|
|
"""
|
|
|
|
})
|
|
|
|
v3 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': 'jake',
|
|
|
|
'model': 'ir.ui.view',
|
|
|
|
'inherit_id': v1,
|
|
|
|
'priority': 17,
|
|
|
|
'arch': """
|
|
|
|
<footer position="attributes">
|
|
|
|
<attribute name="thing">bob</attribute>
|
|
|
|
</footer>
|
|
|
|
"""
|
|
|
|
})
|
|
|
|
|
|
|
|
view = self.registry('ir.ui.view').fields_view_get(
|
|
|
|
self.cr, self.uid, v2, view_type='form', context={
|
|
|
|
# fucking what?
|
|
|
|
'check_view_ids': [v2, v3]
|
|
|
|
})
|
|
|
|
self.assertEqual(view['type'], 'form')
|
|
|
|
self.assertEqual(
|
2014-02-25 09:28:42 +00:00
|
|
|
ET.fromstring(
|
2013-12-12 12:06:00 +00:00
|
|
|
view['arch'],
|
2013-12-12 13:44:22 +00:00
|
|
|
parser=ET.XMLParser(remove_blank_text=True)
|
2014-02-25 09:28:42 +00:00
|
|
|
),
|
|
|
|
E.form(
|
|
|
|
E.p("Replacement data"),
|
|
|
|
E.footer(
|
|
|
|
E.button(name="action_next", type="object", string="New button"),
|
|
|
|
thing="bob"
|
|
|
|
),
|
|
|
|
string="Replacement title", version="7.0"))
|
2013-12-12 12:06:00 +00:00
|
|
|
|
|
|
|
def test_view_inheritance_divergent_models(self):
|
|
|
|
Views = self.registry('ir.ui.view')
|
|
|
|
|
|
|
|
v1 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "bob",
|
|
|
|
'model': 'ir.ui.view.custom',
|
|
|
|
'arch': """
|
|
|
|
<form string="Base title" version="7.0">
|
|
|
|
<separator string="separator" colspan="4"/>
|
|
|
|
<footer>
|
|
|
|
<button name="action_next" type="object" string="Next button"/>
|
|
|
|
or
|
|
|
|
<button string="Skip" special="cancel" />
|
|
|
|
</footer>
|
|
|
|
</form>
|
|
|
|
"""
|
|
|
|
})
|
|
|
|
v2 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': "edmund",
|
|
|
|
'model': 'ir.ui.view',
|
|
|
|
'inherit_id': v1,
|
|
|
|
'arch': """
|
|
|
|
<data>
|
|
|
|
<form position="attributes" version="7.0">
|
|
|
|
<attribute name="string">Replacement title</attribute>
|
|
|
|
</form>
|
|
|
|
<footer position="replace">
|
|
|
|
<footer>
|
|
|
|
<button name="action_next" type="object" string="New button"/>
|
|
|
|
</footer>
|
|
|
|
</footer>
|
|
|
|
<separator string="separator" position="replace">
|
|
|
|
<p>Replacement data</p>
|
|
|
|
</separator>
|
|
|
|
</data>
|
|
|
|
"""
|
|
|
|
})
|
|
|
|
v3 = Views.create(self.cr, self.uid, {
|
|
|
|
'name': 'jake',
|
|
|
|
'model': 'ir.ui.menu',
|
|
|
|
'inherit_id': v1,
|
|
|
|
'priority': 17,
|
|
|
|
'arch': """
|
|
|
|
<footer position="attributes">
|
|
|
|
<attribute name="thing">bob</attribute>
|
|
|
|
</footer>
|
|
|
|
"""
|
|
|
|
})
|
|
|
|
|
|
|
|
view = self.registry('ir.ui.view').fields_view_get(
|
|
|
|
self.cr, self.uid, v2, view_type='form', context={
|
|
|
|
# fucking what?
|
|
|
|
'check_view_ids': [v2, v3]
|
|
|
|
})
|
|
|
|
self.assertEqual(view['type'], 'form')
|
|
|
|
self.assertEqual(
|
2014-02-25 09:28:42 +00:00
|
|
|
ET.fromstring(
|
2013-12-12 12:06:00 +00:00
|
|
|
view['arch'],
|
2013-12-12 13:44:22 +00:00
|
|
|
parser=ET.XMLParser(remove_blank_text=True)
|
2014-02-25 09:28:42 +00:00
|
|
|
),
|
|
|
|
E.form(
|
|
|
|
E.p("Replacement data"),
|
|
|
|
E.footer(
|
|
|
|
E.button(name="action_next", type="object", string="New button")),
|
|
|
|
string="Replacement title", version="7.0"
|
|
|
|
))
|
[ADD] hasclass() xpath function
Server-side, view extension is done via xpath. This includes "template" views
full of HTML.
HTML elements often have a bunch of classes, sometimes even semantic
(!). XPath is generally great, but specifically lousy at dealing with
space-separated values: in standard XPath 1.0 to know if an element has a
class 'foo' the predicate is:
contains(concat(' ', normalize-space(@class), ' '), ' foo ')
and this has to be fully duplicated if there's a second class involved.
Things are slightly better with EXSLT/XPath 2.0 and tokenize, but still not
great:
tokenize(@class, '\s+') = 'foo'
and the equality check is very weird when unaware of XPath's evaluation rules.
``hasclass`` makes this much simpler to deal with: to get any ``foo`` node
with the class ``bar`` is as simple as:
//foo[hasclass('bar')
and it can take multiple class, as with e.g. jquery it will return elements
with all specified classes.
Beware though, the predicate function will be called once for each element to
check, since it's implemented in pure python and not profiled elements should
be filtered as much as possible before this point.
2014-04-15 14:56:59 +00:00
|
|
|
|
2017-04-26 17:36:10 +00:00
|
|
|
def test_modifiers(self):
|
|
|
|
# implemeted elsewhere...
|
|
|
|
modifiers_tests()
|
|
|
|
|
2014-05-27 09:43:08 +00:00
|
|
|
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:
|
2014-05-27 10:18:22 +00:00
|
|
|
* inherit_id -> mode=extension
|
2014-05-27 09:43:08 +00:00
|
|
|
* 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')
|
|
|
|
|
2014-05-27 10:21:52 +00:00
|
|
|
@openerp.tools.mute_logger('openerp.sql_db')
|
2014-05-27 09:43:08 +00:00
|
|
|
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')
|
|
|
|
|
2014-05-27 10:21:52 +00:00
|
|
|
with self.assertRaises(IntegrityError):
|
2014-05-27 09:43:08 +00:00
|
|
|
self.create({
|
|
|
|
'inherit_id': None,
|
|
|
|
'mode': 'extension',
|
|
|
|
'arch': '<qweb/>'
|
|
|
|
})
|
|
|
|
|
2014-05-27 10:21:52 +00:00
|
|
|
@openerp.tools.mute_logger('openerp.sql_db')
|
2014-05-27 09:43:08 +00:00
|
|
|
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/>'
|
|
|
|
}))
|
2014-05-27 10:21:52 +00:00
|
|
|
with self.assertRaises(IntegrityError):
|
2014-05-27 09:43:08 +00:00
|
|
|
view_pure_primary.write({'mode': 'extension'})
|
|
|
|
|
2014-05-27 10:18:22 +00:00
|
|
|
def testInheritPrimaryToExtension(self):
|
2014-05-27 09:43:08 +00:00
|
|
|
"""
|
|
|
|
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'})
|
|
|
|
|
2014-05-27 09:44:55 +00:00
|
|
|
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):
|
|
|
|
"""
|
2014-05-27 09:46:58 +00:00
|
|
|
* 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
|
2014-05-27 09:44:55 +00:00
|
|
|
"""
|
|
|
|
|
2014-05-27 09:46:58 +00:00
|
|
|
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>'
|
|
|
|
})
|
2014-05-27 10:23:02 +00:00
|
|
|
# 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>',
|
|
|
|
})
|
2014-05-27 09:46:58 +00:00
|
|
|
|
|
|
|
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 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)
|
|
|
|
|
2014-05-27 10:23:02 +00:00
|
|
|
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)
|
|
|
|
|
2014-05-27 09:46:58 +00:00
|
|
|
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)
|
|
|
|
|
2014-05-27 09:49:26 +00:00
|
|
|
class TestOptionalViews(ViewCase):
|
|
|
|
"""
|
|
|
|
Tests ability to enable/disable inherited views, formerly known as
|
|
|
|
inherit_option_id
|
|
|
|
"""
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super(TestOptionalViews, self).setUp()
|
|
|
|
self.v0 = self.create({
|
|
|
|
'model': 'a',
|
|
|
|
'arch': '<qweb><base/></qweb>',
|
|
|
|
})
|
|
|
|
self.v1 = self.create({
|
|
|
|
'model': 'a',
|
|
|
|
'inherit_id': self.v0,
|
2014-08-31 14:56:44 +00:00
|
|
|
'active': True,
|
2014-05-27 09:52:57 +00:00
|
|
|
'priority': 10,
|
2014-05-27 09:49:26 +00:00
|
|
|
'arch': '<xpath expr="//base" position="after"><v1/></xpath>',
|
|
|
|
})
|
|
|
|
self.v2 = self.create({
|
|
|
|
'model': 'a',
|
|
|
|
'inherit_id': self.v0,
|
2014-08-31 14:56:44 +00:00
|
|
|
'active': True,
|
2014-05-27 09:52:57 +00:00
|
|
|
'priority': 9,
|
2014-05-27 09:49:26 +00:00
|
|
|
'arch': '<xpath expr="//base" position="after"><v2/></xpath>',
|
|
|
|
})
|
|
|
|
self.v3 = self.create({
|
|
|
|
'model': 'a',
|
|
|
|
'inherit_id': self.v0,
|
2014-08-31 14:56:44 +00:00
|
|
|
'active': False,
|
2014-05-27 09:52:57 +00:00
|
|
|
'priority': 8,
|
2014-05-27 09:49:26 +00:00
|
|
|
'arch': '<xpath expr="//base" position="after"><v3/></xpath>'
|
|
|
|
})
|
|
|
|
|
|
|
|
def test_applied(self):
|
|
|
|
""" mandatory and enabled views should be applied
|
|
|
|
"""
|
|
|
|
arch = self.read_combined(self.v0)['arch']
|
|
|
|
self.assertEqual(
|
|
|
|
ET.fromstring(arch),
|
|
|
|
E.qweb(
|
|
|
|
E.base(),
|
|
|
|
E.v1(),
|
|
|
|
E.v2(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2014-05-27 09:52:57 +00:00
|
|
|
def test_applied_state_toggle(self):
|
2014-08-31 14:56:44 +00:00
|
|
|
""" Change active states of v2 and v3, check that the results
|
2014-05-27 09:52:57 +00:00
|
|
|
are as expected
|
|
|
|
"""
|
2014-08-31 14:56:44 +00:00
|
|
|
self.browse(self.v2).toggle()
|
2014-05-27 09:52:57 +00:00
|
|
|
arch = self.read_combined(self.v0)['arch']
|
|
|
|
self.assertEqual(
|
|
|
|
ET.fromstring(arch),
|
|
|
|
E.qweb(
|
|
|
|
E.base(),
|
|
|
|
E.v1(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2014-08-31 14:56:44 +00:00
|
|
|
self.browse(self.v3).toggle()
|
2014-05-27 09:52:57 +00:00
|
|
|
arch = self.read_combined(self.v0)['arch']
|
|
|
|
self.assertEqual(
|
|
|
|
ET.fromstring(arch),
|
|
|
|
E.qweb(
|
|
|
|
E.base(),
|
|
|
|
E.v1(),
|
|
|
|
E.v3(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2014-08-31 14:56:44 +00:00
|
|
|
self.browse(self.v2).toggle()
|
2014-05-27 09:52:57 +00:00
|
|
|
arch = self.read_combined(self.v0)['arch']
|
|
|
|
self.assertEqual(
|
|
|
|
ET.fromstring(arch),
|
|
|
|
E.qweb(
|
|
|
|
E.base(),
|
|
|
|
E.v1(),
|
|
|
|
E.v2(),
|
|
|
|
E.v3(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
[ADD] hasclass() xpath function
Server-side, view extension is done via xpath. This includes "template" views
full of HTML.
HTML elements often have a bunch of classes, sometimes even semantic
(!). XPath is generally great, but specifically lousy at dealing with
space-separated values: in standard XPath 1.0 to know if an element has a
class 'foo' the predicate is:
contains(concat(' ', normalize-space(@class), ' '), ' foo ')
and this has to be fully duplicated if there's a second class involved.
Things are slightly better with EXSLT/XPath 2.0 and tokenize, but still not
great:
tokenize(@class, '\s+') = 'foo'
and the equality check is very weird when unaware of XPath's evaluation rules.
``hasclass`` makes this much simpler to deal with: to get any ``foo`` node
with the class ``bar`` is as simple as:
//foo[hasclass('bar')
and it can take multiple class, as with e.g. jquery it will return elements
with all specified classes.
Beware though, the predicate function will be called once for each element to
check, since it's implemented in pure python and not profiled elements should
be filtered as much as possible before this point.
2014-04-15 14:56:59 +00:00
|
|
|
class TestXPathExtentions(common.BaseCase):
|
|
|
|
def test_hasclass(self):
|
|
|
|
tree = E.node(
|
|
|
|
E.node({'class': 'foo bar baz'}),
|
|
|
|
E.node({'class': 'foo bar'}),
|
|
|
|
{'class': "foo"})
|
|
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
len(tree.xpath('//node[hasclass("foo")]')),
|
|
|
|
3)
|
|
|
|
self.assertEqual(
|
|
|
|
len(tree.xpath('//node[hasclass("bar")]')),
|
|
|
|
2)
|
|
|
|
self.assertEqual(
|
|
|
|
len(tree.xpath('//node[hasclass("baz")]')),
|
|
|
|
1)
|
|
|
|
self.assertEqual(
|
|
|
|
len(tree.xpath('//node[hasclass("foo")][not(hasclass("bar"))]')),
|
|
|
|
1)
|
|
|
|
self.assertEqual(
|
|
|
|
len(tree.xpath('//node[hasclass("foo", "baz")]')),
|
|
|
|
1)
|