3
0
Fork 0

feat: add basic autocomplete support, for Person

URL endpoint only for now, form widget to come later
This commit is contained in:
Lance Edgar 2024-08-21 11:46:38 -05:00
parent 4bf2bb42fb
commit 9d261de45a
6 changed files with 391 additions and 203 deletions

View file

@ -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',

View file

@ -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)

View file

@ -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)