summaryrefslogtreecommitdiff
path: root/addons/test_website
diff options
context:
space:
mode:
authorRomain Derie <rde@odoo.com>2018-12-28 15:24:33 +0000
committerJeremy Kersten <jke@odoo.com>2019-02-13 20:33:18 +0000
commitef03db9edd9472201cb2c08a32d20ff0f33a5fdf (patch)
tree23c2584beac7668680ae2a74054025b2c52f5e20 /addons/test_website
parent01d925e05254cbf69432c13a8d911537d5cfb878 (diff)
[FIX] website: restore reset_templates functionality
Since AST was introduced to compile Qweb templates, the reset templates functionality is not working anymore. Actually, it does not even appear anymore as the conditions to trigger and show the reset view behavior can't be True anymore. Indeed, the code is still importing the old QWebException from odoo.exceptions which is now just an empty class. The QWebException that should now be used is the one from odoo.base.models.qweb More than just a wrong import, the QWebException properties have completely changed. Since its last working version (Odoo 9.0), a lot of changes occured, mostly: - Odoo 11.0: new model website.page, which completely changed the pages behavior and changed the models. - Odoo 12.0: multi-website, which introduced the Copy On Write (COW) that redefined how we write on view in a website context. We now need to reset copied/specific views that have no model_data_id. This fix is for a stable version. There is some flows that can't be fixed without a refactoring or new fields (mainly because of the arch_fs being erased when writing on `arch`, plus we can't introduce a module reload in stable). The goal of this commit is to fix the basic and standard case of broken views, which is when non-technical users are breaking the views through HTML editor. To reset the views, we will read the original arch in the XML file, as the dev mode is doing. To be eligible for the reset, the broken view need an `arch_fs` set. If the view arch has been modified anywhere else than the frontend, the `arch_fs` will be erased and the view wont be able to be reset. As the broken views will be specific views, we will simply read the generic view in the XML file and write it on the specific. We can't simply remove the specific view as we might be on a specific tree. Specific case, in case of custom views create dropping a snippet in an oe_struc we just delete this inehrited view to allow end user to have their page back. In some case of bad compilation in sub template, we try to guess which inherited view, t-called view is broken by string matching based on 'last_path_node' xpath. Add new module test_website to add tests, it will be usefull for futur test of install/uninstall and ... Todo in master: - try to track on QWebException the real template that is broken and not only the last node from the main template. Co-authored-by: "Romain Derie <rde@odoo.com>" Co-authored-by: "Jérémy Kersten <jke@openerp.com>" closes odoo/odoo#29957
Diffstat (limited to 'addons/test_website')
-rw-r--r--addons/test_website/__init__.py4
-rw-r--r--addons/test_website/__manifest__.py24
-rw-r--r--addons/test_website/controllers/__init__.py4
-rw-r--r--addons/test_website/controllers/main.py13
-rw-r--r--addons/test_website/data/test_website_data.xml54
-rw-r--r--addons/test_website/static/src/js/reset_views.test.js103
-rw-r--r--addons/test_website/tests/__init__.py4
-rw-r--r--addons/test_website/tests/test_reset_views.py102
-rw-r--r--addons/test_website/views/templates.xml8
9 files changed, 316 insertions, 0 deletions
diff --git a/addons/test_website/__init__.py b/addons/test_website/__init__.py
new file mode 100644
index 00000000000..273ede25ea3
--- /dev/null
+++ b/addons/test_website/__init__.py
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import controllers
diff --git a/addons/test_website/__manifest__.py b/addons/test_website/__manifest__.py
new file mode 100644
index 00000000000..557722971d4
--- /dev/null
+++ b/addons/test_website/__manifest__.py
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'Website Test',
+ 'version': '1.0',
+ 'category': 'Hidden',
+ 'sequence': 9876,
+ 'summary': 'Website Test, mainly for module install/uninstall tests',
+ 'description': """This module contains tests related to website. Those are
+contained in a separate module as we are testing module install/uninstall/upgrade
+and we don't want to reload the website module every time, including it's possible
+dependencies. Neither we want to add in website module some routes, views and
+models which only purpose is to run tests.""",
+ 'depends': [
+ 'website',
+ ],
+ 'data': [
+ 'views/templates.xml',
+ 'data/test_website_data.xml',
+ ],
+ 'installable': True,
+ 'application': False,
+}
diff --git a/addons/test_website/controllers/__init__.py b/addons/test_website/controllers/__init__.py
new file mode 100644
index 00000000000..5d4b25db9c0
--- /dev/null
+++ b/addons/test_website/controllers/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import main
diff --git a/addons/test_website/controllers/main.py b/addons/test_website/controllers/main.py
new file mode 100644
index 00000000000..fad797d6984
--- /dev/null
+++ b/addons/test_website/controllers/main.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import http
+from odoo.http import request
+from odoo.addons.portal.controllers.web import Home
+
+
+class WebsiteTest(Home):
+
+ @http.route('/test_view', type='http', auth="public", website=True)
+ def test_view(self, **kw):
+ return request.render('test_website.test_view')
diff --git a/addons/test_website/data/test_website_data.xml b/addons/test_website/data/test_website_data.xml
new file mode 100644
index 00000000000..df827d542a3
--- /dev/null
+++ b/addons/test_website/data/test_website_data.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data noupdate="1">
+ <record id="test_view" model="ir.ui.view">
+ <field name="name">Test View</field>
+ <field name="type">qweb</field>
+ <field name="key">test_website.test_view</field>
+ <field name="arch" type="xml">
+ <t name="Test View" priority="29" t-name="test_website.test_view">
+ <t t-call="website.layout">
+ <p>Test View</p>
+ <p>placeholder</p>
+ </t>
+ </t>
+ </field>
+ </record>
+ <record id="test_page_view" model="ir.ui.view">
+ <field name="name">Test Page View</field>
+ <field name="type">qweb</field>
+ <field name="key">test_website.test_page_view</field>
+ <field name="arch" type="xml">
+ <t name="Test Page View" priority="29" t-name="test_website.test_page_view">
+ <t t-call="website.layout">
+ <div id="oe_structure_test_website_page" class="oe_structure oe_empty"/>
+ <p>Test Page View</p>
+ <p>placeholder</p>
+ </t>
+ </t>
+ </field>
+ </record>
+ <record id="test_page" model="website.page">
+ <field name="website_published">True</field>
+ <field name="url">/test_page_view</field>
+ <field name="view_id" ref="test_page_view"/>
+ </record>
+ <record id="test_view_to_be_t_called" model="ir.ui.view">
+ <field name="name">Test View To Be t-called</field>
+ <field name="type">qweb</field>
+ <field name="key">test_website.test_view_to_be_t_called</field>
+ <field name="arch" type="xml">
+ <t name="Test View To Be t-called" priority="29" t-name="test_website.test_view_to_be_t_called">
+ <p>Test View To Be t-called</p>
+ <p>placeholder</p>
+ </t>
+ </field>
+ </record>
+ <template id="test_view_child_broken" inherit_id="test_website.test_view" active="False">
+ <xpath expr="//p[last()]" position="replace">
+ <p>Test View Child Broken</p>
+ <p>placeholder</p>
+ </xpath>
+ </template>
+ </data>
+</odoo>
diff --git a/addons/test_website/static/src/js/reset_views.test.js b/addons/test_website/static/src/js/reset_views.test.js
new file mode 100644
index 00000000000..957cda6d594
--- /dev/null
+++ b/addons/test_website/static/src/js/reset_views.test.js
@@ -0,0 +1,103 @@
+odoo.define('test_website.reset_views', function (require) {
+'use strict';
+
+var tour = require("web_tour.tour");
+var base = require("web_editor.base");
+
+tour.register('test_reset_page_view_complete_flow_part1', {
+ test: true,
+ url: '/test_page_view',
+ wait_for: base.ready()
+},
+ [
+ // 1. Edit the page through Edit Mode, it will COW the view
+ {
+ content: "enter edit mode",
+ trigger: "a[data-action=edit]"
+ },
+ {
+ content: "drop a snippet",
+ trigger: "#oe_snippets .oe_snippet:has(.s_cover) .oe_snippet_thumbnail",
+ // id starting by 'oe_structure..' will actually create an inherited view
+ run: "drag_and_drop #oe_structure_test_website_page",
+ },
+ {
+ content: "save the page",
+ extra_trigger: '#oe_structure_test_website_page .s_cover',
+ trigger: "#web_editor-top-edit button[data-action=save]",
+ },
+ // 2. Edit that COW'd view in the HTML editor to break it.
+ {
+ content: "open customize menu",
+ extra_trigger: "body:not(.editor_enable)",
+ trigger: '#customize-menu > a',
+ },
+ {
+ content: "open html editor",
+ trigger: '#html_editor',
+ },
+ {
+ content: "add a broken t-field in page DOM",
+ trigger: 'div.ace_line .ace_xml:contains("placeholder")',
+ run: function () {
+ ace.edit('ace-view-editor').getSession().insert({row: 4, column: 1}, '<t t-field="not.exist"/>\n');
+ },
+ },
+ {
+ content: "save the html editor",
+ extra_trigger: '.ace_content:contains("not.exist")',
+ trigger: ".o_ace_view_editor button[data-action=save]",
+ }
+
+ // 3. Reset the broken view
+ ]
+);
+
+tour.register('test_reset_page_view_complete_flow_part2', {
+ test: true,
+ url: '/test_page_view',
+ wait_for: base.ready()
+},
+ [
+ {
+ content: "check that the view got fixed",
+ trigger: 'p:containsExact("Test Page View")',
+ run: function () {}, // it's a check
+ },
+ {
+ content: "check that the inherited COW view is still there (created during edit mode)",
+ trigger: '#oe_structure_test_website_page .s_cover',
+ run: function () {}, // it's a check
+ },
+ //4. Now break the inherited view created when dropping a snippet
+ {
+ content: "open customize menu",
+ trigger: '#customize-menu > a',
+ },
+ {
+ content: "open html editor",
+ trigger: '#html_editor',
+ },
+ {
+ content: "select oe_structure view",
+ trigger: '#ace-view-list',
+ run: function () {
+ var viewId = $('#ace-view-list option:contains("oe_structure")').val();
+ $('#ace-view-list').val(viewId).trigger('change');
+ },
+ },
+ {
+ content: "add a broken t-field in page DOM",
+ trigger: 'div.ace_line .ace_xml:contains("oe_structure_test_website_page")',
+ run: function () {
+ ace.edit('ace-view-editor').getSession().insert({row: 4, column: 1}, '<t t-field="not.exist"/>\n');
+ },
+ },
+ {
+ content: "save the html editor",
+ trigger: ".o_ace_view_editor button[data-action=save]",
+ }
+ ]
+);
+
+});
diff --git a/addons/test_website/tests/__init__.py b/addons/test_website/tests/__init__.py
new file mode 100644
index 00000000000..4c32d5f1959
--- /dev/null
+++ b/addons/test_website/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_reset_views
diff --git a/addons/test_website/tests/test_reset_views.py b/addons/test_website/tests/test_reset_views.py
new file mode 100644
index 00000000000..047b1abe3cd
--- /dev/null
+++ b/addons/test_website/tests/test_reset_views.py
@@ -0,0 +1,102 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import re
+
+import odoo.tests
+from odoo.tools import mute_logger
+
+
+def break_view(view, fr='<p>placeholder</p>', to='<p t-field="not.exist"/>'):
+ view.arch = view.arch.replace(fr, to)
+
+
+@odoo.tests.common.tagged('post_install', '-at_install')
+class TestWebsiteResetViews(odoo.tests.HttpCase):
+ def do_test(self, name):
+ self.browser_js(
+ "/",
+ "odoo.__DEBUG__.services['web_tour.tour'].run('%s')" % name,
+ "odoo.__DEBUG__.services['web_tour.tour'].tours.%s.ready" % name,
+ login="admin"
+ )
+
+ def fix_it(self, page):
+ self.authenticate("admin", "admin")
+ resp = self.url_open(page)
+ self.assertEqual(resp.status_code, 500, "Waiting 500")
+ self.assertTrue('<button id="reset_templates_button"' in resp.text)
+ data = {'templates': [self.find_template(resp)], 'redirect': page}
+ resp = self.url_open('/website/reset_templates', data)
+ self.assertEqual(resp.status_code, 200, "Waiting 200")
+
+ def find_template(self, response):
+ find = re.search(r'<input.*type="checkbox".*name="templates".*value="([0-9]+)?"', response.text)
+ return find and find.group(1)
+
+ def setUp(self):
+ super(TestWebsiteResetViews, self).setUp()
+ self.Website = self.env['website']
+ self.View = self.env['ir.ui.view']
+ self.test_view = self.Website.viewref('test_website.test_view')
+
+ @mute_logger('odoo.addons.website.models.ir_http')
+ def test_01_reset_specific_page_view(self):
+ self.test_page_view = self.Website.viewref('test_website.test_page_view')
+ total_views = self.View.search_count([('type', '=', 'qweb')])
+ # Trigger COW then break the QWEB XML on it
+ break_view(self.test_page_view.with_context(website_id=1))
+ self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view")
+ self.fix_it('/test_page_view')
+
+ @mute_logger('odoo.addons.website.models.ir_http')
+ def test_02_reset_specific_view_controller(self):
+ total_views = self.View.search_count([('type', '=', 'qweb')])
+ # Trigger COW then break the QWEB XML on it
+ break_view(self.test_view.with_context(website_id=1))
+ self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view")
+ self.fix_it('/test_view')
+
+ @mute_logger('odoo.addons.website.models.ir_http')
+ def test_03_reset_specific_view_controller_t_called(self):
+ self.test_view_to_be_t_called = self.Website.viewref('test_website.test_view_to_be_t_called')
+
+ total_views = self.View.search_count([('type', '=', 'qweb')])
+ # Trigger COW then break the QWEB XML on it
+ break_view(self.test_view_to_be_t_called.with_context(website_id=1))
+ break_view(self.test_view, to='<t t-call="test_website.test_view_to_be_t_called"/>')
+ self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view")
+ self.fix_it('/test_view')
+
+ @mute_logger('odoo.addons.website.models.ir_http')
+ def test_04_reset_specific_view_controller_inherit(self):
+ self.test_view_child_broken = self.Website.viewref('test_website.test_view_child_broken')
+
+ # Activate and break the inherited view
+ self.test_view_child_broken.active = True
+ break_view(self.test_view_child_broken.with_context(website_id=1, load_all_views=True))
+
+ self.fix_it('/test_view')
+
+ # This test work in real life, but not in test mode since we cannot rollback savepoint.
+ # @mute_logger('odoo.addons.website.models.ir_http', 'odoo.addons.website.models.ir_ui_view')
+ # def test_05_reset_specific_view_controller_broken_request(self):
+ # total_views = self.View.search_count([('type', '=', 'qweb')])
+ # # Trigger COW then break the QWEB XML on it
+ # break_view(self.test_view.with_context(website_id=1), to='<t t-esc="request.env[\'website\'].browse(\'a\').name" />')
+ # self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view (1)")
+ # self.fix_it('/test_view')
+
+ # also mute ir.ui.view as `get_view_id()` will raise "Could not find view object with xml_id 'not.exist'""
+ @mute_logger('odoo.addons.website.models.ir_http', 'odoo.addons.website.models.ir_ui_view')
+ def test_06_reset_specific_view_controller_inexisting_template(self):
+ total_views = self.View.search_count([('type', '=', 'qweb')])
+ # Trigger COW then break the QWEB XML on it
+ break_view(self.test_view.with_context(website_id=1), to='<t t-call="not.exist"/>')
+ self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view (2)")
+ self.fix_it('/test_view')
+
+ @mute_logger('odoo.addons.website.models.ir_http')
+ def test_07_reset_page_view_complete_flow(self):
+ self.do_test('test_reset_page_view_complete_flow_part1')
+ self.fix_it('/test_page_view')
+ self.do_test('test_reset_page_view_complete_flow_part2')
+ self.fix_it('/test_page_view')
diff --git a/addons/test_website/views/templates.xml b/addons/test_website/views/templates.xml
new file mode 100644
index 00000000000..2ce171bac30
--- /dev/null
+++ b/addons/test_website/views/templates.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="assets_frontend" inherit_id="website.assets_frontend">
+ <xpath expr="script[last()]" position="after">
+ <script type="text/javascript" src="/test_website/static/src/js/reset_views.test.js"></script>
+ </xpath>
+ </template>
+</odoo>