1
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()`. 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 .. attribute:: configurable
Boolean indicating whether the master view supports Boolean indicating whether the master view supports
@ -286,6 +292,7 @@ class MasterView(View):
viewable = True viewable = True
editable = True editable = True
deletable = True deletable = True
has_autocomplete = False
configurable = False configurable = False
# current action # current action
@ -573,6 +580,84 @@ class MasterView(View):
session = self.app.get_session(obj) session = self.app.get_session(obj)
session.delete(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 # configure methods
############################## ##############################
@ -1888,6 +1973,15 @@ class MasterView(View):
f'{permission_prefix}.delete', f'{permission_prefix}.delete',
f"Delete {model_title}") 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 # configure
if cls.configurable: if cls.configurable:
config.add_route(f'{route_prefix}.configure', config.add_route(f'{route_prefix}.configure',

View file

@ -24,6 +24,8 @@
Views for people Views for people
""" """
import sqlalchemy as sa
from wuttjamaican.db.model import Person from wuttjamaican.db.model import Person
from wuttaweb.views import MasterView from wuttaweb.views import MasterView
@ -46,6 +48,7 @@ class PersonView(MasterView):
model_title_plural = "People" model_title_plural = "People"
route_prefix = 'people' route_prefix = 'people'
sort_defaults = 'full_name' sort_defaults = 'full_name'
has_autocomplete = True
grid_columns = [ grid_columns = [
'full_name', 'full_name',
@ -85,6 +88,17 @@ class PersonView(MasterView):
if 'users' in f: if 'users' in f:
f.fields.remove('users') 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): def view_profile(self, session=None):
""" """ """ """
person = self.get_instance(session=session) person = self.get_instance(session=session)

View file

@ -197,6 +197,14 @@ class SettingView(MasterView):
model_title = "Raw Setting" model_title = "Raw Setting"
sort_defaults = 'name' 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): def configure_form(self, f):
""" """ """ """
super().configure_form(f) super().configure_form(f)

View file

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

View file

@ -41,6 +41,30 @@ class TestPersonView(WebTestCase):
self.assertTrue(form.required_fields) self.assertTrue(form.required_fields)
self.assertFalse(form.required_fields['middle_name']) 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): def test_view_profile(self):
self.pyramid_config.include('wuttaweb.views.common') self.pyramid_config.include('wuttaweb.views.common')
self.pyramid_config.include('wuttaweb.views.auth') self.pyramid_config.include('wuttaweb.views.auth')

View file

@ -67,6 +67,14 @@ class TestSettingView(WebTestCase):
data = query.all() data = query.all()
self.assertEqual(len(data), 1) 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): def test_configure_form(self):
view = self.make_view() view = self.make_view()
form = view.make_form(fields=view.get_form_fields()) form = view.make_form(fields=view.get_form_fields())