[IMP] base_name_search_improved: better ux

pull/2477/head
Juan Jose Scarafia 2021-03-10 06:50:39 -03:00 committed by filoquin
parent 989dee515a
commit 8133a2ede6
4 changed files with 100 additions and 93 deletions

View File

@ -3,7 +3,7 @@
{
"name": "Improved Name Search",
"summary": "Friendlier search when typing in relation fields",
"version": "14.0.1.0.0",
"version": "14.0.1.1.0",
"category": "Uncategorized",
"website": "https://github.com/OCA/server-tools",
"author": "Daniel Reis, Odoo Community Association (OCA), ADHOC SA",

View File

@ -23,6 +23,11 @@ def _get_rec_names(self):
return rec_name + other_names
@tools.ormcache(skiparg=0)
def _get_use_smart_name_search(self):
return self.env['ir.model'].search([('model', '=', str(self._name))]).use_smart_name_search
@tools.ormcache(skiparg=0)
def _get_add_smart_search(self):
"Add Smart Search on search views"
@ -46,43 +51,40 @@ def _get_name_search_domain(self):
return []
def _extend_name_results(self, domain, results, limit):
def _extend_name_results(self, domain, results, limit, name_get_uid):
result_count = len(results)
if result_count < limit:
domain += [("id", "not in", [x[0] for x in results])]
recs = self.search(domain, limit=limit - result_count)
results.extend(recs.name_get())
rec_ids = self._search(domain, limit=limit - result_count, access_rights_uid=name_get_uid)
results.extend(models.lazy_name_get(self.browse(rec_ids).with_user(name_get_uid)))
return results
def patch_name_search():
@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
limit = limit or 0
enabled = self.env.context.get("name_search_extended", True)
superself = self.sudo()
if enabled:
def _name_search(self, name='', args=None, operator='ilike', limit=100, name_get_uid=None):
# Perform standard name search
res = _name_search.origin(
self, name=name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid
)
if name and _get_use_smart_name_search(self) and operator in ALLOWED_OPS:
limit = limit or 0
superself = self.sudo()
# we add domain
args = args or [] + _get_name_search_domain(superself)
# Perform standard name search
res = name_search.origin(
self, name=name, args=args, operator=operator, limit=limit
)
# Perform extended name search
# Note: Empty name causes error on
# Customer->More->Portal Access Management
if name and enabled and operator in ALLOWED_OPS:
# Support a list of fields to search on
all_names = _get_rec_names(superself)
base_domain = args or []
# Try regular search on each additional search field
for rec_name in all_names[1:]:
domain = [(rec_name, operator, name)]
res = _extend_name_results(self, base_domain + domain, res, limit)
res = _extend_name_results(self, base_domain + domain, res, limit, name_get_uid)
# Try ordered word search on each of the search fields
for rec_name in all_names:
domain = [(rec_name, operator, name.replace(" ", "%"))]
res = _extend_name_results(self, base_domain + domain, res, limit)
res = _extend_name_results(self, base_domain + domain, res, limit, name_get_uid)
# Try unordered word search on each of the search fields
# we only perform this search if we have at least one
# separator character
@ -96,11 +98,11 @@ def patch_name_search():
word_domain and ["|"] + word_domain or word_domain
) + [(rec_name, operator, word)]
domain = (domain and ["&"] + domain or domain) + word_domain
res = _extend_name_results(self, base_domain + domain, res, limit)
res = _extend_name_results(self, base_domain + domain, res, limit, name_get_uid)
return res
return name_search
return _name_search
def patch_fields_view_get():
@ -148,16 +150,15 @@ class Base(models.AbstractModel):
def _search_smart_search(self, operator, value):
"""
Por ahora este método no llama a
self.name_search(name, operator=operator) ya que este no es tan
self._name_search(name, operator=operator) ya que este no es tan
performante si se llama a ilimitados registros que es lo que el
name search debe devolver. Por eso se reimplementa acá nuevamente.
Además name_search tiene una lógica por la cual trata de devolver
primero los que mejor coinciden, en este caso eso no es necesario
Igualmente seguro se puede mejorar y unificar bastante código
"""
enabled = self.env.context.get("name_search_extended", True)
name = value
if name and enabled and operator in ALLOWED_OPS:
if name and operator in ALLOWED_OPS:
superself = self.sudo()
all_names = _get_rec_names(superself)
domain = _get_name_search_domain(superself)
@ -175,11 +176,33 @@ class Base(models.AbstractModel):
class IrModel(models.Model):
_inherit = "ir.model"
add_smart_search = fields.Boolean(
help="Add Smart Search on search views",
)
name_search_ids = fields.Many2many("ir.model.fields", string="Name Search Fields")
name_search_domain = fields.Char()
add_smart_search = fields.Boolean(help="Add Smart Search on search views",)
use_smart_name_search = fields.Boolean(
string='Smart Name Search Enabled?', help="Use Smart Search for 'name_search', this will affect when searching"
"from other records (for eg. from m2o fields")
name_search_ids = fields.Many2many("ir.model.fields", string="Smart Search Fields")
name_search_domain = fields.Char(string='Smart Search Domain')
smart_search_warning = fields.Html(compute='_smart_search_warning')
@api.depends('name_search_ids')
def _smart_search_warning(self):
msgs = []
for rec in self:
if len(rec.name_search_ids) > 4:
msgs.append(
'Ha seleccionado más de 4 campos para smart search, recomendamos que intente utilizar menos '
'campos')
if any(x.translate for x in rec.name_search_ids):
msgs.append(
'Ha seleccionado campos traducibles en la búsqueda inteligente, de ser posible intente '
'evitar los mismos')
# rec.smart_search_warning = msg
if msgs:
rec.smart_search_warning = (
'<p>Si tiene problemas de performance en las búsquedas le recomendamos revisar estas '
'sugerencias: <ul>%s</ul></p>') % "".join(["<li>%s</li>" % x for x in msgs])
else:
rec.smart_search_warning = False
@api.constrains("name_search_ids", "name_search_domain", "add_smart_search")
def update_search_wo_restart(self):
@ -200,18 +223,12 @@ class IrModel(models.Model):
def _register_hook(self):
_logger.info("Patching fields_view_get on BaseModel")
_logger.info("Patching BaseModel for Smart Search")
models.BaseModel._patch_method("fields_view_get", patch_fields_view_get())
for model in self.sudo().search(self.ids or []):
Model = self.env.get(model.model)
if Model is not None:
Model._patch_method("name_search", patch_name_search())
Model._patch_method("_name_search", patch_name_search())
return super(IrModel, self)._register_hook()
def toggle_smart_search(self):
"""Inverse the value of the field ``add_smart_search`` on the records
in ``self``."""
for record in self:
record.add_smart_search = not record.add_smart_search

View File

@ -18,14 +18,15 @@ class NameSearchCase(TransactionCase):
model_partner.name_search_domain = "[('parent_id', '=', False)]"
self.Partner = self.env["res.partner"]
self.partner1 = self.Partner.create(
{"name": "Luigi Verconti", "phone": "+351 555 777 333"}
{"name": "Luigi Verconti", "customer_rank": 1, "phone": "+351 555 777 333"}
)
self.partner2 = self.Partner.create(
{"name": "Ken Shabby", "phone": "+351 555 333 777"}
{"name": "Ken Shabby", "customer_rank": 1, "phone": "+351 555 333 777"}
)
self.partner3 = self.Partner.create(
{
"name": "Johann Gambolputty of Ulm",
"supplier_rank": 1,
"phone": "+351 777 333 555",
"barcode": "1111",
}
@ -63,7 +64,7 @@ class NameSearchCase(TransactionCase):
def test_MustHonorDomain(self):
"""Must also honor a provided Domain"""
res = self.Partner.name_search("+351", args=[("barcode", "=", "1111")])
res = self.Partner.name_search("+351", args=[("supplier_rank", "=", True)])
gambulputty = self.partner3.id
self.assertEqual(len(res), 1)
self.assertEqual(res[0][0], gambulputty)

View File

@ -2,69 +2,53 @@
<!-- © 2016 Daniel Reis
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_model_form" model="ir.ui.view">
<field name="name">Add Name Searchable to Models</field>
<field name="model">ir.model</field>
<field name="inherit_id" ref="base.view_model_form" />
<field name="arch" type="xml">
<field name="state" position="after">
<field name="add_smart_search" />
<field
name="name_search_ids"
widget="many2many_tags"
domain="[('model_id', '=', id), ('store', '=', True)]"
/>
<field name="name_search_domain" />
</field>
</field>
</record>
<record id="view_model_form_new" model="ir.ui.view">
<field name="name">view.model.form</field>
<field name="model">ir.model</field>
<field name="arch" type="xml">
<form string="Custom Search">
<sheet>
<div class="oe_button_box" name="buttons">
<button
name="toggle_smart_search"
type="object"
class="oe_stat_button"
icon="fa-archive"
>
<field
name="add_smart_search"
widget="boolean_button"
options="{'terminology': {
'string_true': 'Smart Active',
'hover_true': 'Remove Smart Search',
'string_false': 'Not Smart Search',
'hover_false': 'Add Smart Search',
}}"
/>
</button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1>
<field name="name" readonly="1" />
</h1>
</div>
<p class="alert alert-info" role="alert" style="margin-bottom:0px;">
Smart Search se puede activar de dos formas que pueden ser complementarias:
<ul>
<li><b>Vista de búsqueda</b>: a la hora de buscar en vistas listas, kanban y demás, la primer opción será buscar mediante "Smart Search"
</li>
<li><b>Name Search</b>: es para cuando estemos queriendo buscar un registro desde otro registro a través de los widgets m2o/m2m, por ejemplo al agregar un producto en una línea de venta o al elegir un contacto en una factura.
</li>
</ul>
En donde se haya activado "Smart Search" las búsquedas tendrán un comportmiento más "relajado". Nativamente Odoo busca por registors que incluyan las palabras ingresadas tal cual las ha ingresado, al activar esta opción se buscará por registros que contegan dichas palabras y sin importar el orden en el que se ingresen. Por ej. si buscamos un Contacto con "Pedro Picapiedra", naticamente Odoo solo nos retornaría aquellos registros que contengan exactamente ese texto, con smart search activo se devolverá cualquier registro que contenga las palabras "Pedro" y "Picapiedra".<br/><br/>
Al activar Smart Search podrá además agregar configurar estos dos comportamientos opcionales:
<ul>
<li>Definir un <b>dominio personalizado</b> (por ejemplo solo mostrar usuarios internos y no los portal)</li>
<li><b>Búsquedas por otros campos</b>. Podremos definir un conjunto de campos por el cual querramos buscar</li>
</ul>
</p>
<p class="alert alert-warning" role="alert" style="margin-bottom:0px;">
<b>IMPORTANTE:</b> tenga en cuenta que activar smart search puede afectar la performance de las búsquedas y del sistema en general.
<field name="smart_search_warning"/>
</p>
<group>
<group>
<field name="id" invisible="1" />
<field name="model" readonly="1" />
<field name="name_search_domain" />
<field name="add_smart_search" string="Smart Search (search view)" widget="boolean_toggle"/>
<field name="use_smart_name_search" string="Smart Name Search" widget="boolean_toggle"/>
<!-- TODO use new odoo domain widget -->
<field name="name_search_domain" attrs="{'invisible': [('use_smart_name_search', '=', False), ('add_smart_search', '=', False)]}"/>
</group>
</group>
<notebook colspan="4">
<page string="Fields">
<!-- widget="many2many_tags" -->
<field
name="name_search_ids"
colspan="4"
nolabel="1"
domain="[('model_id', '=', id), ('store', '=', True)]"
>
<notebook colspan="4" attrs="{'invisible': [('use_smart_name_search', '=', False), ('add_smart_search', '=', False)]}">
<page string="Smart Search Fields">
<!-- we use default m2m widget and not tags widget so that is clearer the technical name of the field added -->
<field name="name_search_ids" domain="[('model_id', '=', id), ('selectable', '=', True)]">
<tree>
<field name="name" />
<field name="field_description" />
@ -97,6 +81,11 @@
string="Smart Search"
widget="boolean_toggle"
/>
<field
name="use_smart_name_search"
string="Smart Name Search"
widget="boolean_toggle"
/>
</tree>
</field>
</record>
@ -108,21 +97,21 @@
<field name="id" />
<field name="name" />
<field name="model" />
<filter
name="extra_search"
string="With Custom Search"
domain="[('name_search_ids', '!=', False)]"
/>
<filter
name="smart_search"
string="Smart Search"
domain="[('add_smart_search', '=', True)]"
/>
<filter
name="smart_name_search"
string="Smart Name Search"
domain="[('use_smart_name_search', '=', True)]"
/>
</search>
</field>
</record>
<record id="action_improved_name_search" model="ir.actions.act_window">
<field name="name">Custom Searches</field>
<field name="name">Smart Searches</field>
<field name="res_model">ir.model</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_model_search" />
@ -133,12 +122,12 @@
(0, 0, {'view_mode': 'form', 'view_id': ref('view_model_form_new')}
)]"
/>
<field name="context">{'search_default_extra_search': 1}</field>
<field name="context">{'search_default_smart_search': 1, 'search_default_smart_name_search': 1}</field>
<field name="domain">[('transient', '=', False)]</field>
</record>
<menuitem
id="menu_improved_name_search"
name="Custom Searches"
name="Smart Searches"
parent="base.menu_administration"
sequence="6"
action="action_improved_name_search"