Files
enterprise-addons/web_studio/tests/test_report_editor.py
T

1552 lines
64 KiB
Python

import json
from psycopg2.extras import Json
from lxml import etree
from odoo import Command, api
from odoo.addons.base.models.ir_actions_report import IrActionsReport
from odoo.addons.web_studio.controllers.main import WebStudioController
from odoo.addons.web_studio.controllers.report import WebStudioReportController, get_report_view_copy, _get_and_write_studio_view
from odoo.addons.web.controllers.report import ReportController
from odoo.http import _request_stack, route
from odoo.tests.common import HttpCase, TransactionCase
from odoo.tests import tagged
from odoo.tools import DotDict, mute_logger
parserXML = etree.XMLParser(remove_blank_text=False)
parserHTML = etree.HTMLParser(remove_blank_text=False)
def html_to_xml_tree(stringHTML, body=True):
temp = etree.fromstring(stringHTML, parser=parserHTML)
if body:
temp = temp.getroottree().find("body")[0]
temp = etree.tostring(temp)
return etree.fromstring(temp, parser=parserXML)
def _clean_for_simple_assert(tree):
for node in tree.iter(etree.Element):
node.attrib.pop("oe-context", None)
node.attrib.pop("o-diff-key", None)
def get_combined_and_studio_arch(view):
studio_view = _get_and_write_studio_view(view)
return view.get_combined_arch(), studio_view.arch
class TestReportEditor(TransactionCase):
def setUp(self):
super(TestReportEditor, self).setUp()
self.session = DotDict({'debug': ''})
self.is_frontend = False
_request_stack.push(self) # crappy hack to use a fake Request
self.WebStudioController = WebStudioController()
def test_copy_inherit_report(self):
report = self.env['ir.actions.report'].create({
'name': 'test inherit report user',
'report_name': 'web_studio.test_inherit_report_user',
'model': 'res.users',
})
self.env['ir.ui.view'].create({
'type': 'qweb',
'name': 'web_studio.test_inherit_report_hi',
'key': 'web_studio.test_inherit_report_hi',
'arch': '''
<t t-name="web_studio.test_inherit_report_hi">
hi
</t>
''',
})
parent_view = self.env['ir.ui.view'].create({
'type': 'qweb',
'name': 'web_studio.test_inherit_report_user_parent',
'key': 'web_studio.test_inherit_report_user_parent',
'arch': '''
<t t-name="web_studio.test_inherit_report_user_parent_view_parent">
<t t-call="web_studio.test_inherit_report_hi"/>!
</t>
''',
})
self.env['ir.ui.view'].create({
'type': 'qweb',
'name': 'web_studio.test_inherit_report_user',
'key': 'web_studio.test_inherit_report_user',
'arch': '''
<xpath expr="." position="inside">
<t t-call="web_studio.test_inherit_report_hi"/>!!
</xpath>
''',
'inherit_id': parent_view.id,
})
# check original report render to expected output
report_html = report._render_template(report.report_name).decode()
self.assertEqual(''.join(report_html.split()), 'hi!hi!!')
# duplicate original report
report.copy_report_and_template()
copy_report = self.env['ir.actions.report'].search([
('report_name', '=', 'web_studio.test_inherit_report_user_copy_1'),
])
# check duplicated report render to expected output
copy_report_html = copy_report._render_template(copy_report.report_name).decode()
self.assertEqual(''.join(copy_report_html.split()), 'hi!hi!!')
# check that duplicated view is inheritance combination of original view
copy_view = self.env['ir.ui.view'].search([
('key', '=', copy_report.report_name),
])
self.assertFalse(copy_view.inherit_id, 'copied view does not inherit another one')
found = len(copy_view.arch_db.split('test_inherit_report_hi_copy_1')) - 1
self.assertEqual(found, 2, 't-call is duplicated one time and used 2 times')
def test_duplicate(self):
# Inheritance during an upgrade work only with loaded views
# The following force the inheritance to work for all views
# so the created view is correctly inherited
self.env = self.env(context={'load_all_views': True})
# Create a report/view containing "foo"
report = self.env['ir.actions.report'].create({
'name': 'test duplicate',
'report_name': 'web_studio.test_duplicate_foo',
'model': 'res.users',})
self.env['ir.ui.view'].create({
'type': 'qweb',
'name': 'test_duplicate_foo',
'key': 'web_studio.test_duplicate_foo',
'arch': "<t t-name='web_studio.test_duplicate_foo'>foo</t>",})
duplicate_domain = [('report_name', '=like', 'web_studio.test_duplicate_foo_copy_%')]
# Duplicate the report and retrieve the duplicated view
report.copy_report_and_template()
copy1 = self.env['ir.actions.report'].search(duplicate_domain)
copy1.ensure_one() # watchdog
copy1_view = self.env['ir.ui.view'].search([
('key', '=', copy1.report_name)])
copy1_view.ensure_one() # watchdog
# Inherit the view to replace "foo" by "bar"
self.env['ir.ui.view'].create({
'inherit_id': copy1_view.id,
'key': copy1.report_name,
'arch': '''
<xpath expr="." position="replace">
<t t-name='%s'>bar</t>
</xpath>
''' % copy1.report_name,})
# Assert the duplicated view renders "bar" then unlink the report
copy1_html = copy1._render_template(copy1.report_name).decode()
self.assertEqual(''.join(copy1_html.split()), 'bar')
copy1.unlink()
# Re-duplicate the original report, it must renders "foo"
report.copy_report_and_template()
copy2 = self.env['ir.actions.report'].search(duplicate_domain)
copy2.ensure_one()
copy2_html = copy2._render_template(copy2.report_name).decode()
self.assertEqual(''.join(copy2_html.split()), 'foo')
def test_copy_custom_model_rendering(self):
report = self.env['ir.actions.report'].search([('report_name', '=', 'base.report_irmodulereference')])
report.copy_report_and_template()
copy = self.env['ir.actions.report'].search([('report_name', '=', 'base.report_irmodulereference_copy_1')])
report_model = self.env['ir.actions.report']._get_rendering_context_model(copy)
self.assertIsNotNone(report_model)
def test_duplicate_keep_translations(self):
def create_view(name, **kwargs):
arch = '<div>{}</div>'.format(name)
if kwargs.get('inherit_id'):
arch = '<xpath expr="." path="inside">{}</xpath>'.format(arch)
name = 'web_studio.test_keep_translations_{}'.format(name)
return self.env['ir.ui.view'].create(dict({
'type': 'qweb',
'name': name,
'key': name,
'arch': arch,
}, **kwargs))
report = self.env['ir.actions.report'].create({
'name': 'test inherit report user',
'report_name': 'web_studio.test_keep_translations_ab',
'model': 'res.users',
}).with_context(load_all_views=True)
self.env.ref('base.lang_fr').active = True
views = report.env['ir.ui.view']
views += create_view("a_")
root = views[-1]
views += create_view("b_")
views += create_view("aa", inherit_id=root.id, mode="primary")
views += create_view("ab", inherit_id=root.id)
target = views[-1]
views += create_view("aba", inherit_id=target.id)
views[-1].arch = views[-1].arch.replace('aba', 'a_</div>aba<div>ab')
views += create_view("abb", inherit_id=target.id, mode="primary")
for view in views.with_context(lang='fr_FR'):
terms = view._fields['arch_db'].get_trans_terms(view.arch_db)
view.update_field_translations('arch_db', {'fr_FR': {term: '%s in fr' % term for term in terms}})
combined_arch = '<div>a_<div>ab</div><div>a_</div>aba<div>ab</div></div>'
self.assertEqual(etree.tostring(target._get_view_etrees()[0], encoding='unicode'), combined_arch)
# duplicate original report, views will be combined into one
report.copy_report_and_template()
copy_view = self.env['ir.ui.view'].search([
('key', '=', 'web_studio.test_keep_translations_ab_copy_1'),
])
self.assertEqual(copy_view.arch, combined_arch)
# translations of combined views have been copied to the new view
new_arch = '<div>a_ in fr<div>ab in fr</div><div>a_ in fr</div>aba in fr<div>ab in fr</div></div>'
self.assertEqual(copy_view.with_context(lang='fr_FR').arch, new_arch)
def tearDown(self):
super(TestReportEditor, self).tearDown()
_request_stack.pop()
@tagged('post_install', '-at_install')
class TestReportEditorUIUnit(HttpCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.testAction = cls.env["ir.actions.act_window"].create({
"name": "simple partner",
"res_model": "res.partner",
})
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,
})
cls.report = cls.env['ir.actions.report'].create({
'name': 'test report',
'report_name': 'web_studio.test_report',
'model': 'res.partner',
})
cls.report_xml_id = cls.env["ir.model.data"].create({
"name": "studio_test_report",
"model": "ir.actions.report",
"module": "web_studio",
"res_id": cls.report.id,
})
cls.main_view = cls.env['ir.ui.view'].create({
'type': 'qweb',
'name': 'web_studio.test_report',
'key': 'web_studio.test_report',
'arch': '''
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<div><p><br/></p></div>
<t t-foreach="docs" t-as="doc">
<t t-call="web_studio.test_report_document" />
</t>
</t>
</t>
''',
})
cls.main_view_xml_id = cls.env["ir.model.data"].create({
"name": "studio_test_report_view",
"model": "ir.ui.view",
"module": "web_studio",
"res_id": cls.main_view.id,
})
cls.main_view_document = cls.env['ir.ui.view'].create({
'type': 'qweb',
'name': 'web_studio.test_report_document',
'key': 'web_studio.test_report_document',
'arch': '''
<t t-name="web_studio.test_report_document">
<div><p t-field="doc.name" /></div>
<p><br/></p>
</t>
''',
})
cls.main_view_document_xml_id = cls.env["ir.model.data"].create({
"name": "test_report_document",
"model": "ir.ui.view",
"module": "web_studio",
"res_id": cls.main_view_document.id,
})
@property
def tour_url(self):
return f"/odoo/action-{self.testAction.id}/studio?mode=editor&_tab=reports&_report_id={self.report.id}&menu_id={self.testMenu.id}"
def _clear_routing(self):
self.env.registry.clear_cache('routing')
def test_basic_report_edition(self):
original_main_view_doc_arch = self.main_view_document.arch
self.start_tour(self.tour_url, "web_studio.test_basic_report_edition", login="admin")
self.assertEqual(self.report.name, "modified in test")
main_arch, studio_arch = get_combined_and_studio_arch(self.main_view)
self.assertXMLEqual(main_arch, """
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<div>
<p>edited with odoo editor</p>
</div>
<t t-foreach="docs" t-as="doc">
<t t-call="web_studio.test_report_document"/>
</t>
</t>
</t>
""")
self.assertXMLEqual(studio_arch, """
<data>
<data>
<xpath expr="/t[@t-name='web_studio.test_report']//t[@t-call='web.html_container']/div/p/br" position="replace"/>
<xpath expr="/t[@t-name='web_studio.test_report']//t[@t-call='web.html_container']/div/p" position="inside">edited with odoo editor</xpath>
</data>
</data>
""")
doc_arch, studio_arch = get_combined_and_studio_arch(self.main_view_document)
# Not sure about this one. Due to the absence of relevant branding
# The entire view is replaced (case "entire_view" in report.py)
self.assertXMLEqual(doc_arch, """
<t t-name="web_studio.test_report_document">
<div>
<p t-field="doc.name"/>
</div>
<p>edited with odoo editor 2</p>
</t>
""")
self.assertXMLEqual(studio_arch, """
<data>
<data>
<xpath expr="/t[@t-name='web_studio.test_report_document']/p/br" position="replace"/>
<xpath expr="/t[@t-name='web_studio.test_report_document']/p" position="inside">edited with odoo editor 2</xpath>
</data>
</data>
""")
copied_view = get_report_view_copy(self.main_view_document)
self.assertXMLEqual(original_main_view_doc_arch, copied_view.arch)
self.assertEqual(copied_view.name, "web_studio_backup__web_studio.test_report_document")
self.assertEqual(copied_view.key, f"web_studio.__backup__._{self.main_view_document.id}_._{self.main_view_document.key}_")
self.assertFalse(copied_view.active)
self.assertFalse(copied_view.inherit_id)
def test_basic_report_edition_without_datas(self):
passed_in_rendering_context = False
def _get_rendering_context_mock(self_model, report, docids, data):
nonlocal passed_in_rendering_context
passed_in_rendering_context = True
self.assertTrue(self_model.env.context.get("studio"))
self.assertTrue(data.get("studio"))
return {'reason': 'Something went wrong.'}
self.patch(IrActionsReport, '_get_rendering_context', _get_rendering_context_mock)
self.start_tour(self.tour_url, "web_studio.test_basic_report_edition", login="admin")
self.assertTrue(passed_in_rendering_context)
def test_basic_report_edition_xml(self):
self.start_tour(self.tour_url, "web_studio.test_basic_report_edition_xml", login="admin")
self.assertTrue(self.main_view_xml_id.noupdate)
self.assertTrue(self.main_view_document_xml_id.noupdate)
self.assertXMLEqual(self.main_view.arch, """
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<span class="test-added-1">in main view</span>
<div>
<p>
<br/>
</p>
</div>
<t t-foreach="docs" t-as="doc">
<t t-call="web_studio.test_report_document"/>
</t>
</t>
</t>
""")
self.assertXMLEqual(self.main_view_document.arch, """
<t t-name="web_studio.test_report_document">
<div>
<p t-field="doc.name"/>
</div>
<span class="test-added-0">in document view</span>
<p>
<br/>
</p>
</t>
""")
def test_basic_report_edition_discard(self):
self.start_tour(self.tour_url, "web_studio.test_basic_report_edition_discard", login="admin")
self.assertXMLEqual(self.main_view.arch, """
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<div><p><br/></p></div>
<t t-foreach="docs" t-as="doc">
<t t-call="web_studio.test_report_document"/>
</t>
</t>
</t>
""")
self.assertXMLEqual(self.main_view_document.arch, """
<t t-name="web_studio.test_report_document">
<div><p t-field="doc.name"/></div>
<p><br/></p>
</t>
""")
def test_basic_report_edition_cancel_discard(self):
self.start_tour(self.tour_url, "web_studio.test_basic_report_edition_cancel_discard", login="admin")
main_arch, _ = get_combined_and_studio_arch(self.main_view)
self.assertXMLEqual(main_arch, """
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<div>
<p>edited with odoo editor</p>
</div>
<t t-foreach="docs" t-as="doc">
<t t-call="web_studio.test_report_document"/>
</t>
</t>
</t>
""")
def test_basic_report_edition_xml_discard(self):
self.start_tour(self.tour_url, "web_studio.test_basic_report_edition_xml_discard", login="admin")
self.assertXMLEqual(self.main_view.arch, """
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<div><p><br/></p></div>
<t t-foreach="docs" t-as="doc">
<t t-call="web_studio.test_report_document"/>
</t>
</t>
</t>
""")
self.assertXMLEqual(self.main_view_document.arch, """
<t t-name="web_studio.test_report_document">
<div><p t-field="doc.name"/></div>
<p><br/></p>
</t>
""")
def test_basic_report_edition_error(self):
save_report = WebStudioReportController.save_report
self._clear_routing()
self.addCleanup(self._clear_routing)
error = None
@route('/web_studio/save_report', type='jsonrpc', auth='user')
def save_report_mocked(*args, **kwargs):
try:
return save_report(*args, **kwargs)
except Exception as e:
nonlocal error
error = e
raise e
self.patch(WebStudioReportController, "save_report", save_report_mocked)
main_view_arch = self.main_view.arch
document_view_arch = self.main_view_document.arch
with mute_logger("odoo.http"):
self.start_tour(self.tour_url, "web_studio.test_basic_report_edition_error", login="admin")
self.assertTrue(error)
self.assertFalse(self.main_view_xml_id.noupdate)
self.assertFalse(self.main_view_document_xml_id.noupdate)
self.assertFalse(self.report_xml_id.noupdate)
self.assertXMLEqual(self.main_view.arch, main_view_arch)
self.assertXMLEqual(self.main_view_document.arch, document_view_arch)
def test_basic_report_edition_xml_error(self):
save_report = WebStudioReportController.save_report
self._clear_routing()
self.addCleanup(self._clear_routing)
error = None
@route('/web_studio/save_report', type='jsonrpc', auth='user')
def save_report_mocked(*args, **kwargs):
try:
return save_report(*args, **kwargs)
except Exception as e:
nonlocal error
error = e
raise e
self.patch(WebStudioReportController, "save_report", save_report_mocked)
main_view_arch = self.main_view.arch
document_view_arch = self.main_view_document.arch
with mute_logger("odoo.http"):
self.start_tour(self.tour_url, "web_studio.test_basic_report_edition_xml_error", login="admin")
self.assertTrue(error)
self.assertFalse(self.main_view_xml_id.noupdate)
self.assertFalse(self.main_view_document_xml_id.noupdate)
self.assertFalse(self.report_xml_id.noupdate)
self.assertXMLEqual(self.main_view.arch, main_view_arch)
self.assertXMLEqual(self.main_view_document.arch, document_view_arch)
def test_report_reset_archs(self):
self.main_view_document.arch_fs = "web_studio/tests/test_report_editor.xml"
self.start_tour(self.tour_url, "web_studio.test_report_reset_archs", login="admin")
self.assertXMLEqual(self.main_view_document.arch, """<p>from file</p>""")
def test_report_reset_archs_studio_view(self):
self.authenticate("admin", "admin")
studio_view = _get_and_write_studio_view(self.main_view_document, {"arch": "<data/>"})
self.assertTrue(studio_view.active)
self.url_open("/web_studio/reset_report_archs",
data=json.dumps({"params": {"report_id": self.report.id}}),
headers={"Content-Type": "application/json"}
)
self.assertFalse(studio_view.active)
def test_print_preview(self):
self.start_tour(self.tour_url, "web_studio.test_print_preview", login="admin")
def test_reports_view(self):
self.start_tour(self.tour_url, "web_studio.test_reports_view_concurrence", login="admin")
def test_table_rendering(self):
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<p><br/></p>
<table class="valid_table">
<tbody><tr><td>I am valid</td></tr></tbody>
</table>
<table class="invalid_table">
<t t-foreach="doc.child_ids" t-as="child">
<tr><td>I am not valid</td></tr>
</t>
</table>
</t>
"""
self.start_tour(self.tour_url, "web_studio.test_table_rendering", login="admin")
arch, _ = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<p>p edited with odooEditor</p>
<table class="valid_table">
<tbody>
<tr>
<td>I am valid</td>
</tr>
</tbody>
</table>
<table class="invalid_table">
<t t-foreach="doc.child_ids" t-as="child">
<tr>
<td>edited with odooEditor</td>
</tr>
</t>
</table>
</t>
""")
def test_nested_table_rendering(self):
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<table class="invalid_table">
<t t-foreach="doc.child_ids" t-as="child">
<tr>
<td>
<t t-set="var" t-value="doc" />
<table>
<tr><td>valid</td></tr>
</table>
<table>
<t t-foreach="doc.child_ids" t-as="subchild">
<tr><td>
<table><tr><td>I am valid</td></tr></table>
</td></tr>
</t>
</table>
</td>
</tr>
</t>
</table>
</t>
"""
self.authenticate("admin", "admin")
response = self.url_open(
"/web_studio/get_report_qweb",
data=json.dumps({"params": {"report_id": self.report.id}}),
headers={"Content-Type": "application/json"}
)
qweb_html = response.json()["result"]
tree = html_to_xml_tree(qweb_html)
_clean_for_simple_assert(tree)
div_node = tree.xpath("//t[@t-name='web_studio.test_report_document']")[0][0]
self.assertXMLEqual(etree.tostring(div_node), """
<q-table class="invalid_table">
<t t-foreach="doc.child_ids" t-as="child">
<q-tr>
<q-td>
<t t-set="var" t-value="doc"/>
<table>
<tr>
<td>valid</td>
</tr>
</table>
<q-table>
<t t-foreach="doc.child_ids" t-as="subchild">
<q-tr>
<q-td>
<table><tr><td>I am valid</td></tr></table>
</q-td>
</q-tr>
</t>
</q-table>
</q-td>
</q-tr>
</t>
</q-table>
""")
def test_field_placeholder(self):
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div><p t-field="doc.name" title="Name"/></div>
<p><br/></p>
</t>
"""
self.start_tour(self.tour_url + "&debug=assets", "web_studio.test_field_placeholder", login="admin")
arch, _ = get_combined_and_studio_arch(self.main_view)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<div>
<p><br/>edited with odooEditor</p>
</div>
<t t-foreach="docs" t-as="doc">
<t t-call="web_studio.test_report_document"/>
</t>
</t>
</t>
""")
arch, _ = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<div>
<p t-field="doc.name" title="Name"/>
</div>
<p>
<span data-oe-demo="some default value" t-field="doc.function"/>
</p>
</t>
""")
def test_toolbar_appearance(self):
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div class="to_edit">some text <p t-field="doc.name" title="Name"/></div>
<p><br/></p>
</t>
"""
self.start_tour(self.tour_url, "web_studio.test_toolbar_appearance", login="admin")
def test_add_field_blank_report(self):
studio_views = self.env["ir.ui.view"].search([("key", "=ilike", "studio_customization.%")])
self.start_tour(self.tour_url, "web_studio.test_add_field_blank_report", login="admin")
new_view = self.env["ir.ui.view"].search([("key", "=ilike", "studio_customization.%"), ("id", "not in", studio_views.ids), ("name", "like", "document")])
new_view_arch, _ = get_combined_and_studio_arch(new_view)
self.assertXMLEqual(new_view_arch, """
<t t-name="studio_report_document">
<div class="page">
<div class="oe_structure"><span data-oe-demo="some default value" t-field="doc.function"/>
Custo</div>
</div>
</t>
""")
def test_disable_fields_commands_when_unavailable(self):
self.main_view.arch = """
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<t t-call="web.internal_layout">
<t t-set='lines' t-value='docs'/>
<t t-esc="lines.mapped('name')"/>
<p class="some_p"><br/></p>
</t>
</t>
</t>
"""
self.start_tour(self.tour_url, "web_studio.test_disable_fields_commands_when_unavailable", login="admin")
def test_edition_without_lang(self):
self.env["res.lang"]._activate_lang("fr_FR")
self.env["res.users"].browse(2).lang = "fr_FR"
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<p>original term</p>
</t>
"""
translations = {
"fr_FR": {
"original term": "translated term"
}
}
self.main_view_document.update_field_translations("arch_db", translations)
self.start_tour(self.tour_url, "web_studio.test_edition_without_lang", login="admin")
_, studio_arch = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(studio_arch, """
<data>
<data>
<xpath expr="/t[@t-name='web_studio.test_report_document']/p" position="replace">
<p>original term edited</p>
</xpath>
</data>
</data>
""")
new_translations = self.main_view_document.get_field_translations("arch_db")
new_translations_values = {k["lang"]: k for k in new_translations[0]}
self.assertEqual(
new_translations_values["en_US"]["source"],
"original term"
)
self.assertEqual(
new_translations_values["en_US"]["value"],
""
)
self.assertEqual(
new_translations_values["fr_FR"]["source"],
"original term"
)
self.assertEqual(
new_translations_values["fr_FR"]["value"],
"translated term"
)
studio_translation = _get_and_write_studio_view(self.main_view_document).get_field_translations("arch_db")
studio_translation_values = {k["lang"]: k for k in studio_translation[0]}
self.assertEqual(
studio_translation_values["en_US"]["source"],
"original term edited"
)
self.assertEqual(
studio_translation_values["en_US"]["value"],
""
)
self.assertEqual(
studio_translation_values["fr_FR"]["source"],
"original term edited"
)
self.assertEqual(
studio_translation_values["fr_FR"]["value"],
"translated edited term"
)
def test_report_xml_other_record(self):
ResPartner = self.env["res.partner"]
p1 = ResPartner.create({'name': "partner_1"})
p2 = ResPartner.create({'name': "partner_2"})
super_studio_model_infos = self.env.registry.get("ir.model").studio_model_infos
@api.model
def studio_model_infos(self, *args, **kwargs):
sup = super_studio_model_infos(self, *args, **kwargs)
sup["record_ids"] = (p1 | p2).ids
return sup
self.patch(self.env.registry.get("ir.model"), "studio_model_infos", studio_model_infos)
self.start_tour(self.tour_url, "web_studio.test_report_xml_other_record", login="admin")
def test_partial_eval(self):
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<t t-set="my_children" t-value="doc.child_ids" />
<t t-set="some_var" t-value="'some_value'" />
<t t-foreach="my_children" t-as="child">
<div t-att-class="'lol' if report_type != 'html' else 'notlol'">lol</div>
<div t-attf-class="{{ 'couic' }}" >couic</div>
</t>
</t>
"""
self.start_tour(self.tour_url, "web_studio.test_partial_eval", login="admin")
def test_render_multicompany(self):
company1 = self.env.company
self.main_view_document.arch = """
<t t-call="web.external_layout">
<div>doc</div>
</t>
"""
external_report_layout_id = self.env['ir.ui.view'].create({
'type': 'qweb',
'name': 'web_studio.test_layout',
'key': 'web_studio.test_layout',
'arch': '''
<t t-name="web_studio.test_report">
<div class="test_layout">
<img t-if="company.logo" t-att-src="image_data_uri(company.logo)" style="max-height: 45px;" alt="Logo"/>
<div><t t-out="0" /></div>
</div>
</t>
''',
})
company2 = self.env["res.company"].create({"name": "couic", "external_report_layout_id": external_report_layout_id.id})
self.env["res.users"].browse(2).write({"company_ids": [Command.link(company2.id)]})
self.start_tour(self.tour_url, "web_studio.test_render_multicompany", login="admin", cookies={"cids": f"{company2.id}-{company1.id}"})
def test_report_edition_binary_field(self):
self.env["ir.model.fields"].create({
"field_description": "New File",
"name": "x_new_file",
"ttype": "binary",
"model": "res.company",
"model_id": self.env["ir.model"]._get('res.company').id,
"state": "manual",
})
self.env["ir.model.fields"].create({
"field_description": "New File filename",
"name": "x_new_file_filename",
"ttype": "char",
"model": "res.company",
"model_id": self.env["ir.model"]._get('res.company').id,
"state": "manual",
})
self.env["ir.model.fields"].create({
"field_description": "New Image",
"name": "x_new_image",
"ttype": "binary",
"model": "res.company",
"model_id": self.env["ir.model"]._get('res.company').id,
"state": "manual",
})
self.start_tour(self.tour_url, "web_studio.test_report_edition_binary_field", login="admin")
arch, _ = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<div><p t-field="doc.name"/></div>
<p><span data-oe-demo="file default value" t-field="doc.company_id.x_new_file" /><span data-oe-demo="image default value" t-field="doc.company_id.x_new_image" t-options-widget="\'image\'" t-options-qweb_img_raw_data="1" /></p>
</t>
""")
def test_report_edition_dynamic_table(self):
self.start_tour(self.tour_url + "&debug=assets", "web_studio.test_report_edition_dynamic_table", login="admin")
arch, _ = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<div>
<p t-field="doc.name"/>
</div>
<table class="table table-sm">
<tbody>
<tr class="border-bottom border-top-0 border-start-0 border-end-0 border-2 border-dark fw-bold">
<td>Activities</td>
</tr>
<tr t-foreach="doc.activity_ids" t-as="x2many_record">
<td>
<span data-oe-demo="Some Summary" t-field="x2many_record.summary" />
</td>
</tr>
</tbody>
</table>
</t>
""")
def test_saving_xml_editor_reload(self):
self.start_tour(self.tour_url, "web_studio.test_saving_xml_editor_reload", login="admin")
def test_error_at_loading(self):
bad_view = self.env["ir.ui.view"].create({
"name": "bad view",
"inherit_id": self.main_view_document.id,
"arch": """<data />"""
})
arch = """<data>
<xpath expr="/form/h1" position="after">
will crash
</xpath>
</data>"""
self.env.cr.execute(
"""UPDATE ir_ui_view SET arch_db = %s WHERE id = %s""",
(Json({"en_US": arch}), bad_view.id)
)
with mute_logger("odoo.http"):
self.start_tour(self.tour_url, "web_studio.test_error_at_loading", login="admin")
with mute_logger("odoo.http"):
self.start_tour(self.tour_url + "&debug=assets", "web_studio.test_error_at_loading_debug", login="admin")
def test_xml_and_form_diff(self):
self.start_tour(self.tour_url + "&debug=1", "web_studio.test_xml_and_form_diff", login="admin")
def test_recursive_t_calls(self):
self.authenticate("admin", "admin")
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div><p t-field="doc.name" /></div>
<p><br/></p>
<t t-call="web_studio.test_report_document"><div /></t>
</t>
"""
response = self.url_open(
"/web_studio/get_report_qweb",
data=json.dumps({"params": {"report_id": self.report.id}}),
headers={"Content-Type": "application/json"}
)
qweb_html = response.json()["result"]
tree = html_to_xml_tree(qweb_html)
_clean_for_simple_assert(tree)
tcall = tree.xpath("//t[@t-call='web_studio.test_report_document']")[0]
self.assertXMLEqual(etree.tostring(tcall), f"""
<t t-call="web_studio.test_report_document" ws-call-key="2" ws-view-id="{self.main_view.id}">
<t t-name="web_studio.test_report_document" ws-view-id="{self.main_view_document.id}">
<div>
<p t-field="doc.name" data-oe-expression-readable="Name"/>
</div>
<p>
<br/>
</p>
<t t-call="web_studio.test_report_document" ws-call-key="1" ws-view-id="{self.main_view_document.id}">
<div/>
</t>
</t>
</t>
""")
def test_can_have_multiple_times_same_t_call(self):
self.authenticate("admin", "admin")
self.main_view.arch = """
<t t-name="web_studio.test_report">
<t t-call="web.html_container">
<div>
<t t-call="web_studio.test_report_document">
<h1/>
</t>
<t t-call="web_studio.test_report_document">
<h2/>
</t>
</div>
</t>
</t>
"""
self.main_view_document.arch = """<t t-name="web_studio.test_report_document"><t t-out="0" /></t> """
response = self.url_open(
"/web_studio/get_report_qweb",
data=json.dumps({"params": {"report_id": self.report.id}}),
headers={"Content-Type": "application/json"}
)
qweb_html = response.json()["result"]
tree = html_to_xml_tree(qweb_html)
_clean_for_simple_assert(tree)
tcall = tree.xpath("//t[@t-call='web_studio.test_report_document']")
self.assertEqual(len(tcall), 2)
self.assertXMLEqual(etree.tostring(tcall[0]), f"""
<t t-call="web_studio.test_report_document" ws-call-key="2" ws-view-id="{self.main_view.id}">
<t t-name="web_studio.test_report_document" ws-view-id="{self.main_view_document.id}">
<t oe-origin-t-out="0">
<t ws-view-id="{self.main_view.id}" ws-call-group-key="1" ws-call-key="2" ws-view-name="{self.main_view.name}" ws-real-children="1">
<h1/>
</t>
</t>
</t>
</t>
""")
self.assertXMLEqual(etree.tostring(tcall[1]), f"""
<t t-call="web_studio.test_report_document" ws-call-key="3" ws-view-id="{self.main_view.id}">
<t t-name="web_studio.test_report_document" ws-view-id="{self.main_view_document.id}">
<t oe-origin-t-out="0">
<t ws-view-id="{self.main_view.id}" ws-call-group-key="1" ws-call-key="3" ws-view-name="{self.main_view.name}" ws-real-children="1">
<h2/>
</t>
</t>
</t>
</t>
""")
def test_remove_branding_on_copy(self):
self.start_tour(self.tour_url, "web_studio.test_remove_branding_on_copy", login="admin")
def test_test_different_view_document_name(self):
self.main_view_document_xml_id.name = "test_report_document_1"
self.start_tour(self.tour_url, "web_studio.test_different_view_document_name", login="admin")
def test_formalize_qweb_client_t_call_structure(self):
self.authenticate("admin", "admin")
html_container = self.env["ir.ui.view"]._get_template_view("web.html_container")
tcalled_0 = self.env["ir.ui.view"].create({
'type': 'qweb',
'name': 'web_studio.t_call_0',
'key': 'web_studio.t_call_0',
'arch': """<t t-name="web_studio.t_call_0">
<div class="call_0"><t t-out="0" /></div>
</t>"""
})
tcalled_1 = self.env["ir.ui.view"].create({
'type': 'qweb',
'name': 'web_studio.t_call_1',
'key': 'web_studio.t_call_1',
'arch': """<t t-name="web_studio.t_call_1">
<div class="call_1"><p> call one </p><t t-out="0" /></div>
</t>"""
})
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div>
<t t-call="web_studio.t_call_0">
<div>
<p> call zero one </p>
<t t-call="web_studio.t_call_1">
<p> call zero two </p>
</t>
</div>
</t>
</div>
</t>
"""
response = self.url_open(
"/web_studio/get_report_qweb",
data=json.dumps({"params": {"report_id": self.report.id}}),
headers={"Content-Type": "application/json"}
)
qweb_html = response.json()["result"]
tree = html_to_xml_tree(qweb_html)
_clean_for_simple_assert(tree)
verification_tree = etree.fromstring(
"""<div id="wrapwrap" oe-context="{&quot;docs&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: false}, &quot;company&quot;: {&quot;model&quot;: &quot;res.company&quot;, &quot;name&quot;: &quot;Companies&quot;, &quot;in_foreach&quot;: false}}">
<main>
<t oe-origin-t-out="0">
<t ws-view-id="{html_container.id}" ws-call-group-key="1" ws-call-key="1" ws-view-name="{html_container.key}" ws-real-children="1">
<t oe-origin-t-out="0">
<t ws-view-id="{main_view.id}" ws-call-group-key="1" ws-call-key="1" ws-view-name="{main_view.key}" ws-real-children="1">
<div>
<p>
<br/>
</p>
</div>
<t t-foreach="docs" t-as="doc" oe-context="{&quot;docs&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: false}, &quot;company&quot;: {&quot;model&quot;: &quot;res.company&quot;, &quot;name&quot;: &quot;Companies&quot;, &quot;in_foreach&quot;: false}, &quot;doc&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: true}}">
<t t-call="web_studio.test_report_document" ws-call-key="2" ws-view-id="{main_view.id}">
<t t-name="web_studio.test_report_document" ws-view-id="{main_view_document.id}" oe-context="{&quot;docs&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: false}, &quot;company&quot;: {&quot;model&quot;: &quot;res.company&quot;, &quot;name&quot;: &quot;Companies&quot;, &quot;in_foreach&quot;: false}, &quot;doc&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: true}}">
<div>
<t t-call="web_studio.t_call_0" ws-call-key="1" ws-view-id="{main_view_document.id}">
<t t-name="web_studio.t_call_0" ws-view-id="{tcalled_0.id}" oe-context="{&quot;docs&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: false}, &quot;company&quot;: {&quot;model&quot;: &quot;res.company&quot;, &quot;name&quot;: &quot;Companies&quot;, &quot;in_foreach&quot;: false}, &quot;doc&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: true}}">
<div class="call_0">
<t oe-origin-t-out="0">
<t ws-view-id="{main_view_document.id}" ws-call-group-key="1" ws-call-key="1" ws-view-name="{main_view_document.key}" ws-real-children="1">
<div>
<p> call zero one </p>
<t t-call="web_studio.t_call_1" ws-call-key="2" ws-view-id="{main_view_document.id}">
<t t-name="web_studio.t_call_1" ws-view-id="{tcalled_1.id}" oe-context="{&quot;docs&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: false}, &quot;company&quot;: {&quot;model&quot;: &quot;res.company&quot;, &quot;name&quot;: &quot;Companies&quot;, &quot;in_foreach&quot;: false}, &quot;doc&quot;: {&quot;model&quot;: &quot;res.partner&quot;, &quot;name&quot;: &quot;Contact&quot;, &quot;in_foreach&quot;: true}}">
<div class="call_1">
<p> call one </p>
<t oe-origin-t-out="0">
<t ws-view-id="{main_view_document.id}" ws-call-group-key="1" ws-call-key="2" ws-view-name="{main_view_document.key}" ws-real-children="1">
<p> call zero two </p>
</t>
</t>
</div>
</t>
</t>
</div>
</t>
</t>
</div>
</t>
</t>
</div>
</t>
</t>
</t>
</t>
</t>
</t>
</t>
</main>
</div>""")
_clean_for_simple_assert(verification_tree)
self.assertXMLEqual(
etree.tostring(tree),
etree.tounicode(verification_tree).format(
main_view=self.main_view, main_view_document=self.main_view_document, tcalled_0=tcalled_0, tcalled_1=tcalled_1, html_container=html_container
)
)
def test_edit_main_arch(self):
self.env["ir.ui.view"].create({
'type': 'qweb',
'name': 'web_studio.t_call_0',
'key': 'web_studio.t_call_0',
'arch': """<t t-name="web_studio.t_call_0">
<div class="call_0"><t t-out="0" /></div>
</t>"""
})
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div class="outside-t-call">outside</div>
<t t-call="web_studio.t_call_0">
<t t-set="somevar" t-value="'someValue'" />
<div class="in-t-call">inside</div>
</t>
</t>
"""
self.start_tour(self.tour_url, "web_studio.test_edit_main_arch", login="admin")
arch, studio_arch = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<div class="added"><br/></div>
<div class="outside-t-call">outside</div>
<t t-call="web_studio.t_call_0">
<t t-set="somevar" t-value="'someValue'"/>
<div class="in-t-call">inside</div>
</t>
</t>
""")
self.assertXMLEqual(studio_arch, """
<data>
<data>
<xpath expr="/t[@t-name='web_studio.test_report_document']/div" position="before" meta-class="outside-t-call">
<div class="added"><br/></div>
</xpath>
</data>
</data>
""")
def test_edit_in_t_call(self):
self.env["ir.ui.view"].create({
'type': 'qweb',
'name': 'web_studio.t_call_0',
'key': 'web_studio.t_call_0',
'arch': """<t t-name="web_studio.t_call_0">
<div class="call_0"><t t-out="0" /></div>
</t>"""
})
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div class="outside-t-call">outside</div>
<t t-call="web_studio.t_call_0">
<t t-set="somevar" t-value="'someValue'" />
<div class="in-t-call">inside</div>
</t>
</t>
"""
self.start_tour(self.tour_url, "web_studio.test_edit_in_t_call", login="admin")
arch, studio_arch = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<div class="outside-t-call">outside</div>
<t t-call="web_studio.t_call_0">
<t t-set="somevar" t-value="'someValue'"/>
<div class="added"><br/></div>
<div class="in-t-call">inside</div>
</t>
</t>
""")
self.assertXMLEqual(studio_arch, """
<data>
<data>
<xpath expr="/t[@t-name='web_studio.test_report_document']//t[@t-call='web_studio.t_call_0']//t[@t-set='somevar']" position="after" meta-t-set="somevar" meta-t-value="'someValue'">
<div class="added"><br/></div>
</xpath>
</data>
</data>
""")
def test_edit_main_and_in_t_call(self):
self.env["ir.ui.view"].create({
'type': 'qweb',
'name': 'web_studio.t_call_0',
'key': 'web_studio.t_call_0',
'arch': """<t t-name="web_studio.t_call_0">
<div class="call_0"><t t-out="0" /></div>
</t>"""
})
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div class="outside-t-call">outside</div>
<t t-call="web_studio.t_call_0">
<t t-set="somevar" t-value="'someValue'" />
<div class="in-t-call">inside</div>
</t>
</t>
"""
self.start_tour(self.tour_url, "web_studio.test_edit_main_and_in_t_call", login="admin")
arch, studio_arch = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<div class="added0"><br/></div>
<div class="outside-t-call">outside</div>
<t t-call="web_studio.t_call_0">
<t t-set="somevar" t-value="'someValue'"/>
<div class="added1"><br/></div>
<div class="in-t-call">inside</div>
</t>
</t>
""")
self.assertXMLEqual(studio_arch, """
<data>
<data>
<xpath expr="/t[@t-name='web_studio.test_report_document']/div" position="before" meta-class="outside-t-call">
<div class="added0"><br/></div>
</xpath>
</data>
<data>
<xpath expr="/t[@t-name='web_studio.test_report_document']//t[@t-call='web_studio.t_call_0']//t[@t-set='somevar']" position="after" meta-t-set="somevar" meta-t-value="'someValue'">
<div class="added1"><br/></div>
</xpath>
</data>
</data>
""")
def test_image_crop(self):
attach = self.env["ir.attachment"].create({
"name": "test image crop",
"datas": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVR4nGJxKFrEwMDAxAAGgAAAAP//D+IBWx9K7TUAAAAASUVORK5CYII="
})
self.main_view_document.arch = f"""
<t t-name="test_main_doc">
<div>
<img class="myimg"
width="50"
height="50"
data-attachment-id="{attach.id}"
data-original-id="{attach.id}"
data-original-src="/web/image/{attach.id}"
src="data:image/png;base64,{attach.datas.decode("utf-8")}"/>
</div>
</t>
"""
self.start_tour(self.tour_url, "web_studio.test_image_crop", login="admin")
def test_add_non_searchable_field(self):
self.assertFalse(self.env["res.partner"].fields_get(["avatar_1024"])["avatar_1024"]["searchable"])
with self.with_user("admin"):
partner = self.env["res.partner"].search([], limit=1)
partner.image_1024 = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVR4nGJxKFrEwMDAxAAGgAAAAP//D+IBWx9K7TUAAAAASUVORK5CYII="
self.start_tour(self.tour_url, "web_studio.test_add_non_searchable_field", login="admin")
arch, _ = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<div><p t-field="doc.name"/></div>
<p>
<span
data-oe-demo="file default value"
t-field="doc.avatar_1024"
t-options-widget="'image'"
t-options-qweb_img_raw_data="1"
/>
</p>
</t>
""")
def test_translations_are_copied(self):
self.env["res.lang"]._activate_lang("fr_FR")
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div>term1</div>
</t>
"""
self.main_view_document.update_field_translations("arch_db", {"fr_FR": {"term1": "baguette"}})
inheriting = self.env["ir.ui.view"].create({
"type": "qweb",
"inherit_id": self.main_view_document.id,
"arch": """<data>
<xpath expr="//div[1]" position="after">
<div>term2</div>
</xpath>
</data>"""
})
inheriting.update_field_translations("arch_db", {"fr_FR": {"term2": "croissant"}})
self.start_tour(self.tour_url, "web_studio.test_translations_are_copied", login="admin")
html, _ = self.report.with_context(lang="fr_FR")._render_qweb_html(self.report.id, [1])
main = etree.fromstring(html).find(".//main")
self.assertXMLEqual(etree.tostring(main), """
<main>
<div>
<p><br/></p>
</div>
<div>baguette</div>
<div>term3 from edition</div>
<div>croissant</div>
</main>
""")
def test_dont_translate_on_save(self):
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div><p class="test-origin">content</p></div>
<p><br/></p>
</t>"""
self.env["res.lang"]._activate_lang("fr_FR")
self.start_tour(self.tour_url, "web_studio.test_dont_translate_on_save", login="admin")
html, _ = self.report.with_context(lang="fr_FR")._render_qweb_html(self.report.id, [1])
main = etree.fromstring(html).find(".//main")
self.assertXMLEqual(etree.tostring(main), """
<main>
<div>
<p>
<br/>
</p>
</div>
<div>
<span>
<strong>new content</strong>
</span>
<p class="test-origin">content</p>
</div>
<p>
<br/>
</p>
</main>
""")
def test_evaluate_bad_queries(self):
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<div>
<div t-attf-class="{{ docs.search([('create_date', '&lt;', 'false')]) }}">term1</div>
<div t-attf-class="{{ docs.search([('create_date', '&lt;', 'false')]) }}">term2</div>
<div t-attf-class="res_partner_{{ bool(docs.search([('create_date', '!=', False)], limit=1)) }}">term3</div>
</div>
</t>"""
ResPartner = self.env.registry.get("res.partner")
search = ResPartner.search
errors = []
def mock_search(record_set, *args, **kwargs):
try:
return search(record_set, *args, **kwargs)
except Exception as e:
errors.append(e)
raise
self.patch(ResPartner, "search", mock_search)
self.authenticate("admin", "admin")
with mute_logger("odoo.sql_db"):
response = self.url_open(
"/web_studio/get_report_qweb",
data=json.dumps({"params": {"report_id": self.report.id}}),
headers={"Content-Type": "application/json"}
)
self.assertEqual(len(errors), 2)
for e in errors:
self.assertTrue(isinstance(e, ValueError))
qweb_html = response.json()["result"]
tree = html_to_xml_tree(qweb_html)
_clean_for_simple_assert(tree)
div_node = tree.xpath("//t[@t-name='web_studio.test_report_document']")[0][0]
self.assertXMLEqual(etree.tostring(div_node), """
<div>
<div t-attf-class="{{ docs.search([('create_date', '&lt;', 'false')]) }}" oe-origin-class="" class="">term1</div>
<div t-attf-class="{{ docs.search([('create_date', '&lt;', 'false')]) }}" oe-origin-class="" class="">term2</div>
<div t-attf-class="res_partner_{{ bool(docs.search([('create_date', '!=', False)], limit=1)) }}" oe-origin-class="" class="res_partner_True">term3</div>
</div>
""")
def test_do_not_delete_unspecial_spans(self):
self.main_view_document.arch = """
<t t-name="web_studio.test_report_document">
<span>some text</span>
</t>"""
self.start_tour(self.tour_url, "web_studio.test_do_not_delete_unspecial_spans", login="admin")
_, studio_view_arch = get_combined_and_studio_arch(self.main_view_document)
self.assertXMLEqual(studio_view_arch, """
<data>
<data>
<xpath expr="/t[@t-name='web_studio.test_report_document']/span" position="replace">
<span>some textadded</span>
</xpath>
</data>
</data>
""")
def test_edit_header_only_company(self):
external_layout = self.env["ir.ui.view"]._get_template_view("web.external_layout_standard")
with self.with_user("admin"):
self.env.user.company_id.external_report_layout_id = external_layout
self.main_view_document.arch = '''
<t t-name="web_studio.test_report_document">
<t t-call="web.external_layout">
<div><p t-field="doc.name" /></div>
<p><br/></p>
</t>
</t>
'''
self.start_tour(self.tour_url, "web_studio.test_edit_header_only_company", login="admin")
combined = external_layout._get_combined_arch()
added_field = combined.xpath("//span[hasclass('studio-added')]")[0]
added_field = added_field[0]
self.assertTrue(added_field.get("t-field").startswith("company."))
self.assertEqual(added_field.get("data-oe-demo"), "studio company id")
def test_report_without_view(self):
report = self.env["ir.actions.report"].create({
"name": "Test Report(No view)",
"model": "res.partner",
"report_name": "web_studio.test_no_view_report",
"report_type": "qweb-pdf",
})
# Assert that the report has no associated views
views = self.env["ir.ui.view"].search([("key", "=", "web_studio.test_no_view_report")])
self.assertFalse(views, "The report should not have any associated views.")
self.start_tour(self.tour_url, "web_studio.test_report_without_view", login="admin")
self.assertNotEqual(report.report_name, "web_studio.test_no_view_report")
views = self.env["ir.ui.view"].search([("key", "=", report.report_name)])
self.assertEqual(len(views), 1)
def test_save_report_insignificant_diff(self):
self.authenticate("admin", "admin")
self.main_view.arch = """
<t t-name="web_studio.test_report_document">
<div id="wrapwrap">
<div x="a" y="b"></div>
</div>
</t>"""
params = {
"report_id": self.report.id,
"html_parts": {
self.main_view.id: [
{
"call_key": None,
"call_group_key": None,
"type": "full",
"html": """
<t t-name="web_studio.test_report_document" o-diff-key="0">
<div id="wrapwrap" o-diff-key="1">
<div x="a" y="b" o-diff-key="2"></div>
</div>
</t>
"""
}
]
},
}
self.url_open(
"/web_studio/save_report",
data=json.dumps({"params": params}),
headers={"Content-Type": "application/json"}
)
arch, studio_arch = get_combined_and_studio_arch(self.main_view)
self.assertXMLEqual(arch, """
<t t-name="web_studio.test_report_document">
<div id="wrapwrap">
<div x="a" y="b"></div>
</div>
</t>
""")
self.assertXMLEqual(studio_arch.strip(), "<data/>")
def test_xml_resource_with_empty_arch_view(self):
self.authenticate('admin', 'admin')
inherit = self.env["ir.ui.view"].create({
"inherit_id": self.main_view.id,
"type": "qweb",
"arch": "<data/>"
})
# Save inherited report with empty arch ''
save_report = self.url_open(
"/web_studio/save_report",
data=json.dumps({
"params": {
"report_id": self.report.id,
"context": {'allowed_company_ids': self.env.company.ids},
"xml_verbatim": {str(inherit.id): ''}
}
}),
headers={"Content-Type": "application/json"}
)
self.assertEqual(save_report.status_code, 200)
xml_edit_resurces = self.url_open(
"/web_studio/get_xml_editor_resources",
data=json.dumps({
"params": {"key": self.main_view.key}
}),
headers={"Content-Type": "application/json"}
)
self.assertEqual(xml_edit_resurces.status_code, 200)
views = xml_edit_resurces.json()['result']['views']
view_ids = [view['id'] for view in views]
self.assertIn(inherit.id, view_ids)