# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from functools import partial from lxml import etree from lxml.builder import E from psycopg2 import IntegrityError from odoo.osv.orm import modifiers_tests from odoo.exceptions import ValidationError from odoo.tests import common from odoo.tools import mute_logger class ViewXMLID(common.TransactionCase): def test_model_data_id(self): """ Check whether views know their xmlid record. """ view = self.env.ref('base.view_company_form') self.assertTrue(view) self.assertTrue(view.model_data_id) self.assertEqual(view.model_data_id.complete_name, 'base.view_company_form') class ViewCase(common.TransactionCase): def setUp(self): super(ViewCase, self).setUp() self.View = self.env['ir.ui.view'] 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 test_no_match_xpath(self): """ xpath simply uses the provided @expr pattern to find a node """ node = self.env['ir.ui.view'].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.env['ir.ui.view'].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.env['ir.ui.view'].locate_node( E.root(E.foo(), E.bar(), E.baz()), E.field(name="qux"), ) self.assertIsNone(node) node = self.env['ir.ui.view'].locate_node( E.root(E.field(name="foo"), E.field(name="bar"), E.field(name="baz")), E.field(name="qux"), ) self.assertIsNone(node) def test_match_field(self): bar = E.field(name="bar") node = self.env['ir.ui.view'].locate_node( E.root(E.field(name="foo"), bar, E.field(name="baz")), E.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.env['ir.ui.view'].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.env['ir.ui.view'].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.env['ir.ui.view'].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.env['ir.ui.view'].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.env['ir.ui.view'].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 etree.tostring(element, encoding='unicode') 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 = self.View.create({ 'model': self.model, 'name': name, 'arch': arch or self.arch_for(name, parent=parent), 'inherit_id': parent, 'priority': 5, # higher than default views }) self.view_ids[name] = view.id return view def setUp(self): super(TestViewInheritance, self).setUp() self.patch(self.registry, '_init', False) self.model = 'ir.ui.view.custom' self.view_ids = {} a = self.makeView("A") a1 = self.makeView("A1", a.id) a11 = self.makeView("A11", a1.id) self.makeView("A111", a11.id) self.makeView("A12", a1.id) a2 = self.makeView("A2", a.id) self.makeView("A21", a2.id) a22 = self.makeView("A22", a2.id) self.makeView("A221", a22.id) b = self.makeView('B', arch=self.arch_for("B", 'tree')) self.makeView('B1', b.id, arch=self.arch_for("B1", 'tree', parent=b)) c = self.makeView('C', arch=self.arch_for("C", 'tree')) c.write({'priority': 1}) def test_get_inheriting_views_arch(self): self.assertEqual( self.View.get_inheriting_views_arch(self.view_ids['A'], self.model), [ (self.arch_for('A1', parent=True), self.view_ids['A1']), (self.arch_for('A2', parent=True), self.view_ids['A2']), ]) self.assertEqual( self.View.get_inheriting_views_arch(self.view_ids['A21'], self.model), []) self.assertEqual( self.View.get_inheriting_views_arch(self.view_ids['A11'], self.model), [(self.arch_for('A111', parent=True), self.view_ids['A111'])]) def test_default_view(self): default = self.View.default_view(model=self.model, view_type='form') self.assertEqual(default, self.view_ids['A']) default_tree = self.View.default_view(model=self.model, view_type='tree') self.assertEqual(default_tree, self.view_ids['C']) def test_no_default_view(self): self.assertFalse(self.View.default_view(model='does.not.exist', view_type='form')) self.assertFalse(self.View.default_view(model=self.model, view_type='graph')) def test_no_recursion(self): r1 = self.makeView('R1') with self.assertRaises(ValidationError), self.cr.savepoint(): r1.write({'inherit_id': r1.id}) r2 = self.makeView('R2', r1.id) r3 = self.makeView('R3', r2.id) with self.assertRaises(ValidationError), self.cr.savepoint(): r2.write({'inherit_id': r3.id}) with self.assertRaises(ValidationError), self.cr.savepoint(): r1.write({'inherit_id': r3.id}) with self.assertRaises(ValidationError), self.cr.savepoint(): r1.write({ 'inherit_id': r1.id, 'arch': self.arch_for('itself', parent=True), }) 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.base_arch = E.form( E.field(name="target"), string="Title") def test_replace(self): spec = E.field( E.field(name="replacement"), name="target", position="replace") self.View.apply_inheritance_specs(self.base_arch, spec, None) self.assertEqual( self.base_arch, E.form(E.field(name="replacement"), string="Title")) def test_delete(self): spec = E.field(name="target", position="replace") self.View.apply_inheritance_specs(self.base_arch, spec, None) self.assertEqual( self.base_arch, E.form(string="Title")) def test_insert_after(self): spec = E.field( E.field(name="inserted"), name="target", position="after") self.View.apply_inheritance_specs(self.base_arch, spec, None) self.assertEqual( self.base_arch, E.form( E.field(name="target"), E.field(name="inserted"), string="Title" )) def test_insert_before(self): spec = E.field( E.field(name="inserted"), name="target", position="before") self.View.apply_inheritance_specs(self.base_arch, spec, None) self.assertEqual( self.base_arch, E.form( E.field(name="inserted"), E.field(name="target"), string="Title")) def test_insert_inside(self): default = E.field(E.field(name="inserted"), name="target") spec = E.field(E.field(name="inserted 2"), name="target", position='inside') self.View.apply_inheritance_specs(self.base_arch, default, None) self.View.apply_inheritance_specs(self.base_arch, spec, None) self.assertEqual( self.base_arch, E.form( E.field( E.field(name="inserted"), E.field(name="inserted 2"), name="target"), string="Title")) def test_unpack_data(self): spec = E.data( E.field(E.field(name="inserted 0"), name="target"), E.field(E.field(name="inserted 1"), name="target"), E.field(E.field(name="inserted 2"), name="target"), E.field(E.field(name="inserted 3"), name="target"), ) self.View.apply_inheritance_specs(self.base_arch, spec, None) self.assertEqual( self.base_arch, E.form( E.field( E.field(name="inserted 0"), E.field(name="inserted 1"), E.field(name="inserted 2"), E.field(name="inserted 3"), name="target"), string="Title")) @mute_logger('odoo.addons.base.models.ir_ui_view') def test_invalid_position(self): spec = E.field( E.field(name="whoops"), name="target", position="serious_series") with self.assertRaises(ValueError): self.View.apply_inheritance_specs(self.base_arch, spec, None) @mute_logger('odoo.addons.base.models.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( E.field(name="placeholder"), foo="42", version="7.0") with self.assertRaises(ValueError): self.View.apply_inheritance_specs(arch, spec, None) @mute_logger('odoo.addons.base.models.ir_ui_view') def test_target_not_found(self): spec = E.field(name="targut") with self.assertRaises(ValueError): self.View.apply_inheritance_specs(self.base_arch, spec, None) class TestApplyInheritanceWrapSpecs(ViewCase): def setUp(self): super(TestApplyInheritanceWrapSpecs, self).setUp() self.base_arch = E.template(E.div(E.p("Content"))) def apply_spec(self, spec): self.View.apply_inheritance_specs(self.base_arch, spec, None) def test_replace(self): spec = E.xpath( E.div("$0", {'class': "some"}), expr="//p", position="replace") self.apply_spec(spec) self.assertEqual( self.base_arch, E.template(E.div( E.div(E.p('Content'), {'class': 'some'}) )) ) class TestApplyInheritanceMoveSpecs(ViewCase): def setUp(self): super(TestApplyInheritanceMoveSpecs, self).setUp() self.base_arch = E.template( E.div(E.p("Content", {'class': 'some'})), E.div({'class': 'target'}) ) self.wrapped_arch = E.template( E.div("aaaa", E.p("Content", {'class': 'some'}), "bbbb"), E.div({'class': 'target'}) ) def apply_spec(self, arch, spec): self.View.apply_inheritance_specs(arch, spec, None) def test_move_replace(self): spec = E.xpath( E.xpath(expr="//p", position="move"), expr="//div[@class='target']", position="replace") self.apply_spec(self.base_arch, spec) self.assertEqual( self.base_arch, E.template( E.div(), E.p("Content", {'class': 'some'}) ) ) self.apply_spec(self.wrapped_arch, spec) self.assertEqual( self.wrapped_arch, E.template( E.div("aaaabbbb"), E.p("Content", {'class': 'some'}) ) ) def test_move_inside(self): spec = E.xpath( E.xpath(expr="//p", position="move"), expr="//div[@class='target']", position="inside") self.apply_spec(self.base_arch, spec) self.assertEqual( self.base_arch, E.template( E.div(), E.div(E.p("Content", {'class': 'some'}), {'class': 'target'}) ) ) self.apply_spec(self.wrapped_arch, spec) self.assertEqual( self.wrapped_arch, E.template( E.div("aaaabbbb"), E.div(E.p("Content", {'class': 'some'}), {'class': 'target'}) ) ) def test_move_before(self): spec = E.xpath( E.xpath(expr="//p", position="move"), expr="//div[@class='target']", position="before") self.apply_spec(self.base_arch, spec) self.assertEqual( self.base_arch, E.template( E.div(""), E.p("Content", {'class': 'some'}), E.div({'class': 'target'}), ) ) self.apply_spec(self.wrapped_arch, spec) self.assertEqual( self.wrapped_arch, E.template( E.div("aaaabbbb"), E.p("Content", {'class': 'some'}), E.div({'class': 'target'}), ) ) def test_move_after(self): spec = E.xpath( E.xpath(expr="//p", position="move"), expr="//div[@class='target']", position="after") self.apply_spec(self.base_arch, spec) self.assertEqual( self.base_arch, E.template( E.div(), E.div({'class': 'target'}), E.p("Content", {'class': 'some'}), ) ) self.apply_spec(self.wrapped_arch, spec) self.assertEqual( self.wrapped_arch, E.template( E.div("aaaabbbb"), E.div({'class': 'target'}), E.p("Content", {'class': 'some'}), ) ) def test_move_with_other_1(self): # multiple elements with move in first position spec = E.xpath( E.xpath(expr="//p", position="move"), E.p("Content2", {'class': 'new_p'}), expr="//div[@class='target']", position="after") self.apply_spec(self.base_arch, spec) self.assertEqual( self.base_arch, E.template( E.div(), E.div({'class': 'target'}), E.p("Content", {'class': 'some'}), E.p("Content2", {'class': 'new_p'}), ) ) def test_move_with_other_2(self): # multiple elements with move in last position spec = E.xpath( E.p("Content2", {'class': 'new_p'}), E.xpath(expr="//p", position="move"), expr="//div[@class='target']", position="after") self.apply_spec(self.wrapped_arch, spec) self.assertEqual( self.wrapped_arch, E.template( E.div("aaaabbbb"), E.div({'class': 'target'}), E.p("Content2", {'class': 'new_p'}), E.p("Content", {'class': 'some'}), ) ) @mute_logger('odoo.addons.base.models.ir_ui_view') def test_incorrect_move_1(self): # cannot move an inexisting element spec = E.xpath( E.xpath(expr="//p[@name='none']", position="move"), expr="//div[@class='target']", position="after") with self.assertRaises(ValueError): self.apply_spec(self.base_arch, spec) @mute_logger('odoo.addons.base.models.ir_ui_view') def test_incorrect_move_2(self): # move xpath cannot contain any children spec = E.xpath( E.xpath(E.p("Content2", {'class': 'new_p'}), expr="//p", position="move"), expr="//div[@class='target']", position="after") with self.assertRaises(ValueError): self.apply_spec(self.base_arch, spec) def test_incorrect_move_3(self): # move won't be correctly applied if not a direct child of an xpath spec = E.xpath( E.div(E.xpath(E.p("Content2", {'class': 'new_p'}), expr="//p", position="move"), {'class': 'wrapper'}), expr="//div[@class='target']", position="after") self.apply_spec(self.base_arch, spec) self.assertEqual( self.base_arch, E.template( E.div(E.p("Content", {'class': 'some'})), E.div({'class': 'target'}), E.div(E.xpath(E.p("Content2", {'class': 'new_p'}), expr="//p", position="move"), {'class': 'wrapper'}), ) ) class TestApplyInheritedArchs(ViewCase): """ Applies a sequence of modificator archs to a base view """ class TestNoModel(ViewCase): def test_create_view_nomodel(self): view = self.View.create({ 'name': 'dummy', 'arch': '