diff options
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/test_website/__init__.py | 4 | ||||
| -rw-r--r-- | addons/test_website/__manifest__.py | 24 | ||||
| -rw-r--r-- | addons/test_website/controllers/__init__.py | 4 | ||||
| -rw-r--r-- | addons/test_website/controllers/main.py | 13 | ||||
| -rw-r--r-- | addons/test_website/data/test_website_data.xml | 54 | ||||
| -rw-r--r-- | addons/test_website/static/src/js/reset_views.test.js | 103 | ||||
| -rw-r--r-- | addons/test_website/tests/__init__.py | 4 | ||||
| -rw-r--r-- | addons/test_website/tests/test_reset_views.py | 102 | ||||
| -rw-r--r-- | addons/test_website/views/templates.xml | 8 | ||||
| -rw-r--r-- | addons/website/controllers/main.py | 36 | ||||
| -rw-r--r-- | addons/website/models/ir_http.py | 68 | ||||
| -rw-r--r-- | addons/website/models/ir_ui_view.py | 10 | ||||
| -rw-r--r-- | addons/website/views/website_templates.xml | 123 |
13 files changed, 467 insertions, 86 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> diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 6f1a2df1e8e..81d461830ef 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -286,24 +286,36 @@ class Website(Home): views = views.sorted(key=lambda v: (v.inherit_id.id, v.name)) return views.read(['name', 'id', 'key', 'xml_id', 'arch', 'active', 'inherit_id']) - @http.route('/website/reset_templates', type='http', auth='user', methods=['POST'], website=True) + @http.route('/website/reset_templates', type='http', auth='user', methods=['POST'], website=True, csrf=False) def reset_template(self, templates, redirect='/', **kwargs): + """ This method will try to reset a list of broken views ids. + It will read the original `arch` from the view's XML file (arch_fs). + Views without an `arch_fs` can't be reset, except views created when + dropping a snippet in specific oe_structure that create an inherited + view doing an xpath. + Note: The `arch_fs` field is automatically erased when there is a + write on the `arch` field. + + This method is typically useful to reset specific views. In that case we + read the XML file from the generic view. + """ templates = request.httprequest.form.getlist('templates') - modules_to_update = [] for temp_id in templates: view = request.env['ir.ui.view'].browse(int(temp_id)) - if view.page: + if 'oe_structure' in view.key: + # Particular xpathing view created in edit mode + view.unlink() continue - view.model_data_id.write({ - 'noupdate': False - }) - if view.model_data_id.module not in modules_to_update: - modules_to_update.append(view.model_data_id.module) + xml_view = view._get_original_view() # view might already be the xml_view + if xml_view.arch_fs: + view_file_arch = xml_view.with_context(read_arch_from_file=True).arch + # Deactivate COW to not fix a generic view by creating a specific + view.with_context(website_id=None).arch_db = view_file_arch + if view == xml_view: + view.model_data_id.write({ + 'noupdate': False + }) - if modules_to_update: - modules = request.env['ir.module.module'].sudo().search([('name', 'in', modules_to_update)]) - if modules: - modules.button_immediate_upgrade() return request.redirect(redirect) @http.route(['/website/publish'], type='json', auth="public", website=True) diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 49e06997692..c45b9ff20fe 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import logging +from lxml import etree import traceback import os import unittest @@ -12,14 +13,14 @@ import werkzeug.routing import werkzeug.utils import odoo -from odoo import api, models +from odoo import api, models, registry from odoo import SUPERUSER_ID from odoo.http import request from odoo.tools import config -from odoo.exceptions import QWebException from odoo.tools.safe_eval import safe_eval from odoo.osv.expression import FALSE_DOMAIN, OR +from odoo.addons.base.models.qweb import QWebException from odoo.addons.http_routing.models.ir_http import ModelConverter, _guess_mimetype from odoo.addons.portal.controllers.portal import _build_url_w_params @@ -185,7 +186,7 @@ class Http(models.AbstractModel): # if parent excplicitely returns a plain response, then we don't touch it return response except Exception as e: - if 'werkzeug' in config['dev_mode'] and (not isinstance(exception, QWebException) or not exception.qweb.get('cause')): + if 'werkzeug' in config['dev_mode']: raise e exception = e @@ -207,19 +208,9 @@ class Http(models.AbstractModel): if isinstance(exception, QWebException): values.update(qweb_exception=exception) - if isinstance(exception.qweb.get('cause'), odoo.exceptions.AccessError): + if type(exception.error) == odoo.exceptions.AccessError: code = 403 - if code == 500: - logger.error("500 Internal Server Error:\n\n%s", values['traceback']) - if 'qweb_exception' in values: - view = request.env["ir.ui.view"] - views = view._views_get(exception.qweb['template']) - to_reset = views.filtered(lambda view: view.model_data_id.noupdate is True and view.arch_fs) - values['views'] = to_reset - elif code == 403: - logger.warn("403 Forbidden:\n\n%s", values['traceback']) - values.update( status_message=werkzeug.http.HTTP_STATUS_CODES[code], status_code=code, @@ -233,10 +224,51 @@ class Http(models.AbstractModel): if not request.uid: cls._auth_method_public() - try: - html = request.env['ir.ui.view'].render_template('website.%s' % view_id, values) - except Exception: - html = request.env['ir.ui.view'].render_template('website.http_error', values) + with registry(request.env.cr.dbname).cursor() as cr: + env = api.Environment(cr, SUPERUSER_ID, request.env.context) + if code == 500: + logger.error("500 Internal Server Error:\n\n%s", values['traceback']) + View = env["ir.ui.view"] + if 'qweb_exception' in values: + if 'load could not load template' in exception.args: + # When t-calling an inexisting template, we don't have reference to + # the view that did the t-call. We need to find it. + values['views'] = View.search([ + ('type', '=', 'qweb'), + '|', + ('arch_db', 'ilike', 't-call="%s"' % exception.name), + ('arch_db', 'ilike', "t-call='%s'" % exception.name) + ], order='write_date desc', limit=1) + else: + try: + # exception.name might be int, string + exception_template = int(exception.name) + except: + exception_template = exception.name + view = View._view_obj(exception_template) + et = etree.fromstring(view.with_context(inherit_branding=False).read_combined(['arch'])['arch']) + node = et.find(exception.path.replace('/templates/t/', './')) + line = node is not None and etree.tostring(node, encoding='unicode') + # line = exception.html # FALSE -> contains branding <div t-att-data="request.browse('ok')"/> + if line: + # If QWebException occurs in a child view, the parent view is raised + values['editable'] = request.uid and request.website.is_publisher() + values['views'] = View._views_get(exception_template).filtered( + lambda v: line in v.arch + ) + else: + values['views'] = view + # Keep only views that we can reset + values['views'] = values['views'].filtered( + lambda view: view._get_original_view().arch_fs or 'oe_structure' in view.key + ) + elif code == 403: + logger.warn("403 Forbidden:\n\n%s", values['traceback']) + try: + html = env['ir.ui.view'].render_template('website.%s' % view_id, values) + except Exception: + html = env['ir.ui.view'].render_template('website.http_error', values) + return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') @classmethod diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 977a986745b..463aed48f84 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -279,6 +279,16 @@ class View(models.Model): return super(View, self).get_view_id(xml_id) @api.multi + def _get_original_view(self): + """Given a view, retrieve the original view it was COW'd from. + The given view might already be the original one. In that case it will + (and should) return itself. + """ + self.ensure_one() + domain = [('key', '=', self.key), ('model_data_id', '!=', None)] + return self.search(domain, limit=1) # Useless limit has multiple xmlid should not be possible + + @api.multi def render(self, values=None, engine='ir.qweb', minimal_qcontext=False): """ Render the template. If website is enabled on request, then extend rendering context with website values. """ new_context = dict(self._context) diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 9abc878412e..cc7cff9ca93 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -870,17 +870,14 @@ </h4> <div id="error_qweb" class="collapse"> <div class="card-body"> - <p t-if="exception.qweb.get('message')"> + <p t-if="exception.message"> <strong>Error message:</strong> - <pre t-esc="exception.qweb.get('message')"/> + <pre t-esc="exception.message"/> </p> <p> - The error occured while rendering the template <code t-esc="qweb_exception.qweb.get('template')"/> - <t t-if="'expression' in qweb_exception.qweb">and evaluating the following expression: <code t-esc="qweb_exception.qweb['expression']"/></t> + The error occured while rendering the template <code t-esc="qweb_exception.name"/> + <t t-if="qweb_exception.html">and evaluating the following expression: <code t-esc="qweb_exception.html"/></t> </p> - <t t-if="'node' in qweb_exception.qweb"> - <pre id="exception_node" t-esc="qweb_exception.pretty_xml()"/> - </t> </div> </div> </div> @@ -944,62 +941,74 @@ </template> <template id="500"> - <!-- This template should not use any variable except those provided by website.ir_http._handle_exception --> + <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- This template should not use any variable except those provided by website.ir_http._handle_exception --> + <!-- no request.crsf_token, no theme style, no assets, ... cursor can be broken during rendering ! --> + <!-- see test_05_reset_specific_view_controller_broken_request --> + <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> <html> <head> <title t-esc="status_message">Internal Server Error</title> <t t-set="debug" t-value="True"/> - <t t-call-assets="web.assets_common" t-js="false"/> - <t t-call-assets="web.assets_frontend" t-js="false"/> - <t t-call-assets="web.assets_common" t-css="false"/> - <t t-call-assets="web.assets_frontend" t-css="false"/> - <script> - $(document).ready(function() { - var button = $('#reset_templates_button'); - button.click(function() { - var dialog = $('#reset_template_confirmation').modal('show'); - var input = dialog.find('input[type="text"]').val('').focus(); - var dialog_form = dialog.find('form'); - dialog_form.submit(function() { - if (input.val() == dialog.find('.confirm_word').text()) { - dialog.modal('hide'); - button.prop('disabled', true).text('Working...'); - $('#reset_templates_form').trigger('submit'); - } else { - input.val('').focus(); - } + + <link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css"/> + <script src="/web/static/lib/jquery/jquery.js" type="text/javascript"/> + <script type="text/javascript" src="/web/static/lib/bootstrap/js/util.js"/> + <script type="text/javascript" src="/web/static/lib/bootstrap/js/collapse.js"/> + <script type="text/javascript" src="/web/static/lib/bootstrap/js/modal.js"/> + + <t t-if='views'> + <script> + $(document).ready(function() { + var button = $('#reset_templates_button'); + button.click(function() { + var dialog = $('#reset_template_confirmation').modal('show'); + var input = dialog.find('input[type="text"]').val('').focus(); + var dialog_form = dialog.find('form'); + dialog_form.submit(function() { + if (input.val() == dialog.find('.confirm_word').text()) { + dialog.modal('hide'); + button.prop('disabled', true).text('Working...'); + if ($("input[name='templates']:checked").length) { + $('#reset_templates_form').attr('action', '/website/reset_templates'); + $('#reset_templates_form').trigger('submit'); + } + } else { + input.val('').focus(); + } + return false; + }); return false; }); - return false; }); - }); - </script> + </script> + </t> </head> <body> - <div role="dialog" id="reset_template_confirmation" class="modal" tabindex="-1" t-ignore="true"> + <div t-if="views" role="dialog" id="reset_template_confirmation" class="modal" tabindex="-1" t-ignore="true"> <div class="modal-dialog"> <form role="form"> - <div class="modal-content"> - <header class="modal-header"> - <h3 class="modal-title">Reset templates</h3> - <button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button> - </header> - <main class="modal-body"> - <div class="form-group row mb0"> - <label for="page-name" class="col-md-9 col-form-label"> - <p>The selected templates will be reset to their factory settings.</p> - <p>Type '<i class="confirm_word">yes</i>' in the box below if you want to confirm.</p> - </label> - <div class="col-md-3 mt16"> - <input type="text" class="form-control" required="required" placeholder="yes"/> + <div class="modal-content"> + <header class="modal-header"> + <h3 class="modal-title">Reset templates</h3> + <button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button> + </header> + <main class="modal-body"> + <div class="form-group row mb0"> + <label for="page-name" class="col-md-9 col-form-label"> + <p>The selected templates will be reset to their factory settings.</p> + <p>Type '<i class="confirm_word">yes</i>' in the box below if you want to confirm.</p> + </label> + <div class="col-md-3 mt16"> + <input type="text" id="page-name" class="form-control" required="required" placeholder="yes"/> + </div> </div> - </div> - </main> - <footer class="modal-footer"> - <input type="submit" value="Confirm" class="btn btn-primary"/> - <button type="button" class="btn" data-dismiss="modal" aria-label="Cancel">Cancel</button> - </footer> - </div> + </main> + <footer class="modal-footer"> + <input type="submit" value="Confirm" class="btn btn-primary"/> + <button type="button" class="btn" data-dismiss="modal" aria-label="Cancel">Cancel</button> + </footer> + </div> </form> </div> </div> @@ -1021,18 +1030,20 @@ <div class="oe_structure"> <h1 class="container mt32"><t t-esc="status_code"/>: <t t-esc="status_message"/></h1> </div> - <div class="container" t-if="views"> - <div class="alert alert-danger" t-if="qweb_exception and editable" role="alert"> + <div class="container" t-if="views and editable"> + <div class="alert alert-danger" t-if="debug" role="alert"> <h4>Template fallback</h4> - <p>An error occured while rendering the template <code t-esc="qweb_exception.qweb['template']"/>.</p> + <p>An error occured while rendering the template <code t-esc="qweb_exception.name"/>.</p> <p>If this error is caused by a change of yours in the templates, you have the possibility to reset one or more templates to their <strong>factory settings</strong>.</p> - <form action="/website/reset_templates" method="post" id="reset_templates_form"> - <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> + <form action="#" method="post" id="reset_templates_form"> <ul class="oe_template_fallback"> <li t-foreach="views" t-as="view"> <label> <input type="checkbox" name="templates" t-att-value="view.id" t-att-checked="'checked' if view_first else None"/> <t t-esc="view.name"/> + <span t-if="not view._get_original_view()" class="text-danger"> + (!!! This view will be removed ) + </span> </label> </li> </ul> |
