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">
|
||||
${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('');
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in a new issue