Skip to content

Commit

Permalink
[MIG] base_name_search_improved: Migration to 18.0
Browse files Browse the repository at this point in the history
  • Loading branch information
lef-adhoc committed Nov 26, 2024
1 parent 71b74de commit ecc6179
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 133 deletions.
10 changes: 5 additions & 5 deletions base_name_search_improved/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ Improved Name Search
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
:target: https://github.com/OCA/server-tools/tree/17.0/base_name_search_improved
:target: https://github.com/OCA/server-tools/tree/18.0/base_name_search_improved
:alt: OCA/server-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-tools-17-0/server-tools-17-0-base_name_search_improved
:target: https://translation.odoo-community.org/projects/server-tools-18-0/server-tools-18-0-base_name_search_improved
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=17.0
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|
Expand Down Expand Up @@ -115,7 +115,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/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 <https://github.com/OCA/server-tools/issues/new?body=module:%20base_name_search_improved%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20base_name_search_improved%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Expand Down Expand Up @@ -155,6 +155,6 @@ 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 <https://github.com/OCA/server-tools/tree/17.0/base_name_search_improved>`_ project on GitHub.
This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/18.0/base_name_search_improved>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 1 addition & 1 deletion base_name_search_improved/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"name": "Improved Name Search",
"summary": "Friendlier search when typing in relation fields",
"version": "17.0.1.0.0",
"version": "18.0.1.0.0",
"category": "Uncategorized",
"website": "https://github.com/OCA/server-tools",
"author": "Daniel Reis, Odoo Community Association (OCA), ADHOC SA",
Expand Down
183 changes: 77 additions & 106 deletions base_name_search_improved/models/ir_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

import logging
from ast import literal_eval
from collections import defaultdict

from lxml import etree

from odoo import _, api, fields, models, tools
from odoo import api, fields, models, tools
from odoo.exceptions import ValidationError
from odoo.osv import expression

_logger = logging.getLogger(__name__)
# Extended name search is only used on some operators
Expand Down Expand Up @@ -68,58 +68,6 @@ def _extend_name_results(self, domain, results, limit):
return results


def patch_name_search():
@api.model
def _name_search(
self, name="", domain=None, operator="ilike", limit=100, order=None
):
# Perform standard name search
res = _name_search.origin(
self,
name=name,
domain=domain,
limit=limit,
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
res = self.browse(res).ids
limit = limit or 0

# we add domain
args = domain or [] + _get_name_search_domain(self.sudo())

# Support a list of fields to search on
all_names = _get_rec_names(self.sudo())
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)
# 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)
# Try unordered word search on each of the search fields
# we only perform this search if we have at least one
# separator character
# also, if have raise the limit we skeep this iteration
if " " in name and len(res) < limit:
domain = []
for word in name.split():
word_domain = []
for rec_name in all_names:
word_domain = (
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)

return res

return _name_search


class Base(models.AbstractModel):
_inherit = "base"

Expand All @@ -133,28 +81,79 @@ def _compute_smart_search(self):

@api.model
def _search_smart_search(self, operator, value):
"""
For now this method does not call
self._name_search(name, operator=operator) since it is not as
performant if unlimited records are called which is what name
search should return. That is why it is reimplemented here
again. In addition, name_search has a logic which first tries
to return best match, which in this case is not necessary.
Surely, it can be improved and a lot of code can be unified.
"""
name = value
if name and operator in ALLOWED_OPS:
if value and operator in ALLOWED_OPS:
matching_records = self.with_context(
force_smart_name_search=True
).name_search(name=value, operator=operator)
if matching_records:
record_ids = [record[0] for record in matching_records]
return [("id", "in", record_ids)]
return []

@api.model
def _search_display_name(self, operator, value):
domain = super()._search_display_name(operator, value)
if self.env.context.get(
"force_smart_name_search", False
) or _get_use_smart_name_search(self.sudo()):
all_names = _get_rec_names(self.sudo())
domain = _get_name_search_domain(self.sudo())
for word in name.split():
additional_domain = _get_name_search_domain(self.sudo())

for word in value.split():
word_domain = []
for rec_name in all_names:
word_domain = (
word_domain and ["|"] + word_domain or word_domain
) + [(rec_name, operator, word)]
domain = (domain and ["&"] + domain or domain) + word_domain
return domain
return []
additional_domain = (
additional_domain and ["&"] + additional_domain or additional_domain
) + word_domain

return expression.OR([additional_domain, domain])

return domain

@api.model
def name_search(self, name="", args=None, operator="ilike", limit=100):
if not name or not (
self.env.context.get("force_smart_name_search", False)
or _get_use_smart_name_search(self.sudo())
):
return super().name_search(name, args, operator, limit)

all_names = _get_rec_names(self.sudo())
base_domain = args or []
limit = limit or 0
results = []

for rec_name in all_names:
domain = expression.AND([base_domain, [(rec_name, operator, name)]])
results = _extend_name_results(self, domain, results, limit)

for rec_name in all_names:
domain = expression.AND(
[base_domain, [(rec_name, operator, name.replace(" ", "%"))]]
)
results = _extend_name_results(self, domain, results, limit)

if " " in name:
unordered_domain = []
for word in name.split():
word_domain = expression.OR(
[[(rec_name, operator, word)] for rec_name in all_names]
)
unordered_domain = (
expression.AND([unordered_domain, word_domain])
if unordered_domain
else word_domain
)
results = _extend_name_results(
self, expression.AND([base_domain, unordered_domain]), results, limit
)

results = results[:limit]
records = self.browse(results)
return [(record.id, record.display_name) for record in records]

@api.model
def _get_view(self, view_id=None, view_type="form", **options):
Expand Down Expand Up @@ -200,9 +199,10 @@ def _compute_smart_search_warning(self):
# rec.smart_search_warning = msg
if msgs:
rec.smart_search_warning = (
"<p>In case of performance issues we recommend to review "
"these suggestions: <ul>%s</ul></p>"
) % "".join(["<li>%s</li>" % x for x in msgs])
f"<p>In case of performance issues we recommend to review "
f"these suggestions: <ul>"
f"{''.join(f'<li>{x}</li>' for x in msgs)}</ul></p>"
)
else:
rec.smart_search_warning = False

Expand All @@ -224,38 +224,9 @@ def check_name_search_domain(self):
RecursionError,
) as e:
raise ValidationError(
_("Couldn't eval Name Search Domain (%s)") % e
self.env._("Couldn't eval Name Search Domain (%s)") % e
) from e
if not isinstance(name_search_domain, list):
raise ValidationError(_("Name Search Domain must be a list of tuples"))

def _register_hook(self):
"""Apply monkey patches.
Patch `fields_view_get` on the base model, and a freshly generated copy
of `_name_search` on each concrete model.
We want to skip abstract models because patching those may mess up the
inheritance. An example of this is `ir.model` which is assigned the
studio mixin using `inherit = ['studio.mixin', 'ir.model']`.
If the mixin itself is patched, and the method is overridden once again
(in, say, enterprise's `documents_spreadsheet`), the super() method
called in that override is the patched version of `studio.mixin` rather
than the override of `ir.model` in the base module, which is now skipped
entirely.
"""
_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:
patch(Model, "_name_search", patch_name_search())
return super()._register_hook()
raise ValidationError(
self.env._("Name Search Domain must be a list of tuples")
)
6 changes: 3 additions & 3 deletions base_name_search_improved/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ <h1 class="title">Improved Name Search</h1>
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:fe0fce7aeb356dfbf982cb648994712ec1907ee6ab73a221eee4cda4039e7a33
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/server-tools/tree/17.0/base_name_search_improved"><img alt="OCA/server-tools" src="https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/server-tools-17-0/server-tools-17-0-base_name_search_improved"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/server-tools&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/server-tools/tree/18.0/base_name_search_improved"><img alt="OCA/server-tools" src="https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/server-tools-18-0/server-tools-18-0-base_name_search_improved"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/server-tools&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Extends the name search feature to use additional, more relaxed matching
methods, and to allow searching into configurable additional record
fields.</p>
Expand Down Expand Up @@ -450,7 +450,7 @@ <h1><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-tools/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/server-tools/issues/new?body=module:%20base_name_search_improved%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<a class="reference external" href="https://github.com/OCA/server-tools/issues/new?body=module:%20base_name_search_improved%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
Expand Down Expand Up @@ -486,7 +486,7 @@ <h2><a class="toc-backref" href="#toc-entry-9">Maintainers</a></h2>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-tools/tree/17.0/base_name_search_improved">OCA/server-tools</a> project on GitHub.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-tools/tree/18.0/base_name_search_improved">OCA/server-tools</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
Expand Down
20 changes: 11 additions & 9 deletions base_name_search_improved/tests/test_name_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,38 +41,40 @@ def setUpClass(cls):

def test_RelevanceOrderedResults(self):
"""Return results ordered by relevance"""
res = self.Partner._name_search("555 777")
self.assertEqual(res[0], self.partner1.id, "Match full string honoring spaces")
res = self.Partner.name_search("555 777")
self.assertEqual(
res[1], self.partner2.id, "Match words honoring order of appearance"
res[0][0], self.partner1.id, "Match full string honoring spaces"
)
self.assertEqual(
res[2],
res[1][0], self.partner2.id, "Match words honoring order of appearance"
)
self.assertEqual(
res[2][0],
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", domain=[("vat", "=", "3333")])
res = self.Partner.name_search("+351", args=[("vat", "=", "3333")])
gambulputty = self.partner3.id
self.assertEqual(len(res), 1)
self.assertEqual(res[0], gambulputty)
self.assertEqual(res[0][0], gambulputty)

def test_SmartSearchWarning(self):
"""Must check the funtional work of _compute_smart_search_warning"""
Expand Down
Loading

0 comments on commit ecc6179

Please sign in to comment.