[IMP] website: search inside translated pages

Before this commit the search on pages was only done in the main
language.

After this commit the search on pages is based on the translated "arch"
field.

task-2379555
https://github.com/odoo/odoo/pull/65871

Part-of: odoo/odoo#65871
This commit is contained in:
Benoit Socias
2021-09-03 06:59:33 +00:00
parent 9f9c4bb7e4
commit b88bb72beb
2 changed files with 104 additions and 36 deletions
+58 -35
View File
@@ -1616,69 +1616,94 @@ class Website(models.Model):
model = self.env[model_name]
if search_detail.get('requires_sudo'):
model = model.sudo()
fields = ['arch_db' if field == 'view_id.arch_db' else field for field in fields if field in model or field == 'view_id.arch_db']
if model_name == 'website.page':
fields.remove('name') # TODO handle ?
domain = search_detail['base_domain'].copy()
fields = set(fields).intersection(model._fields)
unaccent = get_unaccent_sql_wrapper(self.env.cr)
similarities = [sql.SQL("word_similarity({search}, {field})").format(
search=unaccent(sql.Placeholder('search')),
field=sql.SQL("{table}.{field}").format(
table=sql.Identifier((self.env['ir.ui.view'] if field == 'arch_db' else model)._table),
field=unaccent(sql.Identifier(field))
)
# 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)
))
) for field in fields]
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:
from_clause = sql.SQL("""
{from_clause}
LEFT JOIN {view_table} ON {table}.{view_id} = {view_table}.{id}
LEFT JOIN {view_table} ON {table}.view_id = {view_table}.id
""").format(
from_clause=from_clause,
table=sql.Identifier(model._table),
view_id=sql.Identifier('view_id'),
view_table=sql.Identifier(self.env['ir.ui.view']._table),
id=sql.Identifier('id'),
)
query = sql.SQL("""
SELECT {table}.{id}, {best_similarity} AS _best_similarity
SELECT {table}.id, {best_similarity} AS _best_similarity
{from_clause}
ORDER BY _best_similarity desc
LIMIT 1000
""").format(
table=sql.Identifier(model._table),
id=sql.Identifier('id'),
best_similarity=best_similarity,
from_clause=from_clause,
)
self.env.cr.execute(query, {'search': search})
ids = {row[0] for row in self.env.cr.fetchall() if row[1] >= similarity_threshold}
if self.env.lang:
similarity = sql.SQL("word_similarity({search}, {field})").format(
search=unaccent(sql.Placeholder('search')),
field=unaccent(sql.Identifier('value'))
)
names = ['%s,%s' % ((self.env['ir.ui.view'] if field == 'arch_db' else model)._name, field) for field in fields]
query = sql.SQL("""
SELECT res_id, {similarity} AS _similarity
FROM ir_translation
WHERE lang = {lang}
AND name = ANY({names})
AND type = 'model'
ORDER BY _similarity desc
LIMIT 1000
""").format(
similarity=similarity,
lang=sql.Placeholder('lang'),
names=sql.Placeholder('names'),
)
self.env.cr.execute(query, {'lang': self.env.lang, 'names' :names, 'search': search})
# Specific handling for website.page that inherits its arch_db and name fields
# TODO make more generic
if 'arch_db' in fields:
# Look for partial translations
similarity = sql.SQL("word_similarity({search}, {field})").format(
search=unaccent(sql.Placeholder('search')),
field=unaccent(sql.SQL('t.value'))
)
names = ['%s,%s' % (self.env['ir.ui.view']._name, field) for field in fields]
query = sql.SQL("""
SELECT {table}.id, {similarity} AS _similarity
FROM {table}
LEFT JOIN ir_ui_view v ON {table}.view_id = v.id
LEFT JOIN ir_translation t ON v.id = t.res_id
WHERE t.lang = {lang}
AND t.name = ANY({names})
AND t.type = 'model_terms'
ORDER BY _similarity desc
LIMIT 1000
""").format(
table=sql.Identifier(model._table),
similarity=similarity,
lang=sql.Placeholder('lang'),
names=sql.Placeholder('names'),
)
else:
similarity = sql.SQL("word_similarity({search}, {field})").format(
search=unaccent(sql.Placeholder('search')),
field=unaccent(sql.SQL('value'))
)
names = ['%s,%s' % (model._name, field) for field in fields]
query = sql.SQL("""
SELECT res_id, {similarity} AS _similarity
FROM ir_translation
WHERE lang = {lang}
AND name = ANY({names})
AND type = 'model'
ORDER BY _similarity desc
LIMIT 1000
""").format(
similarity=similarity,
lang=sql.Placeholder('lang'),
names=sql.Placeholder('names'),
)
self.env.cr.execute(query, {'lang': self.env.lang, 'names': names, 'search': search})
ids.update(row[0] for row in self.env.cr.fetchall() if row[1] >= similarity_threshold)
domain = search_detail['base_domain'].copy()
domain.append([('id', 'in', list(ids))])
domain = AND(domain)
records = model.search_read(domain, fields, limit=limit)
@@ -1686,8 +1711,6 @@ class Website(models.Model):
for field, value in record.items():
if isinstance(value, str):
value = value.lower()
if field == 'arch_db':
value = text_from_html(value)
yield from re.findall(match_pattern, value)
def _basic_enumerate_words(self, search_details, search, limit):
@@ -1709,7 +1732,7 @@ class Website(models.Model):
model = model.sudo()
domain = search_detail['base_domain'].copy()
fields_domain = []
fields = ['arch_db' if field == 'view_id.arch_db' else field for field in fields if field in model or field == 'view_id.arch_db']
fields = set(fields).intersection(model._fields)
for field in fields:
fields_domain.append([(field, '=ilike', '%s%%' % first)])
fields_domain.append([(field, '=ilike', '%% %s%%' % first)])
+46 -1
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from psycopg2 import sql
import re
from odoo.addons.http_routing.models.ir_http import slugify
from odoo.addons.website.tools import text_from_html
from odoo import api, fields, models
from odoo.osv import expression
from odoo.tools import escape_psql
from odoo.tools.safe_eval import safe_eval
@@ -254,7 +257,7 @@ class Page(models.Model):
'website_url': {'name': 'url', 'type': 'text'},
}
if with_description:
search_fields.append('view_id.arch_db')
search_fields.append('arch_db')
fetch_fields.append('arch')
mapping['description'] = {'name': 'arch', 'type': 'text', 'html': True, 'match': True}
return {
@@ -271,6 +274,48 @@ class Page(models.Model):
def _search_fetch(self, search_detail, search, limit, order):
with_description = 'description' in search_detail['mapping']
results, count = super()._search_fetch(search_detail, search, limit, order)
if with_description and search:
# Perform search in translations
# TODO Remove when domains will support xml_translate fields
query = sql.SQL("""
SELECT {table}.{id}
FROM {table}
LEFT JOIN ir_ui_view v ON {table}.{view_id} = v.{id}
LEFT JOIN ir_translation t ON v.{id} = t.{res_id}
WHERE t.lang = {lang}
AND t.name = ANY({names})
AND t.type = 'model_terms'
AND t.value ilike {search}
LIMIT {limit}
""").format(
table=sql.Identifier(self._table),
id=sql.Identifier('id'),
view_id=sql.Identifier('view_id'),
res_id=sql.Identifier('res_id'),
lang=sql.Placeholder('lang'),
names=sql.Placeholder('names'),
search=sql.Placeholder('search'),
limit=sql.Placeholder('limit'),
)
self.env.cr.execute(query, {
'lang': self.env.lang,
'names': ['ir.ui.view,arch_db', 'ir.ui.view,name'],
'search': '%%%s%%' % escape_psql(search),
'limit': limit,
})
ids = {row[0] for row in self.env.cr.fetchall()}
ids.update(results.ids)
domains = search_detail['base_domain'].copy()
domains.append([('id', 'in', list(ids))])
domain = expression.AND(domains)
model = self.sudo() if search_detail.get('requires_sudo') else self
results = model.search(
domain,
limit=limit,
order=search_detail.get('order', order)
)
count = max(count, len(results))
def filter_page(search, page, all_pages):
# Search might have matched words in the xml tags and parameters therefore we make
# sure the terms actually appear inside the text.