odoo/openerp/addons/base/tests/test_views.py

1189 lines
38 KiB
Python

# -*- encoding: utf-8 -*-
from functools import partial
import itertools
import unittest2
from lxml import etree as ET
from lxml.builder import E
from psycopg2 import IntegrityError
from openerp.tests import common
import openerp.tools
Field = E.field
class ViewCase(common.TransactionCase):
def setUp(self):
super(ViewCase, self).setUp()
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 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 assertTreesEqual(self, n1, n2, msg=None):
self.assertEqual(n1.tag, n2.tag, msg)
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)
for c1, c2 in itertools.izip_longest(n1, n2):
self.assertEqual(c1, c2, msg)
class TestNodeLocator(common.TransactionCase):
"""
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)
class TestViewInheritance(ViewCase):
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)
def makeView(self, name, parent=None, arch=None):
""" 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, {
'model': self.model,
'name': name,
'arch': arch or self.arch_for(name, parent=parent),
'inherit_id': parent,
'priority': 5, # higher than default views
})
self.ids[name] = view_id
return view_id
def setUp(self):
super(TestViewInheritance, self).setUp()
self.model = 'ir.ui.view.custom'
self.View = self.registry('ir.ui.view')
self._init = self.View.pool._init
self.View.pool._init = False
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)
b = self.makeView('B', arch=self.arch_for("B", 'tree'))
self.makeView('B1', b, arch=self.arch_for("B1", 'tree', parent=b))
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_inheriting_views_arch(self):
self.assertEqual(self.View.get_inheriting_views_arch(
self.cr, self.uid, self.ids['A'], self.model), [
(self.arch_for('A1', parent=True), self.ids['A1']),
(self.arch_for('A2', parent=True), self.ids['A2']),
])
self.assertEqual(self.View.get_inheriting_views_arch(
self.cr, self.uid, self.ids['A21'], self.model),
[])
self.assertEqual(self.View.get_inheriting_views_arch(
self.cr, self.uid, self.ids['A11'], self.model),
[(self.arch_for('A111', parent=True), self.ids['A111'])])
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'])
def test_no_default_view(self):
self.assertFalse(
self.View.default_view(
self.cr, self.uid, model='does.not.exist', view_type='form'))
self.assertFalse(
self.View.default_view(
self.cr, self.uid, model=self.model, view_type='graph'))
class TestApplyInheritanceSpecs(ViewCase):
""" Applies a sequence of inheritance specification nodes to a base
architecture. IO state parameters (cr, uid, model, context) are used for
error reporting
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):
spec = Field(
Field(name="replacement"),
name="target", position="replace")
self.View.apply_inheritance_specs(self.cr, self.uid,
self.base_arch,
spec, None)
self.assertEqual(
self.base_arch,
E.form(Field(name="replacement"), string="Title"))
def test_delete(self):
spec = Field(name="target", position="replace")
self.View.apply_inheritance_specs(self.cr, self.uid,
self.base_arch,
spec, None)
self.assertEqual(
self.base_arch,
E.form(string="Title"))
def test_insert_after(self):
spec = Field(
Field(name="inserted"),
name="target", position="after")
self.View.apply_inheritance_specs(self.cr, self.uid,
self.base_arch,
spec, None)
self.assertEqual(
self.base_arch,
E.form(
Field(name="target"),
Field(name="inserted"),
string="Title"
))
def test_insert_before(self):
spec = Field(
Field(name="inserted"),
name="target", position="before")
self.View.apply_inheritance_specs(self.cr, self.uid,
self.base_arch,
spec, None)
self.assertEqual(
self.base_arch,
E.form(
Field(name="inserted"),
Field(name="target"),
string="Title"))
def test_insert_inside(self):
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)
self.assertEqual(
self.base_arch,
E.form(
Field(
Field(name="inserted"),
Field(name="inserted 2"),
name="target"),
string="Title"))
def test_unpack_data(self):
spec = E.data(
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"),
)
self.View.apply_inheritance_specs(self.cr, self.uid,
self.base_arch,
spec, None)
self.assertEqual(
self.base_arch,
E.form(
Field(
Field(name="inserted 0"),
Field(name="inserted 1"),
Field(name="inserted 2"),
Field(name="inserted 3"),
name="target"),
string="Title"))
@openerp.tools.mute_logger('openerp.addons.base.ir.ir_ui_view')
def test_invalid_position(self):
spec = Field(
Field(name="whoops"),
name="target", position="serious_series")
with self.assertRaises(AttributeError):
self.View.apply_inheritance_specs(self.cr, self.uid,
self.base_arch,
spec, None)
@openerp.tools.mute_logger('openerp.addons.base.ir.ir_ui_view')
def test_incorrect_version(self):
# Version ignored on //field elements, so use something else
arch = E.form(E.element(foo="42"))
spec = E.element(
Field(name="placeholder"),
foo="42", version="7.0")
with self.assertRaises(AttributeError):
self.View.apply_inheritance_specs(self.cr, self.uid,
arch,
spec, None)
@openerp.tools.mute_logger('openerp.addons.base.ir.ir_ui_view')
def test_target_not_found(self):
spec = Field(name="targut")
with self.assertRaises(AttributeError):
self.View.apply_inheritance_specs(self.cr, self.uid,
self.base_arch,
spec, None)
class TestApplyInheritedArchs(ViewCase):
""" Applies a sequence of modificator archs to a base view
"""
class TestNoModel(ViewCase):
def test_create_view_nomodel(self):
View = self.registry('ir.ui.view')
view_id = View.create(self.cr, self.uid, {
'name': 'dummy',
'arch': '<template name="foo"/>',
'inherit_id': False,
'type': 'qweb',
})
fields = ['name', 'arch', 'type', 'priority', 'inherit_id', 'model']
[view] = View.read(self.cr, self.uid, [view_id], fields)
self.assertEqual(view, {
'id': view_id,
'name': 'dummy',
'arch': '<template name="foo"/>',
'type': 'qweb',
'priority': 16,
'inherit_id': False,
'model': False,
})
text_para = E.p("", {'class': 'legalese'})
arch = E.body(
E.div(
E.h1("Title"),
id="header"),
E.p("Welcome!"),
E.div(
E.hr(),
text_para,
id="footer"),
{'class': "index"},)
def test_qweb_translation(self):
"""
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')
orig_text = "Copyright copyrighter"
translated_text = u"Copyrighter, tous droits réservés"
self.text_para.text = orig_text
self.registry('ir.translation').create(self.cr, self.uid, {
'name': 'website',
'type': 'view',
'lang': 'fr_FR',
'src': orig_text,
'value': translated_text,
})
sarch = View.translate_qweb(self.cr, self.uid, None, self.arch, 'fr_FR')
self.text_para.text = translated_text
self.assertEqual(sarch, self.arch)
class TestTemplating(ViewCase):
def setUp(self):
import openerp.modules
super(TestTemplating, self).setUp()
self._pool = openerp.modules.registry.RegistryManager.get(common.DB)
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")
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)
self.assertEqual(
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]',
'data-oe-source-id': str(id)
}), {
'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]'
})
)
)
def test_esc_no_branding(self):
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"}))))
def test_ignore_unbrand(self):
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(
{'order': '2'},
E.content(
{'t-att-href': 'foo'},
"bar")
)
)
),
"t-ignore should apply to injected sub-view branding, not just to"
" the main view's"
)
class test_views(ViewCase):
def test_nonexistent_attribute_removal(self):
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>
""",
})
def _insert_view(self, **kw):
"""Insert view into database via a query to passtrough validation"""
kw.pop('id', None)
kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary')
kw.setdefault('active', True)
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]
def test_custom_view_validation(self):
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
vid = self._insert_view(
name='base view',
model=model,
priority=1,
arch="""<?xml version="1.0"?>
<tree string="view">
<field name="url"/>
</tree>
""",
)
self.assertTrue(validate()) # single view
# validation of a inherited view
self._insert_view(
name='inherited view',
model=model,
priority=1,
inherit_id=vid,
arch="""<?xml version="1.0"?>
<xpath expr="//field[@name='url']" position="before">
<field name="name"/>
</xpath>
""",
)
self.assertTrue(validate()) # inherited view
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(
ET.fromstring(
view['arch'],
parser=ET.XMLParser(remove_blank_text=True)
),
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"))
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(
ET.fromstring(
view['arch'],
parser=ET.XMLParser(remove_blank_text=True)
),
E.form(
E.p("Replacement data"),
E.footer(
E.button(name="action_next", type="object", string="New button")),
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 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 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,
'active': True,
'priority': 10,
'arch': '<xpath expr="//base" position="after"><v1/></xpath>',
})
self.v2 = self.create({
'model': 'a',
'inherit_id': self.v0,
'active': True,
'priority': 9,
'arch': '<xpath expr="//base" position="after"><v2/></xpath>',
})
self.v3 = self.create({
'model': 'a',
'inherit_id': self.v0,
'active': False,
'priority': 8,
'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(),
)
)
def test_applied_state_toggle(self):
""" Change active states of v2 and v3, check that the results
are as expected
"""
self.browse(self.v2).toggle()
arch = self.read_combined(self.v0)['arch']
self.assertEqual(
ET.fromstring(arch),
E.qweb(
E.base(),
E.v1(),
)
)
self.browse(self.v3).toggle()
arch = self.read_combined(self.v0)['arch']
self.assertEqual(
ET.fromstring(arch),
E.qweb(
E.base(),
E.v1(),
E.v3(),
)
)
self.browse(self.v2).toggle()
arch = self.read_combined(self.v0)['arch']
self.assertEqual(
ET.fromstring(arch),
E.qweb(
E.base(),
E.v1(),
E.v2(),
E.v3(),
)
)
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)