From ec70d8563856d043757f70976a1c25ce3b7591c7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 10 Apr 2019 14:20:36 -0500 Subject: [PATCH] Add custom grid filter for phone number fields and use it in various grid views --- tailbone/grids/filters.py | 40 +++++++++++++++++++++++++++++++++++++ tailbone/views/customers.py | 6 ++++-- tailbone/views/employees.py | 8 +++++--- tailbone/views/members.py | 10 ++++++---- tailbone/views/people.py | 6 ++++-- tailbone/views/stores.py | 6 ++++-- 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/tailbone/grids/filters.py b/tailbone/grids/filters.py index 874ad008..5f418c87 100644 --- a/tailbone/grids/filters.py +++ b/tailbone/grids/filters.py @@ -26,6 +26,7 @@ Grid Filters from __future__ import unicode_literals, absolute_import +import re import datetime import logging @@ -765,6 +766,45 @@ class AlchemyGPCFilter(AlchemyGridFilter): return query +class AlchemyPhoneNumberFilter(AlchemyStringFilter): + """ + Special string filter, with logic to deal with phone numbers. + """ + + def parse_value(self, value): + newvalue = None + + # first we try to split according to typical 7- or 10-digit number + digits = re.sub(r'\D', '', value or '') + if len(digits) == 7: + newvalue = "{} {}".format(digits[:3], digits[3:]) + elif len(digits) == 10: + newvalue = "{} {} {}".format(digits[:3], digits[3:6], digits[6:]) + + # if that didn't work, we can also try to split by grouped digits + if not newvalue and value: + parts = re.split(r'\D+', value) + newvalue = ' '.join(parts) + + return newvalue or value + + def filter_contains(self, query, value): + """ + Try to parse the value into "parts" of a phone number, then do a normal + 'ILIKE' query with those parts. + """ + value = self.parse_value(value) + return super(AlchemyPhoneNumberFilter, self).filter_contains(query, value) + + def filter_does_not_contain(self, query, value): + """ + Try to parse the value into "parts" of a phone number, then do a normal + 'NOT ILIKE' query with those parts. + """ + value = self.parse_value(value) + return super(AlchemyPhoneNumberFilter, self).filter_does_not_contain(query, value) + + class GridFilterSet(OrderedDict): """ Collection class for :class:`GridFilter` instances. diff --git a/tailbone/views/customers.py b/tailbone/views/customers.py index 106b7b0f..244512c5 100644 --- a/tailbone/views/customers.py +++ b/tailbone/views/customers.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2018 Lance Edgar +# Copyright © 2010-2019 Lance Edgar # # This file is part of Rattail. # @@ -116,7 +116,9 @@ class CustomersView(MasterView): model.CustomerPhoneNumber.parent_uuid == model.Customer.uuid, model.CustomerPhoneNumber.preference == 1))) g.sorters['phone'] = lambda q, d: q.order_by(getattr(model.CustomerPhoneNumber.number, d)()) - g.set_filter('phone', model.CustomerPhoneNumber.number)#, label="Phone Number") + g.set_filter('phone', model.CustomerPhoneNumber.number, + # label="Phone Number", + factory=grids.filters.AlchemyPhoneNumberFilter) g.set_label('phone', "Phone Number") # email diff --git a/tailbone/views/employees.py b/tailbone/views/employees.py index b8ef0962..b47bfc84 100644 --- a/tailbone/views/employees.py +++ b/tailbone/views/employees.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2018 Lance Edgar +# Copyright © 2010-2019 Lance Edgar # # This file is part of Rattail. # @@ -35,6 +35,7 @@ import colander from deform import widget as dfwidget from webhelpers2.html import tags, HTML +from tailbone import grids from tailbone.db import Session from tailbone.views import MasterView, AutocompleteView @@ -90,8 +91,9 @@ class EmployeesView(MasterView): g.filters['email'] = g.make_filter('email', model.EmployeeEmailAddress.address, label="Email Address") - g.filters['phone'] = g.make_filter('phone', model.EmployeePhoneNumber.number, - label="Phone Number") + g.set_filter('phone', model.EmployeePhoneNumber.number, + label="Phone Number", + factory=grids.filters.AlchemyPhoneNumberFilter) # id if self.request.has_perm('{}.edit'.format(route_prefix)): diff --git a/tailbone/views/members.py b/tailbone/views/members.py index ee8c1856..a39fb8c1 100644 --- a/tailbone/views/members.py +++ b/tailbone/views/members.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2018 Lance Edgar +# Copyright © 2010-2019 Lance Edgar # # This file is part of Rattail. # @@ -29,12 +29,13 @@ from __future__ import unicode_literals, absolute_import import six import sqlalchemy as sa +from rattail.db import model + from deform import widget as dfwidget +from tailbone import grids from tailbone.views import MasterView -from rattail.db import model - class MemberView(MasterView): """ @@ -92,7 +93,8 @@ class MemberView(MasterView): model.MemberPhoneNumber.parent_uuid == model.Member.uuid, model.MemberPhoneNumber.preference == 1))) g.sorters['phone'] = lambda q, d: q.order_by(getattr(model.MemberPhoneNumber.number, d)()) - g.set_filter('phone', model.MemberPhoneNumber.number) + g.set_filter('phone', model.MemberPhoneNumber.number, + factory=grids.filters.AlchemyPhoneNumberFilter) g.set_label('phone', "Phone Number") # email diff --git a/tailbone/views/people.py b/tailbone/views/people.py index e5b96ef1..cdf40766 100644 --- a/tailbone/views/people.py +++ b/tailbone/views/people.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2018 Lance Edgar +# Copyright © 2010-2019 Lance Edgar # # This file is part of Rattail. # @@ -34,6 +34,7 @@ from rattail.db import model, api from pyramid.httpexceptions import HTTPFound, HTTPNotFound from webhelpers2.html import HTML, tags +from tailbone import grids from tailbone.views import MasterView, AutocompleteView @@ -92,7 +93,8 @@ class PeopleView(MasterView): model.PersonPhoneNumber.preference == 1)) g.filters['email'] = g.make_filter('email', model.PersonEmailAddress.address) - g.filters['phone'] = g.make_filter('phone', model.PersonPhoneNumber.number) + g.set_filter('phone', model.PersonPhoneNumber.number, + factory=grids.filters.AlchemyPhoneNumberFilter) g.joiners['customer_id'] = lambda q: q.outerjoin(model.CustomerPerson).outerjoin(model.Customer) g.filters['customer_id'] = g.make_filter('customer_id', model.Customer.id) diff --git a/tailbone/views/stores.py b/tailbone/views/stores.py index 660e14e3..b46fa5f4 100644 --- a/tailbone/views/stores.py +++ b/tailbone/views/stores.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2018 Lance Edgar +# Copyright © 2010-2019 Lance Edgar # # This file is part of Rattail. # @@ -32,6 +32,7 @@ from rattail.db import model import colander +from tailbone import grids from tailbone.views import MasterView @@ -65,7 +66,8 @@ class StoresView(MasterView): model.StorePhoneNumber.parent_uuid == model.Store.uuid, model.StorePhoneNumber.preference == 1))) - g.filters['phone'] = g.make_filter('phone', model.StorePhoneNumber.number) + g.set_filter('phone', model.StorePhoneNumber.number, + factory=grids.filters.AlchemyPhoneNumberFilter) g.filters['email'] = g.make_filter('email', model.StoreEmailAddress.address) g.filters['name'].default_active = True g.filters['name'].default_verb = 'contains'