diff --git a/src/wuttaweb/app.py b/src/wuttaweb/app.py index 88318b4..845b41f 100644 --- a/src/wuttaweb/app.py +++ b/src/wuttaweb/app.py @@ -61,7 +61,7 @@ class WebAppProvider(AppProvider): return self.web_handler -def make_wutta_config(settings, config_maker=None, **kwargs): +def make_wutta_config(settings): """ Make a WuttaConfig object from the given settings. @@ -93,9 +93,8 @@ def make_wutta_config(settings, config_maker=None, **kwargs): "section of config to the path of your " "config file. Lame, but necessary.") - # make config, add to settings - config_maker = config_maker or make_config - wutta_config = config_maker(path, **kwargs) + # make config per usual, add to settings + wutta_config = make_config(path) settings['wutta_config'] = wutta_config # configure database sessions diff --git a/src/wuttaweb/forms/base.py b/src/wuttaweb/forms/base.py index ceaeeb7..c3567b5 100644 --- a/src/wuttaweb/forms/base.py +++ b/src/wuttaweb/forms/base.py @@ -746,39 +746,17 @@ class Form: kwargs = {} if self.model_instance: - - # TODO: i keep finding problems with this, not sure - # what needs to happen. some forms will have a simple - # dict for model_instance, others will have a proper - # SQLAlchemy object. and in the latter case, it may - # not be "wutta-native" but from another DB. - - # so the problem is, how to detect whether we should - # use the model_instance as-is or if we should convert - # to a dict. some options include: - - # - check if instance has dictify() method - # i *think* this was tried and didn't work? but do not recall - - # - check if is instance of model.Base - # this is unreliable since model.Base is wutta-native - - # - check if form has a model_class - # has not been tried yet - - # - check if schema is from colanderalchemy - # this is what we are trying currently... - - if isinstance(schema, SQLAlchemySchemaNode): + # TODO: would it be smarter to test with hasattr() ? + # if hasattr(schema, 'dictify'): + if isinstance(self.model_instance, model.Base): kwargs['appstruct'] = schema.dictify(self.model_instance) else: kwargs['appstruct'] = self.model_instance - # create the Deform instance + form = deform.Form(schema, **kwargs) # nb. must give a reference back to wutta form; this is # for sake of field schema nodes and widgets, e.g. to # access the main model instance - form = deform.Form(schema, **kwargs) form.wutta_form = self self.deform_form = form @@ -944,13 +922,18 @@ class Form: if field_type: attrs['type'] = field_type if messages: - cls = 'is-size-7' - if field_type == 'is-danger': - cls += ' has-text-danger' - messages = [HTML.tag('p', c=[msg], class_=cls) - for msg in messages] - slot = HTML.tag('slot', name='messages', c=messages) - html = HTML.tag('div', c=[html, slot]) + if len(messages) == 1: + msg = messages[0] + if msg.startswith('`') and msg.endswith('`'): + attrs[':message'] = msg + else: + attrs['message'] = msg + # TODO + # else: + # # nb. must pass an array as JSON string + # attrs[':message'] = '[{}]'.format(', '.join([ + # "'{}'".format(msg.replace("'", r"\'")) + # for msg in messages])) return HTML.tag('b-field', c=[html], **attrs) @@ -995,16 +978,7 @@ class Form: model_data = {} def assign(field): - value = field.cstruct - - # TODO: we need a proper true/false on the Vue side, - # but deform/colander want 'true' and 'false' ..so - # for now we explicitly translate here, ugh. also - # note this does not yet allow for null values.. :( - if isinstance(field.typ, colander.Boolean): - value = True if field.typ.true_val else False - - model_data[field.oid] = make_json_safe(value) + model_data[field.oid] = make_json_safe(field.cstruct) for key in self.fields: @@ -1102,7 +1076,7 @@ class Form: """ dform = self.get_deform() if field in dform: - field = dform[field] - if field.error: - return field.error.messages() + error = dform[field].errormsg + if error: + return [error] return [] diff --git a/src/wuttaweb/forms/schema.py b/src/wuttaweb/forms/schema.py index 4402fde..a3a464b 100644 --- a/src/wuttaweb/forms/schema.py +++ b/src/wuttaweb/forms/schema.py @@ -258,12 +258,7 @@ class PersonRef(ObjectRef): This is a subclass of :class:`ObjectRef`. """ - - @property - def model_class(self): - """ """ - model = self.app.model - return model.Person + model_class = Person def sort_query(self, query): """ """ diff --git a/src/wuttaweb/forms/widgets.py b/src/wuttaweb/forms/widgets.py index 837b6f1..ee58a1a 100644 --- a/src/wuttaweb/forms/widgets.py +++ b/src/wuttaweb/forms/widgets.py @@ -33,17 +33,14 @@ in the namespace: * :class:`deform:deform.widget.TextAreaWidget` * :class:`deform:deform.widget.PasswordWidget` * :class:`deform:deform.widget.CheckedPasswordWidget` -* :class:`deform:deform.widget.CheckboxWidget` * :class:`deform:deform.widget.SelectWidget` * :class:`deform:deform.widget.CheckboxChoiceWidget` -* :class:`deform:deform.widget.MoneyInputWidget` """ import colander from deform.widget import (Widget, TextInputWidget, TextAreaWidget, PasswordWidget, CheckedPasswordWidget, - CheckboxWidget, SelectWidget, CheckboxChoiceWidget, - MoneyInputWidget) + SelectWidget, CheckboxChoiceWidget) from webhelpers2.html import HTML from wuttaweb.db import Session @@ -223,7 +220,7 @@ class UserRefsWidget(WuttaCheckboxChoiceWidget): users = [] if cstruct: for uuid in cstruct: - user = self.session.get(model.User, uuid) + user = self.session.query(model.User).get(uuid) if user: users.append(dict([(key, getattr(user, key)) for key in columns + ['uuid']])) diff --git a/src/wuttaweb/grids/base.py b/src/wuttaweb/grids/base.py index 0f2c812..aa2e413 100644 --- a/src/wuttaweb/grids/base.py +++ b/src/wuttaweb/grids/base.py @@ -1047,12 +1047,6 @@ class Grid: filters = filters or {} if self.model_class: - # TODO: i tried using self.get_model_columns() here but in - # many cases that will be too aggressive. however it is - # often the case that the *grid* columns are a subset of - # the unerlying *table* columns. so until a better way - # is found, we choose "too few" instead of "too many" - # filters here. surely must improve it at some point. for key in self.columns: if key in filters: continue diff --git a/src/wuttaweb/templates/deform/checkbox.pt b/src/wuttaweb/templates/deform/checkbox.pt index c4536e8..92c9f62 100644 --- a/src/wuttaweb/templates/deform/checkbox.pt +++ b/src/wuttaweb/templates/deform/checkbox.pt @@ -6,6 +6,6 @@ v-model="${vmodel}" native-value="true" tal:attributes="attributes|field.widget.attributes|{};"> - {{ ${vmodel} ? "Yes" : "No" }} + {{ ${vmodel} }} diff --git a/src/wuttaweb/templates/deform/moneyinput.pt b/src/wuttaweb/templates/deform/moneyinput.pt deleted file mode 100644 index 532a823..0000000 --- a/src/wuttaweb/templates/deform/moneyinput.pt +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/src/wuttaweb/templates/deform/readonly/checkbox.pt b/src/wuttaweb/templates/deform/readonly/checkbox.pt deleted file mode 100644 index 4988213..0000000 --- a/src/wuttaweb/templates/deform/readonly/checkbox.pt +++ /dev/null @@ -1,4 +0,0 @@ - - Yes - No - diff --git a/src/wuttaweb/templates/grids/vue_template.mako b/src/wuttaweb/templates/grids/vue_template.mako index 1ed408e..edcdc24 100644 --- a/src/wuttaweb/templates/grids/vue_template.mako +++ b/src/wuttaweb/templates/grids/vue_template.mako @@ -19,7 +19,7 @@ - + `` tag is also given a ``title`` - attribute with the original (full) text, so that appears on - mouse hover. - - To use this feature for your grid:: - - grid.set_renderer('my_notes_field', self.grid_render_notes) - - # you can also override maxlen - grid.set_renderer('my_notes_field', self.grid_render_notes, maxlen=50) - """ - if value is None: - return - - if len(value) < maxlen: - return value - - return HTML.tag('span', title=value, c=f"{value[:maxlen]}...") - ############################## # support methods ############################## @@ -1395,8 +1317,9 @@ class MasterView(View): Default logic for this method returns a "plain" query on the :attr:`model_class` if that is defined; otherwise ``None``. """ + model = self.app.model model_class = self.get_model_class() - if model_class: + if model_class and issubclass(model_class, model.Base): session = session or self.Session() return session.query(model_class) @@ -1421,6 +1344,33 @@ class MasterView(View): # for key in self.get_model_key(): # grid.set_link(key) + def grid_render_notes(self, record, key, value, maxlen=100): + """ + Custom grid renderer callable for "notes" fields. + + If the given text ``value`` is shorter than ``maxlen`` + characters, it is returned as-is. + + But if it is longer, then it is truncated and an ellispsis is + added. The resulting ```` tag is also given a ``title`` + attribute with the original (full) text, so that appears on + mouse hover. + + To use this feature for your grid:: + + grid.set_renderer('my_notes_field', self.grid_render_notes) + + # you can also override maxlen + grid.set_renderer('my_notes_field', self.grid_render_notes, maxlen=50) + """ + if value is None: + return + + if len(value) < maxlen: + return value + + return HTML.tag('span', title=value, c=f"{value[:maxlen]}...") + def get_instance(self, session=None): """ This should return the "current" model instance based on the diff --git a/src/wuttaweb/views/people.py b/src/wuttaweb/views/people.py index 78cf931..a19df57 100644 --- a/src/wuttaweb/views/people.py +++ b/src/wuttaweb/views/people.py @@ -123,12 +123,6 @@ class PersonView(MasterView): @classmethod def defaults(cls, config): """ """ - - # nb. Person may come from custom model - wutta_config = config.registry.settings['wutta_config'] - app = wutta_config.get_app() - cls.model_class = app.model.Person - cls._defaults(config) cls._people_defaults(config) diff --git a/src/wuttaweb/views/settings.py b/src/wuttaweb/views/settings.py index aa28416..a20e1f6 100644 --- a/src/wuttaweb/views/settings.py +++ b/src/wuttaweb/views/settings.py @@ -51,7 +51,6 @@ class AppInfoView(MasterView): model_name = 'AppInfo' model_title_plural = "App Info" route_prefix = 'appinfo' - filterable = False sort_on_backend = False sort_defaults = 'name' paginated = False diff --git a/src/wuttaweb/views/users.py b/src/wuttaweb/views/users.py index 5e28a26..91ed2e0 100644 --- a/src/wuttaweb/views/users.py +++ b/src/wuttaweb/views/users.py @@ -207,17 +207,6 @@ class UserView(MasterView): role = session.get(model.Role, uuid) user.roles.remove(role) - @classmethod - def defaults(cls, config): - """ """ - - # nb. User may come from custom model - wutta_config = config.registry.settings['wutta_config'] - app = wutta_config.get_app() - cls.model_class = app.model.User - - cls._defaults(config) - def defaults(config, **kwargs): base = globals() diff --git a/tests/forms/test_base.py b/tests/forms/test_base.py index 73e9d85..70cb51f 100644 --- a/tests/forms/test_base.py +++ b/tests/forms/test_base.py @@ -461,10 +461,15 @@ class TestForm(TestCase): # nb. no error message self.assertNotIn('message', html) - # with error message - with patch.object(form, 'get_field_errors', return_value=['something is wrong']): - html = form.render_vue_field('foo') - self.assertIn('something is wrong', html) + # with single "static" error + dform['foo'].error = MagicMock(msg="something is wrong") + html = form.render_vue_field('foo') + self.assertIn(' message="something is wrong"', html) + + # with single "dynamic" error + dform['foo'].error = MagicMock(msg="`something is wrong`") + html = form.render_vue_field('foo') + self.assertIn(':message="`something is wrong`"', html) # add another field, but not to deform, so it should still # display but with no widget @@ -520,33 +525,20 @@ class TestForm(TestCase): data = form.get_vue_model_data() self.assertEqual(len(data), 2) - # confirm bool values make it thru as-is - schema.add(colander.SchemaNode(colander.Bool(), name='baz')) - form = self.make_form(schema=schema, model_instance={ - 'foo': 'one', - 'bar': 'two', - 'baz': True, - }) - data = form.get_vue_model_data() - self.assertEqual(list(data.values()), ['one', 'two', True]) - def test_get_field_errors(self): schema = self.make_schema() - - # simple 'Required' validation failure form = self.make_form(schema=schema) - self.request.method = 'POST' - self.request.POST = {'foo': 'one'} - self.assertFalse(form.validate()) - errors = form.get_field_errors('bar') - self.assertEqual(errors, ['Required']) + dform = form.get_deform() - # no errors - form = self.make_form(schema=schema) - self.request.POST = {'foo': 'one', 'bar': 'two'} - self.assertTrue(form.validate()) - errors = form.get_field_errors('bar') - self.assertEqual(errors, []) + # no error + errors = form.get_field_errors('foo') + self.assertEqual(len(errors), 0) + + # simple error + dform['foo'].error = MagicMock(msg="something is wrong") + errors = form.get_field_errors('foo') + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0], "something is wrong") def test_validate(self): schema = self.make_schema() diff --git a/tests/views/test_master.py b/tests/views/test_master.py index 023449a..d000693 100644 --- a/tests/views/test_master.py +++ b/tests/views/test_master.py @@ -1,6 +1,5 @@ # -*- coding: utf-8; -*- -import decimal import functools from unittest import TestCase from unittest.mock import MagicMock, patch @@ -559,44 +558,6 @@ class TestMasterView(WebTestCase): view.configure_grid(grid) self.assertNotIn('uuid', grid.columns) - def test_grid_render_bool(self): - model = self.app.model - view = self.make_view() - user = model.User(username='barney', active=None) - - # null - value = view.grid_render_bool(user, 'active', None) - self.assertIsNone(value) - - # true - user.active = True - value = view.grid_render_bool(user, 'active', True) - self.assertEqual(value, "Yes") - - # false - user.active = False - value = view.grid_render_bool(user, 'active', False) - self.assertEqual(value, "No") - - def test_grid_render_currency(self): - model = self.app.model - view = self.make_view() - obj = {'amount': None} - - # null - value = view.grid_render_currency(obj, 'amount', None) - self.assertIsNone(value) - - # normal amount - obj['amount'] = decimal.Decimal('100.42') - value = view.grid_render_currency(obj, 'amount', '100.42') - self.assertEqual(value, "$100.42") - - # negative amount - obj['amount'] = decimal.Decimal('-100.42') - value = view.grid_render_currency(obj, 'amount', '-100.42') - self.assertEqual(value, "($100.42)") - def test_grid_render_notes(self): model = self.app.model view = self.make_view()