[IMP] website: remove cache from website.page

The cache placed on the pages is no longer useful thanks to the use of
the new directive t-cache.

closes odoo/odoo#88276

Related: odoo/enterprise#27582
Related: odoo/documentation#2056
Related: odoo/upgrade#3451
Signed-off-by: Vincent Schippefilt (vsc) <vsc@odoo.com>
This commit is contained in:
Gorash
2022-06-03 16:40:40 +02:00
parent b0a2a41d78
commit 0452d0701f
20 changed files with 45 additions and 147 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
<odoo>
<template id="frontend_layout" name="Main Frontend Layout" inherit_id="web.frontend_layout">
<xpath expr="//div[@id='wrapwrap']" position="attributes">
<attribute name="t-attf-class" add="#{request.env['res.lang']._lang_get(request.env.lang).direction == 'rtl' and 'o_rtl' or ''}" separator=" "/>
<attribute name="t-attf-class" add="#{request.env['res.lang']._lang_get_direction(request.env.lang) == 'rtl' and 'o_rtl' or ''}" separator=" "/>
<attribute name="t-attf-class" add="#{'o_portal' if is_portal else ''}" separator=" "/>
</xpath>
<xpath expr="//div[@id='wrapwrap']/header/img" position="replace">
@@ -420,7 +420,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
])
def _test_url_open(self, url):
url += ('?' not in url and '?' or '') + '&nocache'
url += ('?' not in url and '?' or '') + '&debug=disable-t-cache'
return self.url_open(url)
@warmup
+1 -1
View File
@@ -28,7 +28,7 @@ You can use any truthy/falsy value from `str2bool` (eg: 'on', 'f'..)
Multiple debug modes can be activated simultaneously, separated with a
comma (eg: 'tests, assets').
"""
ALLOWED_DEBUG_MODES = ['', '1', 'assets', 'tests']
ALLOWED_DEBUG_MODES = ['', '1', 'assets', 'tests', 'disable-t-cache']
class Http(models.AbstractModel):
-1
View File
@@ -523,7 +523,6 @@
<field name="url">/contactus</field>
<field name="is_published">True</field>
<field name="view_id" ref="contactus"/>
<field name="cache_time">0</field>
<field name="track">True</field>
</record>
-36
View File
@@ -267,48 +267,12 @@ class Http(models.AbstractModel):
path += '?' + request.httprequest.query_string.decode('utf-8')
return request.redirect(path, code=301)
if page:
# prefetch all menus (it will prefetch website.page too)
menu_pages_ids = request.website._get_menu_page_ids()
page.browse([page.id] + menu_pages_ids).mapped('view_id.name')
request.website.menu_id
if page and (request.website.is_publisher() or page.is_visible):
need_to_cache = False
cache_key = page._get_cache_key(request)
if (
page.cache_time # cache > 0
and request.httprequest.method == "GET"
and request.env.user._is_public() # only cache for unlogged user
and 'nocache' not in request.httprequest.args # allow bypass cache / debug
and not request.session.debug
and len(cache_key) and cache_key[-1] is not None # nocache via expr
):
need_to_cache = True
try:
r = page._get_cache_response(cache_key)
if r['time'] + page.cache_time > time.time():
response = odoo.http.Response(r['content'], mimetype=r['contenttype'])
response._cached_template = r['template']
response._cached_page = page
return response
except KeyError:
pass
_, ext = os.path.splitext(req_page)
response = request.render(page.view_id.id, {
'deletable': True,
'main_object': page,
}, mimetype=_guess_mimetype(ext))
if need_to_cache and response.status_code == 200:
r = response.render()
page._set_cache_response(cache_key, {
'content': r,
'contenttype': response.headers['Content-Type'],
'time': time.time(),
'template': getattr(response, 'qcontext', {}).get('response_template')
})
return response
return False
-8
View File
@@ -105,14 +105,6 @@ class View(models.Model):
pages = view.page_ids
# Disable cache of page if we guess some dynamic content (form with csrf, ...)
if vals.get('arch'):
to_invalidate = pages.filtered(
lambda p: p.cache_time and not p._can_be_cached(vals['arch'])
)
to_invalidate and _logger.info('Disable cache for page %s', to_invalidate)
to_invalidate.cache_time = 0
# No need of COW if the view is already specific
if view.website_id:
super(View, view).write(vals)
-5
View File
@@ -176,11 +176,6 @@ class Website(models.Model):
def _get_menu_ids(self):
return self.env['website.menu'].search([('website_id', '=', self.id)]).ids
# self.env.uid for ir.rule groups on menu
@tools.ormcache('self.env.uid', 'self.id')
def _get_menu_page_ids(self):
return self.env['website.menu'].search([('website_id', '=', self.id)]).page_id.ids
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
+2 -4
View File
@@ -98,10 +98,8 @@ class Menu(models.Model):
return menus
def write(self, values):
res = super().write(values)
if 'website_id' in values or 'sequence' in values or 'page_id' in values:
self.clear_caches()
return res
self.clear_caches()
return super().write(values)
def unlink(self):
self.clear_caches()
-33
View File
@@ -31,9 +31,6 @@ class Page(models.Model):
is_homepage = fields.Boolean(compute='_compute_homepage', inverse='_set_homepage', string='Homepage')
is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible')
cache_time = fields.Integer(default=3600, help='Time to cache the page. (0 = no cache)')
cache_key_expr = fields.Char(help='Expression (tuple) to evaluate the cached key. \nE.g.: "(request.params.get("currency"), )"')
# Page options
header_overlay = fields.Boolean()
header_color = fields.Char()
@@ -221,36 +218,6 @@ class Page(models.Model):
self.ensure_one()
return self.view_id.get_website_meta()
@api.model
def _get_cached_blacklist(self):
return ('data-snippet="s_website_form"', 'data-no-page-cache=', )
def _can_be_cached(self, response):
""" return False if at least one blacklisted's word is present in content """
blacklist = self._get_cached_blacklist()
return not any(black in str(response) for black in blacklist)
def _get_cache_key(self, req):
# Always call me with super() AT THE END to have cache_key_expr appended as last element
# It is the only way for end user to not use cache via expr.
# E.g (None if 'token' in request.params else 1,) will bypass cache_time
cache_key = (req.website.id, req.lang, req.httprequest.path)
if self.cache_key_expr: # e.g. (request.geoip.get('country_code'),)
cache_key += safe_eval(self.cache_key_expr, {'request': req})
return cache_key
def _get_cache_response(self, cache_key):
""" Return the cached response corresponding to ``self`` and ``cache_key``.
Raise a KeyError if the item is not in cache.
"""
# HACK: we use the same LRU as ormcache to take advantage from its
# distributed invalidation, but we don't explicitly use ormcache
return self.pool._Registry__cache[('website.page', _cached_response, self.id, cache_key)]
def _set_cache_response(self, cache_key, response):
""" Put in cache the given response. """
self.pool._Registry__cache[('website.page', _cached_response, self.id, cache_key)] = response
@api.model
def _search_get_detail(self, website, order, options):
with_description = options['displayDescription']
+17 -10
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tools import mute_logger
from odoo.tests.common import HttpCase
EXTRA_REQUEST = 2 - 1
@@ -18,8 +19,10 @@ be added:
class UtilPerf(HttpCase):
def _get_url_hot_query(self, url, cache=True):
url += ('?' not in url and '?' or '')
if not cache:
url += '&nocache'
if cache:
url += '&debug='
else:
url += '&debug=disable-t-cache'
# ensure worker is in hot state
self.url_open(url)
@@ -31,6 +34,7 @@ class UtilPerf(HttpCase):
class TestStandardPerformance(UtilPerf):
@mute_logger('odoo.http')
def test_10_perf_sql_img_controller(self):
self.authenticate('demo', 'demo')
# not published user, get the not found image placeholder
@@ -39,6 +43,7 @@ class TestStandardPerformance(UtilPerf):
self.assertEqual(self._get_url_hot_query(url), 6)
self.assertEqual(self._get_url_hot_query(url, cache=False), 6)
@mute_logger('odoo.http')
def test_11_perf_sql_img_controller(self):
self.authenticate('demo', 'demo')
self.env['res.users'].sudo().browse(2).website_published = True
@@ -46,6 +51,7 @@ class TestStandardPerformance(UtilPerf):
self.assertEqual(self._get_url_hot_query(url), 6)
self.assertEqual(self._get_url_hot_query(url, cache=False), 6)
@mute_logger('odoo.http')
def test_20_perf_sql_img_controller_bis(self):
url = '/web/image/website/1/favicon'
self.assertEqual(self._get_url_hot_query(url), 5)
@@ -90,30 +96,30 @@ class TestWebsitePerformance(UtilPerf):
def test_10_perf_sql_queries_page(self):
# standard untracked website.page
self.assertEqual(self._get_url_hot_query(self.page.url), 6)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 11)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 10)
self.menu.unlink()
self.assertEqual(self._get_url_hot_query(self.page.url), 6)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 11)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 10)
def test_15_perf_sql_queries_page(self):
# standard tracked website.page
self.page.track = True
self.assertEqual(self._get_url_hot_query(self.page.url), 14)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 19)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 18)
self.menu.unlink()
self.assertEqual(self._get_url_hot_query(self.page.url), 14)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 19)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 18)
def test_20_perf_sql_queries_homepage(self):
# homepage "/" has its own controller
self.assertEqual(self._get_url_hot_query('/'), 14)
self.assertEqual(self._get_url_hot_query('/', cache=False), 18)
self.assertEqual(self._get_url_hot_query('/', cache=False), 16)
def test_30_perf_sql_queries_page_no_layout(self):
# website.page with no call to layout templates
self.page.arch = '<div>I am a blank page</div>'
self.assertEqual(self._get_url_hot_query(self.page.url), 6)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 7)
self.assertEqual(self._get_url_hot_query(self.page.url), 5)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 5)
def test_40_perf_sql_queries_page_multi_level_menu(self):
# menu structure should not impact SQL requests
@@ -132,8 +138,9 @@ class TestWebsitePerformance(UtilPerf):
menu_aa.parent_id = menu_a
self.assertEqual(self._get_url_hot_query(self.page.url), 6)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 11)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 10)
@mute_logger('odoo.http')
def test_50_perf_sql_web_assets(self):
# assets route /web/assets/..
self.url_open('/') # create assets attachments
+5 -1
View File
@@ -51,7 +51,11 @@
'data-oe-company-name': res_company.name,
})"/>
</xpath>
<xpath expr="//head" position="before">
<xpath expr="//head" position="attributes">
<attribute name="t-cache">main_object if main_object and main_object._name == 'website.page' and not (editable or translatable) else None</attribute>
</xpath>
<xpath expr="//head/*[1]" position="before">
<t t-if="not title">
<t t-if="not additional_title and main_object and 'name' in main_object">
<t t-set="additional_title" t-value="main_object.name"/>
-2
View File
@@ -119,8 +119,6 @@
<field name="website_indexed"/>
<field name="is_published"/>
<field name="date_publish"/>
<field name="cache_time" groups="base.group_no_one"/>
<field name="cache_key_expr" groups="base.group_no_one"/>
</group>
</group>
<label for="menu_ids" string="Related Menu Items"/>
@@ -13,8 +13,8 @@ class TestBlogPerformance(UtilPerf):
self.env['website'].search([]).channel_id = False
def test_10_perf_sql_blog_standard_data(self):
self.assertEqual(self._get_url_hot_query('/blog'), 14)
self.assertEqual(self._get_url_hot_query('/blog', cache=False), 14)
self.assertEqual(self._get_url_hot_query('/blog'), 11)
self.assertEqual(self._get_url_hot_query('/blog', cache=False), 27)
def test_20_perf_sql_blog_bigger_data_scaling(self):
BlogPost = self.env['blog.post']
@@ -26,10 +26,10 @@ class TestBlogPerformance(UtilPerf):
for blog_post in blog_posts:
blog_post.tag_ids += blog_tags
blog_tags = blog_tags[:-1]
self.assertEqual(self._get_url_hot_query('/blog'), 14)
self.assertEqual(self._get_url_hot_query('/blog', cache=False), 14)
self.assertEqual(self._get_url_hot_query(blog_post[0].website_url), 28)
self.assertEqual(self._get_url_hot_query(blog_post[0].website_url, cache=False), 28)
self.assertEqual(self._get_url_hot_query('/blog'), 11)
self.assertEqual(self._get_url_hot_query('/blog', cache=False), 34)
self.assertEqual(self._get_url_hot_query(blog_post[0].website_url), 26)
self.assertEqual(self._get_url_hot_query(blog_post[0].website_url, cache=False), 29)
def test_30_perf_sql_blog_bigger_data_scaling(self):
BlogPost = self.env['blog.post']
@@ -42,6 +42,6 @@ class TestBlogPerformance(UtilPerf):
blog_post.write({'tag_ids': [[6, 0, random.choices(blog_tags.ids, k=random.randint(0, len(blog_tags)))]]})
self.assertLessEqual(self._get_url_hot_query('/blog'), 29)
self.assertLessEqual(self._get_url_hot_query('/blog', cache=False), 28)
self.assertLessEqual(self._get_url_hot_query('/blog', cache=False), 71)
self.assertLessEqual(self._get_url_hot_query(blog_post[0].website_url), 34)
self.assertLessEqual(self._get_url_hot_query(blog_post[0].website_url, cache=False), 33)
@@ -5,4 +5,3 @@ from . import account_payment
from . import payment_acquirer
from . import payment_transaction
from . import res_config_settings
from . import website_page
@@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class Page(models.Model):
_inherit = 'website.page'
@api.model
def _get_cached_blacklist(self):
return super()._get_cached_blacklist() + (
# Contains a form with a dynamically added CSRF token
'data-snippet="s_donation"',
)
+4 -4
View File
@@ -332,7 +332,7 @@ class WebsiteSale(http.Controller):
categs_domain.append(('id', 'in', search_categories.ids))
else:
search_categories = Category
categs = Category.search(categs_domain)
categs = lazy(lambda: Category.search(categs_domain))
if category:
url = "/shop/category/%s" % slug(category)
@@ -344,12 +344,12 @@ class WebsiteSale(http.Controller):
ProductAttribute = request.env['product.attribute']
if products:
# get all products without limit
attributes = ProductAttribute.search([
attributes = lazy(lambda: ProductAttribute.search([
('product_tmpl_ids', 'in', search_product.ids),
('visibility', '=', 'visible'),
])
]))
else:
attributes = ProductAttribute.browse(attributes_ids)
attributes = lazy(lambda: ProductAttribute.browse(attributes_ids))
layout_mode = request.session.get('website_sale_shop_layout_mode')
if not layout_mode:
-1
View File
@@ -22,6 +22,5 @@ from . import sale_order
from . import sale_order_line
from . import website
from . import website_base_unit
from . import website_page
from . import website_snippet_filter
from . import website_visitor
+3 -2
View File
@@ -273,13 +273,12 @@ class Website(models.Model):
self = self.with_company(self.company_id)
SaleOrder = self.env['sale.order'].sudo()
partner_sudo = self.env.user.partner_id
sale_order_id = request.session.get('sale_order_id')
if sale_order_id:
sale_order_sudo = SaleOrder.browse(sale_order_id).exists()
elif not self.env.user._is_public():
sale_order_sudo = partner_sudo.last_website_so_id
sale_order_sudo = self.env.user.partner_id.last_website_so_id
if sale_order_sudo:
available_pricelists = self.get_pricelist_available()
if sale_order_sudo.pricelist_id not in available_pricelists:
@@ -309,6 +308,8 @@ class Website(models.Model):
# Only set when neeeded
pricelist_id = False
partner_sudo = self.env.user.partner_id
# cart creation was requested
if not sale_order_sudo:
so_data = self._prepare_sale_order_values(partner_sudo)
@@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.http import request
class WabsitePage(models.AbstractModel):
_inherit = 'website.page'
def _get_cache_key(self, req):
cart = request.website.sale_get_order()
cache_key = (cart and cart.cart_quantity or 0,)
cache_key += super()._get_cache_key(req)
return cache_key
+4
View File
@@ -201,6 +201,10 @@ class Lang(models.Model):
def _lang_get_id(self, code):
return self.with_context(active_test=True).search([('code', '=', code)]).id
@tools.ormcache('code')
def _lang_get_direction(self, code):
return self.with_context(active_test=True).search([('code', '=', code)]).direction
@tools.ormcache('url_code')
def _lang_get_code(self, url_code):
return self.with_context(active_test=True).search([('url_code', '=', url_code)]).code or url_code