Odoo18-Base/extra-addons/web_studio/tests/test_ui.py

1996 lines
78 KiB
Python

# 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": '''
<form>
<field name="name" />
</form>
'''
})
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": '''
<form>
<group>
<field name="x_name" />
</group>
</form>
'''
})
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, """
<data>
<xpath expr="//form[1]/field[@name='name']" position="before">
<header>
<button string="web_studio_new_button_action_name" name="{action1_Id}" type="action"/>
</header>
</xpath>
</data>""".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, """
<data>
<xpath expr="//form[1]/field[@name='name']" position="before">
<header>
<button string="web_studio_new_button_action_name" name="{action1_Id}" type="action"/>
<button string="web_studio_other_button_action_name" name="{action2_Id}" type="action"/>
</header>
</xpath>
</data>""".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 = """<data>
<xpath expr="//form[1]/field[@name='name']" position="before">
<header/>
</xpath>
</data>"""
#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, """
<data>
<xpath expr="//field[@name='complete_name']" position="before">
<header>
<button string="web_studio_new_button_action_name" name="{actionId}" type="action"/>
</header>
</xpath>
</data>""".format(actionId=action.xml_id))
self.start_tour("/odoo?debug=tests", 'web_studio_test_remove_action_button_in_list_view', login="admin")
arch = """<data>
<xpath expr="//field[@name='complete_name']" position="before">
<header/>
</xpath>
</data>"""
#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': "<data><xpath expr=\"//field[@name='name']\" position=\"after\"> <div class=\"someDiv\"/></xpath></data>",
'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, "<data/>")
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": '''
<form class="test-user-form">
<t groups="{doesnothavegroup}" >
<div class="condition_group" />
</t>
<group>
<field name="name" />
</group>
</form>
'''.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 = '''<form><field name="user_ids" context="{'form_view_ref': '%s'}" /></form>''' % 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, """
<data>
<xpath expr="//field[@name='user_ids']" position="inside">
<form class="test-user-form">
<t groups="{doesnothavegroup}" >
<div class="condition_group" />
</t>
<group>
<field name="name"/>
<field name="log_ids"/>
</group>
</form>
</xpath>
</data>
""".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": '''
<list class="test-user-list">
<field name="display_name" />
</list>
'''
})
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 = '''<form><field name="user_ids" context="{'list_view_ref': '%s'}" /></form>''' % 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, """
<data>
<xpath expr="//field[@name='user_ids']" position="inside">
<list class="test-user-list">
<field name="display_name" />
<field name="log_ids" optional="show" />
</list>
</xpath>
</data>
""")
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": '''
<list class="test-user-list">
<field name="display_name" />
</list>
'''
})
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 = '''<form>
<field name="user_ids" context="{'list_view_ref': '%s'}"/>
<sheet>
<notebook>
<page>
<field name="user_ids" context="{'list_view_ref': '%s'}" />
</page>
</notebook>
</sheet>
</form>''' % (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, """
<data>
<xpath expr="//form[1]/sheet[1]/notebook[1]/page[1]/field[@name='user_ids']" position="inside">
<list class="test-user-list">
<field name="display_name" />
<field name="log_ids" optional="show" />
</list>
</xpath>
</data>
""")
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": '''
<list>
<field name="display_name" />
<field name="employee" groups="{doesnothavegroup}" />
<field name="function" />
<field name="lang" />
</list>
'''.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, """
<data>
<xpath expr="//field[@name='function']" position="after">
<field name="website" optional="show"/>
</xpath>
</data>
""")
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": '''
<form>
<group>
<field name="name"/>
</group>
</form>
'''
})
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": '''
<form>
<group>
<field name="function" groups="{hasgroup}" />
<field name="employee" groups="{doesnothavegroup}" />
<field name="display_name" />
</group>
</form>
'''.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, """
<data>
<xpath expr="//field[@name='display_name']" position="after">
<field name="website"/>
</xpath>
</data>
""")
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": '''
<form>
<field name="display_name" groups="{group}" />
</form>
'''.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 = '''
<form>
<group>
<field name="name" />
</group>
</form>
'''
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": '''
<form>
<div class="o_address_format">
<field name="lang"/>
</div>
</form>
'''
})
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, """
<data>
<xpath expr="//field[@name='lang']" position="attributes">
<attribute name="required">True</attribute>
</xpath>
</data>
""")
def test_custom_selection_field_edit_values(self):
self.testView.arch = '''
<form>
<group>
<field name="name" />
</group>
</form>
'''
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": '''<kanban>
<templates>
<t t-name="card">
<field name="display_name"/>
</t>
</templates>
</kanban>
'''
})
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 = '''
<form>
<field name="name"/>
<field name="child_ids">
<list groups="{hasgroup}">
<field name="type"/>
</list>
<list groups="{doesnothavegroup}">
<field name="name"/>
</list>
</field>
</form>
'''.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 = '''
<form>
<field name="name"/>
<field name="child_ids">
<list groups="{doesnothavegroup}" class="test-subview-list">
<field name="name"/>
</list>
<list groups="{hasgroup}" class="test-subview-list">
<field name="name"/>
</list>
</field>
</form>
'''.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, """
<data>
<xpath expr="//form[1]/field[@name='child_ids']/list[2]/field[@name='name']" position="before">
<field name="active" optional="show"/>
</xpath>
</data>
""")
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": '''
<pivot>
<field name="display_name" type="measure"/>
</pivot>
'''
})
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"], '''
<pivot studio_pivot_measure_field_ids="[{field_id}]">
<field name="display_name" type="measure"/>
</pivot>
'''.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": '''
<list>
<field name="display_name"/>
<field name="title" groups="{doesnothavegroup}" />
</list>
'''.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 = '''
<list>
<field name="display_name"/>
{xml_stringified}
</list>
'''.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": '''
<list>
<field name="display_name" />
<field name="title" />
</list>
'''
})
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 = '''
<list>
<field name="display_name"/>
<field name="title" invisible="{title_modifiers}"/>
</list>
'''.format(title_modifiers="display_name == &quot;Robert&quot;")
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 = '''
<form><sheet>
<group><field name="name" /></group>
</sheet></form>
'''
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": '''
<data>
<xpath expr="//field[@name='name']" position="after">
<field name="title" />
</xpath>
</data>
'''
})
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"], '''
<data>
<xpath expr="//field[@name='title']" position="after">
<field name="website"/>
</xpath>
</data>
''')
def test_edit_form_subview_attributes(self):
self.testView.arch = '''
<form>
<field name="child_ids">
<form class="test-subview-form">
<field name="display_name" />
</form>
</field>
</form>
'''
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, """
<data>
<xpath expr="//form[1]/field[@name='child_ids']/form[1]" position="attributes">
<attribute name="create">false</attribute>
</xpath>
</data>""")
def test_x2many_two_levels_edition(self):
self.testView.arch = '''
<form>
<field name="display_name"/>
<field name="user_ids">
<form class="test-subview-form-1">
<group>
<field name="display_name"/>
<field name="log_ids">
<form invisible="1" class="test-subview-form-2"/>
<form class="test-subview-form-2">
<group><field name="display_name"/></group>
</form>
</field>
</group>
</form>
</field>
</form>'''
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, '''
<data>
<xpath expr="//form[1]/field[@name='user_ids']/form[1]/group[1]/field[@name='log_ids']/form[2]/group[1]/field[@name='display_name']" position="before">
<field name="create_date"/>
</xpath>
</data>
''')
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": '''
<form>
<field name="function" />
<field name="name" />
</form>
'''
})
self.testViewList = self.env["ir.ui.view"].create({
"name": "simple partner",
"model": "res.partner",
"type": "list",
"arch": '''
<list>
<field name="function" />
<field name="name" />
</list>
'''
})
self.testViewKanban = self.env["ir.ui.view"].create({
"name": "simple partner",
"model": "res.partner",
"type": "kanban",
"arch": '''
<kanban>
<t t-name="card">
<field name="function" />
<field name="name" />
</t>
</kanban>
'''
})
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 = '''<form>
<group>
<field name="x_name"/>
</group>
</form>'''
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"""
<data>
<xpath expr="//field[@name='x_name']" position="before">
<field name="{monetary_name_list[0]}"/>
<field name="{currency_name_list[0]}"/>
</xpath>
</data>
""")
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 = '''<form>
<group>
<field name="x_name"/>
<field name="x_studio_currency_test"/>
<field name="x_studio_monetary_test"/>
</group>
</form>'''
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 = '''<form>
<group>
<field name="name"/>
<field name="x_studio_currency_test"/>
<field name="x_studio_monetary_test"/>
</group>
</form>'''
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 = '''
<form>
<group>
<field name="x_name" />
<field name="x_test"/>
</group>
</form>
'''
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 = '''<form>
<group>
<field name="x_name"/>
<field name="x_studio_currency_test"/>
<field name="x_studio_currency_test2"/>
<field name="x_studio_monetary_test"/>
</group>
</form>'''
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 = '''<form>
<group>
<field name="x_name"/>
<field name="x_studio_currency_test"/>
<field name="x_studio_monetary_test"/>
</group>
</form>'''
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, """
<data>
<xpath expr="//field[@name='x_studio_monetary_test']" position="after">
<field name="x_studio_currency_test2"/>
</xpath>
</data>
""")
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 = '''<form>
<group>
<field name="x_name"/>
</group>
</form>'''
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, """
<data>
<xpath expr="//field[@name=\'x_name\']" position="before">
<field name="x_studio_monetary_test"/>
<field name="x_studio_currency_test"/>
</xpath>
</data>
""")
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 = '''<form>
<group>
<field name="x_name"/>
<field name="x_studio_currency_test"/>
</group>
</form>'''
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"""
<data>
<xpath expr="//field[@name=\'x_name\']" position="after">
<field name="{monetary_name_list[0]}"/>
</xpath>
</data>
""")
def test_move_similar_field(self):
self.testView.arch = '''
<form>
<sheet>
<group><group>
<field name="active" />
</group></group>
<notebook>
<page string="one">
<group><group><field name="display_name" /></group></group>
</page>
<page string="two">
<group><group><field name="display_name" /></group></group>
</page>
</notebook>
</sheet>
</form>
'''
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, '''
<data>
<xpath expr="//field[@name='active']" position="before">
<xpath expr="//form[1]/sheet[1]/notebook[1]/page[2]/group[1]/group[1]/field[@name='display_name']" position="move" />
</xpath>
</data>
''')
def create_user_view(self):
self.testView.write({
"name": "simple user",
"model": "res.users",
"arch": '''
<form>
<group>
<field name="name"/>
</group>
</form>
'''
})
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,
'''
<data>
<xpath expr="//form[1]/group[1]/field[@name=\'name\']" position="before">
<field filename="{binary_field.name}_filename" name="{binary_field.name}"/>
<field invisible="True" name="{binary_field.name}_filename"/>
</xpath>
</data>
'''.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,
'''
<data>
<xpath expr="//form[1]/group[1]/field[@name=\'name\']" position="before">
<field filename="{binary_field.name}_filename" name="{binary_field.name}"/>
<field invisible="True" name="{binary_field.name}_filename"/>
</xpath>
</data>
'''.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,
'''
<data>
<xpath expr="//form[1]/group[1]/field[@name=\'name\']" position="before">
<field name="{binary_field.name}"/>
</xpath>
</data>
'''.format(binary_field=binary_field))
def test_undo_new_field(self):
self.testView.arch = '''
<form>
<group>
<field name="name" />
</group>
</form>
'''
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 = """<form><field name='name' required="not context.get('something')"/></form>"""
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, '''
<data>
<xpath expr="//form[1]/field[@name='name']" position="attributes">
<attribute name="required">False</attribute>
</xpath>
</data>
''')
def test_drag_and_drop_boolean(self):
self.testView.arch = '''
<form>
<group>
<field name="name" />
</group>
</form>
'''
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, '''
<data>
<xpath expr="//form[1]/group[1]/field[@name='name']" position="after">
<field name="{boolean_field.name}"/>
</xpath>
</data>
'''.format(boolean_field=boolean_field))
def test_new_field_rename_description(self):
self.testView.arch = '''
<form>
<group>
<field name="name" />
</group>
</form>
'''
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, """
<data>
<xpath expr="//form[1]/group[1]/field[@name='name']" position="after">
<field name="x_studio_my_new_field"/>
</xpath>
</data>
""")
def test_edit_digits_option(self):
self.testView.arch = '''<form class="test-user-list">
<field name="partner_latitude" />
</form>'''
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, """
<data>
<xpath expr="//field[@name='partner_latitude']" position="attributes">
<attribute name="options">{"digits":[4,2]}</attribute>
</xpath>
</data>
""")
def test_button_rainbow_effect(self):
self.testView.arch = """<form><button type="object" name="open_commercial_entity">Button</button></form>"""
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, """
<data>
<xpath expr="//button[@name='open_commercial_entity']" position="attributes">
<attribute name="effect">{'img_url': '/web/content/%s'}</attribute>
</xpath>
</data>
""" % attachment.id)
def test_context_write_cleaned(self):
view = self.env["ir.ui.view"].create({
"model": "res.users",
"type": "form",
"arch": """<form>
<div class="oe_button_box"/>
<field name="display_name" invisible="context.get('default_type')" />
</form>"""
})
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"""
<form>
<div class="oe_button_box">
<button class="oe_stat_button" icon="fa-diamond" type="action" name="{action.xml_id}">
<field widget="statinfo" name="x_user_id_res_partner_count" string="Test studio new button"/>
</button>
</div>
<field name="display_name" invisible="context.get('default_type')"/>
</form>
""")
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": """<form><sheet><div class="oe_button_box"></div></sheet></form>"""
})
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": "<data />",
"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"""
<form>
<sheet>
<div class="oe_button_box">
<button class="oe_stat_button" icon="fa-diamond" type="action" name="{action.xml_id}">
<field widget="statinfo" name="x_parent_id_res_partner_count" string="aa"/>
</button>
</div>
</sheet>
</form>
""")
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": '''
<form>
<field name="%s"/>
</form>
''' % 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, """
<data>
<xpath expr="//field[@name='%s']" position="attributes">
<attribute name="string">new name</attribute>
</xpath>
</data>
""" % 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 = '''<form>
<group>
<field name="x_name"/>
</group>
</form>'''
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 = """<form>
<header>
<button type="action" name="base.action_model_data" string="MyButton" />
</header>
</form>
"""
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": '''<kanban>
<templates>
<t t-name="card">
<field name="display_name" class="fs-6 fw-bold whatever"/>
<field name="phone" class="fw-bolder"/>
<field name="email" class="text-muted"/>
</t>
</templates>
</kanban>
'''
})
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, """
<data>
<xpath expr="//field[@name='display_name']" position="attributes">
<attribute name="class">fs-6 whatever</attribute>
</xpath>
<xpath expr="//field[@name='email']" position="attributes">
<attribute name="class">fw-bold text-muted</attribute>
</xpath>
</data>""")
def test_kanban_menu_ribbon(self):
self.testViewKanban = self.env["ir.ui.view"].create({
"name": "simple partner",
"model": "res.partner",
"type": "kanban",
"arch": '''
<kanban>
<t t-name="card">
<field name="function" />
<field name="name" />
</t>
</kanban>
'''
})
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, """
<data>
<xpath expr="//kanban[1]/t[@t-name='card']" position="before">
<t t-name="menu">
<t t-if="widget.editable">
<a type="open" class="dropdown-item">Edit</a>
</t>
<t t-if="widget.deletable">
<a type="delete" class="dropdown-item">Delete</a>
</t>
</t>
</xpath>
<xpath expr="//field[@name='function']" position="before">
<widget name="web_ribbon" title="Demo"/>
</xpath>
</data>
""")
def test_create_related(self):
self.testViewForm = self.env["ir.ui.view"].create({
"name": "simple partner",
"model": "res.partner",
"type": "form",
"arch": '''
<form>
<group>
<field name="function" />
<field name="name" />
</group>
</form>
'''
})
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"""
<data>
<xpath expr="//form[1]/group[1]/field[@name='name']" position="after">
<field name="{new_field.name}"/>
</xpath>
</data>
""")
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"""
<form>
<field name="name" groups="!{hasGroupXmlId.complete_name},{doesNotHaveGroupXmlId.complete_name}" />
</form>
"""
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, """
<data>
<xpath expr="//form[1]/field[@name='name']" position="attributes">
<attribute name="groups">web_studio.studio_test_doesnothavegroup,!base.group_erp_manager</attribute>
</xpath>
</data>
""")
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": '''
<list>
<field name="display_name" />
</list>
'''
})
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 = """
<form>
<sheet>
<group>
<group>
<field name="display_name"/>
</group>
</group>
</sheet>
</form>
"""
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, '''
<data>
<xpath expr="//form[1]/sheet[1]/group[1]" position="before">
<field name="{html_field.name}"/>
</xpath>
</data>
'''.format(html_field=html_field))