# Part of Odoo. See LICENSE file for full copyright and licensing details. # -*- coding: utf-8 -*- import logging from lxml import etree from lxml.builder import E import json import odoo.tests from odoo import Command, api, http from odoo.tools import mute_logger from odoo.addons.web_studio.controllers.main import WebStudioController _logger = logging.getLogger(__name__) @odoo.tests.tagged('post_install', '-at_install') class TestUi(odoo.tests.HttpCase): def test_new_app_and_report(self): self.start_tour("/odoo", 'web_studio_new_app_tour', login="admin") # the new submenu tour is based on the result of the new app tour self.start_tour("/odoo", 'web_studio_new_submenu_tour', login="admin") # the report tour is based on the result of the new app tour self.start_tour("/odoo?debug=tests", 'web_studio_new_report_tour', login="admin") self.start_tour("/odoo?debug=tests", "web_studio_new_report_basic_layout_tour", login="admin") def test_optional_fields(self): self.start_tour("/odoo?debug=tests", 'web_studio_hide_fields_tour', login="admin") def test_model_option_value(self): self.start_tour("/odoo?debug=tests", 'web_studio_model_option_value_tour', login="admin") def test_rename(self): self.start_tour("/odoo?debug=tests", 'web_studio_main_and_rename', login="admin", timeout=200) def test_approval(self): self.start_tour("/odoo?debug=tests", 'web_studio_approval_tour', login="admin") def test_background(self): attachment = self.env['ir.attachment'].create({ 'datas': b'R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=', 'name': 'testFilename.gif', 'public': True, 'mimetype': 'image/gif' }) self.env.company.background_image = attachment.datas self.start_tour("/odoo?debug=tests", 'web_studio_custom_background_tour', login="admin") def test_create_app_with_pipeline_and_user_assignment(self): self.start_tour("/odoo?debug=tests", 'web_studio_create_app_with_pipeline_and_user_assignment', login="admin") def test_alter_field_existing_in_multiple_views(self): created_model_name = None studio_model_create = type(self.env["ir.model"]).studio_model_create def mock_studio_model_create(*args, **kwargs): nonlocal created_model_name res = studio_model_create(*args, **kwargs) created_model_name = res[0].model return res self.patch(type(self.env["ir.model"]), "studio_model_create", mock_studio_model_create) self.start_tour("/odoo?debug=tests", 'web_studio_alter_field_existing_in_multiple_views_tour', login="admin") # we can't assert xml equality as a lot of stuff in the arch are set randomly view = self.env["ir.ui.view"].search([("model", "=", created_model_name), ("type", "=", "form")], limit=1) tree = etree.fromstring(view.get_combined_arch()) root = tree.getroottree() fields_of_interest = tree.xpath("//field[@name='message_partner_ids']") self.assertEqual(len(fields_of_interest), 2) # First field is on the main model: not below another field # The second one is in a subview self.assertEqual(root.getpath(fields_of_interest[0]), "/form/sheet/group/group[1]/field") self.assertEqual(root.getpath(fields_of_interest[1]), "/form/sheet/field[2]/list/field[1]") # The tour in its final steps is putting invisible on the field in the subview self.assertEqual(fields_of_interest[0].get("invisible"), None) self.assertEqual(fields_of_interest[1].get("invisible"), None) self.assertEqual(fields_of_interest[0].get("column_invisible"), None) self.assertEqual(fields_of_interest[1].get("column_invisible"), "True") def test_add_field_into_empty_group_by(self): self.start_tour("/odoo?debug=tests", 'web_studio_add_field_into_empty_group_by', login="admin") def _get_studio_view(view): domain = [('inherit_id', '=', view.id), ('name', '=', "Odoo Studio: %s customization" % (view.name))] return view.search(domain, order='priority desc, name desc, id desc', limit=1) def _transform_arch_for_assert(arch_string): parser = etree.XMLParser(remove_blank_text=True) arch_string = etree.fromstring(arch_string, parser=parser) return etree.tostring(arch_string, pretty_print=True, encoding='unicode') def assertViewArchEqual(test, original, expected): if original: original = _transform_arch_for_assert(original) if expected: expected = _transform_arch_for_assert(expected) test.assertEqual(original, expected) def watch_edit_view(test, on_edit_view): def clear_routing(): test.env.registry.clear_cache('routing') clear_routing() edit_view = WebStudioController.edit_view @http.route('/web_studio/edit_view', type='json', auth='user') def edit_view_mocked(*args, **kwargs): on_edit_view(*args, **kwargs) return edit_view(*args, **kwargs) test.patch(WebStudioController, "edit_view", edit_view_mocked) test.addCleanup(clear_routing) def watch_create_new_field(test, on_create_new_field): create_new_field = WebStudioController.create_new_field def create_new_field_mocked(*args, **kwargs): response = create_new_field(*args, **kwargs) on_create_new_field(*args, **kwargs) return response test.patch(WebStudioController, "create_new_field", create_new_field_mocked) def setup_view_editor_data(cls): cls.env.company.country_id = cls.env.ref('base.us') cls.testView = cls.env["ir.ui.view"].create({ "name": "simple partner", "model": "res.partner", "type": "form", "arch": '''
''' }) cls.testAction = cls.env["ir.actions.act_window"].create({ "name": "simple partner", "res_model": "res.partner", "view_ids": [Command.create({"view_id": cls.testView.id, "view_mode": "form"})] }) cls.testActionXmlId = cls.env["ir.model.data"].create({ "name": "studio_test_partner_action", "model": "ir.actions.act_window", "module": "web_studio", "res_id": cls.testAction.id, }) cls.testMenu = cls.env["ir.ui.menu"].create({ "name": "Studio Test Partner", "action": "ir.actions.act_window,%s" % cls.testAction.id }) cls.testMenuXmlId = cls.env["ir.model.data"].create({ "name": "studio_test_partner_menu", "model": "ir.ui.menu", "module": "web_studio", "res_id": cls.testMenu.id, }) @odoo.tests.tagged('post_install', '-at_install') class TestStudioUIUnit(odoo.tests.HttpCase): @classmethod def setUpClass(cls): super().setUpClass() setup_view_editor_data(cls) def create_empty_app(self): self.newModel = self.env['ir.model'].create({ 'name': 'Test Model', 'model': 'x_test_model', 'field_id': [ (0, 0, {'name': 'x_name', 'ttype': 'char', 'field_description': 'Name'}), ] }) self.newModel._setup_access_rights() self.newView = self.env["ir.ui.view"].create({ "name": "simpleView", "model": "x_test_model", "type": "form", "arch": '''
''' }) self.newAction = self.env["ir.actions.act_window"].create({ "name": "simple model", "res_model": "x_test_model", "view_ids": [Command.create({"view_id": self.newView.id, "view_mode": "form"})] }) self.newActionXmlId = self.env["ir.model.data"].create({ "name": "studio_app_action", "model": "ir.actions.act_window", "module": "web_studio", "res_id": self.newAction.id, }) self.newMenu = self.env["ir.ui.menu"].create({ "name": "StudioApp", "action": "ir.actions.act_window,%s" % self.newAction.id }) self.newMenuXmlId = self.env["ir.model.data"].create({ "name": "studio_app_menu", "model": "ir.ui.menu", "module": "web_studio", "res_id": self.newMenu.id, }) @mute_logger('odoo.http') def test_web_studio_check_method_in_model(self): self.start_tour("/odoo?debug=tests", 'web_studio_check_method_in_model', login="admin") def test_create_action_button_in_form_view(self): self.start_tour("/odoo?debug=tests", 'web_studio_test_create_action_button_in_form_view', login="admin") studioView = _get_studio_view(self.testView) model = self.env["ir.model"].search([("model", "=", "res.partner")]) action1 = self.env["ir.actions.actions"].search([ ("name", "=", "Privacy Lookup"), ("type", "=", "ir.actions.server"), ("binding_model_id", "=", model.id), ]) action1 = self.env[action1.type].browse(action1.id) assertViewArchEqual(self, studioView.arch, """
""".format(action1_Id=action1.xml_id)) self.start_tour("/odoo?debug=tests", 'web_studio_test_create_second_action_button_in_form_view', login="admin") action2 = self.env["ir.actions.actions"].search([ ("name", "=", "Download (vCard)"), ("type", "=", "ir.actions.server"), ("binding_model_id", "=", model.id), ]) action2 = self.env[action2.type].browse(action2.id) assertViewArchEqual(self, studioView.arch, """
""".format(action1_Id=action1.xml_id, action2_Id=action2.xml_id)) self.start_tour("/odoo?debug=tests", 'web_studio_test_remove_action_button_in_form_view', login="admin") self.start_tour("/odoo?debug=tests", 'web_studio_test_remove_action_button_in_form_view', login="admin") arch = """
""" #FIXME Can't do it otherwise cause of indentation problems self.assertEqual(studioView.arch.replace(" ", ""), arch.replace(" ", "")) def test_create_action_button_in_list_view(self): self.start_tour("/odoo?debug=tests", 'web_studio_test_create_action_button_in_list_view', login="admin") view = self.env["ir.ui.view"].search([ ("name", "=", "res.partner.list"), ], limit=1) studioView = _get_studio_view(view) model = self.env["ir.model"].search([("model", "=", "res.partner")]) action = self.env["ir.actions.actions"].search([ ("name", "=", "Privacy Lookup"), ("type", "=", "ir.actions.server"), ("binding_model_id", "=", model.id), ]) action = self.env[action.type].browse(action.id) assertViewArchEqual(self, studioView.arch, """
""".format(actionId=action.xml_id)) self.start_tour("/odoo?debug=tests", 'web_studio_test_remove_action_button_in_list_view', login="admin") arch = """
""" #FIXME Can't do it otherwise cause of indentation problems self.assertEqual(studioView.arch.replace(" ", ""), arch.replace(" ", "")) def test_form_view_not_altered_by_studio_xml_edition(self): self.start_tour("/odoo?debug=tests", 'web_studio_test_form_view_not_altered_by_studio_xml_edition', login="admin", timeout=200) def test_edit_with_xml_editor(self): studioView = self.env["ir.ui.view"].create({ 'type': self.testView.type, 'model': self.testView.model, 'inherit_id': self.testView.id, 'mode': 'extension', 'priority': 99, 'arch': "
", 'name': "Odoo Studio: %s customization" % (self.testView.name) }) self.start_tour("/odoo?debug=tests", 'web_studio_test_edit_with_xml_editor', login="admin", timeout=200) self.assertEqual(studioView.arch, "") def test_enter_x2many_edition_and_add_field(self): doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) userView = self.env["ir.ui.view"].create({ "name": "simple user", "model": "res.users", "type": "form", "arch": '''
'''.format(doesnothavegroup=doesNotHaveGroupXmlId.complete_name) }) userViewXmlId = self.env["ir.model.data"].create({ "name": "studio_test_user_view", "model": "ir.ui.view", "module": "web_studio", "res_id": userView.id, }) self.testView.arch = '''
''' % userViewXmlId.complete_name studioView = _get_studio_view(self.testView) self.assertFalse(studioView.exists()) self.start_tour("/odoo?debug=tests", 'web_studio_enter_x2many_edition_and_add_field', login="admin", timeout=200) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, """
""".format(doesnothavegroup=doesNotHaveGroupXmlId.complete_name)) def test_enter_x2many_auto_inlined_subview(self): userView = self.env["ir.ui.view"].create({ "name": "simple user", "model": "res.users", "type": "list", "arch": ''' ''' }) userViewXmlId = self.env["ir.model.data"].create({ "name": "studio_test_user_view", "model": "ir.ui.view", "module": "web_studio", "res_id": userView.id, }) self.testView.arch = '''
''' % userViewXmlId.complete_name studioView = _get_studio_view(self.testView) self.assertFalse(studioView.exists()) self.start_tour("/odoo?debug=tests", 'web_studio_enter_x2many_auto_inlined_subview', login="admin", timeout=200) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, """ """) def test_enter_x2many_auto_inlined_subview_with_multiple_field_matching(self): user_view = self.env["ir.ui.view"].create({ "name": "simple user", "model": "res.users", "type": "list", "arch": ''' ''' }) user_view_xml_id = self.env["ir.model.data"].create({ "name": "studio_test_user_view", "model": "ir.ui.view", "module": "web_studio", "res_id": user_view.id, }) self.testView.arch = '''
''' % (user_view_xml_id.complete_name, user_view_xml_id.complete_name) studio_view = _get_studio_view(self.testView) self.assertFalse(studio_view.exists()) self.start_tour("/odoo?debug=tests", 'web_studio_enter_x2many_auto_inlined_subview_with_multiple_field_matching', login="admin", timeout=200) studio_view = _get_studio_view(self.testView) assertViewArchEqual(self, studio_view.arch, """ """) def test_field_with_group(self): operations = [] def edit_view_mocked(*args, **kwargs): operations.extend(kwargs["operations"] if "operations" in kwargs else args[3]) watch_edit_view(self, edit_view_mocked) doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) self.testView.write({ "type": "list", "arch": ''' '''.format(doesnothavegroup=doesNotHaveGroupXmlId.complete_name) }) self.testAction.write({ "view_ids": [Command.clear(), Command.create({"view_id": self.testView.id, "view_mode": "list"})] }) self.start_tour("/odoo?debug=tests", 'web_studio_field_with_group', login="admin", timeout=200) self.assertEqual(len(operations), 1) self.assertEqual(operations[0]["target"]["xpath_info"], [{'tag': 'list', 'indice': 1}, {'tag': 'field', 'indice': 3}]) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, """ """) def test_studio_no_fetch_subview(self): doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) def create_new_field_mocked(*args, **kwargs): # For this test we need to patch groups from the phone field. Unfortunately it seems # that the patch is erased each time we create a new field so we have to patch after. self.patch(type(self.env["res.partner"]).phone, "groups", doesNotHaveGroupXmlId.complete_name) watch_create_new_field(self, create_new_field_mocked) self.patch(type(self.env["res.partner"]).phone, "groups", doesNotHaveGroupXmlId.complete_name) self.testView.write({ "arch": '''
''' }) self.start_tour("/odoo", 'web_studio_no_fetch_subview', login="admin") def test_elements_with_groups_form(self): operations = [] def edit_view_mocked(*args, **kwargs): operations.extend(kwargs["operations"] if "operations" in kwargs else args[3]) watch_edit_view(self, edit_view_mocked) doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) hasGroup = self.env["res.groups"].create({ "name": "studio has group", "users": [Command.link(2)] }) hasGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_hasgroup", "model": "res.groups", "module": "web_studio", "res_id": hasGroup.id, }) self.patch(type(self.env["res.partner"]).function, "groups", doesNotHaveGroupXmlId.complete_name) self.testView.write({ "arch": '''
'''.format(doesnothavegroup=doesNotHaveGroupXmlId.complete_name, hasgroup=hasGroupXmlId.complete_name) }) self.start_tour("/odoo", 'web_studio_elements_with_groups_form', login="admin", timeout=600000) self.assertEqual(len(operations), 1) self.assertEqual(operations[0]["target"]["xpath_info"], [{'indice': 1, 'tag': 'form'}, {'indice': 1, 'tag': 'group'}, {'indice': 3, 'tag': 'field'}]) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, """ """) def test_element_group_in_sidebar(self): group = self.env["res.groups"].create({ "name": "Test Group", "users": [Command.link(2)] }) groupXmlId = self.env["ir.model.data"].create({ "name": "test_group", "model": "res.groups", "module": "web_studio", "res_id": group.id, }) self.testView.write({ "arch": '''
'''.format(group=groupXmlId.complete_name) }) self.start_tour("/odoo?debug=tests", 'test_element_group_in_sidebar', login="admin", timeout=600000) def test_create_one2many_lines_then_edit_name(self): self.testView.arch = '''
''' custom_fields_before_studio = self.env["ir.model.fields"].search([ ("state", "=", "manual"), ]) self.start_tour("/odoo?debug=tests", 'web_studio_test_create_one2many_lines_then_edit_name', login="admin", timeout=30000) custom_fields = self.env["ir.model.fields"].search_read([ ("state", "=", "manual"), ("id", "not in", custom_fields_before_studio.ids), ], fields=["name", "ttype", "field_description"]) self.maxDiff = None self.assertCountEqual( [{key: val for key, val in field.items() if key != 'id'} for field in custom_fields], [ {"name": "x_studio_new_name", 'ttype': 'one2many', 'field_description': 'new name'}, {"name": "x_name", 'ttype': 'char', 'field_description': 'Description'}, {"name": "x_res_partner_id", 'ttype': 'many2one', 'field_description': 'X Res Partner'}, {"name": "x_studio_sequence", 'ttype': 'integer', 'field_description': 'Sequence'}, ] ) def test_address_view_id_no_edit(self): self.testView.write({ "arch": '''
''' }) self.env.company.country_id.address_view_id = self.env.ref('base.view_partner_address_form') self.start_tour("/odoo?debug=tests", 'web_studio_test_address_view_id_no_edit', login="admin", timeout=200) studio_view = _get_studio_view(self.testView) assertViewArchEqual(self, studio_view.arch, """ True """) def test_custom_selection_field_edit_values(self): self.testView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_custom_selection_field_edit_values', login="admin", timeout=200) selection_field = self.env["ir.model.fields"].search( [ ("state", "=", "manual"), ("model", "=", "res.partner"), ("ttype", "=", "selection") ], limit=1 ) self.assertEqual(selection_field.selection_ids.mapped("name"), ["some value", "another value"]) def test_create_new_model_from_existing_view(self): self.testView.write({ "model": "res.users", "type": "kanban", "arch": ''' ''' }) self.testAction.view_ids.view_mode = "kanban" self.start_tour("/odoo?debug=tests", 'web_studio_test_create_new_model_from_existing_view', login="admin", timeout=200) def test_create_model_with_clickable_stages(self): self.start_tour("/odoo?debug=tests", 'web_studio_test_create_model_with_clickable_stages', login="admin", timeout=200) def test_enter_x2many_edition_with_multiple_subviews(self): doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) hasGroup = self.env["res.groups"].create({ "name": "studio has group", "users": [Command.link(2)] }) hasGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_hasgroup", "model": "res.groups", "module": "web_studio", "res_id": hasGroup.id, }) self.testView.arch = '''
'''.format(doesnothavegroup=doesNotHaveGroupXmlId.complete_name, hasgroup=hasGroupXmlId.complete_name) self.start_tour("/odoo?debug=tests", 'web_studio_test_enter_x2many_edition_with_multiple_subviews', login="admin", timeout=200) def test_enter_x2many_edition_with_multiple_subviews_correct_xpath(self): operations = [] def edit_view_mocked(*args, **kwargs): operations.extend(kwargs["operations"] if "operations" in kwargs else args[3]) watch_edit_view(self, edit_view_mocked) doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) hasGroup = self.env["res.groups"].create({ "name": "studio has group", "users": [Command.link(2)] }) hasGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_hasgroup", "model": "res.groups", "module": "web_studio", "res_id": hasGroup.id, }) self.testView.arch = '''
'''.format(doesnothavegroup=doesNotHaveGroupXmlId.complete_name, hasgroup=hasGroupXmlId.complete_name) self.start_tour("/odoo?debug=tests", 'web_studio_test_enter_x2many_edition_with_multiple_subviews_correct_xpath', login="admin", timeout=200) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, """ """) self.assertEqual(len(operations), 1) self.assertDictEqual(operations[0], { 'type': 'add', 'target': { 'tag': 'field', 'attrs': { 'name': 'name' }, 'xpath_info': [ { 'tag': 'list', 'indice': 2 }, { 'tag': 'field', 'indice': 1 }, ], 'subview_xpath': "/form[1]/field[2]/list[2]", }, 'position': 'before', 'node': { 'tag': 'field', 'attrs': { 'name': 'active', 'optional': 'show' } } }) def test_studio_arch_has_measure_field_ids(self): view = self.env["ir.ui.view"].create({ "name": "simple view", "model": "res.partner", "type": "pivot", "arch": ''' ''' }) studio_view = self.env[view.model].with_context(studio=True).get_view(view.id, view.type) field_id = self.env['ir.model.fields'].search([('model', '=', view.model), ('name', 'in', ['display_name'])]).ids[0] assertViewArchEqual(self, studio_view["arch"], ''' '''.format(field_id=field_id)) def test_field_with_groups_in_tree_node_has_groups_too(self): # The field has a group in python in which the user is # The node has also a group in which the user is not hasGroup = self.env["res.groups"].create({ "name": "studio has group", "users": [Command.link(self.env.user.id)] }) hasGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_hasgroup", "model": "res.groups", "module": "web_studio", "res_id": hasGroup.id, }) doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) self.patch(type(self.env["res.partner"]).title, "groups", hasGroupXmlId.complete_name) view = self.env["ir.ui.view"].create({ "name": "simple view", "model": "res.partner", "type": "list", "arch": ''' '''.format(doesnothavegroup=doesNotHaveGroupXmlId.complete_name) }) arch = self.env[view.model].with_context(studio=True).get_view(view.id, view.type)["arch"] studio_groups = json.dumps([{ "id": doesNotHaveGroup.id, "name": doesNotHaveGroup.name, "display_name": doesNotHaveGroup.display_name, "forbid": False, }]) xml_temp = E.field(name="title", groups=doesNotHaveGroupXmlId.complete_name, column_invisible="True", studio_groups=studio_groups) expected = ''' {xml_stringified} '''.format(xml_stringified=etree.tostring(xml_temp).decode("utf-8")) assertViewArchEqual(self, arch, expected) def test_set_tree_column_conditional_invisibility(self): self.testViewList = self.env["ir.ui.view"].create({ "name": "simple partner", "model": "res.partner", "type": "list", "arch": ''' ''' }) self.testAction.write({ "view_ids": [ Command.clear(), Command.create({"view_id": self.testViewList.id, "view_mode": "list"}), ] }) self.start_tour("/odoo?debug=tests", 'web_studio_set_tree_node_conditional_invisibility', login="admin", timeout=200) arch = self.env[self.testViewList.model].with_context(studio=True).get_view(self.testViewList.id, self.testViewList.type)["arch"] expected = ''' '''.format(title_modifiers="display_name == "Robert"") assertViewArchEqual(self, arch, expected) def test_studio_view_is_last(self): # The studio view created should have, in all cases, a priority greater than all views # that are part of the inheritance self.testView.arch = '''
''' self.env["ir.ui.view"].create({ "name": "simple view inherit", "inherit_id": self.testView.id, "mode": "extension", "priority": 123, "model": "res.partner", "type": "form", "arch": ''' ''' }) self.start_tour("/odoo?debug=tests", 'web_studio_test_studio_view_is_last', login="admin", timeout=200) studioView = _get_studio_view(self.testView) self.assertEqual(studioView.priority, 1230) self.maxDiff = None assertViewArchEqual(self, studioView["arch"], ''' ''') def test_edit_form_subview_attributes(self): self.testView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_test_edit_form_subview_attributes', login="admin", timeout=200) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, """ false """) def test_x2many_two_levels_edition(self): self.testView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_x2many_two_levels_edition', login="admin", timeout=200) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, ''' ''') def test_field_group_studio_no_fetch(self): doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) self.patch(type(self.env["res.partner"]).function, "groups", doesNotHaveGroupXmlId.complete_name) self.testViewForm = self.env["ir.ui.view"].create({ "name": "simple partner", "model": "res.partner", "type": "form", "arch": '''
''' }) self.testViewList = self.env["ir.ui.view"].create({ "name": "simple partner", "model": "res.partner", "type": "list", "arch": ''' ''' }) self.testViewKanban = self.env["ir.ui.view"].create({ "name": "simple partner", "model": "res.partner", "type": "kanban", "arch": ''' ''' }) self.testAction.write({ "view_ids": [ Command.clear(), Command.create({"view_id": self.testViewForm.id, "view_mode": "form"}), Command.create({"view_id": self.testViewList.id, "view_mode": "list"}), Command.create({"view_id": self.testViewKanban.id, "view_mode": "kanban"}), ] }) self.start_tour("/odoo?debug=tests", 'web_studio_field_group_studio_no_fetch', login="admin", timeout=200) def test_monetary_create(self): self.create_empty_app() self.newView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_monetary_create', login="admin") # There is a new currency and there is a new monetary fields = self.env[self.newModel.model]._fields currency_name_list = list(filter(lambda key: fields[key]._description_type == 'many2one' and fields[key]._description_relation == 'res.currency', fields.keys())) monetary_name_list = list(filter(lambda key: fields[key].type == 'monetary', fields.keys())) self.assertEqual(len(currency_name_list), 1) self.assertEqual(len(monetary_name_list), 1) # The studio arch contains the new monetary and the new currency studioView = _get_studio_view(self.newView) assertViewArchEqual(self, studioView.arch, f""" """) def test_monetary_change_currency_name(self): self.create_empty_app() currency = self.env["ir.model.fields"].create({ "name": "x_studio_currency_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "many2one", "relation": "res.currency", }) self.env["ir.model.fields"].create({ "name": "x_studio_monetary_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "monetary", "currency_field": "x_studio_currency_test", }) self.newView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_monetary_change_currency_name', login="admin") self.assertEqual(currency.field_description, "NewCurrency") def test_related_monetary_creation(self): res_partner_model_id = self.env["ir.model"].search([("model", "=", "res.partner")]).id self.create_empty_app() self.env["ir.model.fields"].create({ "name": "x_studio_currency_test", "model": "res.partner", "model_id": res_partner_model_id, "ttype": "many2one", "relation": "res.currency", }) self.env["ir.model.fields"].create({ "name": "x_studio_monetary_test", "model": "res.partner", "model_id": res_partner_model_id, "ttype": "monetary", "currency_field": "x_studio_currency_test", }) self.testView.arch = '''
''' self.env["ir.model.fields"].create({ "name": "x_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "many2one", "relation": "res.partner", }) self.newView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_related_monetary_creation', login="admin") # There is only one currency and there is a new monetary fields = self.env["x_test_model"]._fields currency_name_list = list(filter(lambda key: fields[key]._description_type == 'many2one' and fields[key]._description_relation == 'res.currency', fields.keys())) monetary_name_list = list(filter(lambda key: fields[key].type == 'monetary', fields.keys())) self.assertEqual(len(currency_name_list), 1) self.assertEqual(len(monetary_name_list), 1) # The monetary has been created and is a related field self.assertEqual(self.env['x_test_model']._fields[monetary_name_list[0]].related, "x_test.x_studio_monetary_test") # A currency has been created because there was none in the model/view self.assertEqual(currency_name_list[0], 'x_studio_currency_id') def test_monetary_change_currency_field(self): self.create_empty_app() self.env["ir.model.fields"].create({ "name": "x_studio_currency_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "many2one", "relation": "res.currency", }) self.env["ir.model.fields"].create({ "name": "x_studio_currency_test2", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "many2one", "relation": "res.currency", }) monetary = self.env["ir.model.fields"].create({ "name": "x_studio_monetary_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "monetary", "currency_field": "x_studio_currency_test", }) self.newView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_monetary_change_currency_field', login="admin") # The currency_field in the monetary field changed self.assertEqual(monetary.currency_field, 'x_studio_currency_test2') def test_monetary_change_currency_not_in_view(self): self.create_empty_app() self.env["ir.model.fields"].create({ "name": "x_studio_currency_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "many2one", "relation": "res.currency", }) self.env["ir.model.fields"].create({ "name": "x_studio_currency_test2", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "many2one", "relation": "res.currency", }) monetary = self.env["ir.model.fields"].create({ "name": "x_studio_monetary_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "monetary", "currency_field": "x_studio_currency_test", }) self.newView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_monetary_change_currency_not_in_view', login="admin") # The currency_field in the monetary field changed self.assertEqual(monetary.currency_field, 'x_studio_currency_test2') studioView = _get_studio_view(self.newView) assertViewArchEqual(self, studioView.arch, """ """) def test_monetary_add_existing_monetary(self): self.create_empty_app() self.env["ir.model.fields"].create({ "name": "x_studio_currency_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "many2one", "relation": "res.currency", }) self.env["ir.model.fields"].create({ "name": "x_studio_monetary_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "monetary", "currency_field": "x_studio_currency_test", }) self.newView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_monetary_add_existing_monetary', login="admin") # The studio arch contains the monetary and the associated currency studioView = _get_studio_view(self.newView) assertViewArchEqual(self, studioView.arch, """ """) def test_monetary_create_monetary_with_existing_currency(self): self.create_empty_app() self.env["ir.model.fields"].create({ "name": "x_studio_currency_test", "model": "x_test_model", "model_id": self.newModel.id, "ttype": "many2one", "relation": "res.currency", }) self.newView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_monetary_create_monetary_with_existing_currency', login="admin") # There is only one currency and there is a new monetary fields = self.env[self.newModel.model]._fields currency_name_list = list(filter(lambda key: fields[key]._description_type == 'many2one' and fields[key]._description_relation == 'res.currency', fields.keys())) monetary_name_list = list(filter(lambda key: fields[key].type == 'monetary', fields.keys())) self.assertEqual(len(currency_name_list), 1) self.assertEqual(len(monetary_name_list), 1) # The studio arch contains the new monetary but no currency as it exist in the original arch studioView = _get_studio_view(self.newView) assertViewArchEqual(self, studioView.arch, f""" """) def test_move_similar_field(self): self.testView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_test_move_similar_field', login="admin", timeout=400) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, ''' ''') def create_user_view(self): self.testView.write({ "name": "simple user", "model": "res.users", "arch": '''
''' }) self.testAction.write({ "name": "simple user", "res_model": "res.users", }) self.testActionXmlId.name = "studio_test_user_action" self.testMenu.name = "Studio Test User" self.testMenuXmlId.name = "studio_test_user_menu" def test_related_binary_field_with_filename(self): self.create_user_view() partner_field = self.env["ir.model.fields"].create({ "field_description": "New File", "name": "x_new_file", "ttype": "binary", "model": "res.partner", "model_id": self.env["ir.model"]._get('res.partner').id, "state": "manual", }) self.env["ir.model.fields"].create({ "field_description": "New File filename", "name": "x_new_file_filename", "ttype": "char", "model": "res.partner", "model_id": self.env["ir.model"]._get('res.partner').id, "state": "manual", }) self.start_tour("/odoo?debug=tests", 'web_studio_test_related_file', login="admin", timeout=400) studioView = _get_studio_view(self.testView) binary_field = self.env["ir.model.fields"].search([('model', '=', 'res.users'), ('ttype', '=', 'binary'), ('name', 'like', 'x_studio_related')]) self.assertEqual(len(binary_field), 1) self.assertEqual(binary_field.related_field_id, partner_field) assertViewArchEqual(self, studioView.arch, ''' '''.format(binary_field=binary_field)) def test_nested_related_binary_field_with_filename(self): self.create_user_view() self.env["ir.model.fields"].create({ "field_description": "New File", "name": "x_new_file", "ttype": "binary", "model": "res.partner.category", "model_id": self.env["ir.model"]._get('res.partner.category').id, "state": "manual", }) self.env["ir.model.fields"].create({ "field_description": "New File filename", "name": "x_new_file_filename", "ttype": "char", "model": "res.partner.category", "model_id": self.env["ir.model"]._get('res.partner.category').id, "state": "manual", }) partner_field = self.env["ir.model.fields"].create({ "field_description": "New File", "name": "x_new_related_file", "ttype": "binary", "model": "res.partner", "model_id": self.env["ir.model"]._get('res.partner').id, "state": "manual", "related": "category_id.x_new_file" }) self.env["ir.model.fields"].create({ "field_description": "New File filename", "name": "x_new_related_file_filename", "ttype": "char", "model": "res.partner", "model_id": self.env["ir.model"]._get('res.partner').id, "state": "manual", "related": "category_id.x_new_file_filename" }) self.start_tour("/odoo?debug=tests", 'web_studio_test_related_file', login="admin", timeout=400) studioView = _get_studio_view(self.testView) binary_field = self.env["ir.model.fields"].search([('model', '=', 'res.users'), ('ttype', '=', 'binary'), ('name', 'like', 'x_studio_related')]) self.assertEqual(len(binary_field), 1) self.assertEqual(binary_field.related_field_id, partner_field) assertViewArchEqual(self, studioView.arch, ''' '''.format(binary_field=binary_field)) def test_related_binary_field_without_filename(self): self.create_user_view() partner_field = self.env["ir.model.fields"].create({ "field_description": "New File", "name": "x_new_file", "ttype": "binary", "model": "res.partner", "model_id": self.env["ir.model"]._get('res.partner').id, "state": "manual", }) self.start_tour("/odoo?debug=tests", 'web_studio_test_related_file', login="admin", timeout=400) studioView = _get_studio_view(self.testView) binary_field = self.env["ir.model.fields"].search([('model', '=', 'res.users'), ('ttype', '=', 'binary'), ('name', 'like', 'x_studio_related')]) self.assertEqual(len(binary_field), 1) self.assertEqual(binary_field.related_field_id, partner_field) assertViewArchEqual(self, studioView.arch, ''' '''.format(binary_field=binary_field)) def test_undo_new_field(self): self.testView.arch = '''
''' self.start_tour("/odoo?debug=tests", "web_studio_test_undo_new_field", login="admin", timeout=200) def test_change_lone_attr_modifier_form(self): self.testView.arch = """
""" self.start_tour("/odoo?debug=tests", "web_studio_test_change_lone_attr_modifier_form", login="admin") studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, ''' False ''') def test_drag_and_drop_boolean(self): self.testView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_boolean_field_drag_and_drop', login="admin", timeout=200) studioView = _get_studio_view(self.testView) boolean_field = self.env['ir.model.fields'].search([('name', 'like', 'x_studio_boolean')])[0] assertViewArchEqual(self, studioView.arch, ''' '''.format(boolean_field=boolean_field)) def test_new_field_rename_description(self): self.testView.arch = '''
''' self.start_tour("/odoo", "web_studio_test_new_field_rename_description", login="admin") new_field = self.env["ir.model.fields"]._get("res.partner", "x_studio_my_new_field") self.assertEqual(new_field.field_description, "my new field") studioView = _get_studio_view(self.testView) self.assertXMLEqual(studioView.arch, """ """) def test_edit_digits_option(self): self.testView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_test_edit_digits_option', login="admin", timeout=200) studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, """ {"digits":[4,2]} """) def test_button_rainbow_effect(self): self.testView.arch = """
""" self.start_tour("/odoo", "web_studio.test_button_rainbow_effect", login="admin") studioView = _get_studio_view(self.testView) attachment = self.env["ir.attachment"].search([("name", "=", "my_studio_image.png")]) self.assertXMLEqual(studioView.arch, """ {'img_url': '/web/content/%s'} """ % attachment.id) def test_context_write_cleaned(self): view = self.env["ir.ui.view"].create({ "model": "res.users", "type": "form", "arch": """
""" }) operations = [ { "type": "add", "target": { "tag": "div", "attrs": { "class": "oe_button_box" } }, "position": "inside", "node": { "tag": "button", "field": self.env["ir.model.fields"]._get("res.partner", "user_id").id, "string": "Test studio new button", "attrs": { "class": "oe_stat_button", "icon": "fa-diamond" } } } ] create_action = self.env.registry["ir.actions.actions"]._create action_created = False def mock_act_create(rec_set, *args, **kwargs): nonlocal action_created action_created = True context = dict(rec_set.env.context) del context["tz"] self.assertEqual(context, {'lang': 'en_US', 'uid': 2, 'arbitrary_key': 'arbitrary', "studio": 1}) return create_action(rec_set, *args, **kwargs) self.patch(self.env.registry["ir.actions.actions"], "_create", mock_act_create) self.authenticate("admin", "admin") with mute_logger("odoo.addons.base.models.ir_ui_view"): response = self.url_open("/web_studio/edit_view", headers={"Content-Type": "application/json"}, data=json.dumps({ "params": { "view_id": view.id, "studio_view_arch": "", "model": "res.users", "operations": operations, "context": { "default_type": "some_type", "arbitrary_key": "arbitrary", "studio": 1, } }, })) action = self.env["ir.actions.act_window"].search([("res_model", "=", "res.partner")], order="create_date DESC", limit=1) self.assertTrue(action_created, True) self.assertEqual(action.type, "ir.actions.act_window") self.assertEqual(action.name, "Test studio new button") self.assertXMLEqual(response.json()["result"]["views"]["form"]["arch"], f"""
""") def test_res_users_fake_fields(self): user_fields = self.env["res.users"].fields_get() assertable = [field["string"] for field in user_fields.values() if field["string"] in ("Administration", "Multi Companies")] self.assertEqual(len(assertable), 2) action = self.env.ref("base.action_res_users") url = f"/odoo/action-studio?mode=editor&_tab=views&_view_type=list&_action={action.id}&debug=1" self.start_tour(url, 'web_studio.test_res_users_fake_fields', login="admin") def test_add_button_xml_id(self): base_view = self.env["ir.ui.view"].create({ "name": "test_partner_simple", "model": "res.partner", "mode": "primary", "type": "form", "arch": """
""" }) operations = [ { "type": "add", "target": { "tag": "div", "attrs": { "class": "oe_button_box" } }, "view_id": base_view.id, "position": "inside", "node": { "tag": "button", "field": self.env["ir.model.fields"]._get("res.partner", "parent_id").id, "string": "aa", "attrs": { "class": "oe_stat_button", "icon": "fa-diamond" } } } ] with mute_logger("odoo.addons.base.models.ir_ui_view"): self.authenticate("admin", "admin") self.url_open("/web_studio/edit_view", data=json.dumps({ "params": { "view_id": base_view.id, "model": "res.partner", "studio_view_arch": "", "operations": operations, "context": {"studio": 1}, } }), headers={"Content-Type": "application/json"}) action = self.env["ir.actions.act_window"].search([], limit=1, order="create_date DESC") self.assertTrue(action.xml_id.startswith("studio_customization.")) form = base_view.get_combined_arch() self.assertXMLEqual(form, f"""
""") def test_reload_after_restoring_default_view(self): self.start_tour("/odoo?debug=tests", 'web_studio_test_reload_after_restoring_default_view', login="admin") def test_edit_reified_field(self): # find some reified field name reified_fname = next( fname for fname in self.env["res.users"].fields_get() if fname.startswith(('in_group_', 'sel_groups_')) ) self.testView.write({ "name": "simple user", "model": "res.users", "arch": '''
''' % reified_fname }) self.testAction.res_model = "res.users" self.start_tour("/odoo?debug=tests", 'web_studio_test_edit_reified_field', login="admin") studioView = _get_studio_view(self.testView) assertViewArchEqual(self, studioView.arch, """ new name """ % reified_fname) def test_add_all_types_fields_related(self): self.create_user_view() self.start_tour("/odoo?debug=tests", 'web_studio_test_add_all_types_fields_related', login="admin") field = self.env["ir.model.fields"].search([('model', '=', 'res.users'), ('name', 'like', 'x_studio_related')]) target_field = self.env["ir.model.fields"].search([('model', '=', 'res.users'), ('name', '=', 'display_name')]) self.assertEqual(len(field), 1) self.assertEqual(field.related_field_id, target_field) def test_add_many2one_without_related(self): """ Test ensure raise a warning and not the current view does not contain studio arch when the user tries to add one2many field but the model does not contain any related many2one field. """ self.create_empty_app() self.newView.arch = '''
''' self.start_tour("/odoo?debug=tests", 'web_studio_add_one2many_no_related_many2one', login="admin") studioView = _get_studio_view(self.newView) # Does not contain studio arch assertViewArchEqual(self, studioView.arch, False) def test_approval_button_xml_id(self): self.testView.arch = """
""" self.start_tour("/odoo?debug=tests", "web_studio_test_approval_button_xml_id", login="admin") approvals = self.env["studio.approval.rule"].get_approval_spec([{"action_id": "base.action_model_data", "model": "res.partner", "res_id": False}]) partner_approvals = dict(approvals["res.partner"]) self.assertTrue(partner_approvals[(False, False, "base.action_model_data")]["rules"]) def test_kanban_field_bold(self): self.testView.write({ "model": "res.partner", "type": "kanban", "arch": ''' ''' }) self.testAction.view_ids.view_mode = "kanban" self.start_tour("/odoo?debug=tests", 'web_studio_test_kanban_field_bold', login="admin", timeout=200) studioView = _get_studio_view(self.testView) self.assertXMLEqual(studioView.arch, """ fs-6 whatever fw-bold text-muted """) def test_kanban_menu_ribbon(self): self.testViewKanban = self.env["ir.ui.view"].create({ "name": "simple partner", "model": "res.partner", "type": "kanban", "arch": ''' ''' }) self.testAction.write({ "view_ids": [ Command.clear(), Command.create({"view_id": self.testViewKanban.id, "view_mode": "kanban"}), ] }) self.start_tour("/odoo?debug=tests", 'web_studio_test_kanban_menu_ribbon', login="admin") studio_view = _get_studio_view(self.testViewKanban) self.assertXMLEqual(studio_view.arch, """ Edit Delete """) def test_create_related(self): self.testViewForm = self.env["ir.ui.view"].create({ "name": "simple partner", "model": "res.partner", "type": "form", "arch": '''
''' }) self.testAction.write({ "view_ids": [ Command.clear(), Command.create({"view_id": self.testViewForm.id, "view_mode": "form"}), ] }) self.assertEqual(self.env["res.partner"]._fields["child_ids"].type, "one2many") self.start_tour("/odoo?debug=tests", 'web_studio_test_create_related', login="admin") partnerModel = self.env["ir.model"]._get("res.partner") new_field = self.env["ir.model.fields"].search([("model_id", "=", partnerModel.id)], order="id DESC", limit=1) self.assertEqual(new_field.related, "parent_id.child_ids") studio_view = _get_studio_view(self.testViewForm) self.assertXMLEqual(studio_view.arch, f""" """) def test_negated_groups(self): doesNotHaveGroup = self.env["res.groups"].create({ "name": "studio does not have" }) doesNotHaveGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_doesnothavegroup", "model": "res.groups", "module": "web_studio", "res_id": doesNotHaveGroup.id, }) hasGroup = self.env["res.groups"].create({ "name": "studio has group", }) hasGroupXmlId = self.env["ir.model.data"].create({ "name": "studio_test_hasgroup", "model": "res.groups", "module": "web_studio", "res_id": hasGroup.id, }) self.testView.arch = f"""
""" self.start_tour("/web?debug=tests", "web_studio_test_negated_groups", login="admin") studio_view = _get_studio_view(self.testView) assertViewArchEqual(self, studio_view.arch, """ web_studio.studio_test_doesnothavegroup,!base.group_erp_manager """) def test_use_action_domain(self): self.env["res.partner"].search([['employee', '=', True]]).action_archive() self.env["res.partner"].create({ "name": "Michel", "employee": True, }) self.env["res.partner"].create({ "name": "Paul", "employee": False, }) self.testAction.domain = "[['employee', '=', True]]" self.testView.write({ "type": "list", "arch": ''' ''' }) self.testAction.write({ "view_ids": [Command.clear(), Command.create({"view_id": self.testView.id, "view_mode": "list"})] }) self.start_tour("/web?debug=tests", "web_studio_test_use_action_domain", login="admin") def test_drag_before_sheet(self): self.testView.arch = """
""" self.start_tour("/web?debug=tests", "web_studio_test_drag_before_sheet", login="admin") studio_view = _get_studio_view(self.testView) [html_field] = self.env['ir.model.fields'].search([('name', '=like', 'x_studio_html_field_%')], limit=1) assertViewArchEqual(self, studio_view.arch, ''' '''.format(html_field=html_field))