Add support for Autocomplete Handlers

all autocompleters needed thus far are included; this avoids the need
to implement each via Tailbone View classes etc.
This commit is contained in:
Lance Edgar 2021-09-30 19:27:42 -04:00
parent c0f3eae14b
commit 0e9d0ebaf0
10 changed files with 550 additions and 0 deletions

View file

@ -45,10 +45,37 @@ class AppHandler(object):
aka. "one handler to bind them all" aka. "one handler to bind them all"
""" """
default_autocompleters = {
'brands': 'rattail.autocomplete.brands:BrandAutocompleter',
'customers': 'rattail.autocomplete.customers:CustomerAutocompleter',
'customers.phone': 'rattail.autocomplete.customers:CustomerPhoneAutocompleter',
'employees': 'rattail.autocomplete.employees:EmployeeAutocompleter',
'departments': 'rattail.autocomplete.departments:DepartmentAutocompleter',
'people': 'rattail.autocomplete.people:PersonAutocompleter',
'people.employees': 'rattail.autocomplete.people:PersonEmployeeAutocompleter',
'products': 'rattail.autocomplete.products:ProductAutocompleter',
'products.all': 'rattail.autocomplete.products:ProductAllAutocompleter',
'vendors': 'rattail.autocomplete.vendors:VendorAutocompleter',
}
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
def get_autocompleter(self, key, **kwargs):
"""
Returns an Autocompleter instance corresponding to the given
key. Config may determine where the Autocompleter class
lives, or there are some defaults registered within the app
handler which may be used.
"""
spec = self.config.get('rattail', 'autocomplete.{}'.format(key))
if not spec:
spec = self.default_autocompleters.get(key)
if spec:
return load_object(spec)(self.config)
raise NotImplementedError("cannot locate autocompleter for key: {}".format(key))
def get_board_handler(self, **kwargs): def get_board_handler(self, **kwargs):
if not hasattr(self, 'board_handler'): if not hasattr(self, 'board_handler'):
from rattail.board import get_board_handler from rattail.board import get_board_handler

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete Handlers
"""
from __future__ import unicode_literals, absolute_import
from .base import Autocompleter

View file

@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete handlers - base class
"""
from __future__ import unicode_literals, absolute_import
import sqlalchemy as sa
class Autocompleter(object):
"""
Base class and partial default implementation for autocomplete
handlers. It is expected that all autocomplete handlers will
ultimately inherit from this base class, therefore it defines the
implementation "interface" loosely speaking. Custom autocomplete
handlers are welcome to supplement or override this as needed, and
in fact must do so for certain aspects.
.. attribute: autocompleter_key
The key indicates what "type" of autocompleter this is. It
should be a string, e.g. ``'products'``. It will generally
correspond to the route names used in Tailbone, though not
always.
"""
autocompleter_key = None
def __init__(self, config):
if not self.autocompleter_key:
raise NotImplementedError("You must define `autocompleter_key` "
"attribute for handler class: {}".format(
self.__class__))
self.config = config
self.app = self.config.get_app()
self.enum = config.get_enum()
self.model = config.get_model()
def get_model_class(self):
return self.model_class
@property
def autocomplete_fieldname(self):
raise NotImplementedError("You must define `autocomplete_fieldname` "
"attribute for handler class: {}".format(
self.__class__))
def autocomplete(self, session, term, **kwargs):
"""
The main reason this class exists. This method accepts a
``term`` (string) argument and will return a sequence of
matching results.
"""
term = self.prepare_autocomplete_term(term)
if not term:
return []
results = self.get_autocomplete_data(session, term)
return [{'label': self.autocomplete_display(x),
'value': self.autocomplete_value(x)}
for x in results]
def prepare_autocomplete_term(self, term, **kwargs):
"""
If necessary, massage the incoming search term for use with
the autocomplete query.
"""
return term
def get_autocomplete_data(self, session, term, **kwargs):
"""
Collect data for all matching results, for the given search term.
"""
query = self.make_autocomplete_query(session, term)
return query.all()
def make_autocomplete_query(self, session, term, **kwargs):
"""
Build the complete query from which to obtain search results.
"""
# we are querying one table (and column) primarily
model_class = self.get_model_class()
query = session.query(model_class)
# filter according to business logic etc. if applicable
query = self.filter_autocomplete_query(session, query)
# filter according to search term(s)
column = getattr(model_class, self.autocomplete_fieldname)
criteria = [column.ilike('%{}%'.format(word))
for word in term.split()]
query = query.filter(sa.and_(*criteria))
# sort results by something meaningful
query = query.order_by(column)
return query
def filter_autocomplete_query(self, session, query, **kwargs):
return query
def autocomplete_display(self, obj):
return getattr(obj, self.autocomplete_fieldname)
def autocomplete_value(self, obj):
return obj.uuid

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete Handler for Brands
"""
from __future__ import unicode_literals, absolute_import
from rattail.autocomplete import Autocompleter
from rattail.db import model
class BrandAutocompleter(Autocompleter):
"""
Autocompleter for Brands
"""
autocompleter_key = 'brands'
model_class = model.Brand
autocomplete_fieldname = 'name'

View file

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete Handler for Customers
"""
from __future__ import unicode_literals, absolute_import
import re
import sqlalchemy as sa
from sqlalchemy import orm
from rattail.autocomplete import Autocompleter
from rattail.db import model
class CustomerAutocompleter(Autocompleter):
"""
Autocompleter for Customers (by name)
"""
autocompleter_key = 'customers'
model_class = model.Customer
autocomplete_fieldname = 'name'
class CustomerPhoneAutocompleter(Autocompleter):
"""
Autocompleter for Customers (by phone)
.. note::
As currently implemented, this view will only work with a
PostgreSQL database. It normalizes the user's search term and
the database values to numeric digits only (i.e. removes
special characters from each) in order to be able to perform
smarter matching. However normalizing the database value
currently uses the PG SQL ``regexp_replace()`` function.
"""
autocompleter_key = 'customers.phone'
invalid_pattern = re.compile(r'\D')
def prepare_autocomplete_term(self, term, **kwargs):
return self.invalid_pattern.sub('', term)
def make_autocomplete_query(self, session, term, **kwargs):
model = self.model
return session.query(model.CustomerPhoneNumber)\
.filter(sa.func.regexp_replace(model.CustomerPhoneNumber.number, r'\D', '', 'g').like('%{}%'.format(term)))\
.order_by(model.CustomerPhoneNumber.number)\
.options(orm.joinedload(model.CustomerPhoneNumber.customer))
def autocomplete_display(self, phone):
return "{} {}".format(phone.number, phone.customer)
def autocomplete_value(self, phone):
return phone.customer.uuid

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete Handler for Departments
"""
from __future__ import unicode_literals, absolute_import
from rattail.autocomplete import Autocompleter
from rattail.db import model
class DepartmentAutocompleter(Autocompleter):
"""
Autocompleter for Departments
"""
autocompleter_key = 'departments'
model_class = model.Department
autocomplete_fieldname = 'name'

View file

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete Handler for Employees
"""
from __future__ import unicode_literals, absolute_import
from rattail.autocomplete import Autocompleter
from rattail.db import model
class EmployeeAutocompleter(Autocompleter):
"""
Autocompleter for Employees
"""
autocompleter_key = 'employees'
model_class = model.Person
autocomplete_fieldname = 'display_name'
def filter_autocomplete_query(self, session, query, **kwargs):
model = self.model
query = query.join(model.Employee)\
.filter(model.Employee.status == self.enum.EMPLOYEE_STATUS_CURRENT)
return query
def autocomplete_value(self, person):
return person.employee.uuid

View file

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete Handler for People
"""
from __future__ import unicode_literals, absolute_import
from rattail.autocomplete import Autocompleter
from rattail.db import model
class PersonAutocompleter(Autocompleter):
"""
Autocompleter for People
"""
autocompleter_key = 'people'
model_class = model.Person
autocomplete_fieldname = 'display_name'
class PersonEmployeeAutocompleter(Autocompleter):
"""
Autocompleter for People, but restricted to return only results
for people who are (or have been) an employee.
"""
autocompleter_key = 'people.employees'
model_class = model.Person
autocomplete_fieldname = 'display_name'
def filter_autocomplete_query(self, session, query, **kwargs):
model = self.model
query = query.join(model.Employee)
return query

View file

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete Handler for Products
"""
from __future__ import unicode_literals, absolute_import
import sqlalchemy as sa
from rattail.autocomplete import Autocompleter
from rattail.db import model
class ProductAutocompleter(Autocompleter):
"""
Autocompleter for Products
"""
autocompleter_key = 'products'
model_class = model.Product
autocomplete_fieldname = 'description'
def make_autocomplete_query(self, session, term, **kwargs):
model = self.model
query = session.query(model.Product)\
.outerjoin(model.Brand)\
.filter(sa.or_(
model.Brand.name.ilike('%{}%'.format(term)),
model.Product.description.ilike('%{}%'.format(term))))
query = self.filter_autocomplete_query(session, query)
query = query.order_by(model.Brand.name,
model.Product.description)\
.options(orm.joinedload(model.Product.brand))
return query
def filter_autocomplete_query(self, session, query, **kwargs):
# do not show "deleted" items by default
query = query.filter(model.Product.deleted == False)
return query
def autocomplete_display(self, product):
return product.full_description
class ProductAllAutocompleter(ProductAutocompleter):
"""
Autocompleter for Products, which shows *all* results, including
"deleted" items etc.
"""
def filter_autocomplete_query(self, session, query, **kwargs):
return query

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Autocomplete Handler for Vendors
"""
from __future__ import unicode_literals, absolute_import
from rattail.autocomplete import Autocompleter
from rattail.db import model
class VendorAutocompleter(Autocompleter):
"""
Autocompleter for Vendors
"""
autocompleter_key = 'vendors'
model_class = model.Vendor
autocomplete_fieldname = 'name'