feat: improve widget, rendering for Role notes
This commit is contained in:
parent
b4b72d92aa
commit
bdfa0197b2
11 changed files with 201 additions and 19 deletions
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
7
src/wuttaweb/templates/deform/readonly/notes.pt
Normal file
7
src/wuttaweb/templates/deform/readonly/notes.pt
Normal 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>
|
1
src/wuttaweb/templates/deform/readonly/objectref.pt
Normal file
1
src/wuttaweb/templates/deform/readonly/objectref.pt
Normal file
|
@ -0,0 +1 @@
|
|||
<span>${str(field.schema.model_instance or '')}</span>
|
11
src/wuttaweb/templates/deform/textarea.pt
Normal file
11
src/wuttaweb/templates/deform/textarea.pt
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue