diff --git a/addons/account/tests/test_portal_attachment.py b/addons/account/tests/test_portal_attachment.py index 182c0d33a9b442936068ea813ff98f454efd23f2..584d8e8b2595976eba7baa9df2596636e98a2ea7 100644 --- a/addons/account/tests/test_portal_attachment.py +++ b/addons/account/tests/test_portal_attachment.py @@ -10,7 +10,7 @@ from odoo.tools import mute_logger @tests.tagged('post_install', '-at_install') class TestUi(tests.HttpCase): - @mute_logger('odoo.addons.website.models.ir_http', 'odoo.http') + @mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http') def test_01_portal_attachment(self): """Test the portal chatter attachment route.""" diff --git a/addons/http_routing/__manifest__.py b/addons/http_routing/__manifest__.py index f4636332f45eb46c9c76315fa2e6f36d5bfc7181..f93dcdf96dc9e16283edabfb4a053500e14174ca 100644 --- a/addons/http_routing/__manifest__.py +++ b/addons/http_routing/__manifest__.py @@ -11,6 +11,7 @@ Proposes advanced routing options not available in web or base to keep base modules simple. """, 'data': [ + 'views/http_routing_template.xml', 'views/res_lang_views.xml', ], 'depends': ['web'], diff --git a/addons/http_routing/models/ir_http.py b/addons/http_routing/models/ir_http.py index 71d62d054dbaf2788aa9be2ac6f14ba46ba3f063..06d2ee87aa848c7cc490ba2731d554c235aa2977 100644 --- a/addons/http_routing/models/ir_http.py +++ b/addons/http_routing/models/ir_http.py @@ -5,6 +5,7 @@ import json import logging import os import re +import traceback import unicodedata import werkzeug @@ -15,8 +16,9 @@ except ImportError: slugify_lib = None import odoo -from odoo import api, models +from odoo import api, models, registry, exceptions from odoo.addons.base.models.ir_http import RequestUID, ModelConverter +from odoo.addons.base.models.qweb import QWebException from odoo.http import request from odoo.osv import expression from odoo.tools import config, ustr, pycompat @@ -540,3 +542,87 @@ class IrHttp(models.AbstractModel): if request.httprequest.query_string: path += '?' + request.httprequest.query_string.decode('utf-8') return werkzeug.utils.redirect(path, code=301) + + @classmethod + def _get_exception_code_values(cls, exception): + """ Return a tuple with the error code following by the values matching the exception""" + code = 500 # default code + values = dict( + exception=exception, + traceback=traceback.format_exc(), + ) + # only except_orm exceptions contain a message + if isinstance(exception, exceptions.except_orm): + values['error_message'] = exception.name + code = 400 + if isinstance(exception, exceptions.AccessError): + code = 403 + + elif isinstance(exception, QWebException): + values.update(qweb_exception=exception) + + if type(exception.error) == exceptions.AccessError: + code = 403 + + elif isinstance(exception, werkzeug.exceptions.HTTPException): + code = exception.code + + values.update( + status_message=werkzeug.http.HTTP_STATUS_CODES[code], + status_code=code, + ) + + return (code, values) + + @classmethod + def _get_values_500_error(cls, env, values, exception): + values['view'] = env["ir.ui.view"] + return values + + @classmethod + def _get_error_html(cls, env, code, values): + return env['ir.ui.view'].render_template('http_routing.%s' % code, values) + + @classmethod + def _handle_exception(cls, exception): + is_frontend_request = bool(getattr(request, 'is_frontend', False)) + if not is_frontend_request: + # Don't touch non frontend requests exception handling + return super(IrHttp, cls)._handle_exception(exception) + try: + response = super(IrHttp, cls)._handle_exception(exception) + + if isinstance(response, Exception): + exception = response + else: + # 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']: + raise e + exception = e + + code, values = cls._get_exception_code_values(exception) + + if code is None: + # Hand-crafted HTTPException likely coming from abort(), + # usually for a redirect response -> return it directly + return exception + + if not request.uid: + cls._auth_method_public() + with registry(request.env.cr.dbname).cursor() as cr: + env = api.Environment(cr, request.uid, request.env.context) + if code == 500: + _logger.error("500 Internal Server Error:\n\n%s", values['traceback']) + values = cls._get_values_500_error(env, values, exception) + elif code == 403: + _logger.warn("403 Forbidden:\n\n%s", values['traceback']) + elif code == 400: + _logger.warn("400 Bad Request:\n\n%s", values['traceback']) + try: + html = cls._get_error_html(env, code, values) + except Exception: + html = env['ir.ui.view'].render_template('http_routing.http_error', values) + + return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') diff --git a/addons/http_routing/views/http_routing_template.xml b/addons/http_routing/views/http_routing_template.xml new file mode 100644 index 0000000000000000000000000000000000000000..648b4c3afd88607bd0dd8963c277c24764f6521f --- /dev/null +++ b/addons/http_routing/views/http_routing_template.xml @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <template id="http_error"> + <t t-call="web.frontend_layout"> + <div id="wrap"> + <div class="oe_structure"> + <h1 class="container mt32"><t t-esc="status_code"/>: <t t-esc="status_message"/></h1> + </div> + <t t-if="editable or debug"> + <t t-call="http_routing.http_error_debug"/> + </t> + </div> + </t> + </template> + + <template id="error_message"> + <p t-if="error_message"> + <strong>Error message:</strong> + <pre t-esc="error_message"/> + </p> + </template> + + <template id="http_error_debug"> + <div class="container accordion mb32 mt32" id="debug_infos"> + <div class="card" t-if="error_message"> + <h4 class="card-header"> + <a data-toggle="collapse" href="#error_main">Error</a> + </h4> + <div id="error_main" class="collapse show"> + <div class="card-body"> + <t t-call="http_routing.error_message"/> + </div> + </div> + </div> + <div class="card" t-if="qweb_exception"> + <h4 class="card-header"> + <a data-toggle="collapse" href="#error_qweb">QWeb</a> + </h4> + <div id="error_qweb" class="collapse show"> + <div class="card-body"> + <p t-if="exception.message"> + <strong>Error message:</strong> + <pre t-esc="exception.message"/> + </p> + <p> + 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> + </div> + </div> + </div> + <div class="card" t-if="traceback"> + <h4 class="card-header"> + <a data-toggle="collapse" href="#error_traceback">Traceback</a> + </h4> + <div id="error_traceback" t-attf-class="collapse #{not error_message and not qweb_exception and 'show'}"> + <div class="card-body"> + <pre id="exception_traceback" t-esc="traceback"/> + </div> + </div> + </div> + </div> + </template> + + <template id="400"> + <t t-call="web.frontend_layout"> + <div id="wrap"> + <div class="container"> + <h1 class="mt-5">Oops! Something went wrong.</h1> + <p>Take a look at the error message below.</p> + </div> + <t t-if="editable or request.session.debug"> + <t t-call="http_routing.http_error_debug"/> + </t> + <t t-else=""> + <div class="container"> + <t t-call="http_routing.error_message"/> + </div> + </t> + </div> + </t> + </template> + + <template id="403"> + <t t-call="web.frontend_layout"> + <div id="wrap"> + <div class="container"> + <h1 class="mt-5">403: Forbidden</h1> + <p>The page you were looking for could not be authorized.</p> + </div> + <t t-if="editable or request.session.debug"> + <t t-call="http_routing.http_error_debug"/> + </t> + <t t-else=""> + <div class="container"> + <t t-call="http_routing.error_message"/> + </div> + </t> + </div> + </t> + </template> + + <template id="404"> + <t t-call="web.frontend_layout"> + <div id="wrap"> + <t t-raw="0"/> + <div class="oe_structure oe_empty"> + <div class="container"> + <h1 class="mt-5">404: Page not found!</h1> + <p> + The page you were looking for could not be found; it is possible you have + typed the address incorrectly, but it has most probably been removed due + to the recent reorganisation. + </p> + </div> + </div> + </div> + </t> + </template> + + <template id="500"> + <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- This template should not use any variable except those provided by http_routing.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"/> + + <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"/> + <style> + html { + font-size: 14px; + } + </style> + </head> + <body> + <div id="wrapwrap"> + <header> + <div class="navbar navbar-expand-md navbar-light bg-light"> + <div class="container"> + <div class="collapse navbar-collapse navbar-top-collapse"> + <ul class="navbar-nav ml-auto" id="top_menu"> + <li class="nav-item"><a href="/" class="nav-link">Home</a></li> + <li class="nav-item"><a href="javascript: window.history.back()" class="nav-link">Back</a></li> + </ul> + </div> + </div> + </div> + </header> + <main> + <div id="error_message" class="oe_structure"> + <h2 class="container mt32"><t t-esc="status_code"/>: <t t-esc="status_message"/></h2> + </div> + + <t t-if="editable or debug"> + <t t-call="http_routing.http_error_debug"/> + </t> + </main> + </div> + </body> + </html> + </template> +</odoo> diff --git a/addons/portal/views/portal_templates.xml b/addons/portal/views/portal_templates.xml index 40b595518ed035e74ef60c28652ea9afb74c54c2..3547c21dfe0aaa984708dd4575fa3d89e1ccf5ef 100644 --- a/addons/portal/views/portal_templates.xml +++ b/addons/portal/views/portal_templates.xml @@ -493,4 +493,13 @@ </body> </t> </template> + + <template id="portal_404" inherit_id="http_routing.404"> + <xpath expr="//p" position="after"> + <p>Maybe you were looking for one of these popular pages ?</p> + <ul> + <li><a href="/my">Documents</a></li> + </ul> + </xpath> + </template> </odoo> diff --git a/addons/sms/views/assets.xml b/addons/sms/views/assets.xml index ba7f5a5914f928ed16f84c6dc4eb9653b8280667..be507fa3f9290a339f631656a6b35365cac3ef63 100644 --- a/addons/sms/views/assets.xml +++ b/addons/sms/views/assets.xml @@ -16,7 +16,7 @@ <template id="qunit_suite" name="sms_widget_tests" inherit_id="web.qunit_suite"> <xpath expr="." position="inside"> - <script type="text/javascript" src="/sms/static/src/tests/sms_widget_test.js"></script> + <script type="text/javascript" src="/sms/static/tests/sms_widget_test.js"></script> </xpath> </template> </data> diff --git a/addons/test_website/tests/test_error.py b/addons/test_website/tests/test_error.py index 63dad4133f11b450786c0351a0f750113cee57cc..f4c9334ee9745a103f0344bb89113a5bb8c94ecd 100644 --- a/addons/test_website/tests/test_error.py +++ b/addons/test_website/tests/test_error.py @@ -5,6 +5,6 @@ from odoo.tools import mute_logger @odoo.tests.common.tagged('post_install', '-at_install') class TestWebsiteError(odoo.tests.HttpCase): - @mute_logger('odoo.addons.website.models.ir_http', 'odoo.http') + @mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http') def test_01_run_test(self): self.start_tour("/test_error_view", 'test_error_website') diff --git a/addons/test_website/tests/test_reset_views.py b/addons/test_website/tests/test_reset_views.py index b9bf38013632f92f6d3550595891b75d6451c849..a11e82824c354f174a02eaaf33b102263bfceb50 100644 --- a/addons/test_website/tests/test_reset_views.py +++ b/addons/test_website/tests/test_reset_views.py @@ -31,7 +31,7 @@ class TestWebsiteResetViews(odoo.tests.HttpCase): 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') + @mute_logger('odoo.addons.http_routing.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')]) @@ -40,7 +40,7 @@ class TestWebsiteResetViews(odoo.tests.HttpCase): 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') + @mute_logger('odoo.addons.http_routing.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 @@ -49,7 +49,7 @@ class TestWebsiteResetViews(odoo.tests.HttpCase): 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') + @mute_logger('odoo.addons.http_routing.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') @@ -60,7 +60,7 @@ class TestWebsiteResetViews(odoo.tests.HttpCase): 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') + @mute_logger('odoo.addons.http_routing.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') @@ -71,7 +71,7 @@ class TestWebsiteResetViews(odoo.tests.HttpCase): 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') + # @mute_logger('odoo.addons.http_routing.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 @@ -80,7 +80,7 @@ class TestWebsiteResetViews(odoo.tests.HttpCase): # 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') + @mute_logger('odoo.addons.http_routing.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 @@ -88,14 +88,14 @@ class TestWebsiteResetViews(odoo.tests.HttpCase): 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') + @mute_logger('odoo.addons.http_routing.models.ir_http') def test_07_reset_page_view_complete_flow(self): self.start_tour("/", 'test_reset_page_view_complete_flow_part1', login="admin") self.fix_it('/test_page_view') self.start_tour("/", 'test_reset_page_view_complete_flow_part2', login="admin") self.fix_it('/test_page_view') - @mute_logger('odoo.addons.website.models.ir_http') + @mute_logger('odoo.addons.http_routing.models.ir_http') def test_08_reset_specific_page_view_hard_mode(self): self.test_page_view = self.Website.viewref('test_website.test_page_view') total_views = self.View.search_count([('type', '=', 'qweb')]) diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 6d421357c90c3b3e3c8cd8219cbacb5698d49c2a..ef9a9433ce700429014fcbfa59bbdc13dbd00b97 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -2,7 +2,6 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import logging from lxml import etree -import traceback import os import unittest @@ -14,14 +13,12 @@ import werkzeug.utils from functools import partial import odoo -from odoo import api, models, registry +from odoo import api, models from odoo import SUPERUSER_ID from odoo.http import request -from odoo.tools import config 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 @@ -239,7 +236,6 @@ class Http(models.AbstractModel): 'deletable': True, 'main_object': mypage, }, mimetype=_guess_mimetype(ext)) - return False @classmethod def _serve_redirect(cls): @@ -269,109 +265,47 @@ class Http(models.AbstractModel): return False @classmethod - def _handle_exception(cls, exception): - code = 500 # default code - is_website_request = bool(getattr(request, 'is_frontend', False) and get_request_website()) - if not is_website_request: - # Don't touch non website requests exception handling - return super(Http, cls)._handle_exception(exception) - else: + def _get_exception_code_values(cls, exception): + code, values = super(Http, cls)._get_exception_code_values(exception) + if request.website.is_publisher() and isinstance(exception, werkzeug.exceptions.NotFound): + code = 'page_404' + values['path'] = request.httprequest.path[1:] + return (code, values) + + @classmethod + def _get_values_500_error(cls, env, values, exception): + View = env["ir.ui.view"] + values = super(Http, cls)._get_values_500_error(env, values, exception) + if 'qweb_exception' in values: try: - response = super(Http, cls)._handle_exception(exception) - - if isinstance(response, Exception): - exception = response - else: - # 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']: - raise e - exception = e - - values = dict( - exception=exception, - traceback=traceback.format_exc(), - ) - - # only except_orm exceptions contain a message - if isinstance(exception, odoo.exceptions.except_orm): - values['error_message'] = exception.name - code = 400 - - if isinstance(exception, werkzeug.exceptions.HTTPException): - if exception.code is None: - # Hand-crafted HTTPException likely coming from abort(), - # usually for a redirect response -> return it directly - return exception - else: - code = exception.code - - if isinstance(exception, odoo.exceptions.AccessError): - code = 403 - - if isinstance(exception, QWebException): - values.update(qweb_exception=exception) - - # retro compatibility to remove in 12.2 - exception.qweb = dict(message=exception.message, expression=exception.html) - - if type(exception.error) == odoo.exceptions.AccessError: - code = 403 - - values.update( - status_message=werkzeug.http.HTTP_STATUS_CODES[code], - status_code=code, - ) - - view_id = code - if request.website.is_publisher() and isinstance(exception, werkzeug.exceptions.NotFound): - view_id = 'page_404' - values['path'] = request.httprequest.path[1:] - - if not request.uid: - cls._auth_method_public() - - with registry(request.env.cr.dbname).cursor() as cr: - env = api.Environment(cr, request.uid, request.env.context) - if code == 500: - logger.error("500 Internal Server Error:\n\n%s", values['traceback']) - View = env["ir.ui.view"] - values['view'] = View - if 'qweb_exception' in values: - try: - # exception.name might be int, string - exception_template = int(exception.name) - except: - exception_template = exception.name - view = View._view_obj(exception_template) - if exception.html and exception.html in view.arch: - values['view'] = view - else: - # There might be 2 cases where the exception code can't be found - # in the view, either the error is in a child view or the code - # contains branding (<div t-att-data="request.browse('ok')"/>). - et = etree.fromstring(view.with_context(inherit_branding=False).read_combined(['arch'])['arch']) - node = et.xpath(exception.path) - line = node is not None and etree.tostring(node[0], encoding='unicode') - if line: - values['view'] = View._views_get(exception_template).filtered( - lambda v: line in v.arch - ) - values['view'] = values['view'] and values['view'][0] - - # Needed to show reset template on translated pages (`_prepare_qcontext` will set it for main lang) - values['editable'] = request.uid and request.website.is_publisher() - elif code == 403: - logger.warn("403 Forbidden:\n\n%s", values['traceback']) - elif code == 400: - logger.warn("400 Bad Request:\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') + # exception.name might be int, string + exception_template = int(exception.name) + except: + exception_template = exception.name + view = View._view_obj(exception_template) + if exception.html and exception.html in view.arch: + values['view'] = view + else: + # There might be 2 cases where the exception code can't be found + # in the view, either the error is in a child view or the code + # contains branding (<div t-att-data="request.browse('ok')"/>). + et = etree.fromstring(view.with_context(inherit_branding=False).read_combined(['arch'])['arch']) + node = et.xpath(exception.path) + line = node is not None and etree.tostring(node[0], encoding='unicode') + if line: + values['view'] = View._views_get(exception_template).filtered( + lambda v: line in v.arch + ) + values['view'] = values['view'] and values['view'][0] + # Needed to show reset template on translated pages (`_prepare_qcontext` will set it for main lang) + values['editable'] = request.uid and request.website.is_publisher() + return values + + @classmethod + def _get_error_html(cls, env, code, values): + if code == 'page_404': + return env['ir.ui.view'].render_template('website.%s' % code, values) + return super(Http, cls)._get_error_html(env, code, values) def binary_content(self, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='name', download=False, diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index d7a02f98437488efc01dae582a8d0f5f7061350c..30f3c8206ac09733a37ed6d066ab68de96f887ab 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -788,7 +788,7 @@ </template> <template id="page_404"> - <t t-call="website.404"> + <t t-call="http_routing.404"> <div class="container"> <div class="alert alert-info mt32"> <p>This page does not exist, but you can create it as you are administrator of this site.</p> @@ -800,256 +800,104 @@ </t> </template> -<template id="http_error"> - <t t-call="website.layout"> - <div id="wrap"> - <div class="oe_structure"> - <h1 class="container mt32"><t t-esc="status_code"/>: <t t-esc="status_message"/></h1> - </div> - <t t-if="editable or debug"> - <t t-call="website.http_error_debug"/> - </t> - </div> - </t> -</template> - -<template id="error_message"> - <p t-if="error_message"> - <strong>Error message:</strong> - <pre t-esc="error_message"/> - </p> -</template> - -<template id="http_error_debug"> - <div class="container accordion mb32 mt32" id="debug_infos"> - <div class="card" t-if="error_message"> - <h4 class="card-header"> - <a data-toggle="collapse" href="#error_main">Error</a> - </h4> - <div id="error_main" class="collapse show"> - <div class="card-body"> - <t t-call="website.error_message"/> - </div> - </div> - </div> - <div class="card" t-if="qweb_exception"> - <h4 class="card-header"> - <a data-toggle="collapse" href="#error_qweb">QWeb</a> - </h4> - <div id="error_qweb" class="collapse show"> - <div class="card-body"> - <p t-if="exception.message"> - <strong>Error message:</strong> - <pre t-esc="exception.message"/> - </p> - <p> - 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> - </div> - </div> - </div> - <div class="card" t-if="traceback"> - <h4 class="card-header"> - <a data-toggle="collapse" href="#error_traceback">Traceback</a> - </h4> - <div id="error_traceback" t-attf-class="collapse #{not error_message and not qweb_exception and 'show'}"> - <div class="card-body"> - <pre id="exception_traceback" t-esc="traceback"/> - </div> - </div> - </div> - </div> -</template> - -<template id="400"> - <t t-call="website.layout"> - <div id="wrap"> - <div class="container"> - <h1 class="mt-5">Something went wrong.</h1> - <p>Take a look at the error message below.</p> - </div> - <t t-if="editable or request.session.debug"> - <t t-call="website.http_error_debug"/> - </t> - <t t-else=""> - <div class="container"> - <t t-call="website.error_message"/> - </div> - </t> - </div> - </t> -</template> - -<template id="403"> - <t t-call="website.layout"> - <div id="wrap"> - <div class="container"> - <h1 class="mt-5">403: Forbidden</h1> - <p>The page you were looking for could not be authorized.</p> - </div> - <t t-if="editable or request.session.debug"> - <t t-call="website.http_error_debug"/> - </t> - <t t-else=""> - <div class="container"> - <t t-call="website.error_message"/> - </div> - </t> - </div> - </t> -</template> - -<template id="404"> - <t t-call="website.layout"> - <div id="wrap"> - <t t-raw="0"/> - <div class="oe_structure oe_empty"> - <div class="container"> - <h1 class="mt-5">404: Page not found!</h1> - <p> - The page you were looking for could not be found; it is possible you have - typed the address incorrectly, but it has most probably been removed due - to the recent website reorganisation. - </p> - <p>Maybe you were looking for one of these popular pages ?</p> - <ul> - <li><a href="/">Homepage</a></li> - <li><a href="/contactus">Contact Us</a></li> - </ul> - </div> - </div> - </div> - </t> -</template> - -<template id="500"> +<template id="qweb_500" inherit_id="http_routing.500"> <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> <!-- 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"/> - - <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"/> - <style> - html { - font-size: 14px; - } - </style> - - <t t-if='view'> - <script> - $(document).ready(function() { - var button = $('.reset_templates_button'); - button.click(function() { - $('#reset_templates_mode').val($(this).data('mode')); - 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').attr('action', '/website/reset_template'); - $('#reset_templates_form').trigger('submit'); - } else { - input.val('').focus(); - } - return false; - }); + <xpath expr="//script[last()]" position="before"> + <script type="text/javascript" src="/web/static/lib/bootstrap/js/modal.js"/> + </xpath> + <xpath expr="//style" position="after"> + <t t-if='view'> + <script> + $(document).ready(function() { + var button = $('.reset_templates_button'); + button.click(function() { + $('#reset_templates_mode').val($(this).data('mode')); + 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').attr('action', '/website/reset_template'); + $('#reset_templates_form').trigger('submit'); + } else { + input.val('').focus(); + } return false; }); + return false; }); - </script> - </t> - </head> - <body> - <div t-if="view" 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"> - <h4 class="modal-title">Reset templates</h4> - <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-12 col-form-label"> - <p>The selected templates will be reset to their factory settings.</p> - </label> - </div> - <div class="form-group row mb0"> - <label for="page-name" class="col-md-9 col-form-label"> - <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> + }); + </script> + </t> + </xpath> + <xpath expr="//div[@id='wrapwrap']" position="before"> + <div t-if="view" 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"> + <h4 class="modal-title">Reset templates</h4> + <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-12 col-form-label"> + <p>The selected templates will be reset to their factory settings.</p> + </label> + </div> + <div class="form-group row mb0"> + <label for="page-name" class="col-md-9 col-form-label"> + <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> - </main> - <footer class="modal-footer"> - <button type="button" class="btn" data-dismiss="modal" aria-label="Cancel">Cancel</button> - <input type="submit" value="Confirm" class="btn btn-primary"/> - </footer> - </div> - </form> - </div> - </div> - - <div id="wrapwrap"> - <header> - <div class="navbar navbar-expand-md navbar-light bg-light"> - <div class="container"> - <div class="collapse navbar-collapse navbar-top-collapse"> - <ul class="navbar-nav ml-auto" id="top_menu"> - <li class="nav-item"><a href="/" class="nav-link">Home</a></li> - <li class="nav-item"><a href="javascript: window.history.back()" class="nav-link">Back</a></li> - </ul> </div> - </div> - </div> - </header> - <main> - <div class="oe_structure"> - <h2 class="container mt32"><t t-esc="status_code"/>: <t t-esc="status_message"/></h2> + </main> + <footer class="modal-footer"> + <button type="button" class="btn" data-dismiss="modal" aria-label="Cancel">Cancel</button> + <input type="submit" value="Confirm" class="btn btn-primary"/> + </footer> </div> - <div class="container" t-if="view 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.name"/>.</p> - <p>If this error is caused by a change of yours in the templates, you have the possibility to reset the template to its <strong>factory settings</strong>.</p> - <form action="#" method="post" id="reset_templates_form"> - <ul> - <li> - <label> - <t t-esc="view.name"/> - </label> - </li> - </ul> - <input type="hidden" name="redirect" t-att-value="request.httprequest.path"/> - <input type="hidden" id="reset_templates_view_id" name="view_id" t-att-value="view.id"/> - <input type="hidden" id="reset_templates_mode" name="mode"/> - <button data-mode="soft" class="reset_templates_button btn btn-info">Restore previous version (soft reset).</button> - <button t-if="view.arch_fs" data-mode="hard" class="reset_templates_button btn btn-outline-danger">Reset to initial version (hard reset).</button> - </form> - </div> - </div> - - <t t-if="editable or debug"> - <t t-call="website.http_error_debug"/> - </t> - </main> + </form> </div> - </body> - </html> + </div> + </xpath> + <xpath expr="//div[@id='error_message']" position="after"> + <div class="container" t-if="view 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.name"/>.</p> + <p>If this error is caused by a change of yours in the templates, you have the possibility to reset the template to its <strong>factory settings</strong>.</p> + <form action="#" method="post" id="reset_templates_form"> + <ul> + <li> + <label> + <t t-esc="view.name"/> + </label> + </li> + </ul> + <input type="hidden" name="redirect" t-att-value="request.httprequest.path"/> + <input type="hidden" id="reset_templates_view_id" name="view_id" t-att-value="view.id"/> + <input type="hidden" id="reset_templates_mode" name="mode"/> + <button data-mode="soft" class="reset_templates_button btn btn-info">Restore previous version (soft reset).</button> + <button t-if="view.arch_fs" data-mode="hard" class="reset_templates_button btn btn-outline-danger">Reset to initial version (hard reset).</button> + </form> + </div> + </div> + </xpath> +</template> + +<template id="portal_404" inherit_id="portal.portal_404"> + <xpath expr="//li" position="before"> + <li><a href="/">Homepage</a></li> + </xpath> </template> <template id="robots">