Add global search
review582002
This commit is contained in:
1 parent
290791e0df
commit
c1fd506f77
10 files changed
+149
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Add global search
|
||||
* Replace view shortcut by menu favorite
|
||||
* Store default language in database
|
||||
* Add icon attribute on fields
|
||||
|
||||
@@ -294,6 +294,11 @@ CLass methods:
|
||||
Searcher for the :class:`trytond.model.fields.Function` field
|
||||
:attr:`rec_name`.
|
||||
|
||||
.. classmethod:: ModelStorage.search_global(cls, text)
|
||||
|
||||
Yield tuples (id, rec_name, icon) for records matching text.
|
||||
It is used for the global search.
|
||||
|
||||
.. classmethod:: ModelStorage.browse(ids)
|
||||
|
||||
Return a list of record instance for the ``ids``.
|
||||
|
||||
@@ -21,6 +21,8 @@ Prerequisites
|
||||
* Optional: sphinx (http://sphinx.pocoo.org/)
|
||||
* Optional: simplejson (http://undefined.org/python/#simplejson)
|
||||
* Optional: cdecimal (http://www.bytereef.org/mpdecimal/index.html)
|
||||
* Optional: python-Levenshtein
|
||||
(http://github.com/miohtama/python-Levenshtein)
|
||||
|
||||
Install Tryton
|
||||
==============
|
||||
|
||||
@@ -71,6 +71,7 @@ setup(name=PACKAGE,
|
||||
'timezone': ['pytz'],
|
||||
'simplejson': ['simplejson'],
|
||||
'cdecimal': ['cdecimal'],
|
||||
'Levenshtein': ['python-Levenshtein'],
|
||||
},
|
||||
zip_safe=False,
|
||||
test_suite='trytond.tests',
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
import datetime
|
||||
import re
|
||||
import heapq
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..report import Report
|
||||
from ..wizard import Wizard, StateView, StateAction, Button
|
||||
@@ -10,6 +12,10 @@ from ..cache import Cache
|
||||
from ..pool import Pool
|
||||
from ..pyson import Bool, Eval
|
||||
from ..rpc import RPC
|
||||
try:
|
||||
from ..tools.StringMatcher import StringMatcher
|
||||
except ImportError:
|
||||
from difflib import SequenceMatcher as StringMatcher
|
||||
|
||||
__all__ = [
|
||||
'Model', 'ModelField', 'ModelAccess', 'ModelFieldAccess', 'ModelButton',
|
||||
@@ -39,6 +45,7 @@ class Model(ModelSQL, ModelView):
|
||||
depends=['module'])
|
||||
module = fields.Char('Module',
|
||||
help="Module in which this model is defined", readonly=True)
|
||||
global_search_p = fields.Boolean('Global Search')
|
||||
fields = fields.One2Many('ir.model.field', 'model', 'Fields',
|
||||
required=True)
|
||||
|
||||
@@ -58,6 +65,7 @@ class Model(ModelSQL, ModelView):
|
||||
cls._order.insert(0, ('model', 'ASC'))
|
||||
cls.__rpc__.update({
|
||||
'list_models': RPC(),
|
||||
'global_search': RPC(),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@@ -106,6 +114,40 @@ class Model(ModelSQL, ModelView):
|
||||
# Restart the cache of models_get
|
||||
Property._models_get_cache.clear()
|
||||
|
||||
@classmethod
|
||||
def global_search(cls, text, limit):
|
||||
"""
|
||||
Search on models for text
|
||||
Returns a list of tuple (ratio, model, model_name, id, rec_name, icon)
|
||||
The size of the list is limited to limit
|
||||
"""
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
|
||||
if not limit > 0:
|
||||
raise ValueError('limit must be > 0: %r' % (limit,))
|
||||
|
||||
models = cls.search(['OR',
|
||||
('global_search_p', '=', True),
|
||||
('name', '=', 'ir.ui.menu'),
|
||||
])
|
||||
access = ModelAccess.get_access([m.model for m in models])
|
||||
s = StringMatcher()
|
||||
s.set_seq2(text.decode('utf-8'))
|
||||
|
||||
def generate():
|
||||
for model in models:
|
||||
if not access[model.model]['read']:
|
||||
continue
|
||||
Model = pool.get(model.model)
|
||||
if not hasattr(Model, 'search_global'):
|
||||
continue
|
||||
for id_, rec_name, icon in Model.search_global(text):
|
||||
s.set_seq1(rec_name)
|
||||
yield (s.ratio(), model.model, model.rec_name,
|
||||
id_, rec_name, icon)
|
||||
return heapq.nlargest(int(limit), generate())
|
||||
|
||||
|
||||
class ModelField(ModelSQL, ModelView):
|
||||
"Model field"
|
||||
|
||||
@@ -150,6 +150,15 @@ class UIMenu(ModelSQL, ModelView):
|
||||
#TODO Handle list
|
||||
return [('name',) + tuple(clause[1:])]
|
||||
|
||||
@classmethod
|
||||
def search_global(cls, text):
|
||||
# TODO improve search clause
|
||||
for record in cls.search([
|
||||
('rec_name', 'ilike', '%%%s%%' % text),
|
||||
]):
|
||||
if record.action:
|
||||
yield record.id, record.rec_name, record.icon
|
||||
|
||||
@classmethod
|
||||
def search(cls, domain, offset=0, limit=None, order=None, count=False,
|
||||
query_string=False):
|
||||
|
||||
@@ -8,6 +8,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="model"/>
|
||||
<label name="module"/>
|
||||
<field name="module"/>
|
||||
<label name="global_search_p"/>
|
||||
<field name="global_search_p"/>
|
||||
<separator name="info" colspan="6"/>
|
||||
<field name="info" colspan="6"/>
|
||||
<field name="fields" colspan="6" readonly="1"/>
|
||||
|
||||
@@ -5,5 +5,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name"/>
|
||||
<field name="model"/>
|
||||
<field name="info"/>
|
||||
<field name="global_search_p"/>
|
||||
<field name="module"/>
|
||||
</tree>
|
||||
@@ -414,6 +414,17 @@ class ModelStorage(Model):
|
||||
return []
|
||||
return [(rec_name,) + clause[1:]]
|
||||
|
||||
@classmethod
|
||||
def search_global(cls, text):
|
||||
'''
|
||||
Yield tuples (id, rec_name, icon) for text
|
||||
'''
|
||||
# TODO improve search clause
|
||||
for record in cls.search([
|
||||
('rec_name', 'ilike', '%%%s%%' % text),
|
||||
]):
|
||||
yield record.id, record.rec_name, None
|
||||
|
||||
@classmethod
|
||||
def browse(cls, ids):
|
||||
'''
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
|
||||
# Code come from python-Levenshtein
|
||||
|
||||
from Levenshtein import *
|
||||
from warnings import warn
|
||||
|
||||
|
||||
class StringMatcher:
|
||||
"""A SequenceMatcher-like class built on the top of Levenshtein"""
|
||||
|
||||
def _reset_cache(self):
|
||||
self._ratio = self._distance = None
|
||||
self._opcodes = self._editops = self._matching_blocks = None
|
||||
|
||||
def __init__(self, isjunk=None, seq1='', seq2=''):
|
||||
if isjunk:
|
||||
warn("isjunk not NOT implemented, it will be ignored")
|
||||
self._str1, self._str2 = seq1, seq2
|
||||
self._reset_cache()
|
||||
|
||||
def set_seqs(self, seq1, seq2):
|
||||
self._str1, self._str2 = seq1, seq2
|
||||
self._reset_cache()
|
||||
|
||||
def set_seq1(self, seq1):
|
||||
self._str1 = seq1
|
||||
self._reset_cache()
|
||||
|
||||
def set_seq2(self, seq2):
|
||||
self._str2 = seq2
|
||||
self._reset_cache()
|
||||
|
||||
def get_opcodes(self):
|
||||
if not self._opcodes:
|
||||
if self._editops:
|
||||
self._opcodes = opcodes(self._editops, self._str1, self._str2)
|
||||
else:
|
||||
self._opcodes = opcodes(self._str1, self._str2)
|
||||
return self._opcodes
|
||||
|
||||
def get_editops(self):
|
||||
if not self._editops:
|
||||
if self._opcodes:
|
||||
self._editops = editops(self._opcodes, self._str1, self._str2)
|
||||
else:
|
||||
self._editops = editops(self._str1, self._str2)
|
||||
return self._editops
|
||||
|
||||
def get_matching_blocks(self):
|
||||
if not self._matching_blocks:
|
||||
self._matching_blocks = matching_blocks(self.get_opcodes(),
|
||||
self._str1, self._str2)
|
||||
return self._matching_blocks
|
||||
|
||||
def ratio(self):
|
||||
if not self._ratio:
|
||||
self._ratio = ratio(self._str1, self._str2)
|
||||
return self._ratio
|
||||
|
||||
def quick_ratio(self):
|
||||
# This is usually quick enough :o)
|
||||
if not self._ratio:
|
||||
self._ratio = ratio(self._str1, self._str2)
|
||||
return self._ratio
|
||||
|
||||
def real_quick_ratio(self):
|
||||
len1, len2 = len(self._str1), len(self._str2)
|
||||
return 2.0 * min(len1, len2) / (len1 + len2)
|
||||
|
||||
def distance(self):
|
||||
if not self._distance:
|
||||
self._distance = distance(self._str1, self._str2)
|
||||
return self._distance
|
||||
Reference in new issue
Block a user