2
0
Fork 0

feat: add basic autocomplete support, for Person

URL endpoint only for now, form widget to come later
This commit is contained in:
Lance Edgar 2024-08-21 11:46:38 -05:00
parent 4bf2bb42fb
commit 9d261de45a
6 changed files with 391 additions and 203 deletions

View file

@ -263,6 +263,12 @@ class MasterView(View):
This is optional; see also :meth:`get_form_fields()`.
.. attribute:: has_autocomplete
Boolean indicating whether the view model supports
"autocomplete" - i.e. it should have an :meth:`autocomplete()`
view. Default is ``False``.
.. attribute:: configurable
Boolean indicating whether the master view supports
@ -286,6 +292,7 @@ class MasterView(View):
viewable = True
editable = True
deletable = True
has_autocomplete = False
configurable = False
# current action
@ -573,6 +580,84 @@ class MasterView(View):
session = self.app.get_session(obj)
session.delete(obj)
##############################
# autocomplete methods
##############################
def autocomplete(self):
"""
View which accepts a single ``term`` param, and returns a JSON
list of autocomplete results to match.
By default, this view is included only if
:attr:`has_autocomplete` is true. It usually maps to a URL
like ``/widgets/autocomplete``.
Subclass generally does not need to override this method, but
rather should override the others which this calls:
* :meth:`autocomplete_data()`
* :meth:`autocomplete_normalize()`
"""
term = self.request.GET.get('term', '')
if not term:
return []
data = self.autocomplete_data(term)
if not data:
return []
max_results = 100 # TODO
results = []
for obj in data[:max_results]:
normal = self.autocomplete_normalize(obj)
if normal:
results.append(normal)
return results
def autocomplete_data(self, term):
"""
Should return the data/query for the "matching" model records,
based on autocomplete search term. This is called by
:meth:`autocomplete()`.
Subclass must override this; default logic returns no data.
:param term: String search term as-is from user, e.g. "foo bar".
:returns: List of data records, or SQLAlchemy query.
"""
def autocomplete_normalize(self, obj):
"""
Should return a "normalized" version of the given model
record, suitable for autocomplete JSON results. This is
called by :meth:`autocomplete()`.
Subclass may need to override this; default logic is
simplistic but will work for basic models. It returns the
"autocomplete results" dict for the object::
{
'value': obj.uuid,
'label': str(obj),
}
The 2 keys shown are required; any other keys will be ignored
by the view logic but may be useful on the frontend widget.
:param obj: Model record/instance.
:returns: Dict of "autocomplete results" format, as shown
above.
"""
return {
'value': obj.uuid,
'label': str(obj),
}
##############################
# configure methods
##############################
@ -1888,6 +1973,15 @@ class MasterView(View):
f'{permission_prefix}.delete',
f"Delete {model_title}")
# autocomplete
if cls.has_autocomplete:
config.add_route(f'{route_prefix}.autocomplete',
f'{url_prefix}/autocomplete')
config.add_view(cls, attr='autocomplete',
route_name=f'{route_prefix}.autocomplete',
renderer='json',
permission=f'{route_prefix}.list')
# configure
if cls.configurable:
config.add_route(f'{route_prefix}.configure',

View file

@ -24,6 +24,8 @@
Views for people
"""
import sqlalchemy as sa
from wuttjamaican.db.model import Person
from wuttaweb.views import MasterView
@ -46,6 +48,7 @@ class PersonView(MasterView):
model_title_plural = "People"
route_prefix = 'people'
sort_defaults = 'full_name'
has_autocomplete = True
grid_columns = [
'full_name',
@ -85,6 +88,17 @@ class PersonView(MasterView):
if 'users' in f:
f.fields.remove('users')
def autocomplete_query(self, term):
""" """
model = self.app.model
session = self.Session()
query = session.query(model.Person)
criteria = [model.Person.full_name.ilike(f'%{word}%')
for word in term.split()]
query = query.filter(sa.and_(*criteria))\
.order_by(model.Person.full_name)
return query
def view_profile(self, session=None):
""" """
person = self.get_instance(session=session)

View file

@ -197,6 +197,14 @@ class SettingView(MasterView):
model_title = "Raw Setting"
sort_defaults = 'name'
# TODO: master should handle this (per model key)
def configure_grid(self, g):
""" """
super().configure_grid(g)
# name
g.set_link('name')
def configure_form(self, f):
""" """
super().configure_form(f)

View file

@ -10,7 +10,7 @@ from pyramid.response import Response
from pyramid.httpexceptions import HTTPNotFound
from wuttjamaican.conf import WuttaConfig
from wuttaweb.views import master
from wuttaweb.views import master as mod
from wuttaweb.views import View
from wuttaweb.subscribers import new_request_set_user
from tests.util import WebTestCase
@ -19,14 +19,15 @@ from tests.util import WebTestCase
class TestMasterView(WebTestCase):
def make_view(self):
return master.MasterView(self.request)
return mod.MasterView(self.request)
def test_defaults(self):
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Widget',
model_key='uuid',
has_autocomplete=True,
configurable=True):
master.MasterView.defaults(self.pyramid_config)
mod.MasterView.defaults(self.pyramid_config)
##############################
# class methods
@ -35,317 +36,315 @@ class TestMasterView(WebTestCase):
def test_get_model_class(self):
# no model class by default
self.assertIsNone(master.MasterView.get_model_class())
self.assertIsNone(mod.MasterView.get_model_class())
# subclass may specify
MyModel = MagicMock()
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertIs(master.MasterView.get_model_class(), MyModel)
self.assertIs(mod.MasterView.get_model_class(), MyModel)
def test_get_model_name(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_model_name)
self.assertRaises(AttributeError, mod.MasterView.get_model_name)
# subclass may specify model name
master.MasterView.model_name = 'Widget'
self.assertEqual(master.MasterView.get_model_name(), 'Widget')
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name='Widget'):
self.assertEqual(mod.MasterView.get_model_name(), 'Widget')
# or it may specify model class
MyModel = MagicMock(__name__='Blaster')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_model_name(), 'Blaster')
self.assertEqual(mod.MasterView.get_model_name(), 'Blaster')
def test_get_model_name_normalized(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_model_name_normalized)
self.assertRaises(AttributeError, mod.MasterView.get_model_name_normalized)
# subclass may specify *normalized* model name
master.MasterView.model_name_normalized = 'widget'
self.assertEqual(master.MasterView.get_model_name_normalized(), 'widget')
del master.MasterView.model_name_normalized
with patch.multiple(mod.MasterView, create=True,
model_name_normalized='widget'):
self.assertEqual(mod.MasterView.get_model_name_normalized(), 'widget')
# or it may specify *standard* model name
master.MasterView.model_name = 'Blaster'
self.assertEqual(master.MasterView.get_model_name_normalized(), 'blaster')
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name='Blaster'):
self.assertEqual(mod.MasterView.get_model_name_normalized(), 'blaster')
# or it may specify model class
MyModel = MagicMock(__name__='Dinosaur')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_model_name_normalized(), 'dinosaur')
self.assertEqual(mod.MasterView.get_model_name_normalized(), 'dinosaur')
def test_get_model_title(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_model_title)
self.assertRaises(AttributeError, mod.MasterView.get_model_title)
# subclass may specify model title
master.MasterView.model_title = 'Wutta Widget'
self.assertEqual(master.MasterView.get_model_title(), "Wutta Widget")
del master.MasterView.model_title
with patch.multiple(mod.MasterView, create=True,
model_title='Wutta Widget'):
self.assertEqual(mod.MasterView.get_model_title(), "Wutta Widget")
# or it may specify model name
master.MasterView.model_name = 'Blaster'
self.assertEqual(master.MasterView.get_model_title(), "Blaster")
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name='Blaster'):
self.assertEqual(mod.MasterView.get_model_title(), "Blaster")
# or it may specify model class
MyModel = MagicMock(__name__='Dinosaur')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_model_title(), "Dinosaur")
self.assertEqual(mod.MasterView.get_model_title(), "Dinosaur")
def test_get_model_title_plural(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_model_title_plural)
self.assertRaises(AttributeError, mod.MasterView.get_model_title_plural)
# subclass may specify *plural* model title
master.MasterView.model_title_plural = 'People'
self.assertEqual(master.MasterView.get_model_title_plural(), "People")
del master.MasterView.model_title_plural
with patch.multiple(mod.MasterView, create=True,
model_title_plural='People'):
self.assertEqual(mod.MasterView.get_model_title_plural(), "People")
# or it may specify *singular* model title
master.MasterView.model_title = 'Wutta Widget'
self.assertEqual(master.MasterView.get_model_title_plural(), "Wutta Widgets")
del master.MasterView.model_title
with patch.multiple(mod.MasterView, create=True,
model_title='Wutta Widget'):
self.assertEqual(mod.MasterView.get_model_title_plural(), "Wutta Widgets")
# or it may specify model name
master.MasterView.model_name = 'Blaster'
self.assertEqual(master.MasterView.get_model_title_plural(), "Blasters")
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name='Blaster'):
self.assertEqual(mod.MasterView.get_model_title_plural(), "Blasters")
# or it may specify model class
MyModel = MagicMock(__name__='Dinosaur')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_model_title_plural(), "Dinosaurs")
self.assertEqual(mod.MasterView.get_model_title_plural(), "Dinosaurs")
def test_get_model_key(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_model_key)
self.assertRaises(AttributeError, mod.MasterView.get_model_key)
# subclass may specify model key
master.MasterView.model_key = 'uuid'
self.assertEqual(master.MasterView.get_model_key(), ('uuid',))
del master.MasterView.model_key
with patch.multiple(mod.MasterView, create=True,
model_key='uuid'):
self.assertEqual(mod.MasterView.get_model_key(), ('uuid',))
def test_get_route_prefix(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_route_prefix)
self.assertRaises(AttributeError, mod.MasterView.get_route_prefix)
# subclass may specify route prefix
master.MasterView.route_prefix = 'widgets'
self.assertEqual(master.MasterView.get_route_prefix(), 'widgets')
del master.MasterView.route_prefix
with patch.multiple(mod.MasterView, create=True,
route_prefix='widgets'):
self.assertEqual(mod.MasterView.get_route_prefix(), 'widgets')
# subclass may specify *normalized* model name
master.MasterView.model_name_normalized = 'blaster'
self.assertEqual(master.MasterView.get_route_prefix(), 'blasters')
del master.MasterView.model_name_normalized
with patch.multiple(mod.MasterView, create=True,
model_name_normalized='blaster'):
self.assertEqual(mod.MasterView.get_route_prefix(), 'blasters')
# or it may specify *standard* model name
master.MasterView.model_name = 'Dinosaur'
self.assertEqual(master.MasterView.get_route_prefix(), 'dinosaurs')
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name = 'Dinosaur'):
self.assertEqual(mod.MasterView.get_route_prefix(), 'dinosaurs')
# or it may specify model class
MyModel = MagicMock(__name__='Truck')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_route_prefix(), 'trucks')
self.assertEqual(mod.MasterView.get_route_prefix(), 'trucks')
def test_get_permission_prefix(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_permission_prefix)
self.assertRaises(AttributeError, mod.MasterView.get_permission_prefix)
# subclass may specify permission prefix
with patch.object(master.MasterView, 'permission_prefix', new='widgets', create=True):
self.assertEqual(master.MasterView.get_permission_prefix(), 'widgets')
with patch.object(mod.MasterView, 'permission_prefix', new='widgets', create=True):
self.assertEqual(mod.MasterView.get_permission_prefix(), 'widgets')
# subclass may specify route prefix
with patch.object(master.MasterView, 'route_prefix', new='widgets', create=True):
self.assertEqual(master.MasterView.get_permission_prefix(), 'widgets')
with patch.object(mod.MasterView, 'route_prefix', new='widgets', create=True):
self.assertEqual(mod.MasterView.get_permission_prefix(), 'widgets')
# or it may specify model class
Truck = MagicMock(__name__='Truck')
with patch.object(master.MasterView, 'model_class', new=Truck, create=True):
self.assertEqual(master.MasterView.get_permission_prefix(), 'trucks')
with patch.object(mod.MasterView, 'model_class', new=Truck, create=True):
self.assertEqual(mod.MasterView.get_permission_prefix(), 'trucks')
def test_get_url_prefix(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_url_prefix)
self.assertRaises(AttributeError, mod.MasterView.get_url_prefix)
# subclass may specify url prefix
master.MasterView.url_prefix = '/widgets'
self.assertEqual(master.MasterView.get_url_prefix(), '/widgets')
del master.MasterView.url_prefix
with patch.multiple(mod.MasterView, create=True,
url_prefix='/widgets'):
self.assertEqual(mod.MasterView.get_url_prefix(), '/widgets')
# or it may specify route prefix
master.MasterView.route_prefix = 'trucks'
self.assertEqual(master.MasterView.get_url_prefix(), '/trucks')
del master.MasterView.route_prefix
with patch.multiple(mod.MasterView, create=True,
route_prefix='trucks'):
self.assertEqual(mod.MasterView.get_url_prefix(), '/trucks')
# or it may specify *normalized* model name
master.MasterView.model_name_normalized = 'blaster'
self.assertEqual(master.MasterView.get_url_prefix(), '/blasters')
del master.MasterView.model_name_normalized
with patch.multiple(mod.MasterView, create=True,
model_name_normalized='blaster'):
self.assertEqual(mod.MasterView.get_url_prefix(), '/blasters')
# or it may specify *standard* model name
master.MasterView.model_name = 'Dinosaur'
self.assertEqual(master.MasterView.get_url_prefix(), '/dinosaurs')
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name='Dinosaur'):
self.assertEqual(mod.MasterView.get_url_prefix(), '/dinosaurs')
# or it may specify model class
MyModel = MagicMock(__name__='Machine')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_url_prefix(), '/machines')
self.assertEqual(mod.MasterView.get_url_prefix(), '/machines')
def test_get_instance_url_prefix(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_instance_url_prefix)
self.assertRaises(AttributeError, mod.MasterView.get_instance_url_prefix)
# typical example with url_prefix and simple key
master.MasterView.url_prefix = '/widgets'
master.MasterView.model_key = 'uuid'
self.assertEqual(master.MasterView.get_instance_url_prefix(), '/widgets/{uuid}')
del master.MasterView.url_prefix
del master.MasterView.model_key
with patch.multiple(mod.MasterView, create=True,
url_prefix='/widgets',
model_key='uuid'):
self.assertEqual(mod.MasterView.get_instance_url_prefix(), '/widgets/{uuid}')
# typical example with composite key
master.MasterView.url_prefix = '/widgets'
master.MasterView.model_key = ('foo', 'bar')
self.assertEqual(master.MasterView.get_instance_url_prefix(), '/widgets/{foo}|{bar}')
del master.MasterView.url_prefix
del master.MasterView.model_key
with patch.multiple(mod.MasterView, create=True,
url_prefix='/widgets',
model_key=('foo', 'bar')):
self.assertEqual(mod.MasterView.get_instance_url_prefix(), '/widgets/{foo}|{bar}')
def test_get_template_prefix(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_template_prefix)
self.assertRaises(AttributeError, mod.MasterView.get_template_prefix)
# subclass may specify template prefix
master.MasterView.template_prefix = '/widgets'
self.assertEqual(master.MasterView.get_template_prefix(), '/widgets')
del master.MasterView.template_prefix
with patch.multiple(mod.MasterView, create=True,
template_prefix='/widgets'):
self.assertEqual(mod.MasterView.get_template_prefix(), '/widgets')
# or it may specify url prefix
master.MasterView.url_prefix = '/trees'
self.assertEqual(master.MasterView.get_template_prefix(), '/trees')
del master.MasterView.url_prefix
with patch.multiple(mod.MasterView, create=True,
url_prefix='/trees'):
self.assertEqual(mod.MasterView.get_template_prefix(), '/trees')
# or it may specify route prefix
master.MasterView.route_prefix = 'trucks'
self.assertEqual(master.MasterView.get_template_prefix(), '/trucks')
del master.MasterView.route_prefix
with patch.multiple(mod.MasterView, create=True,
route_prefix='trucks'):
self.assertEqual(mod.MasterView.get_template_prefix(), '/trucks')
# or it may specify *normalized* model name
master.MasterView.model_name_normalized = 'blaster'
self.assertEqual(master.MasterView.get_template_prefix(), '/blasters')
del master.MasterView.model_name_normalized
with patch.multiple(mod.MasterView, create=True,
model_name_normalized='blaster'):
self.assertEqual(mod.MasterView.get_template_prefix(), '/blasters')
# or it may specify *standard* model name
master.MasterView.model_name = 'Dinosaur'
self.assertEqual(master.MasterView.get_template_prefix(), '/dinosaurs')
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name='Dinosaur'):
self.assertEqual(mod.MasterView.get_template_prefix(), '/dinosaurs')
# or it may specify model class
MyModel = MagicMock(__name__='Machine')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_template_prefix(), '/machines')
self.assertEqual(mod.MasterView.get_template_prefix(), '/machines')
def test_get_grid_key(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_grid_key)
self.assertRaises(AttributeError, mod.MasterView.get_grid_key)
# subclass may specify grid key
master.MasterView.grid_key = 'widgets'
self.assertEqual(master.MasterView.get_grid_key(), 'widgets')
del master.MasterView.grid_key
with patch.multiple(mod.MasterView, create=True,
grid_key='widgets'):
self.assertEqual(mod.MasterView.get_grid_key(), 'widgets')
# or it may specify route prefix
master.MasterView.route_prefix = 'trucks'
self.assertEqual(master.MasterView.get_grid_key(), 'trucks')
del master.MasterView.route_prefix
with patch.multiple(mod.MasterView, create=True,
route_prefix='trucks'):
self.assertEqual(mod.MasterView.get_grid_key(), 'trucks')
# or it may specify *normalized* model name
master.MasterView.model_name_normalized = 'blaster'
self.assertEqual(master.MasterView.get_grid_key(), 'blasters')
del master.MasterView.model_name_normalized
with patch.multiple(mod.MasterView, create=True,
model_name_normalized='blaster'):
self.assertEqual(mod.MasterView.get_grid_key(), 'blasters')
# or it may specify *standard* model name
master.MasterView.model_name = 'Dinosaur'
self.assertEqual(master.MasterView.get_grid_key(), 'dinosaurs')
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name='Dinosaur'):
self.assertEqual(mod.MasterView.get_grid_key(), 'dinosaurs')
# or it may specify model class
MyModel = MagicMock(__name__='Machine')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_grid_key(), 'machines')
self.assertEqual(mod.MasterView.get_grid_key(), 'machines')
def test_get_config_title(self):
# error by default (since no model class)
self.assertRaises(AttributeError, master.MasterView.get_config_title)
self.assertRaises(AttributeError, mod.MasterView.get_config_title)
# subclass may specify config title
master.MasterView.config_title = 'Widgets'
self.assertEqual(master.MasterView.get_config_title(), "Widgets")
del master.MasterView.config_title
with patch.multiple(mod.MasterView, create=True,
config_title='Widgets'):
self.assertEqual(mod.MasterView.get_config_title(), "Widgets")
# subclass may specify *plural* model title
master.MasterView.model_title_plural = 'People'
self.assertEqual(master.MasterView.get_config_title(), "People")
del master.MasterView.model_title_plural
with patch.multiple(mod.MasterView, create=True,
model_title_plural='People'):
self.assertEqual(mod.MasterView.get_config_title(), "People")
# or it may specify *singular* model title
master.MasterView.model_title = 'Wutta Widget'
self.assertEqual(master.MasterView.get_config_title(), "Wutta Widgets")
del master.MasterView.model_title
with patch.multiple(mod.MasterView, create=True,
model_title='Wutta Widget'):
self.assertEqual(mod.MasterView.get_config_title(), "Wutta Widgets")
# or it may specify model name
master.MasterView.model_name = 'Blaster'
self.assertEqual(master.MasterView.get_config_title(), "Blasters")
del master.MasterView.model_name
with patch.multiple(mod.MasterView, create=True,
model_name='Blaster'):
self.assertEqual(mod.MasterView.get_config_title(), "Blasters")
# or it may specify model class
MyModel = MagicMock(__name__='Dinosaur')
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=MyModel):
self.assertEqual(master.MasterView.get_config_title(), "Dinosaurs")
self.assertEqual(mod.MasterView.get_config_title(), "Dinosaurs")
##############################
# support methods
##############################
def test_get_class_hierarchy(self):
class MyView(master.MasterView):
class MyView(mod.MasterView):
pass
view = MyView(self.request)
classes = view.get_class_hierarchy()
self.assertEqual(classes, [View, master.MasterView, MyView])
self.assertEqual(classes, [View, mod.MasterView, MyView])
def test_has_perm(self):
model = self.app.model
auth = self.app.get_auth_handler()
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Setting'):
view = self.make_view()
@ -374,7 +373,7 @@ class TestMasterView(WebTestCase):
model = self.app.model
auth = self.app.get_auth_handler()
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Setting'):
view = self.make_view()
@ -410,20 +409,20 @@ class TestMasterView(WebTestCase):
# basic sanity check using /master/index.mako
# (nb. it skips /widgets/index.mako since that doesn't exist)
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Widget',
creatable=False):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
response = view.render_to_response('index', {})
self.assertIsInstance(response, Response)
# basic sanity check using /appinfo/index.mako
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='AppInfo',
route_prefix='appinfo',
url_prefix='/appinfo',
creatable=False):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
response = view.render_to_response('index', {
# nb. grid is required for this template
'grid': MagicMock(),
@ -431,15 +430,15 @@ class TestMasterView(WebTestCase):
self.assertIsInstance(response, Response)
# bad template name causes error
master.MasterView.model_name = 'Widget'
with patch.multiple(mod.MasterView, create=True,
model_name='Widget'):
self.assertRaises(IOError, view.render_to_response, 'nonexistent', {})
del master.MasterView.model_name
def test_get_index_title(self):
master.MasterView.model_title_plural = "Wutta Widgets"
view = master.MasterView(self.request)
with patch.multiple(mod.MasterView, create=True,
model_title_plural = "Wutta Widgets"):
view = mod.MasterView(self.request)
self.assertEqual(view.get_index_title(), "Wutta Widgets")
del master.MasterView.model_title_plural
def test_collect_labels(self):
@ -450,14 +449,14 @@ class TestMasterView(WebTestCase):
# labels come from all classes; subclass wins
with patch.object(View, 'labels', new={'foo': "Foo", 'bar': "Bar"}, create=True):
with patch.object(master.MasterView, 'labels', new={'foo': "FOO FIGHTERS"}, create=True):
with patch.object(mod.MasterView, 'labels', new={'foo': "FOO FIGHTERS"}, create=True):
view = self.make_view()
labels = view.collect_labels()
self.assertEqual(labels, {'foo': "FOO FIGHTERS", 'bar': "Bar"})
def test_set_labels(self):
model = self.app.model
with patch.object(master.MasterView, 'model_class', new=model.Setting, create=True):
with patch.object(mod.MasterView, 'model_class', new=model.Setting, create=True):
# no labels by default
view = self.make_view()
@ -466,7 +465,7 @@ class TestMasterView(WebTestCase):
self.assertEqual(grid.labels, {})
# labels come from all classes; subclass wins
with patch.object(master.MasterView, 'labels', new={'name': "SETTING NAME"}, create=True):
with patch.object(mod.MasterView, 'labels', new={'name': "SETTING NAME"}, create=True):
view = self.make_view()
view.set_labels(grid)
self.assertEqual(grid.labels, {'name': "SETTING NAME"})
@ -475,27 +474,27 @@ class TestMasterView(WebTestCase):
model = self.app.model
# no model class
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Widget',
model_key='uuid'):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
grid = view.make_model_grid()
self.assertIsNone(grid.model_class)
# explicit model class
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
grid = view.make_model_grid(session=self.session)
self.assertIs(grid.model_class, model.Setting)
# no actions by default
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
grid = view.make_model_grid(session=self.session)
self.assertEqual(grid.actions, [])
# now let's test some more actions logic
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting,
viewable=True,
editable=True,
@ -518,14 +517,14 @@ class TestMasterView(WebTestCase):
view = self.make_view()
# empty by default
self.assertFalse(hasattr(master.MasterView, 'model_class'))
self.assertFalse(hasattr(mod.MasterView, 'model_class'))
data = view.get_grid_data(session=self.session)
self.assertEqual(data, [])
# grid with model class will produce data query
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
query = view.get_grid_data(session=self.session)
self.assertIsInstance(query, orm.Query)
data = query.all()
@ -536,9 +535,9 @@ class TestMasterView(WebTestCase):
model = self.app.model
# uuid field is pruned
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
grid = view.make_grid(model_class=model.Setting,
columns=['uuid', 'name', 'value'])
self.assertIn('uuid', grid.columns)
@ -574,13 +573,13 @@ class TestMasterView(WebTestCase):
self.assertEqual(self.session.query(model.Setting).count(), 1)
# default not implemented
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
self.assertRaises(NotImplementedError, view.get_instance)
# fetch from DB if model class is known
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
# existing setting is returned
self.request.matchdict = {'name': 'foo'}
@ -599,9 +598,9 @@ class TestMasterView(WebTestCase):
self.session.add(setting)
self.session.commit()
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
master.MasterView.defaults(self.pyramid_config)
mod.MasterView.defaults(self.pyramid_config)
view = self.make_view()
url = view.get_action_url_view(setting, 0)
self.assertEqual(url, self.request.route_url('settings.view', name='foo'))
@ -611,9 +610,9 @@ class TestMasterView(WebTestCase):
setting = model.Setting(name='foo', value='bar')
self.session.add(setting)
self.session.commit()
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
master.MasterView.defaults(self.pyramid_config)
mod.MasterView.defaults(self.pyramid_config)
view = self.make_view()
# typical
@ -630,9 +629,9 @@ class TestMasterView(WebTestCase):
setting = model.Setting(name='foo', value='bar')
self.session.add(setting)
self.session.commit()
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
master.MasterView.defaults(self.pyramid_config)
mod.MasterView.defaults(self.pyramid_config)
view = self.make_view()
# typical
@ -648,15 +647,15 @@ class TestMasterView(WebTestCase):
model = self.app.model
# no model class
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Widget',
model_key='uuid'):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
form = view.make_model_form()
self.assertIsNone(form.model_class)
# explicit model class
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
form = view.make_model_form()
self.assertIs(form.model_class, model.Setting)
@ -665,9 +664,9 @@ class TestMasterView(WebTestCase):
model = self.app.model
# uuid field is pruned
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
form = view.make_form(model_class=model.Setting,
fields=['uuid', 'name', 'value'])
self.assertIn('uuid', form.fields)
@ -681,17 +680,17 @@ class TestMasterView(WebTestCase):
self.assertEqual(self.session.query(model.Setting).count(), 1)
# no model class
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Widget',
model_key='uuid'):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
form = view.make_model_form(fields=['name', 'description'])
form.validated = {'name': 'first'}
obj = view.objectify(form)
self.assertIs(obj, form.validated)
# explicit model class (editing)
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting,
editing=True):
form = view.make_model_form()
@ -703,7 +702,7 @@ class TestMasterView(WebTestCase):
self.assertEqual(obj.value, 'blarg')
# explicit model class (creating)
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting,
creating=True):
form = view.make_model_form()
@ -715,9 +714,9 @@ class TestMasterView(WebTestCase):
def test_persist(self):
model = self.app.model
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
# new instance is persisted
setting = model.Setting(name='foo', value='bar')
@ -741,12 +740,12 @@ class TestMasterView(WebTestCase):
self.pyramid_config.add_route('settings.delete', '/settings/{name}/delete')
# sanity/coverage check using /settings/
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Setting',
model_key='name',
get_index_url=MagicMock(return_value='/settings/'),
grid_columns=['name', 'value']):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
response = view.index()
# then again with data, to include view action url
@ -769,12 +768,12 @@ class TestMasterView(WebTestCase):
model = self.app.model
# sanity/coverage check using /settings/new
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Setting',
model_key='name',
get_index_url=MagicMock(return_value='/settings/'),
form_fields=['name', 'value']):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
# no setting yet
self.assertIsNone(self.app.get_setting(self.session, 'foo.bar'))
@ -825,13 +824,13 @@ class TestMasterView(WebTestCase):
# sanity/coverage check using /settings/XXX
setting = {'name': 'foo.bar', 'value': 'baz'}
self.request.matchdict = {'name': 'foo.bar'}
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Setting',
model_key='name',
get_index_url=MagicMock(return_value='/settings/'),
grid_columns=['name', 'value'],
form_fields=['name', 'value']):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
with patch.object(view, 'get_instance', return_value=setting):
response = view.view()
@ -854,12 +853,12 @@ class TestMasterView(WebTestCase):
# sanity/coverage check using /settings/XXX/edit
self.request.matchdict = {'name': 'foo.bar'}
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Setting',
model_key='name',
get_index_url=MagicMock(return_value='/settings/'),
form_fields=['name', 'value']):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
with patch.object(view, 'get_instance', new=get_instance):
# get the form page
@ -918,12 +917,12 @@ class TestMasterView(WebTestCase):
# sanity/coverage check using /settings/XXX/delete
self.request.matchdict = {'name': 'foo.bar'}
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_name='Setting',
model_key='name',
get_index_url=MagicMock(return_value='/settings/'),
form_fields=['name', 'value']):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
with patch.object(view, 'get_instance', new=get_instance):
# get the form page
@ -960,14 +959,55 @@ class TestMasterView(WebTestCase):
self.session.commit()
setting = self.session.query(model.Setting).one()
with patch.multiple(master.MasterView, create=True,
with patch.multiple(mod.MasterView, create=True,
model_class=model.Setting,
form_fields=['name', 'value']):
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
view.delete_instance(setting)
self.session.commit()
self.assertEqual(self.session.query(model.Setting).count(), 0)
def test_autocomplete(self):
model = self.app.model
person1 = model.Person(full_name="George Jones")
self.session.add(person1)
person2 = model.Person(full_name="George Strait")
self.session.add(person2)
self.session.commit()
# no results for empty term
self.request.GET = {}
view = self.make_view()
results = view.autocomplete()
self.assertEqual(len(results), 0)
# search yields no results
self.request.GET = {'term': 'sally'}
view = self.make_view()
with patch.object(view, 'autocomplete_data', return_value=[]):
view = self.make_view()
results = view.autocomplete()
self.assertEqual(len(results), 0)
# search yields 2 results
self.request.GET = {'term': 'george'}
view = self.make_view()
with patch.object(view, 'autocomplete_data', return_value=[person1, person2]):
results = view.autocomplete()
self.assertEqual(len(results), 2)
self.assertEqual([res['value'] for res in results],
[p.uuid for p in [person1, person2]])
def test_autocomplete_normalize(self):
model = self.app.model
view = self.make_view()
person = model.Person(full_name="Betty Boop", uuid='bogus')
normal = view.autocomplete_normalize(person)
self.assertEqual(normal, {'value': 'bogus',
'label': "Betty Boop"})
def test_configure(self):
self.pyramid_config.include('wuttaweb.views.common')
self.pyramid_config.include('wuttaweb.views.auth')
@ -983,10 +1023,10 @@ class TestMasterView(WebTestCase):
{'name': 'wutta.value2', 'save_if_empty': False},
]
view = master.MasterView(self.request)
view = mod.MasterView(self.request)
with patch.object(self.request, 'current_route_url', return_value='/appinfo/configure'):
with patch.object(master, 'Session', return_value=self.session):
with patch.multiple(master.MasterView, create=True,
with patch.object(mod, 'Session', return_value=self.session):
with patch.multiple(mod.MasterView, create=True,
model_name='AppInfo',
route_prefix='appinfo',
template_prefix='/appinfo',

View file

@ -41,6 +41,30 @@ class TestPersonView(WebTestCase):
self.assertTrue(form.required_fields)
self.assertFalse(form.required_fields['middle_name'])
def test_autocomplete_query(self):
model = self.app.model
person1 = model.Person(full_name="George Jones")
self.session.add(person1)
person2 = model.Person(full_name="George Strait")
self.session.add(person2)
self.session.commit()
view = self.make_view()
with patch.object(view, 'Session', return_value=self.session):
# both people match
query = view.autocomplete_query('george')
self.assertEqual(query.count(), 2)
# just 1 match
query = view.autocomplete_query('jones')
self.assertEqual(query.count(), 1)
# no matches
query = view.autocomplete_query('sally')
self.assertEqual(query.count(), 0)
def test_view_profile(self):
self.pyramid_config.include('wuttaweb.views.common')
self.pyramid_config.include('wuttaweb.views.auth')

View file

@ -67,6 +67,14 @@ class TestSettingView(WebTestCase):
data = query.all()
self.assertEqual(len(data), 1)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.Setting)
self.assertFalse(grid.is_linked('name'))
view.configure_grid(grid)
self.assertTrue(grid.is_linked('name'))
def test_configure_form(self):
view = self.make_view()
form = view.make_form(fields=view.get_form_fields())