feat: add basic support for "view" part of CRUD
still no SQLAlchemy yet, view must be explicit about data/model. but should support simple dict records, which will be needed in a few places anyway
This commit is contained in:
parent
754e0989e4
commit
4c467f5267
14 changed files with 745 additions and 82 deletions
|
@ -59,8 +59,8 @@ class TestForm(TestCase):
|
|||
def tearDown(self):
|
||||
testing.tearDown()
|
||||
|
||||
def make_form(self, request=None, **kwargs):
|
||||
return base.Form(request or self.request, **kwargs)
|
||||
def make_form(self, **kwargs):
|
||||
return base.Form(self.request, **kwargs)
|
||||
|
||||
def make_schema(self):
|
||||
schema = colander.Schema(children=[
|
||||
|
@ -124,19 +124,33 @@ class TestForm(TestCase):
|
|||
self.assertIs(form.schema, schema)
|
||||
self.assertIs(form.get_schema(), schema)
|
||||
|
||||
# auto-generating schema not yet supported
|
||||
# schema is auto-generated if fields provided
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
schema = form.get_schema()
|
||||
self.assertEqual(len(schema.children), 2)
|
||||
self.assertEqual(schema['foo'].name, 'foo')
|
||||
|
||||
# but auto-generating without fields is not supported
|
||||
form = self.make_form()
|
||||
self.assertIsNone(form.schema)
|
||||
self.assertRaises(NotImplementedError, form.get_schema)
|
||||
|
||||
def test_get_deform(self):
|
||||
schema = self.make_schema()
|
||||
|
||||
# basic
|
||||
form = self.make_form(schema=schema)
|
||||
self.assertFalse(hasattr(form, 'deform_form'))
|
||||
dform = form.get_deform()
|
||||
self.assertIsInstance(dform, deform.Form)
|
||||
self.assertIs(form.deform_form, dform)
|
||||
|
||||
# with model instance / cstruct
|
||||
myobj = {'foo': 'one', 'bar': 'two'}
|
||||
form = self.make_form(schema=schema, model_instance=myobj)
|
||||
dform = form.get_deform()
|
||||
self.assertEqual(dform.cstruct, myobj)
|
||||
|
||||
def test_get_label(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
self.assertEqual(form.get_label('foo'), "Foo")
|
||||
|
@ -193,6 +207,13 @@ class TestForm(TestCase):
|
|||
# nb. no error message
|
||||
self.assertNotIn('message', html)
|
||||
|
||||
# readonly
|
||||
html = form.render_vue_field('foo', readonly=True)
|
||||
self.assertIn('<b-field :horizontal="true" label="Foo">', html)
|
||||
self.assertNotIn('<b-input name="foo"', html)
|
||||
# nb. no error message
|
||||
self.assertNotIn('message', html)
|
||||
|
||||
# with single "static" error
|
||||
dform['foo'].error = MagicMock(msg="something is wrong")
|
||||
html = form.render_vue_field('foo')
|
||||
|
|
|
@ -75,3 +75,76 @@ class TestGrid(TestCase):
|
|||
first = columns[0]
|
||||
self.assertEqual(first['field'], 'foo')
|
||||
self.assertEqual(first['label'], 'Foo')
|
||||
|
||||
def test_get_vue_data(self):
|
||||
|
||||
# null by default
|
||||
grid = self.make_grid()
|
||||
data = grid.get_vue_data()
|
||||
self.assertIsNone(data)
|
||||
|
||||
# is usually a list
|
||||
mydata = [
|
||||
{'foo': 'bar'},
|
||||
]
|
||||
grid = self.make_grid(data=mydata)
|
||||
data = grid.get_vue_data()
|
||||
self.assertIs(data, mydata)
|
||||
self.assertEqual(data, [{'foo': 'bar'}])
|
||||
|
||||
# if grid has actions, that list may be supplemented
|
||||
grid.actions.append(base.GridAction(self.request, 'view', url='/blarg'))
|
||||
data = grid.get_vue_data()
|
||||
self.assertIsNot(data, mydata)
|
||||
self.assertEqual(data, [{'foo': 'bar', '_action_url_view': '/blarg'}])
|
||||
|
||||
|
||||
class TestGridAction(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig()
|
||||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
||||
|
||||
def make_action(self, key, **kwargs):
|
||||
return base.GridAction(self.request, key, **kwargs)
|
||||
|
||||
def test_render_icon(self):
|
||||
|
||||
# icon is derived from key by default
|
||||
action = self.make_action('blarg')
|
||||
html = action.render_icon()
|
||||
self.assertIn('<i class="fas fa-blarg">', html)
|
||||
|
||||
# oruga not yet supported
|
||||
self.request.use_oruga = True
|
||||
self.assertRaises(NotImplementedError, action.render_icon)
|
||||
|
||||
def test_render_label(self):
|
||||
|
||||
# label is derived from key by default
|
||||
action = self.make_action('blarg')
|
||||
label = action.render_label()
|
||||
self.assertEqual(label, "Blarg")
|
||||
|
||||
# otherwise use what caller provides
|
||||
action = self.make_action('foo', label="Bar")
|
||||
label = action.render_label()
|
||||
self.assertEqual(label, "Bar")
|
||||
|
||||
def test_get_url(self):
|
||||
obj = {'foo': 'bar'}
|
||||
|
||||
# null by default
|
||||
action = self.make_action('blarg')
|
||||
url = action.get_url(obj)
|
||||
self.assertIsNone(url)
|
||||
|
||||
# or can be "static"
|
||||
action = self.make_action('blarg', url='/foo')
|
||||
url = action.get_url(obj)
|
||||
self.assertEqual(url, '/foo')
|
||||
|
||||
# or can be "dynamic"
|
||||
action = self.make_action('blarg', url=lambda o, i: '/yeehaw')
|
||||
url = action.get_url(obj)
|
||||
self.assertEqual(url, '/yeehaw')
|
||||
|
|
|
@ -19,8 +19,9 @@ class TestMasterView(WebTestCase):
|
|||
|
||||
def test_defaults(self):
|
||||
master.MasterView.model_name = 'Widget'
|
||||
# TODO: should inspect pyramid routes after this, to be certain
|
||||
master.MasterView.defaults(self.pyramid_config)
|
||||
with patch.object(master.MasterView, 'viewable', new=False):
|
||||
# TODO: should inspect pyramid routes after this, to be certain
|
||||
master.MasterView.defaults(self.pyramid_config)
|
||||
del master.MasterView.model_name
|
||||
|
||||
##############################
|
||||
|
@ -122,6 +123,16 @@ class TestMasterView(WebTestCase):
|
|||
self.assertEqual(master.MasterView.get_model_title_plural(), "Dinosaurs")
|
||||
del master.MasterView.model_class
|
||||
|
||||
def test_get_model_key(self):
|
||||
|
||||
# error by default (since no model class)
|
||||
self.assertRaises(AttributeError, master.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
|
||||
|
||||
def test_get_route_prefix(self):
|
||||
|
||||
# error by default (since no model class)
|
||||
|
@ -179,6 +190,25 @@ class TestMasterView(WebTestCase):
|
|||
self.assertEqual(master.MasterView.get_url_prefix(), '/machines')
|
||||
del master.MasterView.model_class
|
||||
|
||||
def test_get_instance_url_prefix(self):
|
||||
|
||||
# error by default (since no model class)
|
||||
self.assertRaises(AttributeError, master.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
|
||||
|
||||
# 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
|
||||
|
||||
def test_get_template_prefix(self):
|
||||
|
||||
# error by default (since no model class)
|
||||
|
@ -281,12 +311,6 @@ class TestMasterView(WebTestCase):
|
|||
# support methods
|
||||
##############################
|
||||
|
||||
def test_get_index_title(self):
|
||||
master.MasterView.model_title_plural = "Wutta Widgets"
|
||||
view = master.MasterView(self.request)
|
||||
self.assertEqual(view.get_index_title(), "Wutta Widgets")
|
||||
del master.MasterView.model_title_plural
|
||||
|
||||
def test_render_to_response(self):
|
||||
|
||||
def widgets(request): return {}
|
||||
|
@ -317,24 +341,51 @@ class TestMasterView(WebTestCase):
|
|||
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)
|
||||
self.assertEqual(view.get_index_title(), "Wutta Widgets")
|
||||
del master.MasterView.model_title_plural
|
||||
|
||||
def test_get_instance(self):
|
||||
view = master.MasterView(self.request)
|
||||
self.assertRaises(NotImplementedError, view.get_instance)
|
||||
|
||||
##############################
|
||||
# view methods
|
||||
##############################
|
||||
|
||||
def test_index(self):
|
||||
|
||||
# basic sanity check using /appinfo
|
||||
master.MasterView.model_name = 'AppInfo'
|
||||
master.MasterView.route_prefix = 'appinfo'
|
||||
master.MasterView.template_prefix = '/appinfo'
|
||||
master.MasterView.grid_columns = ['foo', 'bar']
|
||||
# sanity/coverage check using /settings/
|
||||
master.MasterView.model_name = 'Setting'
|
||||
master.MasterView.model_key = 'name'
|
||||
master.MasterView.grid_columns = ['name', 'value']
|
||||
view = master.MasterView(self.request)
|
||||
response = view.index()
|
||||
# then again with data, to include view action url
|
||||
data = [{'name': 'foo', 'value': 'bar'}]
|
||||
with patch.object(view, 'index_get_grid_data', return_value=data):
|
||||
response = view.index()
|
||||
del master.MasterView.model_name
|
||||
del master.MasterView.route_prefix
|
||||
del master.MasterView.template_prefix
|
||||
del master.MasterView.model_key
|
||||
del master.MasterView.grid_columns
|
||||
|
||||
def test_view(self):
|
||||
|
||||
# sanity/coverage check using /settings/XXX
|
||||
master.MasterView.model_name = 'Setting'
|
||||
master.MasterView.grid_columns = ['name', 'value']
|
||||
master.MasterView.form_fields = ['name', 'value']
|
||||
view = master.MasterView(self.request)
|
||||
setting = {'name': 'foo.bar', 'value': 'baz'}
|
||||
self.request.matchdict = {'name': 'foo.bar'}
|
||||
with patch.object(view, 'get_instance', return_value=setting):
|
||||
response = view.view()
|
||||
del master.MasterView.model_name
|
||||
del master.MasterView.grid_columns
|
||||
del master.MasterView.form_fields
|
||||
|
||||
def test_configure(self):
|
||||
model = self.app.model
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
from tests.views.utils import WebTestCase
|
||||
|
||||
from pyramid.httpexceptions import HTTPNotFound
|
||||
|
||||
from wuttaweb.views import settings
|
||||
|
||||
|
||||
|
@ -43,3 +45,23 @@ class TestSettingView(WebTestCase):
|
|||
self.session.commit()
|
||||
data = view.index_get_grid_data(session=self.session)
|
||||
self.assertEqual(len(data), 1)
|
||||
|
||||
def test_get_instance(self):
|
||||
view = self.make_view()
|
||||
self.request.matchdict = {'name': 'foo'}
|
||||
|
||||
# setting not found
|
||||
setting = view.get_instance(session=self.session)
|
||||
self.assertIsInstance(setting, HTTPNotFound)
|
||||
|
||||
# setting is returned
|
||||
self.app.save_setting(self.session, 'foo', 'bar')
|
||||
self.session.commit()
|
||||
setting = view.get_instance(session=self.session)
|
||||
self.assertEqual(setting, {'name': 'foo', 'value': 'bar'})
|
||||
|
||||
def test_get_instance_title(self):
|
||||
setting = {'name': 'foo', 'value': 'bar'}
|
||||
view = self.make_view()
|
||||
title = view.get_instance_title(setting)
|
||||
self.assertEqual(title, 'foo')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue