[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:
@@ -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)])
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user