[FIX] website: remove force prefetch for translate fields

Issue
-----
When website is installed, the rendering of template uses a side effect
of the ORM cache (cache shared between sudoed env vs non-sudoed env) and
the fields prefetching feature to work correctly.

The `self.visibility` in (`_handle_visibility`, website/ir_ui_view.py)
is done in sudo mode, then it will fetch all prefetchable fields and put
them in the cache (that will be read in non-sudo mode in the render of
the template).  Another example of issue related to this:
https://github.com/odoo/odoo/pull/83341.

Because of this, the fields of mixin `website.seo.metadata` were forced
to be prefetchable (the default for translate is to be not prefetchable
since https://github.com/odoo/odoo/pull/82896), which causes a useless
LEFT JOIN on "ir_translation" in most of business flow.

Fix
---
Remove the `prefetch=True` on mixin fields, and add a extra read to fill
the cache in case of website rendering.  It also allows to read these
fields at the same time.

Part-of: odoo/odoo#85220
This commit is contained in:
Rémy Voet (ryv)
2022-03-03 11:03:55 +00:00
parent ce1dc0c404
commit d2b68d186e
7 changed files with 47 additions and 43 deletions
@@ -70,7 +70,7 @@ class TestCrmPerformance(CrmPerformanceCase):
country_be = self.env.ref('base.be')
lang_be = self.env['res.lang']._lang_get('fr_BE')
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=188): # tcf only: 173 - com runbot: 174/175
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=190): # tcf only: 175 - com runbot: 176/177
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['crm.lead']) as lead_form:
lead_form.country_id = country_be
@@ -89,7 +89,7 @@ class TestCrmPerformance(CrmPerformanceCase):
@warmup
def test_lead_create_form_partner(self):
""" Test a single lead creation using Form with a partner """
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=193): # tcf only: 178 - com runbot: 179/180
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=195): # tcf only: 180 - com runbot: 181/182
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['crm.lead']) as lead_form:
lead_form.partner_id = self.partners[0]
@@ -52,7 +52,7 @@ class TestEventPerformance(EventPerformanceCase):
batch_size = 20
# simple without type involved + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=5488): # tef only: 5062 (5058) - com runbot: 5062 - ent runbot 5482-5487
with freeze_time(self.reference_now), self.assertQueryCount(event_user=5495): # tef only: 5069 (5065) - com runbot: 5069 - ent runbot 5489-5494
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -89,7 +89,7 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=5601): # tef only: 5171 - com runbot: 5175 - ent runbot 5595-5600
with freeze_time(self.reference_now), self.assertQueryCount(event_user=5608): # tef only: 5178 - com runbot: 5182 - ent runbot 5602-5607
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -107,7 +107,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# no type, no website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=226): # tef only: 176 - com runbot: 176
with freeze_time(self.reference_now), self.assertQueryCount(event_user=234): # tef only: 184 - com runbot: 184
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
@@ -125,7 +125,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# no type, website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=735): # tef only: 622 - com runbot: 623
with freeze_time(self.reference_now), self.assertQueryCount(event_user=750): # tef only: 637 - com runbot: 638
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
@@ -144,7 +144,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# type and website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=770): # tef only: 660 - com runbot: 663
with freeze_time(self.reference_now), self.assertQueryCount(event_user=783): # tef only: 673 - com runbot: 676
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
@@ -172,7 +172,7 @@ class TestEventPerformance(EventPerformanceCase):
def test_event_create_single_notype_website(self):
""" Test a single event creation """
# simple without type involved + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=377): # tef only: 351 (346) - com runbot: 350 - ent runbot 376
with freeze_time(self.reference_now), self.assertQueryCount(event_user=384): # tef only: 358 (353) - com runbot: 357 - ent runbot 383
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
@@ -203,7 +203,7 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=413): # tef only: 388 (382) - com runbot: 386 - ent runbot 412
with freeze_time(self.reference_now), self.assertQueryCount(event_user=420): # tef only: 395 (389) - com runbot: 393 - ent runbot 419
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
@@ -225,7 +225,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
"""
event = self.env['event.event'].browse(self.test_event.ids)
with freeze_time(self.reference_now), self.assertQueryCount(event_user=720): # tef only: 673 - com runbot 716 - ent runbot 719
with freeze_time(self.reference_now), self.assertQueryCount(event_user=721): # tef only: 674 - com runbot 717 - ent runbot 720
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
@@ -249,7 +249,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
"""
event = self.env['event.event'].browse(self.test_event.ids)
with freeze_time(self.reference_now), self.assertQueryCount(event_user=244): # tef only: 200 - com runbot 239 - ent runbot 243
with freeze_time(self.reference_now), self.assertQueryCount(event_user=245): # tef only: 201 - com runbot 240 - ent runbot 245
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
@@ -271,7 +271,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
form like) """
event = self.env['event.event'].browse(self.test_event.ids)
with freeze_time(self.reference_now), self.assertQueryCount(event_user=730): # tef only: 684 - com runbot 727
with freeze_time(self.reference_now), self.assertQueryCount(event_user=731): # tef only: 685 - com runbot 728
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
@@ -292,7 +292,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
""" Test a single registration creation using Form """
event = self.env['event.event'].browse(self.test_event.ids)
with freeze_time(self.reference_now), self.assertQueryCount(event_user=229): # tef only: 209 - com runbot 215
with freeze_time(self.reference_now), self.assertQueryCount(event_user=230): # tef only: 210 - com runbot 216
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.registration']) as reg_form:
reg_form.event_id = event
@@ -308,7 +308,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
""" Test a single registration creation using Form """
event = self.env['event.event'].browse(self.test_event.ids)
with freeze_time(self.reference_now), self.assertQueryCount(event_user=232): # tef only: 212 - com runbot 217
with freeze_time(self.reference_now), self.assertQueryCount(event_user=233): # tef only: 213 - com runbot 218
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.registration']) as reg_form:
reg_form.event_id = event
@@ -321,7 +321,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
""" Test a single registration creation using Form """
event = self.env['event.event'].browse(self.test_event.ids)
with freeze_time(self.reference_now), self.assertQueryCount(event_user=141): # tef only: 123 - com runbot 125
with freeze_time(self.reference_now), self.assertQueryCount(event_user=142): # tef only: 124 - com runbot 126
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.registration'].with_context(event_lead_rule_skip=True)) as reg_form:
reg_form.event_id = event
@@ -335,7 +335,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
event = self.env['event.event'].browse(self.test_event.ids)
# simple customer data
with freeze_time(self.reference_now), self.assertQueryCount(event_user=141): # tef only: 134 - com runbot 139
with freeze_time(self.reference_now), self.assertQueryCount(event_user=142): # tef only: 135 - com runbot 140
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = dict(
self.customer_data[0],
@@ -349,7 +349,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
event = self.env['event.event'].browse(self.test_event.ids)
# partner-based customer
with freeze_time(self.reference_now), self.assertQueryCount(event_user=148): # tef only: 142 - com runbot 147
with freeze_time(self.reference_now), self.assertQueryCount(event_user=149): # tef only: 143 - com runbot 148
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = {
'event_id': event.id,
@@ -364,7 +364,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
event = self.env['event.event'].browse(self.test_event.ids)
# partner-based customer
with freeze_time(self.reference_now), self.assertQueryCount(event_user=59): # tef only: 55 - com runbot 57
with freeze_time(self.reference_now), self.assertQueryCount(event_user=60): # tef only: 56 - com runbot 58
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = {
'event_id': event.id,
@@ -379,7 +379,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
event = self.env['event.event'].browse(self.test_event.ids)
# website customer data
with freeze_time(self.reference_now), self.assertQueryCount(event_user=150): # tef only: 141 - com runbot 146
with freeze_time(self.reference_now), self.assertQueryCount(event_user=151): # tef only: 142 - com runbot 147
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = dict(
self.website_customer_data[0],
@@ -428,7 +428,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate('user_eventmanager', 'user_eventmanager')
with self.assertQueryCount(default=65): # tef only: 64 (+1 ent)
with self.assertQueryCount(default=69): # tef only: 68 (+1 ent)
self._test_url_open('/event/%i' % self.test_event.id)
@warmup
@@ -436,7 +436,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate(None, None)
with self.assertQueryCount(default=43): # tef only: 43
with self.assertQueryCount(default=47): # tef only: 47
self._test_url_open('/event/%i' % self.test_event.id)
@warmup
@@ -444,7 +444,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate('user_eventmanager', 'user_eventmanager')
with self.assertQueryCount(default=51): # tef only: 50 (+1 ent)
with self.assertQueryCount(default=53): # tef only: 52 (+1 ent)
self._test_url_open('/event')
@warmup
@@ -452,7 +452,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate(None, None)
with self.assertQueryCount(default=32): # tef only: 32
with self.assertQueryCount(default=34): # tef only: 34
self._test_url_open('/event')
# @warmup
+1 -1
View File
@@ -658,7 +658,7 @@ class Website(Home):
fields.extend(['website_indexed', 'website_id'])
record = request.env[res_model].browse(res_id)
res = record._read_format(fields)[0]
res = record.read(fields)[0]
res['has_social_default_image'] = request.website.has_social_default_image
if res_model not in ('website.page', 'ir.ui.view') and 'seo_name' in record: # allow custom slugify
+5 -1
View File
@@ -389,7 +389,7 @@ class View(models.Model):
(request.website.is_public_user() or self.id not in request.session.get('views_unlock', [])):
pwd = request.params.get('visibility_password')
if pwd and self.env.user._crypt_context().verify(
pwd, self.sudo().visibility_password):
pwd, self.visibility_password):
request.session.setdefault('views_unlock', list()).append(self.id)
else:
error = werkzeug.exceptions.Forbidden('website_visibility_password_required')
@@ -469,6 +469,10 @@ class View(models.Model):
editable=editable,
))
# fetch non-prefetchable fields of the mixin SeoMetadata, as they
# will be read later in non-sudo mode
self.sudo().read(['website_meta_title', 'website_meta_description', 'website_meta_keywords'])
return qcontext
@api.model
+3 -3
View File
@@ -23,9 +23,9 @@ class SeoMetadata(models.AbstractModel):
_description = 'SEO metadata'
is_seo_optimized = fields.Boolean("SEO optimized", compute='_compute_is_seo_optimized')
website_meta_title = fields.Char("Website meta title", translate=True, prefetch=True)
website_meta_description = fields.Text("Website meta description", translate=True, prefetch=True)
website_meta_keywords = fields.Char("Website meta keywords", translate=True, prefetch=True)
website_meta_title = fields.Char("Website meta title", translate=True)
website_meta_description = fields.Text("Website meta description", translate=True)
website_meta_keywords = fields.Char("Website meta keywords", translate=True)
website_meta_og_img = fields.Char("Website opengraph image")
seo_name = fields.Char("Seo name", translate=True, prefetch=True)
+7 -7
View File
@@ -93,30 +93,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), 5)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 8)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 9)
self.menu.unlink()
self.assertEqual(self._get_url_hot_query(self.page.url), 5)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 8)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 9)
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), 13)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 16)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 17)
self.menu.unlink()
self.assertEqual(self._get_url_hot_query(self.page.url), 13)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 16)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 17)
def test_20_perf_sql_queries_homepage(self):
# homepage "/" has its own controller
self.assertEqual(self._get_url_hot_query('/'), 12)
self.assertEqual(self._get_url_hot_query('/', cache=False), 15)
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), 5)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 5)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 6)
def test_40_perf_sql_queries_page_multi_level_menu(self):
# menu structure should not impact SQL requests
@@ -135,7 +135,7 @@ class TestWebsitePerformance(UtilPerf):
menu_aa.parent_id = menu_a
self.assertEqual(self._get_url_hot_query(self.page.url), 5)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 8)
self.assertEqual(self._get_url_hot_query(self.page.url, cache=False), 9)
def test_50_perf_sql_web_assets(self):
# assets route /web/assets/..
@@ -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'), 25)
self.assertEqual(self._get_url_hot_query('/blog', cache=False), 24)
self.assertEqual(self._get_url_hot_query('/blog'), 26)
self.assertEqual(self._get_url_hot_query('/blog', cache=False), 25)
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'), 25)
self.assertEqual(self._get_url_hot_query('/blog', cache=False), 24)
self.assertEqual(self._get_url_hot_query(blog_post[0].website_url), 29)
self.assertEqual(self._get_url_hot_query(blog_post[0].website_url, cache=False), 28)
self.assertEqual(self._get_url_hot_query('/blog'), 26)
self.assertEqual(self._get_url_hot_query('/blog', cache=False), 25)
self.assertEqual(self._get_url_hot_query(blog_post[0].website_url), 31)
self.assertEqual(self._get_url_hot_query(blog_post[0].website_url, cache=False), 30)
def test_30_perf_sql_blog_bigger_data_scaling(self):
BlogPost = self.env['blog.post']
@@ -41,7 +41,7 @@ class TestBlogPerformance(UtilPerf):
for blog_post in blog_posts:
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'), 28)
self.assertLessEqual(self._get_url_hot_query('/blog', cache=False), 28)
self.assertLessEqual(self._get_url_hot_query('/blog'), 27)
self.assertLessEqual(self._get_url_hot_query('/blog', cache=False), 26)
self.assertLessEqual(self._get_url_hot_query(blog_post[0].website_url), 32)
self.assertLessEqual(self._get_url_hot_query(blog_post[0].website_url, cache=False), 31)