[IMP] website: perf - cache public page
closes odoo/odoo#56331 Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import logging
|
||||
from lxml import etree
|
||||
import os
|
||||
import unittest
|
||||
import time
|
||||
|
||||
import pytz
|
||||
import werkzeug
|
||||
@@ -18,6 +19,7 @@ from odoo import registry, SUPERUSER_ID
|
||||
from odoo.http import request
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
from odoo.osv.expression import FALSE_DOMAIN
|
||||
from odoo.tools.lru import LRU
|
||||
|
||||
from odoo.addons.http_routing.models.ir_http import ModelConverter, _guess_mimetype
|
||||
from odoo.addons.portal.controllers.portal import _build_url_w_params
|
||||
@@ -68,6 +70,7 @@ class Http(models.AbstractModel):
|
||||
@classmethod
|
||||
def clear_caches(cls):
|
||||
super(Http, cls)._clear_routing_map()
|
||||
cls.clear_cached_pages()
|
||||
return super(Http, cls).clear_caches()
|
||||
|
||||
@classmethod
|
||||
@@ -139,15 +142,23 @@ class Http(models.AbstractModel):
|
||||
|
||||
@classmethod
|
||||
def _register_website_track(cls, response):
|
||||
if getattr(response, 'status_code', 0) != 200 or not hasattr(response, 'qcontext'):
|
||||
if getattr(response, 'status_code', 0) != 200:
|
||||
return False
|
||||
main_object = response.qcontext.get('main_object')
|
||||
website_page = getattr(main_object, '_name', False) == 'website.page' and main_object
|
||||
template = response.qcontext.get('response_template')
|
||||
|
||||
template = False
|
||||
if hasattr(response, 'qcontext'): # classic response
|
||||
main_object = response.qcontext.get('main_object')
|
||||
website_page = getattr(main_object, '_name', False) == 'website.page' and main_object
|
||||
template = response.qcontext.get('response_template')
|
||||
elif hasattr(response, '_cached_page'):
|
||||
website_page, template = response._cached_page, response._cached_template
|
||||
|
||||
view = template and request.env['website'].get_template(template)
|
||||
if view and view.track:
|
||||
request.env['website.visitor']._handle_webpage_dispatch(response, website_page)
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _dispatch(cls):
|
||||
"""
|
||||
@@ -232,6 +243,10 @@ class Http(models.AbstractModel):
|
||||
installed = request.registry._init_modules | set(odoo.conf.server_wide_modules)
|
||||
return mods + [mod for mod in installed if mod.startswith('website')]
|
||||
|
||||
@classmethod
|
||||
def clear_cached_pages(cls):
|
||||
cls._cached_pages = LRU(1024)
|
||||
|
||||
@classmethod
|
||||
def _serve_page(cls):
|
||||
req_page = request.httprequest.path
|
||||
@@ -240,15 +255,54 @@ 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):
|
||||
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.params # allow bypass cache / debug
|
||||
and len(cache_key) and cache_key[-1] is not None # nocache via expr
|
||||
):
|
||||
need_to_cache = True
|
||||
if not hasattr(cls, '_cached_pages'):
|
||||
cls.clear_cached_pages() # init LRU
|
||||
|
||||
# use cached version
|
||||
try:
|
||||
r = cls._cached_pages[cache_key]
|
||||
if r['time'] + page.cache_time > time.time():
|
||||
response = werkzeug.Response(r['content'], mimetype=r['contenttype'])
|
||||
response._cached_template = r['template']
|
||||
response._cached_page = page
|
||||
return response
|
||||
else:
|
||||
cls._cached_pages[cache_key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
_, ext = os.path.splitext(req_page)
|
||||
return request.render(page.view_id.id, {
|
||||
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()
|
||||
cls._cached_pages[cache_key] = {
|
||||
'content': r,
|
||||
'contenttype': response.headers['Content-Type'],
|
||||
'time': time.time(),
|
||||
'template': getattr(response, 'qcontext', {}).get('response_template')
|
||||
}
|
||||
|
||||
return response
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
from odoo.addons.http_routing.models.ir_http import slugify
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
class Page(models.Model):
|
||||
@@ -21,6 +22,9 @@ 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()
|
||||
@@ -202,3 +206,12 @@ class Page(models.Model):
|
||||
def get_website_meta(self):
|
||||
self.ensure_one()
|
||||
return self.view_id.get_website_meta()
|
||||
|
||||
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.session.geoip.get('country_code'),)
|
||||
cache_key += safe_eval(self.cache_key_expr, {'request': req})
|
||||
return cache_key
|
||||
|
||||
@@ -19,6 +19,8 @@ EXTRA_REQUEST = 5
|
||||
|
||||
class UtilPerf(HttpCase):
|
||||
def _get_url_hot_query(self, url):
|
||||
url += ('?' not in url and '?' or '') + '&nocache'
|
||||
|
||||
# ensure worker is in hot state
|
||||
self.url_open(url)
|
||||
self.url_open(url)
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
<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"/>
|
||||
|
||||
@@ -15,4 +15,5 @@ from . import res_config_settings
|
||||
from . import digest
|
||||
from . import res_company
|
||||
from . import product_attribute
|
||||
from . import website_page
|
||||
from . import website_visitor
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- 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
|
||||
Reference in New Issue
Block a user