feat: add basic autocomplete support, for Person
URL endpoint only for now, form widget to come later
This commit is contained in:
parent
4bf2bb42fb
commit
9d261de45a
6 changed files with 391 additions and 203 deletions
|
@ -263,6 +263,12 @@ class MasterView(View):
|
|||
|
||||
This is optional; see also :meth:`get_form_fields()`.
|
||||
|
||||
.. attribute:: has_autocomplete
|
||||
|
||||
Boolean indicating whether the view model supports
|
||||
"autocomplete" - i.e. it should have an :meth:`autocomplete()`
|
||||
view. Default is ``False``.
|
||||
|
||||
.. attribute:: configurable
|
||||
|
||||
Boolean indicating whether the master view supports
|
||||
|
@ -286,6 +292,7 @@ class MasterView(View):
|
|||
viewable = True
|
||||
editable = True
|
||||
deletable = True
|
||||
has_autocomplete = False
|
||||
configurable = False
|
||||
|
||||
# current action
|
||||
|
@ -573,6 +580,84 @@ class MasterView(View):
|
|||
session = self.app.get_session(obj)
|
||||
session.delete(obj)
|
||||
|
||||
##############################
|
||||
# autocomplete methods
|
||||
##############################
|
||||
|
||||
def autocomplete(self):
|
||||
"""
|
||||
View which accepts a single ``term`` param, and returns a JSON
|
||||
list of autocomplete results to match.
|
||||
|
||||
By default, this view is included only if
|
||||
:attr:`has_autocomplete` is true. It usually maps to a URL
|
||||
like ``/widgets/autocomplete``.
|
||||
|
||||
Subclass generally does not need to override this method, but
|
||||
rather should override the others which this calls:
|
||||
|
||||
* :meth:`autocomplete_data()`
|
||||
* :meth:`autocomplete_normalize()`
|
||||
"""
|
||||
term = self.request.GET.get('term', '')
|
||||
if not term:
|
||||
return []
|
||||
|
||||
data = self.autocomplete_data(term)
|
||||
if not data:
|
||||
return []
|
||||
|
||||
max_results = 100 # TODO
|
||||
|
||||
results = []
|
||||
for obj in data[:max_results]:
|
||||
normal = self.autocomplete_normalize(obj)
|
||||
if normal:
|
||||
results.append(normal)
|
||||
|
||||
return results
|
||||
|
||||
def autocomplete_data(self, term):
|
||||
"""
|
||||
Should return the data/query for the "matching" model records,
|
||||
based on autocomplete search term. This is called by
|
||||
:meth:`autocomplete()`.
|
||||
|
||||
Subclass must override this; default logic returns no data.
|
||||
|
||||
:param term: String search term as-is from user, e.g. "foo bar".
|
||||
|
||||
:returns: List of data records, or SQLAlchemy query.
|
||||
"""
|
||||
|
||||
def autocomplete_normalize(self, obj):
|
||||
"""
|
||||
Should return a "normalized" version of the given model
|
||||
record, suitable for autocomplete JSON results. This is
|
||||
called by :meth:`autocomplete()`.
|
||||
|
||||
Subclass may need to override this; default logic is
|
||||
simplistic but will work for basic models. It returns the
|
||||
"autocomplete results" dict for the object::
|
||||
|
||||
{
|
||||
'value': obj.uuid,
|
||||
'label': str(obj),
|
||||
}
|
||||
|
||||
The 2 keys shown are required; any other keys will be ignored
|
||||
by the view logic but may be useful on the frontend widget.
|
||||
|
||||
:param obj: Model record/instance.
|
||||
|
||||
:returns: Dict of "autocomplete results" format, as shown
|
||||
above.
|
||||
"""
|
||||
return {
|
||||
'value': obj.uuid,
|
||||
'label': str(obj),
|
||||
}
|
||||
|
||||
##############################
|
||||
# configure methods
|
||||
##############################
|
||||
|
@ -1888,6 +1973,15 @@ class MasterView(View):
|
|||
f'{permission_prefix}.delete',
|
||||
f"Delete {model_title}")
|
||||
|
||||
# autocomplete
|
||||
if cls.has_autocomplete:
|
||||
config.add_route(f'{route_prefix}.autocomplete',
|
||||
f'{url_prefix}/autocomplete')
|
||||
config.add_view(cls, attr='autocomplete',
|
||||
route_name=f'{route_prefix}.autocomplete',
|
||||
renderer='json',
|
||||
permission=f'{route_prefix}.list')
|
||||
|
||||
# configure
|
||||
if cls.configurable:
|
||||
config.add_route(f'{route_prefix}.configure',
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
Views for people
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from wuttjamaican.db.model import Person
|
||||
from wuttaweb.views import MasterView
|
||||
|
||||
|
@ -46,6 +48,7 @@ class PersonView(MasterView):
|
|||
model_title_plural = "People"
|
||||
route_prefix = 'people'
|
||||
sort_defaults = 'full_name'
|
||||
has_autocomplete = True
|
||||
|
||||
grid_columns = [
|
||||
'full_name',
|
||||
|
@ -85,6 +88,17 @@ class PersonView(MasterView):
|
|||
if 'users' in f:
|
||||
f.fields.remove('users')
|
||||
|
||||
def autocomplete_query(self, term):
|
||||
""" """
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
query = session.query(model.Person)
|
||||
criteria = [model.Person.full_name.ilike(f'%{word}%')
|
||||
for word in term.split()]
|
||||
query = query.filter(sa.and_(*criteria))\
|
||||
.order_by(model.Person.full_name)
|
||||
return query
|
||||
|
||||
def view_profile(self, session=None):
|
||||
""" """
|
||||
person = self.get_instance(session=session)
|
||||
|
|
|
@ -197,6 +197,14 @@ class SettingView(MasterView):
|
|||
model_title = "Raw Setting"
|
||||
sort_defaults = 'name'
|
||||
|
||||
# TODO: master should handle this (per model key)
|
||||
def configure_grid(self, g):
|
||||
""" """
|
||||
super().configure_grid(g)
|
||||
|
||||
# name
|
||||
g.set_link('name')
|
||||
|
||||
def configure_form(self, f):
|
||||
""" """
|
||||
super().configure_form(f)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue