Add customer phone autocomplete and customer "info" AJAX view.

This autocomplete view is a little different than the typical ones used
prior, and required some refactoring of the base autocomplete view as well
as the autocomplete template.
This commit is contained in:
Lance Edgar 2014-07-13 12:43:58 -07:00
parent bfd1b034ee
commit f9d22f59f2
4 changed files with 125 additions and 32 deletions

View file

@ -1,4 +1,6 @@
<%def name="autocomplete(field_name, service_url, field_value=None, field_display=None, width='300px', selected=None, cleared=None)">
## -*- coding: utf-8 -*-
## TODO: This function signature is getting out of hand...
<%def name="autocomplete(field_name, service_url, field_value=None, field_display=None, width=u'300px', select=None, selected=None, cleared=None, options={})">
<div id="${field_name}-container" class="autocomplete-container">
${h.hidden(field_name, id=field_name, value=field_value)}
${h.text(field_name+'-textbox', id=field_name+'-textbox', value=field_display,
@ -13,19 +15,26 @@
$('#${field_name}-textbox').autocomplete({
source: '${service_url}',
autoFocus: true,
% for key, value in options.items():
${key}: ${value},
% endfor
focus: function(event, ui) {
return false;
},
select: function(event, ui) {
$('#${field_name}').val(ui.item.value);
$('#${field_name}-display span:first').text(ui.item.label);
$('#${field_name}-textbox').hide();
$('#${field_name}-display').show();
% if selected:
${selected}(ui.item.value, ui.item.label);
% endif
return false;
}
% if select:
select: ${select}
% else:
select: function(event, ui) {
$('#${field_name}').val(ui.item.value);
$('#${field_name}-display span:first').text(ui.item.label);
$('#${field_name}-textbox').hide();
$('#${field_name}-display').show();
% if selected:
${selected}(ui.item.value, ui.item.label);
% endif
return false;
}
% endif
});
$('#${field_name}-change').click(function() {
$('#${field_name}').val('');

View file

@ -28,7 +28,7 @@ Pyramid Views
from .core import *
from .grids import *
from .crud import *
from .autocomplete import *
from tailbone.views.autocomplete import AutocompleteView
def home(request):

View file

@ -1,9 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar
# Copyright © 2010-2014 Lance Edgar
#
# This file is part of Rattail.
#
@ -26,14 +25,20 @@
Autocomplete View
"""
from .core import View
from ..db import Session
__all__ = ['AutocompleteView']
from tailbone.views.core import View
from tailbone.db import Session
class AutocompleteView(View):
"""
Base class for generic autocomplete views.
"""
def prepare_term(self, term):
"""
If necessary, massage the incoming search term for use with the query.
"""
return term
def filter_query(self, q):
return q
@ -51,11 +56,21 @@ class AutocompleteView(View):
def display(self, instance):
return getattr(instance, self.fieldname)
def value(self, instance):
"""
Determine the data value for a query result instance.
"""
return instance.uuid
def __call__(self):
term = self.request.params.get('term')
"""
View implementation.
"""
term = self.request.params.get(u'term') or u''
term = term.strip()
if term:
term = term.strip()
term = self.prepare_term(term)
if not term:
return []
results = self.query(term).all()
return [{'label': self.display(x), 'value': x.uuid} for x in results]
return [{u'label': self.display(x), u'value': self.value(x)} for x in results]

View file

@ -25,12 +25,14 @@
Customer Views
"""
from sqlalchemy import and_
import re
from . import SearchableAlchemyGridView, CrudView
from ..forms import EnumFieldRenderer
from sqlalchemy import func, and_
from sqlalchemy.orm import joinedload
from ..db import Session
from tailbone.views import SearchableAlchemyGridView, CrudView, AutocompleteView
from tailbone.forms import EnumFieldRenderer
from tailbone.db import Session
from rattail import enum
from rattail.db import model
@ -133,12 +135,67 @@ class CustomerCrud(CrudView):
return fs
class CustomerNameAutocomplete(AutocompleteView):
"""
Autocomplete view which operates on customer name.
"""
mapped_class = model.Customer
fieldname = u'name'
class CustomerPhoneAutocomplete(AutocompleteView):
"""
Autocomplete view which operates on customer phone number.
.. 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.
"""
invalid_pattern = re.compile(ur'\D')
def prepare_term(self, term):
return self.invalid_pattern.sub(u'', term)
def query(self, term):
return Session.query(model.CustomerPhoneNumber)\
.filter(func.regexp_replace(model.CustomerPhoneNumber.number, ur'\D', u'', u'g').like(u'%{0}%'.format(term)))\
.order_by(model.CustomerPhoneNumber.number)\
.options(joinedload(model.CustomerPhoneNumber.customer))
def display(self, phone):
return u"{0} {1}".format(phone.number, phone.customer)
def value(self, phone):
return phone.customer.uuid
def customer_info(request):
"""
View which returns simple dictionary of info for a particular customer.
"""
uuid = request.params.get(u'uuid')
customer = Session.query(model.Customer).get(uuid) if uuid else None
if not customer:
return {}
return {
u'uuid': customer.uuid,
u'name': customer.name,
u'phone_number': customer.phone.number if customer.phone else u'',
}
def add_routes(config):
config.add_route('customers', '/customers')
config.add_route('customer.create', '/customers/new')
config.add_route('customer.read', '/customers/{uuid}')
config.add_route('customer.update', '/customers/{uuid}/edit')
config.add_route('customer.delete', '/customers/{uuid}/delete')
config.add_route(u'customers', u'/customers')
config.add_route(u'customer.create', u'/customers/new')
config.add_route(u'customer.info', u'/customers/info')
config.add_route(u'customers.autocomplete', u'/customers/autocomplete')
config.add_route(u'customers.autocomplete.phone', u'/customers/autocomplete/phone')
config.add_route(u'customer.read', u'/customers/{uuid}')
config.add_route(u'customer.update', u'/customers/{uuid}/edit')
config.add_route(u'customer.delete', u'/customers/{uuid}/delete')
def includeme(config):
@ -147,6 +204,7 @@ def includeme(config):
config.add_view(CustomersGrid, route_name='customers',
renderer='/customers/index.mako',
permission='customers.list')
config.add_view(CustomerCrud, attr='create', route_name='customer.create',
renderer='/customers/crud.mako',
permission='customers.create')
@ -158,3 +216,14 @@ def includeme(config):
permission='customers.update')
config.add_view(CustomerCrud, attr='delete', route_name='customer.delete',
permission='customers.delete')
config.add_view(CustomerNameAutocomplete, route_name=u'customers.autocomplete',
renderer=u'json',
permission=u'customers.list')
config.add_view(CustomerPhoneAutocomplete, route_name=u'customers.autocomplete.phone',
renderer=u'json',
permission=u'customers.list')
config.add_view(customer_info, route_name=u'customer.info',
renderer=u'json',
permission=u'customers.read')