1552 lines
64 KiB
Python
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="{"docs": {"model": "res.partner", "name": "Contact", "in_foreach": false}, "company": {"model": "res.company", "name": "Companies", "in_foreach": 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="{"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 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="{"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 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', '<', '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, 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', '<', '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>
|
|
""")
|
|
|
|
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)
|