diff --git a/addons/http_routing/models/ir_http.py b/addons/http_routing/models/ir_http.py
index 5469310228f..c0fd4a46a19 100644
--- a/addons/http_routing/models/ir_http.py
+++ b/addons/http_routing/models/ir_http.py
@@ -411,7 +411,7 @@ class IrHttp(models.AbstractModel):
lang = preferred_lang or cls._get_default_lang()
request.lang = lang
- context['lang'] = lang.code
+ context['lang'] = lang._get_cached('code')
# bind modified context
request.context = context
diff --git a/addons/test_website/controllers/main.py b/addons/test_website/controllers/main.py
index 1973e363653..1e8ed2df24f 100644
--- a/addons/test_website/controllers/main.py
+++ b/addons/test_website/controllers/main.py
@@ -119,3 +119,9 @@ class WebsiteTest(Home):
@http.route(['/get_post_nomultilang'], type='http', auth="public", methods=['GET', 'POST'], website=True, multilang=False, sitemap=False)
def get_post_method_no_multilang(self, **kw):
return request.make_response('get_post_nomultilang')
+
+ # Test Perfs
+
+ @http.route(['/empty_controller_test'], type='http', auth='public', website=True, multilang=False, sitemap=False)
+ def empty_controller_test(self, **kw):
+ return 'Basic Controller Content'
diff --git a/addons/test_website/tests/__init__.py b/addons/test_website/tests/__init__.py
index b7044f07875..08a794bfaab 100644
--- a/addons/test_website/tests/__init__.py
+++ b/addons/test_website/tests/__init__.py
@@ -2,9 +2,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_controller_args
-from . import test_is_multilang
-from . import test_reset_views
from . import test_error
-from . import test_views_during_module_operation
+from . import test_is_multilang
from . import test_multi_company
+from . import test_performance
+from . import test_reset_views
from . import test_session
+from . import test_views_during_module_operation
diff --git a/addons/test_website/tests/test_performance.py b/addons/test_website/tests/test_performance.py
new file mode 100644
index 00000000000..cd06dfda860
--- /dev/null
+++ b/addons/test_website/tests/test_performance.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.addons.website.tests.test_performance import UtilPerf
+
+
+class TestPerformance(UtilPerf):
+ def test_10_perf_sql_website_controller_minimalist(self):
+ url = '/empty_controller_test'
+ self.assertEqual(self._get_url_hot_query(url), 3)
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 8019abba5c4..49cc8160a06 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -68,6 +68,9 @@ class Website(Home):
@http.route('/', type='http', auth="public", website=True, sitemap=True)
def index(self, **kw):
+ # prefetch all menus (it will prefetch website.page too)
+ top_menu = request.website.menu_id
+
homepage = request.website.homepage_id
if homepage and (homepage.sudo().is_visible or request.env.user.has_group('base.group_user')) and homepage.url != '/':
return request.env['ir.http'].reroute(homepage.url)
@@ -76,7 +79,6 @@ class Website(Home):
if website_page:
return website_page
else:
- top_menu = request.website.menu_id
first_menu = top_menu and top_menu.child_id and top_menu.child_id.filtered(lambda menu: menu.is_visible)
if first_menu and first_menu[0].url not in ('/', '', '#') and (not (first_menu[0].url.startswith(('/?', '/#', ' ')))):
return request.redirect(first_menu[0].url)
@@ -485,7 +487,10 @@ class WebsiteBinary(http.Controller):
kw['unique'] = unique
return Binary().content_image(**kw)
+ # if not icon provided in DOM, browser tries to access /favicon.ico, eg when opening an order pdf
@http.route(['/favicon.ico'], type='http', auth='public', website=True, multilang=False, sitemap=False)
def favicon(self, **kw):
- # when opening a pdf in chrome, chrome tries to open the default favicon url
- return self.content_image(model='website', id=str(request.website.id), field='favicon', **kw)
+ website = request.website
+ response = request.redirect(website.image_url(website, 'favicon'), code=301)
+ response.headers['Cache-Control'] = 'public, max-age=%s' % (365 * 24 * 60)
+ return response
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 80ec1d9f0c7..ae68a21e174 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -132,8 +132,8 @@ class Http(models.AbstractModel):
if not request.session.uid:
env = api.Environment(request.cr, SUPERUSER_ID, request.context)
website = env['website'].get_current_website()
- if website and website.user_id:
- request.uid = website.user_id.id
+ request.uid = website and website._get_cached('user_id')
+
if not request.uid:
super(Http, cls)._auth_method_public()
@@ -199,8 +199,9 @@ class Http(models.AbstractModel):
# context (eg: /shop), and it's not going to propagate to the global context of the tab
# If the company of the website is not in the allowed companies of the user, set the main
# company of the user.
- if request.website.company_id in request.env.user.company_ids:
- context['allowed_company_ids'] = request.website.company_id.ids
+ website_company_id = request.website._get_cached('company_id')
+ if website_company_id in request.env.user.company_ids.ids:
+ context['allowed_company_ids'] = [website_company_id]
else:
context['allowed_company_ids'] = request.env.user.company_id.ids
@@ -222,7 +223,7 @@ class Http(models.AbstractModel):
@classmethod
def _get_default_lang(cls):
if getattr(request, 'website', False):
- return request.website.default_lang_id
+ return request.env['res.lang'].browse(request.website._get_cached('default_lang_id'))
return super(Http, cls)._get_default_lang()
@classmethod
@@ -239,9 +240,12 @@ class Http(models.AbstractModel):
published_domain = page_domain
# specific page first
page = request.env['website.page'].sudo().search(published_domain, order='website_id asc', limit=1)
+ if page:
+ # prefetch all menus (it will prefetch website.page too)
+ request.website.menu_id
if page and (request.website.is_publisher() or page.is_visible):
_, ext = os.path.splitext(req_page)
- return request.render(page.get_view_identifier(), {
+ return request.render(page.view_id.id, {
'deletable': True,
'main_object': page,
}, mimetype=_guess_mimetype(ext))
@@ -363,7 +367,7 @@ class Http(models.AbstractModel):
if request.env.user.has_group('website.group_website_publisher'):
session_info.update({
'website_id': request.website.id,
- 'website_company_id': request.website.company_id.id,
+ 'website_company_id': request.website._get_cached('company_id'),
})
return session_info
diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py
index 08152d59d6f..bc4a24205af 100644
--- a/addons/website/models/ir_ui_view.py
+++ b/addons/website/models/ir_ui_view.py
@@ -450,12 +450,9 @@ class View(models.Model):
website=request.website,
url_for=url_for,
res_company=request.website.company_id.sudo(),
- default_lang_code=request.env['ir.http']._get_default_lang().code,
languages=request.env['res.lang'].get_available(),
translatable=translatable,
editable=editable,
- # retrocompatibility, remove me in master
- menu_data={'children': []} if request.website.is_user() else None,
))
return qcontext
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 610f198d4d6..d1abd719fa9 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -143,7 +143,8 @@ class Website(models.Model):
# don't add child menu if parent is forbidden
if menu.parent_id and menu.parent_id in menus:
menu.parent_id._cache['child_id'] += (menu.id,)
-
+ # prefetch every website.page and ir.ui.view at once
+ menus.mapped('is_visible')
website.menu_id = menus and menus.filtered(lambda m: not m.parent_id)[0].id or False
# self.env.uid for ir.rule groups on menu
@@ -263,6 +264,8 @@ class Website(models.Model):
# Bootstrap default menu hierarchy, create a new minimalist one if no default
default_menu = self.env.ref('website.main_menu')
self.copy_menu_hierarchy(default_menu)
+ home_menu = self.env['website.menu'].search([('website_id', '=', self.id), ('url', '=', '/')])
+ home_menu.page_id = self.homepage_id
def copy_menu_hierarchy(self, top_menu):
def copy_menu(menu, t_menu):
@@ -946,6 +949,18 @@ class Website(models.Model):
# if the current URL is indeed canonical or not.
return current_url == canonical_url
+ @tools.ormcache('self.id')
+ def _get_cached_values(self):
+ self.ensure_one()
+ return {
+ 'user_id': self.user_id.id,
+ 'company_id': self.company_id.id,
+ 'default_lang_id': self.default_lang_id.id,
+ }
+
+ def _get_cached(self, field):
+ return self._get_cached_values()[field]
+
class BaseModel(models.AbstractModel):
_inherit = 'base'
diff --git a/addons/website/models/website_page.py b/addons/website/models/website_page.py
index 392f4899257..e287bc22b0c 100644
--- a/addons/website/models/website_page.py
+++ b/addons/website/models/website_page.py
@@ -58,10 +58,6 @@ class Page(models.Model):
return most_specific_page == page_to_test
- def get_view_identifier(self):
- """ Get identifier of this page view that may be used to render it """
- return self.view_id.id
-
def get_page_properties(self):
self.ensure_one()
res = self.read([
diff --git a/addons/website/tests/test_performance.py b/addons/website/tests/test_performance.py
index f3c4834a0a7..2e02135116a 100644
--- a/addons/website/tests/test_performance.py
+++ b/addons/website/tests/test_performance.py
@@ -17,24 +17,7 @@ EXTRA_REQUEST = 5
"""
-class TestWebsitePerformance(HttpCase):
-
- def setUp(self):
- super().setUp()
-
- self.page = self.env['website.page'].create({
- 'url': '/sql_page',
- 'name': 'Page Test SQL Perf',
- 'type': 'qweb',
- 'arch': '