fix: misc. improvements for display of grids, form errors
This commit is contained in:
parent
bf2ca4b475
commit
2503836ef5
8 changed files with 183 additions and 70 deletions
|
@ -746,17 +746,39 @@ class Form:
|
|||
kwargs = {}
|
||||
|
||||
if self.model_instance:
|
||||
# TODO: would it be smarter to test with hasattr() ?
|
||||
# if hasattr(schema, 'dictify'):
|
||||
if isinstance(self.model_instance, model.Base):
|
||||
|
||||
# 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):
|
||||
kwargs['appstruct'] = schema.dictify(self.model_instance)
|
||||
else:
|
||||
kwargs['appstruct'] = self.model_instance
|
||||
|
||||
form = deform.Form(schema, **kwargs)
|
||||
# create the Deform instance
|
||||
# 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
|
||||
|
||||
|
@ -922,18 +944,13 @@ class Form:
|
|||
if field_type:
|
||||
attrs['type'] = field_type
|
||||
if messages:
|
||||
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]))
|
||||
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])
|
||||
|
||||
return HTML.tag('b-field', c=[html], **attrs)
|
||||
|
||||
|
@ -1076,7 +1093,7 @@ class Form:
|
|||
"""
|
||||
dform = self.get_deform()
|
||||
if field in dform:
|
||||
error = dform[field].errormsg
|
||||
if error:
|
||||
return [error]
|
||||
field = dform[field]
|
||||
if field.error:
|
||||
return field.error.messages()
|
||||
return []
|
||||
|
|
|
@ -35,12 +35,14 @@ in the namespace:
|
|||
* :class:`deform:deform.widget.CheckedPasswordWidget`
|
||||
* :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,
|
||||
SelectWidget, CheckboxChoiceWidget)
|
||||
SelectWidget, CheckboxChoiceWidget,
|
||||
MoneyInputWidget)
|
||||
from webhelpers2.html import HTML
|
||||
|
||||
from wuttaweb.db import Session
|
||||
|
|
|
@ -1047,7 +1047,7 @@ class Grid:
|
|||
filters = filters or {}
|
||||
|
||||
if self.model_class:
|
||||
for key in self.columns:
|
||||
for key in self.get_model_columns():
|
||||
if key in filters:
|
||||
continue
|
||||
prop = getattr(self.model_class, key, None)
|
||||
|
|
7
src/wuttaweb/templates/deform/moneyinput.pt
Normal file
7
src/wuttaweb/templates/deform/moneyinput.pt
Normal file
|
@ -0,0 +1,7 @@
|
|||
<tal:omit tal:define="name name|field.name;
|
||||
oid oid|field.oid;
|
||||
vmodel vmodel|'modelData.'+oid;">
|
||||
<b-input name="${name}"
|
||||
v-model="${vmodel}"
|
||||
tal:attributes="attributes|field.widget.attributes|{};" />
|
||||
</tal:omit>
|
|
@ -19,7 +19,7 @@
|
|||
<b-button @click="copyDirectLink()"
|
||||
title="Copy grid link to clipboard"
|
||||
:is-small="smallFilters">
|
||||
<b-icon pack="fas" icon="share-alt" />
|
||||
<b-icon pack="fas" icon="share" />
|
||||
</b-button>
|
||||
|
||||
<b-button type="is-primary"
|
||||
|
|
|
@ -989,6 +989,84 @@ class MasterView(View):
|
|||
self.app.save_setting(session, setting['name'], setting['value'],
|
||||
force_create=True)
|
||||
|
||||
##############################
|
||||
# grid rendering methods
|
||||
##############################
|
||||
|
||||
def grid_render_bool(self, record, key, value):
|
||||
"""
|
||||
Custom grid value renderer for "boolean" fields.
|
||||
|
||||
This converts a bool value to "Yes" or "No" - unless the value
|
||||
is ``None`` in which case this renders empty string.
|
||||
To use this feature for your grid::
|
||||
|
||||
grid.set_renderer('my_bool_field', self.grid_render_bool)
|
||||
"""
|
||||
if value is None:
|
||||
return
|
||||
|
||||
return "Yes" if value else "No"
|
||||
|
||||
def grid_render_currency(self, record, key, value, scale=2):
|
||||
"""
|
||||
Custom grid value renderer for "currency" fields.
|
||||
|
||||
This expects float or decimal values, and will round the
|
||||
decimal as appropriate, and add the currency symbol.
|
||||
|
||||
:param scale: Number of decimal digits to be displayed;
|
||||
default is 2 places.
|
||||
|
||||
To use this feature for your grid::
|
||||
|
||||
grid.set_renderer('my_currency_field', self.grid_render_currency)
|
||||
|
||||
# you can also override scale
|
||||
grid.set_renderer('my_currency_field', self.grid_render_currency, scale=4)
|
||||
"""
|
||||
|
||||
# nb. get new value since the one provided will just be a
|
||||
# (json-safe) *string* if the original type was Decimal
|
||||
value = record[key]
|
||||
|
||||
if value is None:
|
||||
return
|
||||
|
||||
if value < 0:
|
||||
fmt = f"(${{:0,.{scale}f}})"
|
||||
return fmt.format(0 - value)
|
||||
|
||||
fmt = f"${{:0,.{scale}f}}"
|
||||
return fmt.format(value)
|
||||
|
||||
def grid_render_notes(self, record, key, value, maxlen=100):
|
||||
"""
|
||||
Custom grid value renderer 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 ``<span>`` 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
|
||||
##############################
|
||||
|
@ -1317,9 +1395,8 @@ 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 and issubclass(model_class, model.Base):
|
||||
if model_class:
|
||||
session = session or self.Session()
|
||||
return session.query(model_class)
|
||||
|
||||
|
@ -1344,33 +1421,6 @@ 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 ``<span>`` 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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue