Refactor autocomplete view logic to leverage new "autocompleters"
finally! this cleans up some view config and AFAIK there is no loss in functionality etc.
This commit is contained in:
parent
e0dff55ffa
commit
a7f4b2e6ef
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2019 Lance Edgar
|
||||
# Copyright © 2010-2021 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -29,9 +29,6 @@ from __future__ import unicode_literals, absolute_import
|
|||
from .core import View
|
||||
from .master import MasterView
|
||||
|
||||
# TODO: deprecate / remove some of this
|
||||
from .autocomplete import AutocompleteView
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
# -*- 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 View
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
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
|
||||
|
||||
def make_query(self, term):
|
||||
"""
|
||||
Make and return the "complete" query for the given search term.
|
||||
"""
|
||||
# we are querying one table (and column) primarily
|
||||
query = Session.query(self.mapped_class)
|
||||
column = getattr(self.mapped_class, self.fieldname)
|
||||
|
||||
# filter according to business logic, if applicable
|
||||
query = self.filter_query(query)
|
||||
|
||||
# filter according to search term(s)
|
||||
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 query(self, term):
|
||||
return self.make_query(term)
|
||||
|
||||
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 get_data(self, term):
|
||||
return self.query(term).all()
|
||||
|
||||
def __call__(self):
|
||||
"""
|
||||
View implementation.
|
||||
"""
|
||||
term = self.request.params.get(u'term') or u''
|
||||
term = term.strip()
|
||||
if term:
|
||||
term = self.prepare_term(term)
|
||||
if not term:
|
||||
return []
|
||||
results = self.get_data(term)
|
||||
return [{'label': self.display(x), 'value': self.value(x)} for x in results]
|
|
@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
|
|||
|
||||
from rattail.db import model
|
||||
|
||||
from tailbone.views import MasterView, AutocompleteView
|
||||
from tailbone.views import MasterView
|
||||
|
||||
|
||||
class BrandView(MasterView):
|
||||
|
@ -38,6 +38,7 @@ class BrandView(MasterView):
|
|||
model_class = model.Brand
|
||||
has_versions = True
|
||||
bulk_deletable = True
|
||||
supports_autocomplete = True
|
||||
|
||||
mergeable = True
|
||||
merge_additive_fields = [
|
||||
|
@ -133,21 +134,6 @@ class BrandView(MasterView):
|
|||
self.Session.flush()
|
||||
self.Session.delete(removing)
|
||||
|
||||
# TODO: deprecate / remove this
|
||||
BrandsView = BrandView
|
||||
|
||||
|
||||
class BrandsAutocomplete(AutocompleteView):
|
||||
|
||||
mapped_class = model.Brand
|
||||
fieldname = 'name'
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
# autocomplete
|
||||
config.add_route('brands.autocomplete', '/brands/autocomplete')
|
||||
config.add_view(BrandsAutocomplete, route_name='brands.autocomplete',
|
||||
renderer='json', permission='brands.list')
|
||||
|
||||
BrandView.defaults(config)
|
||||
|
|
|
@ -26,11 +26,8 @@ Customer Views
|
|||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import re
|
||||
|
||||
import six
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
|
||||
import colander
|
||||
from pyramid.httpexceptions import HTTPNotFound
|
||||
|
@ -38,7 +35,7 @@ from webhelpers2.html import HTML, tags
|
|||
|
||||
from tailbone import grids
|
||||
from tailbone.db import Session
|
||||
from tailbone.views import MasterView, AutocompleteView
|
||||
from tailbone.views import MasterView
|
||||
|
||||
from rattail.db import model
|
||||
|
||||
|
@ -52,6 +49,7 @@ class CustomerView(MasterView):
|
|||
has_versions = True
|
||||
people_detachable = True
|
||||
touchable = True
|
||||
supports_autocomplete = True
|
||||
|
||||
# whether to show "view full profile" helper for customer view
|
||||
show_profiles_helper = True
|
||||
|
@ -451,9 +449,6 @@ class CustomerView(MasterView):
|
|||
route_name='{}.detach_person'.format(route_prefix),
|
||||
permission='{}.detach_person'.format(permission_prefix))
|
||||
|
||||
# TODO: deprecate / remove this
|
||||
CustomersView = CustomerView
|
||||
|
||||
|
||||
# # TODO: this is referenced by some custom apps, but should be moved??
|
||||
# def unique_id(value, field):
|
||||
|
@ -473,43 +468,6 @@ def unique_id(node, value):
|
|||
raise colander.Invalid(node, "Customer ID must be unique")
|
||||
|
||||
|
||||
class CustomerNameAutocomplete(AutocompleteView):
|
||||
"""
|
||||
Autocomplete view which operates on customer name.
|
||||
"""
|
||||
mapped_class = model.Customer
|
||||
fieldname = '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(r'\D')
|
||||
|
||||
def prepare_term(self, term):
|
||||
return self.invalid_pattern.sub('', term)
|
||||
|
||||
def query(self, term):
|
||||
return Session.query(model.CustomerPhoneNumber)\
|
||||
.filter(sa.func.regexp_replace(model.CustomerPhoneNumber.number, r'\D', '', 'g').like('%{0}%'.format(term)))\
|
||||
.order_by(model.CustomerPhoneNumber.number)\
|
||||
.options(orm.joinedload(model.CustomerPhoneNumber.customer))
|
||||
|
||||
def display(self, phone):
|
||||
return "{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.
|
||||
|
@ -527,14 +485,6 @@ def customer_info(request):
|
|||
|
||||
def includeme(config):
|
||||
|
||||
# autocomplete
|
||||
config.add_route('customers.autocomplete', '/customers/autocomplete')
|
||||
config.add_view(CustomerNameAutocomplete, route_name='customers.autocomplete',
|
||||
renderer='json', permission='customers.list')
|
||||
config.add_route('customers.autocomplete.phone', '/customers/autocomplete/phone')
|
||||
config.add_view(CustomerPhoneAutocomplete, route_name='customers.autocomplete.phone',
|
||||
renderer='json', permission='customers.list')
|
||||
|
||||
# info
|
||||
config.add_route('customer.info', '/customers/info')
|
||||
config.add_view(customer_info, route_name='customer.info',
|
||||
|
|
|
@ -30,11 +30,10 @@ import six
|
|||
|
||||
from rattail.db import model
|
||||
|
||||
from deform import widget as dfwidget
|
||||
from webhelpers2.html import HTML
|
||||
|
||||
from tailbone import grids
|
||||
from tailbone.views import MasterView, AutocompleteView
|
||||
from tailbone.views import MasterView
|
||||
|
||||
|
||||
class DepartmentView(MasterView):
|
||||
|
@ -44,6 +43,7 @@ class DepartmentView(MasterView):
|
|||
model_class = model.Department
|
||||
touchable = True
|
||||
has_versions = True
|
||||
supports_autocomplete = True
|
||||
|
||||
grid_columns = [
|
||||
'number',
|
||||
|
@ -239,21 +239,6 @@ class DepartmentView(MasterView):
|
|||
|
||||
cls._defaults(config)
|
||||
|
||||
# TODO: deprecate / remove this
|
||||
DepartmentsView = DepartmentView
|
||||
|
||||
|
||||
class DepartmentsAutocomplete(AutocompleteView):
|
||||
|
||||
mapped_class = model.Department
|
||||
fieldname = 'name'
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
# autocomplete
|
||||
config.add_route('departments.autocomplete', '/departments/autocomplete')
|
||||
config.add_view(DepartmentsAutocomplete, route_name='departments.autocomplete',
|
||||
renderer='json', permission='departments.list')
|
||||
|
||||
DepartmentView.defaults(config)
|
||||
|
|
|
@ -36,8 +36,7 @@ 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
|
||||
from tailbone.views import MasterView
|
||||
|
||||
|
||||
class EmployeeView(MasterView):
|
||||
|
@ -47,6 +46,7 @@ class EmployeeView(MasterView):
|
|||
model_class = model.Employee
|
||||
has_versions = True
|
||||
touchable = True
|
||||
supports_autocomplete = True
|
||||
|
||||
labels = {
|
||||
'id': "ID",
|
||||
|
@ -310,31 +310,6 @@ class EmployeeView(MasterView):
|
|||
(model.EmployeeDepartment, 'employee_uuid'),
|
||||
]
|
||||
|
||||
# TODO: deprecate / remove this
|
||||
EmployeesView = EmployeeView
|
||||
|
||||
|
||||
class EmployeesAutocomplete(AutocompleteView):
|
||||
"""
|
||||
Autocomplete view for the Employee model, but restricted to return only
|
||||
results for current employees.
|
||||
"""
|
||||
mapped_class = model.Person
|
||||
fieldname = 'display_name'
|
||||
|
||||
def filter_query(self, q):
|
||||
return q.join(model.Employee)\
|
||||
.filter(model.Employee.status == self.enum.EMPLOYEE_STATUS_CURRENT)
|
||||
|
||||
def value(self, person):
|
||||
return person.employee.uuid
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
# autocomplete
|
||||
config.add_route('employees.autocomplete', '/employees/autocomplete')
|
||||
config.add_view(EmployeesAutocomplete, route_name='employees.autocomplete',
|
||||
renderer='json', permission='employees.list')
|
||||
|
||||
EmployeeView.defaults(config)
|
||||
|
|
|
@ -97,6 +97,7 @@ class MasterView(View):
|
|||
delete_confirm = 'full'
|
||||
bulk_deletable = False
|
||||
set_deletable = False
|
||||
supports_autocomplete = False
|
||||
supports_set_enabled_toggle = False
|
||||
populatable = False
|
||||
mergeable = False
|
||||
|
@ -3573,6 +3574,35 @@ class MasterView(View):
|
|||
return self.after_delete_url
|
||||
return self.get_index_url()
|
||||
|
||||
##############################
|
||||
# Autocomplete Stuff
|
||||
##############################
|
||||
|
||||
def autocomplete(self):
|
||||
"""
|
||||
View which accepts a single ``term`` param, and returns a list
|
||||
of autocomplete results to match.
|
||||
"""
|
||||
app = self.get_rattail_app()
|
||||
key = self.get_autocompleter_key()
|
||||
# url may include key, for more specific autocompleter
|
||||
if 'key' in self.request.matchdict:
|
||||
key = '{}.{}'.format(key, self.request.matchdict['key'])
|
||||
autocompleter = app.get_autocompleter(key)
|
||||
|
||||
term = self.request.params.get('term', '')
|
||||
return autocompleter.autocomplete(self.Session(), term)
|
||||
|
||||
def get_autocompleter_key(self):
|
||||
"""
|
||||
Must return the "key" to be used when locating the
|
||||
Autocompleter object, for use with autocomplete view.
|
||||
"""
|
||||
if hasattr(self, 'autocompleter_key'):
|
||||
if self.autocompleter_key:
|
||||
return self.autocompleter_key
|
||||
return self.get_route_prefix()
|
||||
|
||||
##############################
|
||||
# Associated Rows Stuff
|
||||
##############################
|
||||
|
@ -3965,6 +3995,25 @@ class MasterView(View):
|
|||
config.add_view(cls, attr='quickie', route_name='{}.quickie'.format(route_prefix),
|
||||
permission='{}.quickie'.format(permission_prefix))
|
||||
|
||||
# autocomplete
|
||||
if cls.supports_autocomplete:
|
||||
|
||||
# default
|
||||
config.add_route('{}.autocomplete'.format(route_prefix),
|
||||
'{}/autocomplete'.format(url_prefix))
|
||||
config.add_view(cls, attr='autocomplete',
|
||||
route_name='{}.autocomplete'.format(route_prefix),
|
||||
renderer='json',
|
||||
permission='{}.list'.format(permission_prefix))
|
||||
|
||||
# special
|
||||
config.add_route('{}.autocomplete_special'.format(route_prefix),
|
||||
'{}/autocomplete/{{key}}'.format(url_prefix))
|
||||
config.add_view(cls, attr='autocomplete',
|
||||
route_name='{}.autocomplete_special'.format(route_prefix),
|
||||
renderer='json',
|
||||
permission='{}.list'.format(permission_prefix))
|
||||
|
||||
# create
|
||||
if cls.creatable:
|
||||
config.add_tailbone_permission(permission_prefix, '{}.create'.format(permission_prefix),
|
||||
|
|
|
@ -41,7 +41,7 @@ from pyramid.httpexceptions import HTTPFound, HTTPNotFound
|
|||
from webhelpers2.html import HTML, tags
|
||||
|
||||
from tailbone import forms, grids
|
||||
from tailbone.views import MasterView, AutocompleteView
|
||||
from tailbone.views import MasterView
|
||||
|
||||
|
||||
class PersonView(MasterView):
|
||||
|
@ -56,6 +56,7 @@ class PersonView(MasterView):
|
|||
bulk_deletable = True
|
||||
is_contact = True
|
||||
manage_notes_from_profile_view = False
|
||||
supports_autocomplete = True
|
||||
|
||||
labels = {
|
||||
'default_phone': "Phone Number",
|
||||
|
@ -854,25 +855,6 @@ class PersonView(MasterView):
|
|||
config.add_view(cls, attr='request_merge', route_name='{}.request_merge'.format(route_prefix),
|
||||
permission='{}.request_merge'.format(permission_prefix))
|
||||
|
||||
# TODO: deprecate / remove this
|
||||
PeopleView = PersonView
|
||||
|
||||
|
||||
class PeopleAutocomplete(AutocompleteView):
|
||||
|
||||
mapped_class = model.Person
|
||||
fieldname = 'display_name'
|
||||
|
||||
|
||||
class PeopleEmployeesAutocomplete(PeopleAutocomplete):
|
||||
"""
|
||||
Autocomplete view for the Person model, but restricted to return only
|
||||
results for people who are employees.
|
||||
"""
|
||||
|
||||
def filter_query(self, q):
|
||||
return q.join(model.Employee)
|
||||
|
||||
|
||||
class PersonNoteView(MasterView):
|
||||
"""
|
||||
|
@ -1044,15 +1026,6 @@ class MergePeopleRequestView(MasterView):
|
|||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
# autocomplete
|
||||
config.add_route('people.autocomplete', '/people/autocomplete')
|
||||
config.add_view(PeopleAutocomplete, route_name='people.autocomplete',
|
||||
renderer='json', permission='people.list')
|
||||
config.add_route('people.autocomplete.employees', '/people/autocomplete/employees')
|
||||
config.add_view(PeopleEmployeesAutocomplete, route_name='people.autocomplete.employees',
|
||||
renderer='json', permission='people.list')
|
||||
|
||||
PersonView.defaults(config)
|
||||
PersonNoteView.defaults(config)
|
||||
MergePeopleRequestView.defaults(config)
|
||||
|
|
|
@ -51,7 +51,7 @@ from webhelpers2.html import tags, HTML
|
|||
|
||||
from tailbone import forms, grids
|
||||
from tailbone.db import Session
|
||||
from tailbone.views import MasterView, AutocompleteView
|
||||
from tailbone.views import MasterView
|
||||
from tailbone.util import raw_datetime
|
||||
|
||||
|
||||
|
@ -84,6 +84,7 @@ class ProductView(MasterView):
|
|||
model_class = model.Product
|
||||
has_versions = True
|
||||
results_downloadable_xlsx = True
|
||||
supports_autocomplete = True
|
||||
|
||||
labels = {
|
||||
'item_id': "Item ID",
|
||||
|
@ -1886,31 +1887,6 @@ class ProductView(MasterView):
|
|||
renderer='json',
|
||||
permission='{}.versions'.format(permission_prefix))
|
||||
|
||||
# TODO: deprecate / remove this
|
||||
ProductsView = ProductView
|
||||
|
||||
|
||||
class ProductsAutocomplete(AutocompleteView):
|
||||
"""
|
||||
Autocomplete view for products.
|
||||
"""
|
||||
mapped_class = model.Product
|
||||
fieldname = 'description'
|
||||
|
||||
def query(self, term):
|
||||
q = Session.query(model.Product).outerjoin(model.Brand)
|
||||
q = q.filter(sa.or_(
|
||||
model.Brand.name.ilike('%{}%'.format(term)),
|
||||
model.Product.description.ilike('%{}%'.format(term))))
|
||||
if not self.request.has_perm('products.view_deleted'):
|
||||
q = q.filter(model.Product.deleted == False)
|
||||
q = q.order_by(model.Brand.name, model.Product.description)
|
||||
q = q.options(orm.joinedload(model.Product.brand))
|
||||
return q
|
||||
|
||||
def display(self, product):
|
||||
return product.full_description
|
||||
|
||||
|
||||
def print_labels(request):
|
||||
profile = request.params.get('profile')
|
||||
|
@ -1942,10 +1918,6 @@ def print_labels(request):
|
|||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('products.autocomplete', '/products/autocomplete')
|
||||
config.add_view(ProductsAutocomplete, route_name='products.autocomplete',
|
||||
renderer='json', permission='products.list')
|
||||
|
||||
config.add_route('products.print_labels', '/products/labels')
|
||||
config.add_view(print_labels, route_name='products.print_labels',
|
||||
renderer='json', permission='products.print_labels')
|
||||
|
|
4
tailbone/views/vendors/__init__.py
vendored
4
tailbone/views/vendors/__init__.py
vendored
|
@ -26,9 +26,7 @@ Views pertaining to vendors
|
|||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
from .core import VendorView, VendorsAutocomplete
|
||||
# TODO: deprecate / remove this
|
||||
from .core import VendorsView
|
||||
from .core import VendorView
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
|
18
tailbone/views/vendors/core.py
vendored
18
tailbone/views/vendors/core.py
vendored
|
@ -32,7 +32,7 @@ from rattail.db import model
|
|||
|
||||
from webhelpers2.html import tags
|
||||
|
||||
from tailbone.views import MasterView, AutocompleteView
|
||||
from tailbone.views import MasterView
|
||||
|
||||
|
||||
class VendorView(MasterView):
|
||||
|
@ -42,6 +42,7 @@ class VendorView(MasterView):
|
|||
model_class = model.Vendor
|
||||
has_versions = True
|
||||
touchable = True
|
||||
supports_autocomplete = True
|
||||
|
||||
labels = {
|
||||
'id': "ID",
|
||||
|
@ -167,21 +168,6 @@ class VendorView(MasterView):
|
|||
(model.VendorContact, 'vendor_uuid'),
|
||||
]
|
||||
|
||||
# TODO: deprecate / remove this
|
||||
VendorsView = VendorView
|
||||
|
||||
|
||||
class VendorsAutocomplete(AutocompleteView):
|
||||
|
||||
mapped_class = model.Vendor
|
||||
fieldname = 'name'
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
# autocomplete
|
||||
config.add_route('vendors.autocomplete', '/vendors/autocomplete')
|
||||
config.add_view(VendorsAutocomplete, route_name='vendors.autocomplete',
|
||||
renderer='json', permission='vendors.list')
|
||||
|
||||
VendorView.defaults(config)
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
|
||||
from mock import Mock
|
||||
from pyramid import testing
|
||||
import sqlalchemy as sa
|
||||
|
||||
from .. import TestCase, mock_query
|
||||
from tailbone.views import autocomplete
|
||||
|
||||
|
||||
class BareAutocompleteViewTests(TestCase):
|
||||
|
||||
def view(self, **kwargs):
|
||||
request = testing.DummyRequest(**kwargs)
|
||||
return autocomplete.AutocompleteView(request)
|
||||
|
||||
def test_attributes(self):
|
||||
view = self.view()
|
||||
self.assertRaises(AttributeError, getattr, view, 'mapped_class')
|
||||
self.assertRaises(AttributeError, getattr, view, 'fieldname')
|
||||
|
||||
def test_filter_query(self):
|
||||
view = self.view()
|
||||
query = Mock()
|
||||
filtered = view.filter_query(query)
|
||||
self.assertTrue(filtered is query)
|
||||
|
||||
def test_make_query(self):
|
||||
view = self.view()
|
||||
# No mapped_class defined for view.
|
||||
self.assertRaises(AttributeError, view.make_query, 'test')
|
||||
|
||||
def test_query(self):
|
||||
view = self.view()
|
||||
query = Mock()
|
||||
view.make_query = Mock(return_value=query)
|
||||
filtered = view.query('test')
|
||||
self.assertTrue(filtered is query)
|
||||
|
||||
def test_display(self):
|
||||
view = self.view()
|
||||
instance = Mock()
|
||||
# No fieldname defined for view.
|
||||
self.assertRaises(AttributeError, view.display, instance)
|
||||
|
||||
def test_call(self):
|
||||
# Empty or missing query term yields empty list.
|
||||
view = self.view(params={})
|
||||
self.assertEqual(view(), [])
|
||||
view = self.view(params={'term': None})
|
||||
self.assertEqual(view(), [])
|
||||
view = self.view(params={'term': ''})
|
||||
self.assertEqual(view(), [])
|
||||
view = self.view(params={'term': '\t'})
|
||||
self.assertEqual(view(), [])
|
||||
# No mapped_class defined for view.
|
||||
view = self.view(params={'term': 'bogus'})
|
||||
self.assertRaises(AttributeError, view)
|
||||
|
||||
|
||||
class SampleAutocompleteViewTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SampleAutocompleteViewTests, self).setUp()
|
||||
self.Session_query = autocomplete.Session.query
|
||||
self.query = mock_query()
|
||||
autocomplete.Session.query = self.query
|
||||
|
||||
def tearDown(self):
|
||||
super(SampleAutocompleteViewTests, self).tearDown()
|
||||
autocomplete.Session.query = self.Session_query
|
||||
|
||||
def view(self, **kwargs):
|
||||
request = testing.DummyRequest(**kwargs)
|
||||
view = autocomplete.AutocompleteView(request)
|
||||
view.mapped_class = Mock()
|
||||
view.fieldname = 'thing'
|
||||
return view
|
||||
|
||||
def test_make_query(self):
|
||||
view = self.view()
|
||||
whatever = sa.text('whatever')
|
||||
view.mapped_class.thing.ilike.return_value = whatever
|
||||
self.assertTrue(view.make_query('test') is self.query)
|
||||
view.mapped_class.thing.ilike.assert_called_with('%test%')
|
||||
self.query.filter.assert_called_with(whatever)
|
||||
self.query.order_by.assert_called_with(view.mapped_class.thing)
|
Loading…
Reference in a new issue