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:
parent
bfd1b034ee
commit
f9d22f59f2
|
@ -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">
|
<div id="${field_name}-container" class="autocomplete-container">
|
||||||
${h.hidden(field_name, id=field_name, value=field_value)}
|
${h.hidden(field_name, id=field_name, value=field_value)}
|
||||||
${h.text(field_name+'-textbox', id=field_name+'-textbox', value=field_display,
|
${h.text(field_name+'-textbox', id=field_name+'-textbox', value=field_display,
|
||||||
|
@ -13,9 +15,15 @@
|
||||||
$('#${field_name}-textbox').autocomplete({
|
$('#${field_name}-textbox').autocomplete({
|
||||||
source: '${service_url}',
|
source: '${service_url}',
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
|
% for key, value in options.items():
|
||||||
|
${key}: ${value},
|
||||||
|
% endfor
|
||||||
focus: function(event, ui) {
|
focus: function(event, ui) {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
% if select:
|
||||||
|
select: ${select}
|
||||||
|
% else:
|
||||||
select: function(event, ui) {
|
select: function(event, ui) {
|
||||||
$('#${field_name}').val(ui.item.value);
|
$('#${field_name}').val(ui.item.value);
|
||||||
$('#${field_name}-display span:first').text(ui.item.label);
|
$('#${field_name}-display span:first').text(ui.item.label);
|
||||||
|
@ -26,6 +34,7 @@
|
||||||
% endif
|
% endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
% endif
|
||||||
});
|
});
|
||||||
$('#${field_name}-change').click(function() {
|
$('#${field_name}-change').click(function() {
|
||||||
$('#${field_name}').val('');
|
$('#${field_name}').val('');
|
||||||
|
|
|
@ -28,7 +28,7 @@ Pyramid Views
|
||||||
from .core import *
|
from .core import *
|
||||||
from .grids import *
|
from .grids import *
|
||||||
from .crud import *
|
from .crud import *
|
||||||
from .autocomplete import *
|
from tailbone.views.autocomplete import AutocompleteView
|
||||||
|
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2012 Lance Edgar
|
# Copyright © 2010-2014 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -26,14 +25,20 @@
|
||||||
Autocomplete View
|
Autocomplete View
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .core import View
|
from tailbone.views.core import View
|
||||||
from ..db import Session
|
from tailbone.db import Session
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AutocompleteView']
|
|
||||||
|
|
||||||
|
|
||||||
class AutocompleteView(View):
|
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):
|
def filter_query(self, q):
|
||||||
return q
|
return q
|
||||||
|
@ -51,11 +56,21 @@ class AutocompleteView(View):
|
||||||
def display(self, instance):
|
def display(self, instance):
|
||||||
return getattr(instance, self.fieldname)
|
return getattr(instance, self.fieldname)
|
||||||
|
|
||||||
|
def value(self, instance):
|
||||||
|
"""
|
||||||
|
Determine the data value for a query result instance.
|
||||||
|
"""
|
||||||
|
return instance.uuid
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
term = self.request.params.get('term')
|
"""
|
||||||
if term:
|
View implementation.
|
||||||
|
"""
|
||||||
|
term = self.request.params.get(u'term') or u''
|
||||||
term = term.strip()
|
term = term.strip()
|
||||||
|
if term:
|
||||||
|
term = self.prepare_term(term)
|
||||||
if not term:
|
if not term:
|
||||||
return []
|
return []
|
||||||
results = self.query(term).all()
|
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]
|
||||||
|
|
|
@ -25,12 +25,14 @@
|
||||||
Customer Views
|
Customer Views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy import and_
|
import re
|
||||||
|
|
||||||
from . import SearchableAlchemyGridView, CrudView
|
from sqlalchemy import func, and_
|
||||||
from ..forms import EnumFieldRenderer
|
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 import enum
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
|
@ -133,12 +135,67 @@ class CustomerCrud(CrudView):
|
||||||
return fs
|
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):
|
def add_routes(config):
|
||||||
config.add_route('customers', '/customers')
|
config.add_route(u'customers', u'/customers')
|
||||||
config.add_route('customer.create', '/customers/new')
|
config.add_route(u'customer.create', u'/customers/new')
|
||||||
config.add_route('customer.read', '/customers/{uuid}')
|
config.add_route(u'customer.info', u'/customers/info')
|
||||||
config.add_route('customer.update', '/customers/{uuid}/edit')
|
config.add_route(u'customers.autocomplete', u'/customers/autocomplete')
|
||||||
config.add_route('customer.delete', '/customers/{uuid}/delete')
|
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):
|
def includeme(config):
|
||||||
|
@ -147,6 +204,7 @@ def includeme(config):
|
||||||
config.add_view(CustomersGrid, route_name='customers',
|
config.add_view(CustomersGrid, route_name='customers',
|
||||||
renderer='/customers/index.mako',
|
renderer='/customers/index.mako',
|
||||||
permission='customers.list')
|
permission='customers.list')
|
||||||
|
|
||||||
config.add_view(CustomerCrud, attr='create', route_name='customer.create',
|
config.add_view(CustomerCrud, attr='create', route_name='customer.create',
|
||||||
renderer='/customers/crud.mako',
|
renderer='/customers/crud.mako',
|
||||||
permission='customers.create')
|
permission='customers.create')
|
||||||
|
@ -158,3 +216,14 @@ def includeme(config):
|
||||||
permission='customers.update')
|
permission='customers.update')
|
||||||
config.add_view(CustomerCrud, attr='delete', route_name='customer.delete',
|
config.add_view(CustomerCrud, attr='delete', route_name='customer.delete',
|
||||||
permission='customers.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')
|
||||||
|
|
Loading…
Reference in a new issue