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:
parent
c0f3eae14b
commit
0e9d0ebaf0
|
@ -45,10 +45,37 @@ class AppHandler(object):
|
|||
|
||||
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):
|
||||
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):
|
||||
if not hasattr(self, 'board_handler'):
|
||||
from rattail.board import get_board_handler
|
||||
|
|
29
rattail/autocomplete/__init__.py
Normal file
29
rattail/autocomplete/__init__.py
Normal 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
|
125
rattail/autocomplete/base.py
Normal file
125
rattail/autocomplete/base.py
Normal 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
|
39
rattail/autocomplete/brands.py
Normal file
39
rattail/autocomplete/brands.py
Normal 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'
|
76
rattail/autocomplete/customers.py
Normal file
76
rattail/autocomplete/customers.py
Normal 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
|
39
rattail/autocomplete/departments.py
Normal file
39
rattail/autocomplete/departments.py
Normal 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'
|
48
rattail/autocomplete/employees.py
Normal file
48
rattail/autocomplete/employees.py
Normal 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
|
54
rattail/autocomplete/people.py
Normal file
54
rattail/autocomplete/people.py
Normal 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
|
74
rattail/autocomplete/products.py
Normal file
74
rattail/autocomplete/products.py
Normal 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
|
39
rattail/autocomplete/vendors.py
Normal file
39
rattail/autocomplete/vendors.py
Normal 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'
|
Loading…
Reference in a new issue