# 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("/web", 'web_studio_new_app_tour', login="admin") # the report tour is based on the result of the former tour self.start_tour("/web?debug=tests", 'web_studio_new_report_tour', login="admin") self.start_tour("/web?debug=tests", "web_studio_new_report_basic_layout_tour", login="admin") def test_optional_fields(self): self.start_tour("/web?debug=tests", 'web_studio_hide_fields_tour', login="admin") def test_model_option_value(self): self.start_tour("/web?debug=tests", 'web_studio_model_option_value_tour', login="admin") def test_rename(self): self.start_tour("/web?debug=tests", 'web_studio_main_and_rename', login="admin", timeout=200) def test_approval(self): self.start_tour("/web?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("/web?debug=tests", 'web_studio_custom_background_tour', login="admin") def test_create_app_with_pipeline_and_user_assignment(self): self.start_tour("/web?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("/web?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]/tree/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("/web?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("/web?debug=tests", 'web_studio_check_method_in_model', login="admin") def test_create_action_button_in_form_view(self): self.start_tour("/web?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("/web?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("/web?debug=tests", 'web_studio_test_remove_action_button_in_form_view', login="admin") self.start_tour("/web?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("/web?debug=tests", 'web_studio_test_create_action_button_in_list_view', login="admin") view = self.env["ir.ui.view"].search([ ("name", "=", "res.partner.tree"), ], 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("/web?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("/web?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("/web?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("/web?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": "tree", "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("/web?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": "tree", "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("/web?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": "tree", "arch": ''' '''.format(doesnothavegroup=doesNotHaveGroupXmlId.complete_name) }) self.testAction.write({ "view_ids": [Command.clear(), Command.create({"view_id": self.testView.id, "view_mode": "tree"})] }) self.start_tour("/web?debug=tests", 'web_studio_field_with_group', login="admin", timeout=200) self.assertEqual(len(operations), 1) self.assertEqual(operations[0]["target"]["xpath_info"], [{'tag': 'tree', '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("/web", '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("/web", '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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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': 'tree', 'indice': 2 }, { 'tag': 'field', 'indice': 1 }, ], 'subview_xpath': "/form[1]/field[2]/tree[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)) @mute_logger('odoo.addons.base.models.ir_ui_view') def test_postprocess_empty_groups_attribute(self): view = self.env["ir.ui.view"].create({ "name": "simple view", "model": "res.partner", "type": "form", "arch": '''
''' }) studio_view = self.env[view.model].with_context(studio=True).get_view(view.id, view.type) assertViewArchEqual(self, studio_view["arch"], '''
''') 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": "tree", "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", column_invisible="True", groups=doesNotHaveGroupXmlId.complete_name, 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": "tree", "arch": ''' ''' }) self.testAction.write({ "view_ids": [ Command.clear(), Command.create({"view_id": self.testViewList.id, "view_mode": "tree"}), ] }) self.start_tour("/web?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("/web?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("/web?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("/web?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": "tree", "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": "tree"}), Command.create({"view_id": self.testViewKanban.id, "view_mode": "kanban"}), ] }) self.start_tour("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web?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("/web", "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("/web?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("/web", "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"/web?debug=1#action=studio&mode=editor&_tab=views&_view_type=list&_action={action.id}" 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("/web?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("/web?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_approval_button_xml_id(self): self.testView.arch = """
""" self.start_tour("/web?debug=tests", "web_studio_test_approval_button_xml_id", login="admin") tree = self.testView._get_combined_arch() button = tree.xpath("//button")[0] self.assertEqual(button.get("studio_approval"), "True") approvals = self.env["studio.approval.rule"].get_approval_spec("res.partner", False, "base.action_model_data", False) self.assertEqual(len(approvals["rules"]), 1) rule = self.env["studio.approval.rule"].browse(approvals["rules"][0]["id"]) # actions have different record set, but the api of ir.actions.actions # guarantees that the ids match across the two models. self.assertEqual(rule.action_id.id, self.env.ref("base.action_model_data").id) 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": "tree", "arch": ''' ''' }) self.testAction.write({ "view_ids": [Command.clear(), Command.create({"view_id": self.testView.id, "view_mode": "tree"})] }) 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))