3
0
Fork 0

feat: improve widget, rendering for Role notes

This commit is contained in:
Lance Edgar 2024-08-13 16:29:34 -05:00
parent b4b72d92aa
commit bdfa0197b2
11 changed files with 201 additions and 19 deletions

View file

@ -30,10 +30,11 @@ in the namespace:
* :class:`deform:deform.widget.Widget` (base class)
* :class:`deform:deform.widget.TextInputWidget`
* :class:`deform:deform.widget.TextAreaWidget`
* :class:`deform:deform.widget.SelectWidget`
"""
from deform.widget import Widget, TextInputWidget, SelectWidget
from deform.widget import Widget, TextInputWidget, TextAreaWidget, SelectWidget
from webhelpers2.html import HTML
@ -48,6 +49,18 @@ class ObjectRefWidget(SelectWidget):
the form schema; via
:meth:`~wuttaweb.forms.schema.ObjectRef.widget_maker()`.
In readonly mode, this renders a ``<span>`` tag around the
:attr:`model_instance` (converted to string).
Otherwise it renders a select (dropdown) element allowing user to
choose from available records.
This is a subclass of :class:`deform:deform.widget.SelectWidget`
and uses these Deform templates:
* ``select``
* ``readonly/objectref``
.. attribute:: model_instance
Reference to the model record instance, i.e. the "far side" of
@ -60,23 +73,26 @@ class ObjectRefWidget(SelectWidget):
when the :class:`~wuttaweb.forms.schema.ObjectRef` type
instance (associated with the node) is serialized.
"""
readonly_template = 'readonly/objectref'
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
def serialize(self, field, cstruct, **kw):
"""
Serialize the widget.
In readonly mode, returns a ``<span>`` tag around the
:attr:`model_instance` rendered as string.
class NotesWidget(TextAreaWidget):
"""
Widget for use with "notes" fields.
Otherwise renders via the ``deform/select`` template.
"""
readonly = kw.get('readonly', self.readonly)
if readonly:
obj = field.schema.model_instance
return HTML.tag('span', c=str(obj or ''))
In readonly mode, this shows the notes with a background to make
them stand out a bit more.
return super().serialize(field, cstruct, **kw)
Otherwise it effectively shows a ``<textarea>`` input element.
This is a subclass of :class:`deform:deform.widget.TextAreaWidget`
and uses these Deform templates:
* ``textarea``
* ``readonly/notes``
"""
readonly_template = 'readonly/notes'

View file

@ -24,6 +24,7 @@
Base grid classes
"""
import functools
import json
import logging
@ -81,6 +82,12 @@ class Grid:
model records) or else an object capable of producing such a
list, e.g. SQLAlchemy query.
.. attribute:: renderers
Dict of column (cell) value renderer overrides.
See also :meth:`set_renderer()`.
.. attribute:: actions
List of :class:`GridAction` instances represenging action links
@ -106,6 +113,7 @@ class Grid:
key=None,
columns=None,
data=None,
renderers={},
actions=[],
linked_columns=[],
vue_tagname='wutta-grid',
@ -114,6 +122,7 @@ class Grid:
self.model_class = model_class
self.key = key
self.data = data
self.renderers = renderers or {}
self.actions = actions or []
self.linked_columns = linked_columns or []
self.vue_tagname = vue_tagname
@ -194,6 +203,47 @@ class Grid:
if key in self.columns:
self.columns.remove(key)
def set_renderer(self, key, renderer, **kwargs):
"""
Set/override the value renderer for a column.
:param key: Name of column.
:param renderer: Callable as described below.
Depending on the nature of grid data, sometimes a cell's
"as-is" value will be undesirable for display purposes.
The logic in :meth:`get_vue_data()` will first "convert" all
grid data as necessary so that it is at least JSON-compatible.
But then it also will invoke a renderer override (if defined)
to obtain the "final" cell value.
A renderer must be a callable which accepts 3 args ``(record,
key, value)``:
* ``record`` is the "original" record from :attr:`data`
* ``key`` is the column name
* ``value`` is the JSON-safe cell value
Whatever the renderer returns, is then used as final cell
value. For instance::
from webhelpers2.html import HTML
def render_foo(record, key, value):
return HTML.literal("<p>this is the final cell value</p>")
grid = Grid(columns=['foo', 'bar'])
grid.set_renderer('foo', render_foo)
Renderer overrides are tracked via :attr:`renderers`.
"""
if kwargs:
renderer = functools.partial(renderer, **kwargs)
self.renderers[key] = renderer
def set_link(self, key, link=True):
"""
Explicitly enable or disable auto-link behavior for a given
@ -352,12 +402,18 @@ class Grid:
# we have action(s), so add URL(s) for each record in data
data = []
for i, record in enumerate(original_data):
original_record = record
# convert data if needed, for json compat
record = make_json_safe(record,
# TODO: is this a good idea?
warn=False)
# customize value rendering where applicable
for key in self.renderers:
value = record[key]
record[key] = self.renderers[key](original_record, key, value)
# add action urls to each record
for action in self.actions:
url = action.get_url(record, i)

View file

@ -0,0 +1,7 @@
<div tal:omit-tag="">
<span tal:condition="not cstruct"></span>
<pre tal:condition="cstruct"
class="is-family-sans-serif"
style="white-space: pre-wrap;"
>${cstruct}</pre>
</div>

View file

@ -0,0 +1 @@
<span>${str(field.schema.model_instance or '')}</span>

View file

@ -0,0 +1,11 @@
<div tal:define="name name|field.name;
oid oid|field.oid;
vmodel vmodel|'modelData.'+oid;
rows rows|field.widget.rows;
cols cols|field.widget.cols;"
tal:omit-tag="">
<b-input name="${name}"
v-model="${vmodel}"
type="textarea"
tal:attributes="attributes|field.widget.attributes|{};" />
</div>

View file

@ -28,6 +28,7 @@ import sqlalchemy as sa
from sqlalchemy import orm
from pyramid.renderers import render_to_response
from webhelpers2.html import HTML
from wuttaweb.views import View
from wuttaweb.util import get_form_data, get_model_fields
@ -1032,6 +1033,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 ``<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

View file

@ -27,6 +27,7 @@ Views for roles
from wuttjamaican.db.model import Role
from wuttaweb.views import MasterView
from wuttaweb.db import Session
from wuttaweb.forms import widgets
class RoleView(MasterView):
@ -62,6 +63,9 @@ class RoleView(MasterView):
# name
g.set_link('name')
# notes
g.set_renderer('notes', self.grid_render_notes)
def configure_form(self, f):
""" """
super().configure_form(f)
@ -73,6 +77,9 @@ class RoleView(MasterView):
# name
f.set_validator('name', self.unique_name)
# notes
f.set_widget('notes', widgets.NotesWidget())
def unique_name(self, node, value):
""" """
model = self.app.model