forked from Mapan/odoo17e
1424 lines
58 KiB
Python
1424 lines
58 KiB
Python
import json
|
|
from psycopg2.extras import Json
|
|
from lxml import etree
|
|
from psycopg2 import DataError
|
|
|
|
from odoo import Command
|
|
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 _remove_oe_context(tree):
|
|
for node in tree.iter(etree.Element):
|
|
node.attrib.pop("oe-context", 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(target._read_template(target.id), 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"/web#action=studio&mode=editor&_action={self.testAction.id}&_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")
|
|
self.assertTrue(self.main_view_xml_id.noupdate)
|
|
self.assertTrue(self.main_view_document_xml_id.noupdate)
|
|
self.assertTrue(self.report_xml_id.noupdate)
|
|
|
|
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>
|
|
<xpath expr="/t[@t-name='web_studio.test_report']" position="replace" mode="inner">
|
|
<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>
|
|
</xpath>
|
|
</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>
|
|
<xpath expr="/t[@t-name='web_studio.test_report_document']" position="replace" mode="inner">
|
|
<div>
|
|
<p t-field="doc.name"/>
|
|
</div>
|
|
<p>edited with odoo editor 2</p>
|
|
</xpath>
|
|
</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.assertTrue(self.report_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='json', 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='json', 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_table_rendering(self):
|
|
self.main_view_document.arch = """
|
|
<t t-name="web_studio.test_report_document">
|
|
<p><br/></p>
|
|
<table class="valid_table">
|
|
<tr><td>I am valid</td></tr>
|
|
</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)
|
|
_remove_oe_context(tree)
|
|
div_node = tree.xpath("//t[@t-name='web_studio.test_report_document']")[0][0]
|
|
self.assertXMLEqual(etree.tostring(div_node), """
|
|
<div class="invalid_table" oe-origin-tag="table" oe-origin-style="">
|
|
<t t-foreach="doc.child_ids" t-as="child">
|
|
<div oe-origin-tag="tr" oe-origin-style="">
|
|
<div oe-origin-tag="td" oe-origin-style="">
|
|
<t t-set="var" t-value="doc"/>
|
|
<table>
|
|
<tr>
|
|
<td>valid</td>
|
|
</tr>
|
|
</table>
|
|
<div oe-origin-tag="table" oe-origin-style="">
|
|
<t t-foreach="doc.child_ids" t-as="subchild">
|
|
<div oe-origin-tag="tr" oe-origin-style="">
|
|
<div oe-origin-tag="td" oe-origin-style="">
|
|
<table><tr><td>I am valid</td></tr></table>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
""")
|
|
|
|
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>
|
|
"""
|
|
|
|
url = self.tour_url.replace("/web", "/web?debug=assets")
|
|
self.start_tour(url, "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 t-field="doc.function">some default value</span>
|
|
<br/>
|
|
</p>
|
|
</t>
|
|
""")
|
|
|
|
def test_toolbar_appearance(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, "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"><p><span t-field="doc.function">some default value</span><br/>Custo</p></div>
|
|
</div>
|
|
</t>
|
|
""")
|
|
|
|
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><xpath expr="/t[@t-name='web_studio.test_report_document']" position="replace" mode="inner">
|
|
<p>original term edited</p>
|
|
</xpath></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"})
|
|
|
|
original_search = ResPartner.search
|
|
|
|
def mock_search(self, *args, **kwargs):
|
|
if not args and not kwargs:
|
|
return (p1 | p2).ids
|
|
return original_search(*args, **kwargs)
|
|
|
|
self.patch(type(ResPartner), "search", mock_search)
|
|
|
|
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)]})
|
|
|
|
tour_url = self.tour_url + f"&cids={company2.id}-{company1.id}"
|
|
self.start_tour(tour_url, "web_studio.test_render_multicompany", login="admin")
|
|
|
|
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.partner",
|
|
"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 t-field="doc.company_id.x_new_file">file default value</span><span t-field="doc.company_id.x_new_image" t-options-widget="\'image\'" t-options-qweb_img_raw_data="1">image default value</span><br/></p>
|
|
</t>
|
|
""")
|
|
|
|
def test_report_edition_dynamic_table(self):
|
|
url = self.tour_url.replace("/web", "/web?debug=assets")
|
|
self.start_tour(url, "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>First Column</td>
|
|
</tr>
|
|
<tr t-foreach="doc.activity_ids" t-as="x2many_record">
|
|
<td>
|
|
<span t-field="x2many_record.summary">Some Summary</span>
|
|
<br/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<p>
|
|
<br/>
|
|
</p>
|
|
</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"):
|
|
url = self.tour_url.replace("/web", "/web?debug=1")
|
|
self.start_tour(url, "web_studio.test_error_at_loading_debug", login="admin")
|
|
|
|
def test_xml_and_form_diff(self):
|
|
url = self.tour_url.replace("/web", "/web?debug=1")
|
|
self.start_tour(url, "web_studio.test_xml_and_form_diff", login="admin")
|
|
|
|
def test_record_model_differs_from_action(self):
|
|
dummy = self.env["ir.model"].create({
|
|
"name": "dummy.test",
|
|
"model": "x_dummy.test"
|
|
})
|
|
self.env['ir.model.access'].create({
|
|
"name": "dummy",
|
|
"perm_read": True,
|
|
"model_id": dummy.id,
|
|
})
|
|
|
|
self.report.model = dummy.model
|
|
self.report.name = "dummy test"
|
|
self.start_tour(f"/web#action=studio&mode=editor&_action={self.testAction.id}&_tab=reports&menu_id={self.testMenu.id}", "web_studio.test_record_model_differs_from_action", 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)
|
|
_remove_oe_context(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" 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)
|
|
_remove_oe_context(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-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-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("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)
|
|
_remove_oe_context(tree)
|
|
|
|
verification_tree = etree.fromstring(
|
|
"""<div id="wrapwrap" oe-context="{"docs": {"model": "res.partner", "name": "Contact", "in_foreach": false}, "company": {"model": "res.company", "name": "Companies", "in_foreach": false}}">
|
|
<main>
|
|
<t ws-was-t-out="1">
|
|
<t ws-view-id="{html_container.id}" ws-call-group-key="1" ws-call-key="1" ws-real-children="1">
|
|
<t ws-was-t-out="1">
|
|
<t ws-view-id="{main_view.id}" ws-call-group-key="1" ws-call-key="1" ws-real-children="1">
|
|
<div>
|
|
<p>
|
|
<br/>
|
|
</p>
|
|
</div>
|
|
<t t-foreach="docs" t-as="doc" oe-context="{"docs": {"model": "res.partner", "name": "Contact", "in_foreach": false}, "company": {"model": "res.company", "name": "Companies", "in_foreach": false}, "doc": {"model": "res.partner", "name": "Contact", "in_foreach": 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="{"docs": {"model": "res.partner", "name": "Contact", "in_foreach": false}, "company": {"model": "res.company", "name": "Companies", "in_foreach": false}, "doc": {"model": "res.partner", "name": "Contact", "in_foreach": 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="{"docs": {"model": "res.partner", "name": "Contact", "in_foreach": false}, "company": {"model": "res.company", "name": "Companies", "in_foreach": false}, "doc": {"model": "res.partner", "name": "Contact", "in_foreach": true}}">
|
|
<div class="call_0">
|
|
<t ws-was-t-out="1">
|
|
<t ws-view-id="{main_view_document.id}" ws-call-group-key="1" ws-call-key="1" 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="{"docs": {"model": "res.partner", "name": "Contact", "in_foreach": false}, "company": {"model": "res.company", "name": "Companies", "in_foreach": false}, "doc": {"model": "res.partner", "name": "Contact", "in_foreach": true}}">
|
|
<div class="call_1">
|
|
<p> call one </p>
|
|
<t ws-was-t-out="1">
|
|
<t ws-view-id="{main_view_document.id}" ws-call-group-key="1" ws-call-key="2" 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>""")
|
|
_remove_oe_context(verification_tree)
|
|
|
|
self.assertXMLEqual(
|
|
etree.tostring(tree),
|
|
etree.tounicode(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"/>
|
|
<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>
|
|
<xpath expr="/t[@t-name='web_studio.test_report_document']" position="replace" mode="inner">
|
|
<div class="added"/>
|
|
<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>
|
|
</xpath>
|
|
</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"/>
|
|
<div class="in-t-call">inside</div>
|
|
</t>
|
|
</t>
|
|
""")
|
|
self.assertXMLEqual(studio_arch, """
|
|
<data>
|
|
<xpath expr="/t[@t-name='web_studio.test_report_document']" position="replace" mode="inner">
|
|
<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"/>
|
|
<div class="in-t-call">inside</div>
|
|
</t>
|
|
</xpath>
|
|
</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"/>
|
|
<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"/>
|
|
<div class="in-t-call">inside</div>
|
|
</t>
|
|
</t>
|
|
""")
|
|
self.assertXMLEqual(studio_arch, """
|
|
<data>
|
|
<xpath expr="/t[@t-name='web_studio.test_report_document']" position="replace" mode="inner">
|
|
<div class="added0"/>
|
|
<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"/>
|
|
<div class="in-t-call">inside</div>
|
|
</t>
|
|
</xpath>
|
|
</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">
|
|
<img class="myimg"
|
|
data-original-id="{attach.id}"
|
|
data-original-src="/web/image/{attach.id}"
|
|
src="data:image/png;base64,{attach.datas.decode("utf-8")}"/>
|
|
</t>
|
|
"""
|
|
|
|
self.start_tour(self.tour_url, "web_studio.test_image_crop", login="admin")
|
|
|
|
def test_add_non_searchable_field(self):
|
|
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
|
|
t-field="doc.avatar_1024"
|
|
t-options-widget="'image'"
|
|
t-options-qweb_img_raw_data="1"
|
|
>file default value</span>
|
|
<br/>
|
|
</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_translations_with_br_nodes_are_still_matched(self):
|
|
self.env["res.lang"]._activate_lang("fr_FR")
|
|
self.main_view_document.arch = """
|
|
<t t-name="web_studio.test_report_document">
|
|
<strong>Hi</strong><br/>
|
|
</t>
|
|
"""
|
|
self.main_view_document.update_field_translations("arch_db", {
|
|
"fr_FR": {"<strong>Hi</strong><br/>": "<strong>Salut</strong><br/>"}})
|
|
self.start_tour(self.tour_url, "web_studio.test_translations_with_br_nodes_are_still_matched", 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>added text</div>
|
|
<strong>Salut</strong>
|
|
<br/>
|
|
</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', '<', 'false')]) }}">term1</div>
|
|
<div t-attf-class="{{ docs.search([('create_date', '<', '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, DataError))
|
|
|
|
qweb_html = response.json()["result"]
|
|
tree = html_to_xml_tree(qweb_html)
|
|
_remove_oe_context(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', '<', 'false')]) }}" oe-origin-class="" class="">term1</div>
|
|
<div t-attf-class="{{ docs.search([('create_date', '<', '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>
|
|
""")
|