diff --git a/base_name_search_improved/README.rst b/base_name_search_improved/README.rst
index 4e82a868969..3dee5936f43 100644
--- a/base_name_search_improved/README.rst
+++ b/base_name_search_improved/README.rst
@@ -7,7 +7,7 @@ Improved Name Search
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:88daa951eef68e162381052878cb525cb0c1e6b0a72cd2a1f9d138dca31bd6f4
+ !! source digest: sha256:fe0fce7aeb356dfbf982cb648994712ec1907ee6ab73a221eee4cda4039e7a33
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@@ -40,7 +40,7 @@ relaxed search also looks up for records containing all the words, so
"John M. Brown" would be a match. It also tolerates words in a different
order, so searching for "brown john" also works.
-|image1|
+|image0|
Additionally, an Administrator can configure other fields to also lookup
into. For example, Customers could be additionally searched by City or
@@ -64,7 +64,7 @@ tried. The specific methods used are:
All results found are presented in that order, hopefully presenting them
in order of relevance.
-.. |image1| image:: https://raw.githubusercontent.com/OCA/server-tools/11.0/base_name_search_improved/images/image0.png
+.. |image0| image:: https://raw.githubusercontent.com/OCA/server-tools/11.0/base_name_search_improved/images/image0.png
.. |image2| image:: https://raw.githubusercontent.com/OCA/server-tools/11.0/base_name_search_improved/images/image2.png
**Table of contents**
diff --git a/base_name_search_improved/__manifest__.py b/base_name_search_improved/__manifest__.py
index f110da53c7d..d2574acbb19 100644
--- a/base_name_search_improved/__manifest__.py
+++ b/base_name_search_improved/__manifest__.py
@@ -3,7 +3,7 @@
{
"name": "Improved Name Search",
"summary": "Friendlier search when typing in relation fields",
- "version": "16.0.1.0.0",
+ "version": "17.0.1.0.0",
"category": "Uncategorized",
"website": "https://github.com/OCA/server-tools",
"author": "Daniel Reis, Odoo Community Association (OCA), ADHOC SA",
diff --git a/base_name_search_improved/hooks.py b/base_name_search_improved/hooks.py
index d302f64a211..2e266b448ec 100644
--- a/base_name_search_improved/hooks.py
+++ b/base_name_search_improved/hooks.py
@@ -1,14 +1,15 @@
import logging
-from odoo import SUPERUSER_ID, api
-
_logger = logging.getLogger(__name__)
-def uninstall_hook(cr, registry):
+def uninstall_hook(env):
_logger.info("Reverting Patches...")
- env = api.Environment(cr, SUPERUSER_ID, {})
- env["ir.model.fields"].with_context(_force_unlink=True).search(
- [("name", "=", "smart_search")]
- ).unlink()
+ fields_to_unlink = (
+ env["ir.model.fields"]
+ .with_context(_force_unlink=True)
+ .search([("name", "=", "smart_search")])
+ )
+ if fields_to_unlink:
+ fields_to_unlink.unlink()
_logger.info("Done!")
diff --git a/base_name_search_improved/models/ir_model.py b/base_name_search_improved/models/ir_model.py
index 792b97080b7..4bf4d8a3971 100644
--- a/base_name_search_improved/models/ir_model.py
+++ b/base_name_search_improved/models/ir_model.py
@@ -3,6 +3,7 @@
import logging
from ast import literal_eval
+from collections import defaultdict
from lxml import etree
@@ -55,12 +56,13 @@ def _get_name_search_domain(self):
return []
-def _extend_name_results(self, domain, results, limit, name_get_uid):
+def _extend_name_results(self, domain, results, limit):
result_count = len(results)
if result_count < limit:
domain += [("id", "not in", results)]
rec_ids = self._search(
- domain, limit=limit - result_count, access_rights_uid=name_get_uid
+ domain,
+ limit=limit - result_count,
)
results.extend(rec_ids)
return results
@@ -69,16 +71,15 @@ def _extend_name_results(self, domain, results, limit, name_get_uid):
def patch_name_search():
@api.model
def _name_search(
- self, name="", args=None, operator="ilike", limit=100, name_get_uid=None
+ self, name="", domain=None, operator="ilike", limit=100, order=None
):
# Perform standard name search
res = _name_search.origin(
self,
name=name,
- args=args,
- operator=operator,
+ domain=domain,
limit=limit,
- name_get_uid=name_get_uid,
+ order=order,
)
if name and _get_use_smart_name_search(self.sudo()) and operator in ALLOWED_OPS:
# _name_search.origin is a query, we need to convert it to a list
@@ -86,7 +87,7 @@ def _name_search(
limit = limit or 0
# we add domain
- args = args or [] + _get_name_search_domain(self.sudo())
+ args = domain or [] + _get_name_search_domain(self.sudo())
# Support a list of fields to search on
all_names = _get_rec_names(self.sudo())
@@ -94,15 +95,11 @@ def _name_search(
# 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, name_get_uid
- )
+ res = _extend_name_results(self, base_domain + domain, res, limit)
# 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, name_get_uid
- )
+ res = _extend_name_results(self, base_domain + domain, res, limit)
# Try unordered word search on each of the search fields
# we only perform this search if we have at least one
# separator character
@@ -116,9 +113,7 @@ def _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, name_get_uid
- )
+ res = _extend_name_results(self, base_domain + domain, res, limit)
return res
@@ -130,8 +125,7 @@ class Base(models.AbstractModel):
# TODO perhaps better to create only the field when enabled on the model
smart_search = fields.Char(
- compute="_compute_smart_search",
- search="_search_smart_search",
+ compute="_compute_smart_search", search="_search_smart_search", translate=False
)
def _compute_smart_search(self):
@@ -214,7 +208,7 @@ def _compute_smart_search_warning(self):
@api.constrains("name_search_ids", "name_search_domain", "add_smart_search")
def update_search_wo_restart(self):
- self.clear_caches()
+ self.env.registry.clear_cache()
@api.constrains("name_search_domain")
def check_name_search_domain(self):
@@ -252,9 +246,16 @@ def _register_hook(self):
"""
_logger.info("Patching BaseModel for Smart Search")
+ patched_models = defaultdict(set)
+
+ def patch(model, name, method):
+ if model not in patched_models[name]:
+ ModelClass = type(model)
+ method.origin = getattr(ModelClass, name)
+ setattr(ModelClass, name, method)
+
for model in self.sudo().search(self.ids or []):
Model = self.env.get(model.model)
if Model is not None and not Model._abstract:
- Model._patch_method("_name_search", patch_name_search())
-
+ patch(Model, "_name_search", patch_name_search())
return super()._register_hook()
diff --git a/base_name_search_improved/readme/CONFIGURE.md b/base_name_search_improved/readme/CONFIGURE.md
index a4080a66f0d..8cff7627e63 100644
--- a/base_name_search_improved/readme/CONFIGURE.md
+++ b/base_name_search_improved/readme/CONFIGURE.md
@@ -5,4 +5,4 @@ the top right search box, is not affected.
Additional search fields can be configured at Settings \> Technical \>
Database \> Models, using the "Name Search Fields" field.
-
+
diff --git a/base_name_search_improved/readme/DESCRIPTION.md b/base_name_search_improved/readme/DESCRIPTION.md
index 88b7ee5448b..e117da56048 100644
--- a/base_name_search_improved/readme/DESCRIPTION.md
+++ b/base_name_search_improved/readme/DESCRIPTION.md
@@ -10,13 +10,13 @@ relaxed search also looks up for records containing all the words, so
"John M. Brown" would be a match. It also tolerates words in a different
order, so searching for "brown john" also works.
-
+
Additionally, an Administrator can configure other fields to also lookup
into. For example, Customers could be additionally searched by City or
Phone number.
-
+
How it works:
diff --git a/base_name_search_improved/static/description/index.html b/base_name_search_improved/static/description/index.html
index e4652116dcd..5e41a927a1c 100644
--- a/base_name_search_improved/static/description/index.html
+++ b/base_name_search_improved/static/description/index.html
@@ -1,4 +1,3 @@
-
@@ -9,10 +8,11 @@
/*
:Author: David Goodger (goodger@python.org)
-:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
+:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
+Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
@@ -275,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }
-pre.code .ln { color: grey; } /* line numbers */
+pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -301,7 +301,7 @@
span.pre {
white-space: pre }
-span.problematic {
+span.problematic, pre.problematic {
color: red }
span.section-subtitle {
@@ -367,40 +367,36 @@ Improved Name Search
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:88daa951eef68e162381052878cb525cb0c1e6b0a72cd2a1f9d138dca31bd6f4
+!! source digest: sha256:fe0fce7aeb356dfbf982cb648994712ec1907ee6ab73a221eee4cda4039e7a33
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

-Extends the name search feature to use additional, more relaxed
-matching methods, and to allow searching into configurable additional
-record fields.
-The name search is the lookup feature to select a related record.
-For example, selecting a Customer on a new Sales order.
-For example, typing “john brown” doesn’t match “John M. Brown”.
-The relaxed search also looks up for records containing all the words,
-so “John M. Brown” would be a match.
-It also tolerates words in a different order, so searching
-for “brown john” also works.
-
-Additionally, an Administrator can configure other fields to also lookup into.
-For example, Customers could be additionally searched by City or Phone number.
-
+

+Extends the name search feature to use additional, more relaxed matching
+methods, and to allow searching into configurable additional record
+fields.
+The name search is the lookup feature to select a related record. For
+example, selecting a Customer on a new Sales order.
+For example, typing “john brown” doesn’t match “John M. Brown”. The
+relaxed search also looks up for records containing all the words, so
+“John M. Brown” would be a match. It also tolerates words in a different
+order, so searching for “brown john” also works.
+
+Additionally, an Administrator can configure other fields to also lookup
+into. For example, Customers could be additionally searched by City or
+Phone number.
+
How it works:
-Regular name search is performed, and the additional search logic is only
-triggered if not enough results are found.
-This way, no overhead is added on searches that would normally yield results.
-But if not enough results are found, then additional search methods are tried.
-The specific methods used are:
+Regular name search is performed, and the additional search logic is
+only triggered if not enough results are found. This way, no overhead is
+added on searches that would normally yield results.
+But if not enough results are found, then additional search methods are
+tried. The specific methods used are:
- Try regular search on each of the additional fields
- Try ordered word search on each of the search fields
- Try unordered word search on each of the search fields
-All results found are presented in that order,
-hopefully presenting them in order of relevance.
+All results found are presented in that order, hopefully presenting them
+in order of relevance.
Table of contents
@@ -419,14 +415,12 @@ Improved Name Search
-
The fuzzy search is automatically enabled on all Models.
-Note that this only affects typing in related fields.
-The regular search(), used in the top right search box, is not affected.
-
Additional search fields can be configured at Settings > Technical > Database > Models,
-using the “Name Search Fields” field.
-
+
The fuzzy search is automatically enabled on all Models. Note that this
+only affects typing in related fields. The regular search(), used in
+the top right search box, is not affected.
+
Additional search fields can be configured at Settings > Technical >
+Database > Models, using the “Name Search Fields” field.
+

@@ -437,9 +431,18 @@
- Also use fuzzy search, such as the Levenshtein distance:
https://www.postgresql.org/docs/9.5/static/fuzzystrmatch.html
-- The list of additional fields to search could benefit from caching, for efficiency.
-- This feature could also be implemented for regular search on the name field.
-- While adding m2o or other related field that also have an improved name search, that improved name search is not used (while if name_search is customizend on a module and you add a field of that model on another model it works ok). Esto por ejemplo es en productos si agregamos campo “categoría pública” y a categoría pública le ponemos “parent_id”. Entonces vamos a ver que si buscamos por una categoría padre no busca nada, en vez si hacemos esa lógica en name_search de modulo si funciona
+- The list of additional fields to search could benefit from caching,
+for efficiency.
+- This feature could also be implemented for regular search on the
+name field.
+- While adding m2o or other related field that also have an improved
+name search, that improved name search is not used (while if
+name_search is customizend on a module and you add a field of that
+model on another model it works ok). Esto por ejemplo es en productos
+si agregamos campo “categoría pública” y a categoría pública le
+ponemos “parent_id”. Entonces vamos a ver que si buscamos por una
+categoría padre no busca nada, en vez si hacemos esa lógica en
+name_search de modulo si funciona
@@ -447,7 +450,7 @@
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
-feedback.
+
feedback.
Do not contact contributors directly about support or help with technical issues.
@@ -477,11 +480,13 @@
This module is maintained by the OCA.
-

+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/server-tools project on GitHub.
+
This module is part of the OCA/server-tools project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/base_name_search_improved/tests/test_name_search.py b/base_name_search_improved/tests/test_name_search.py
index 1f4d6cd7164..2a6f9df3be2 100644
--- a/base_name_search_improved/tests/test_name_search.py
+++ b/base_name_search_improved/tests/test_name_search.py
@@ -6,25 +6,31 @@
@tagged("post_install", "-at_install")
class NameSearchCase(TransactionCase):
- def setUp(self):
- super(NameSearchCase, self).setUp()
- phone_field = self.env.ref("base.field_res_partner__phone")
- model_partner = self.env.ref("base.model_res_partner")
- model_partner.name_search_ids = phone_field
- model_partner.add_smart_search = True
- model_partner.use_smart_name_search = True
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.phone_field = cls.env.ref("base.field_res_partner__phone")
+ cls.city_field = cls.env.ref("base.field_res_partner__city")
+ cls.email_field = cls.env.ref("base.field_res_partner__email")
+ cls.address_field = cls.env.ref("base.field_res_partner__contact_address")
+ cls.zip_field = cls.env.ref("base.field_res_partner__zip")
+
+ cls.model_partner = cls.env.ref("base.model_res_partner")
+ cls.model_partner.name_search_ids = cls.phone_field
+ cls.model_partner.add_smart_search = True
+ cls.model_partner.use_smart_name_search = True
# this use does not make muche sense but with base module we dont have
# much models to use for tests
- model_partner.name_search_domain = "[('parent_id', '=', False)]"
- self.Partner = self.env["res.partner"]
- self.partner1 = self.Partner.create(
+ cls.model_partner.name_search_domain = "[('parent_id', '=', False)]"
+ cls.Partner = cls.env["res.partner"]
+ cls.partner1 = cls.Partner.create(
{"name": "Luigi Verconti", "vat": "1111", "phone": "+351 555 777 333"}
)
- self.partner2 = self.Partner.create(
+ cls.partner2 = cls.Partner.create(
{"name": "Ken Shabby", "vat": "2222", "phone": "+351 555 333 777"}
)
- self.partner3 = self.Partner.create(
+ cls.partner3 = cls.Partner.create(
{
"name": "Johann Gambolputty of Ulm",
"vat": "3333",
@@ -35,37 +41,75 @@ def setUp(self):
def test_RelevanceOrderedResults(self):
"""Return results ordered by relevance"""
- res = self.Partner.name_search("555 777")
- self.assertEqual(
- res[0][0], self.partner1.id, "Match full string honoring spaces"
- )
+ res = self.Partner._name_search("555 777")
+ self.assertEqual(res[0], self.partner1.id, "Match full string honoring spaces")
self.assertEqual(
- res[1][0], self.partner2.id, "Match words honoring order of appearance"
+ res[1], self.partner2.id, "Match words honoring order of appearance"
)
self.assertEqual(
- res[2][0],
+ res[2],
self.partner3.id,
"Match all words, regardless of order of appearance",
)
def test_NameSearchMustMatchAllWords(self):
"""Must Match All Words"""
- res = self.Partner.name_search("ulm aaa 555 777")
+ res = self.Partner._name_search("ulm aaa 555 777")
self.assertFalse(res)
def test_NameSearchDifferentFields(self):
"""Must Match All Words"""
- res = self.Partner.name_search("ulm 555 777")
+ res = self.Partner._name_search("ulm 555 777")
self.assertEqual(len(res), 1)
def test_NameSearchDomain(self):
"""Must not return a partner with parent"""
- res = self.Partner.name_search("Edward Foster")
+ res = self.Partner._name_search("Edward Foster")
self.assertFalse(res)
def test_MustHonorDomain(self):
"""Must also honor a provided Domain"""
- res = self.Partner.name_search("+351", args=[("vat", "=", "3333")])
+ res = self.Partner._name_search("+351", domain=[("vat", "=", "3333")])
gambulputty = self.partner3.id
self.assertEqual(len(res), 1)
- self.assertEqual(res[0][0], gambulputty)
+ self.assertEqual(res[0], gambulputty)
+
+ def test_SmartSearchWarning(self):
+ """Must check the funtional work of _compute_smart_search_warning"""
+ self.model_partner.name_search_ids = [
+ (4, self.city_field.id),
+ (4, self.phone_field.id),
+ (4, self.email_field.id),
+ (4, self.address_field.id),
+ ]
+ self.model_partner._compute_smart_search_warning()
+ self.assertFalse(
+ self.model_partner.smart_search_warning,
+ "There should be no warnings",
+ )
+
+ self.model_partner.name_search_ids = [(4, self.zip_field.id)]
+ self.model_partner._compute_smart_search_warning()
+ self.assertIn(
+ "You have selected more than 4 fields for smart search",
+ self.model_partner.smart_search_warning,
+ "There should be a warning as there are more than 4 fields",
+ )
+
+ translatable_field = self.env["ir.model.fields"].create(
+ {
+ "name": "x_translatable_field",
+ "field_description": "Translatable Field",
+ "ttype": "char",
+ "model_id": self.model_partner.id,
+ "model": self.model_partner.model,
+ "translate": True,
+ }
+ )
+ self.model_partner.name_search_ids = [(4, translatable_field.id)]
+ self.model_partner._compute_smart_search_warning()
+ self.assertIn(
+ "You have selected translatable fields in the smart search",
+ self.model_partner.smart_search_warning,
+ "There should be a warning as there are translatable fields",
+ )
diff --git a/base_name_search_improved/views/ir_model_views.xml b/base_name_search_improved/views/ir_model_views.xml
index b991f0e6510..ceb0fbb983e 100644
--- a/base_name_search_improved/views/ir_model_views.xml
+++ b/base_name_search_improved/views/ir_model_views.xml
@@ -65,13 +65,13 @@
@@ -97,7 +97,7 @@
ir.model
-
+