[FIX] website, test_website: prevent fuzzy search to crash with inherits

This commit ensures that when searching on a `inherits` field of
a model (like `name` of `website.page`), it works properly when
`pg_trgm` is activated.
Indeed, `name` is a field of `website.page` record but only at the ORM
level, not in SQL, due to how `inherits` works.

So, when the `pg_trgm` extension is enabled, it will switch from ORM
queries to raw SQL query (to use the native SQL similarity feature and
not our custom python/orm one, as obviously the SQL one is better, more
powerfull/accurate and faster).
But this will actually make the code fail when searching on fields from
a model which has `inherits` and that you search on that `inherits`
model fields.

Note that in 15.2, the `pg_trgm` extension is auto installed when
possible thanks to [1] and [2].

[1]: https://github.com/odoo/odoo/commit/eedf37d6e286b995c47b946be1a6b66817094eff
[2]: https://github.com/odoo/odoo/commit/75e6b645acdc95aece506ce0249fd0760838281c

opw-3063592

closes odoo/odoo#107178

X-original-commit: 3d978caad9de2010603581687ee39a5240b74074
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
Romain Derie
2022-12-04 23:41:55 +01:00
parent a34e5d20c8
commit a6bf6cf44d
2 changed files with 80 additions and 19 deletions
+24
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import psycopg2
from odoo.addons.website.controllers.main import Website
from odoo.addons.website.tools import MockRequest
@@ -72,3 +73,26 @@ class TestAutoComplete(TransactionCase):
# There are no "X*" records
self._autocomplete('XEF1000', 0, "Not found")
def test_02_pages_search(self):
if not self.env.registry.has_trigram:
try:
self.env.cr.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm")
self.env.registry.has_trigram = True
except psycopg2.Error:
_logger.warning("pg_trgm extension can't be installed, which is required to run this test")
return
with MockRequest(self.env, website=self.env['website'].browse(1)):
# This should not crash. This ensures that when searching on `name`
# field of `website.page` model, it works properly when `pg_trgm` is
# activated.
# Indeed, `name` is a field of `website.page` record but only at the
# ORM level, not in SQL, due to how `inherits` works.
self.env['website'].browse(1)._search_with_fuzzy(
'pages', 'test', limit=5, order='name asc, website_id desc, id', options={
'displayDescription': False, 'displayDetail': False,
'displayExtraDetail': False, 'displayExtraLink': False,
'displayImage': False, 'allowFuzzy': True
}
)
+56 -19
View File
@@ -1611,35 +1611,72 @@ class Website(models.Model):
fields = set(fields).intersection(model._fields)
unaccent = get_unaccent_wrapper(self.env.cr)
similarities = [sql.SQL("word_similarity({search}, {field})").format(
search=unaccent(sql.Placeholder('search')),
# Specific handling for website.page that inherits its arch_db and name fields
# TODO make more generic
field=unaccent(sql.SQL("{table}.{field}").format(
table=sql.Identifier((self.env['ir.ui.view'] if field == 'arch_db' or (field == 'name' and 'arch_db' in fields) else model)._table),
field=sql.Identifier(field)
)) if not (self.env['ir.ui.view'] if field == 'arch_db' or (field == 'name' and 'arch_db' in fields) else model)._fields[field].translate else
unaccent(sql.SQL("COALESCE({table}.{field}->>{lang}, {table}.{field}->>'en_US')").format(
table=sql.Identifier((self.env['ir.ui.view'] if field == 'arch_db' or (field == 'name' and 'arch_db' in fields) else model)._table),
field=sql.Identifier(field),
lang=sql.Literal(lang)
))
) for field in fields]
# Specific handling for fields being actually part of another model
# through the `inherits` mechanism.
# It gets the list of fields requested to search upon and that are
# actually not part of the requested model itself but part of a
# `inherits` model:
# {
# 'name': {
# 'table': 'ir_ui_view',
# 'fname': 'view_id',
# },
# 'url': {
# 'table': 'ir_ui_view',
# 'fname': 'view_id',
# },
# 'another_field': {
# 'table': 'another_table',
# 'fname': 'record_id',
# },
# }
inherits_fields = {
inherits_model_fname: {
'table': self.env[inherits_model_name]._table,
'fname': inherits_field_name,
}
for inherits_model_name, inherits_field_name in model._inherits.items()
for inherits_model_fname in self.env[inherits_model_name]._fields.keys()
if inherits_model_fname in fields
}
similarities = []
for field in fields:
# Field might belong to another model (`inherits` mechanism)
table = inherits_fields[field]['table'] if field in inherits_fields else model._table
similarities.append(
sql.SQL("word_similarity({search}, {field})").format(
search=unaccent(sql.Placeholder('search')),
field=unaccent(sql.SQL("{table}.{field}").format(
table=sql.Identifier(table),
field=sql.Identifier(field)
)) if not model._fields[field].translate else
unaccent(sql.SQL("COALESCE({table}.{field}->>{lang}, {table}.{field}->>'en_US')").format(
table=sql.Identifier(table),
field=sql.Identifier(field),
lang=sql.Literal(lang)
)),
)
)
best_similarity = sql.SQL('GREATEST({similarities})').format(
similarities=sql.SQL(', ').join(similarities)
)
from_clause = sql.SQL("FROM {table}").format(table=sql.Identifier(model._table))
# Specific handling for website.page that inherits its arch_db and name fields
# TODO make more generic
if 'arch_db' in fields:
# Specific handling for fields being actually part of another model
# through the `inherits` mechanism.
for table_to_join in {
field['table']: field['fname'] for field in inherits_fields.values()
}.items(): # Removes duplicate inherits model
from_clause = sql.SQL("""
{from_clause}
LEFT JOIN {view_table} ON {table}.view_id = {view_table}.id
LEFT JOIN {inherits_table} ON {table}.{inherits_field} = {inherits_table}.id
""").format(
from_clause=from_clause,
table=sql.Identifier(model._table),
view_table=sql.Identifier(self.env['ir.ui.view']._table),
inherits_table=sql.Identifier(table_to_join[0]),
inherits_field=sql.Identifier(table_to_join[1]),
)
query = sql.SQL("""
SELECT {table}.id, {best_similarity} AS _best_similarity