Daniel Reis
9 years ago
committed by
Daniel Reis
12 changed files with 273 additions and 0 deletions
-
115base_name_search_improved/README.rst
-
2base_name_search_improved/__init__.py
-
19base_name_search_improved/__openerp__.py
-
BINbase_name_search_improved/images/image0.png
-
BINbase_name_search_improved/images/image1.png
-
BINbase_name_search_improved/images/image2.png
-
2base_name_search_improved/models/__init__.py
-
61base_name_search_improved/models/ir_model.py
-
BINbase_name_search_improved/static/description/icon.png
-
5base_name_search_improved/tests/__init__.py
-
44base_name_search_improved/tests/test_name_search.py
-
25base_name_search_improved/views/ir_model.xml
@ -0,0 +1,115 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
==================== |
|||
Improved Name Search |
|||
==================== |
|||
|
|||
Extends the name search feature to use fuzzy matching methods, and |
|||
allowing to search in additional related record attributes. |
|||
|
|||
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 fuzzy search looks up for record 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" would also works. |
|||
|
|||
.. image:: images/image0.png |
|||
|
|||
Additionally, an Administrator can configure other fields to also lookup into. |
|||
For example, Customers could be additionally searched by City or Phone number. |
|||
|
|||
.. image:: images/image2.png |
|||
|
|||
How it works: |
|||
|
|||
Regular name search is performed, and the additional search logic is only |
|||
triggered if no results are found. This way, no significan overhead is added |
|||
on searches that would normally yield results. |
|||
|
|||
But if no results are found, then sdditional search methods are tried until |
|||
some results are found. The sepcific 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 |
|||
|
|||
|
|||
Installation |
|||
============ |
|||
|
|||
No specific requirements. |
|||
|
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
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. |
|||
|
|||
.. image:: images/image1.png |
|||
|
|||
|
|||
Usage |
|||
===== |
|||
|
|||
Just type into any related field, such as Customer on a Sale Order. |
|||
|
|||
|
|||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
|||
:alt: Try me on Runbot |
|||
:target: https://runbot.odoo-community.org/runbot/149/8.0 |
|||
|
|||
.. repo_id is available in https://github.com/OCA/maintainer-tools/blob/master/tools/repos_with_ids.txt |
|||
.. branch is "8.0" for example |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
* The list of additional fields to search could benefit from caching, for efficiency. |
|||
* This feature could be implemented for regular ``search`` on the ``name`` field. |
|||
|
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues |
|||
<https://github.com/OCA/serevr-tools/issues>`_. In case of trouble, please |
|||
check there if your issue has already been reported. If you spotted it first, |
|||
help us smashing it by providing a detailed and welcomed feedback. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Images |
|||
------ |
|||
|
|||
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Daniel Reis <https://github.com/dreispt> |
|||
|
|||
Maintainer |
|||
---------- |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
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. |
|||
|
|||
To contribute to this module, please visit https://odoo-community.org. |
@ -0,0 +1,2 @@ |
|||
# -*- coding: utf-8 -*- |
|||
from . import models |
@ -0,0 +1,19 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Daniel Reis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
{ |
|||
'name': 'Improved Name Search', |
|||
'summary': 'Friendlier search when typing in relation fields', |
|||
'version': '8.0.1.0.0', |
|||
'category': 'Uncategorized', |
|||
'website': 'https://odoo-community.org/', |
|||
'author': 'Daniel Reis, Odoo Community Association (OCA)', |
|||
'license': 'AGPL-3', |
|||
'data': [ |
|||
'views/ir_model.xml', |
|||
], |
|||
'installable': True, |
|||
'depends': [ |
|||
'base', |
|||
], |
|||
} |
After Width: 896 | Height: 281 | Size: 16 KiB |
After Width: 890 | Height: 206 | Size: 11 KiB |
After Width: 894 | Height: 279 | Size: 15 KiB |
@ -0,0 +1,2 @@ |
|||
# -*- coding: utf-8 -*- |
|||
from . import ir_model |
@ -0,0 +1,61 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Daniel Reis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from openerp import models, fields, api |
|||
from openerp import SUPERUSER_ID |
|||
|
|||
|
|||
class ModelExtended(models.Model): |
|||
_inherit = 'ir.model' |
|||
|
|||
name_search_ids = fields.Many2many( |
|||
'ir.model.fields', |
|||
string='Name Search Fields') |
|||
|
|||
def _register_hook(self, cr, ids=None): |
|||
|
|||
def make_name_search(): |
|||
|
|||
@api.model |
|||
def name_search(self, name='', args=None, |
|||
operator='ilike', limit=100): |
|||
# Regular name search |
|||
res = name_search.origin( |
|||
self, name=name, args=args, operator=operator, limit=limit) |
|||
|
|||
allowed_ops = ['ilike', 'like', '='] |
|||
if not res and operator in allowed_ops and self._rec_name: |
|||
# Support a list of fields to search on |
|||
model = self.env['ir.model'].search( |
|||
[('model', '=', str(self._model))]) |
|||
other_names = model.name_search_ids.mapped('name') |
|||
# Try regular search on each additional search field |
|||
for rec_name in other_names: |
|||
domain = [(rec_name, operator, name)] |
|||
recs = self.search(domain, limit=limit) |
|||
if recs: |
|||
return recs.name_get() |
|||
# Try ordered word search on each of the search fields |
|||
for rec_name in [self._rec_name] + other_names: |
|||
domain = [(rec_name, operator, name.replace(' ', '%'))] |
|||
recs = self.search(domain, limit=limit) |
|||
if recs: |
|||
return recs.name_get() |
|||
# Try unordered word search on each of the search fields |
|||
for rec_name in [self._rec_name] + other_names: |
|||
domain = [(rec_name, operator, x) |
|||
for x in name.split() if x] |
|||
recs = self.search(domain, limit=limit) |
|||
if recs: |
|||
return recs.name_get() |
|||
return res |
|||
return name_search |
|||
|
|||
if ids is None: |
|||
ids = self.search(cr, SUPERUSER_ID, []) |
|||
for model in self.browse(cr, SUPERUSER_ID, ids): |
|||
Model = self.pool.get(model.model) |
|||
if Model: |
|||
Model._patch_method('name_search', make_name_search()) |
|||
return super(ModelExtended, self)._register_hook(cr) |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Daniel Reis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from . import test_name_search |
@ -0,0 +1,44 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Daniel Reis |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|||
|
|||
from openerp.tests.common import TransactionCase, at_install, post_install |
|||
|
|||
|
|||
@at_install(False) |
|||
@post_install(True) |
|||
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 |
|||
self.Partner = self.env['res.partner'] |
|||
self.partner1 = self.Partner.create( |
|||
{'name': 'Johann Gambolputty of Ulm', |
|||
'phone': '+351 555 777'}) |
|||
self.partner2 = self.Partner.create( |
|||
{'name': 'Luigi Verconti', |
|||
'phone': '+351 777 555'}) |
|||
|
|||
def test_NameSearchSearchWithSpaces(self): |
|||
"""Name Search Match full string, honoring spaces""" |
|||
res = self.Partner.name_search('777 555') |
|||
self.assertEqual(res[0][0], self.partner2.id) |
|||
|
|||
def test_NameSearchOrdered(self): |
|||
"""Name Search Match by words, honoring order""" |
|||
res = self.Partner.name_search('johann ulm') |
|||
# res is a list of tuples (id, name) |
|||
self.assertEqual(res[0][0], self.partner1.id) |
|||
|
|||
def test_NameSearchUnordered(self): |
|||
"""Name Search Math by unordered words""" |
|||
res = self.Partner.name_search('ulm gambol') |
|||
self.assertEqual(res[0][0], self.partner1.id) |
|||
|
|||
def test_NameSearchMustMatchAllWords(self): |
|||
"""Name Search Must Match All Words""" |
|||
res = self.Partner.name_search('ulm 555 777') |
|||
self.assertFalse(res) |
@ -0,0 +1,25 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- © 2016 Daniel Reis |
|||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|||
|
|||
<openerp> |
|||
<data> |
|||
|
|||
<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="name_search_ids" |
|||
widget="many2many_tags" |
|||
domain="[('model_id', '=', id)]" |
|||
/> |
|||
</field> |
|||
|
|||
</field> |
|||
</record> |
|||
|
|||
</data> |
|||
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue