# 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": '''
'''
self.start_tour("/odoo?debug=tests", 'web_studio_related_monetary_creation', login="admin")
# There is only one currency and there is a new monetary
fields = self.env["x_test_model"]._fields
currency_name_list = list(filter(lambda key: fields[key]._description_type == 'many2one' and fields[key]._description_relation == 'res.currency', fields.keys()))
monetary_name_list = list(filter(lambda key: fields[key].type == 'monetary', fields.keys()))
self.assertEqual(len(currency_name_list), 1)
self.assertEqual(len(monetary_name_list), 1)
# The monetary has been created and is a related field
self.assertEqual(self.env['x_test_model']._fields[monetary_name_list[0]].related, "x_test.x_studio_monetary_test")
# A currency has been created because there was none in the model/view
self.assertEqual(currency_name_list[0], 'x_studio_currency_id')
def test_monetary_change_currency_field(self):
self.create_empty_app()
self.env["ir.model.fields"].create({
"name": "x_studio_currency_test",
"model": "x_test_model",
"model_id": self.newModel.id,
"ttype": "many2one",
"relation": "res.currency",
})
self.env["ir.model.fields"].create({
"name": "x_studio_currency_test2",
"model": "x_test_model",
"model_id": self.newModel.id,
"ttype": "many2one",
"relation": "res.currency",
})
monetary = self.env["ir.model.fields"].create({
"name": "x_studio_monetary_test",
"model": "x_test_model",
"model_id": self.newModel.id,
"ttype": "monetary",
"currency_field": "x_studio_currency_test",
})
self.newView.arch = '''
'''
self.start_tour("/odoo?debug=tests", 'web_studio_monetary_add_existing_monetary', login="admin")
# The studio arch contains the monetary and the associated currency
studioView = _get_studio_view(self.newView)
assertViewArchEqual(self, studioView.arch, """
""")
def test_monetary_create_monetary_with_existing_currency(self):
self.create_empty_app()
self.env["ir.model.fields"].create({
"name": "x_studio_currency_test",
"model": "x_test_model",
"model_id": self.newModel.id,
"ttype": "many2one",
"relation": "res.currency",
})
self.newView.arch = '''
'''
self.start_tour("/odoo?debug=tests", 'web_studio_monetary_create_monetary_with_existing_currency', login="admin")
# There is only one currency and there is a new monetary
fields = self.env[self.newModel.model]._fields
currency_name_list = list(filter(lambda key: fields[key]._description_type == 'many2one' and fields[key]._description_relation == 'res.currency', fields.keys()))
monetary_name_list = list(filter(lambda key: fields[key].type == 'monetary', fields.keys()))
self.assertEqual(len(currency_name_list), 1)
self.assertEqual(len(monetary_name_list), 1)
# The studio arch contains the new monetary but no currency as it exist in the original arch
studioView = _get_studio_view(self.newView)
assertViewArchEqual(self, studioView.arch, f"""
""")
def test_move_similar_field(self):
self.testView.arch = '''
""")
def test_reload_after_restoring_default_view(self):
self.start_tour("/odoo?debug=tests", 'web_studio_test_reload_after_restoring_default_view', login="admin")
def test_edit_reified_field(self):
# find some reified field name
reified_fname = next(
fname
for fname in self.env["res.users"].fields_get()
if fname.startswith(('in_group_', 'sel_groups_'))
)
self.testView.write({
"name": "simple user",
"model": "res.users",
"arch": '''
''' % reified_fname
})
self.testAction.res_model = "res.users"
self.start_tour("/odoo?debug=tests", 'web_studio_test_edit_reified_field', login="admin")
studioView = _get_studio_view(self.testView)
assertViewArchEqual(self, studioView.arch, """
new name
""" % reified_fname)
def test_add_all_types_fields_related(self):
self.create_user_view()
self.start_tour("/odoo?debug=tests", 'web_studio_test_add_all_types_fields_related', login="admin")
field = self.env["ir.model.fields"].search([('model', '=', 'res.users'), ('name', 'like', 'x_studio_related')])
target_field = self.env["ir.model.fields"].search([('model', '=', 'res.users'), ('name', '=', 'display_name')])
self.assertEqual(len(field), 1)
self.assertEqual(field.related_field_id, target_field)
def test_add_many2one_without_related(self):
""" Test ensure raise a warning and not the current view does not contain studio arch when the user
tries to add one2many field but the model does not contain any related many2one field.
"""
self.create_empty_app()
self.newView.arch = '''
'''
self.start_tour("/odoo?debug=tests", 'web_studio_add_one2many_no_related_many2one', login="admin")
studioView = _get_studio_view(self.newView)
# Does not contain studio arch
assertViewArchEqual(self, studioView.arch, False)
def test_approval_button_xml_id(self):
self.testView.arch = """