3
0
Fork 0

fix: add GridWidget and form.set_grid() for convenience

omg how did i not do this sooner
This commit is contained in:
Lance Edgar 2024-12-09 20:56:09 -06:00
parent f68fe26ada
commit 40562c126e
8 changed files with 117 additions and 17 deletions

View file

@ -6,5 +6,10 @@ Glossary
.. glossary:: .. glossary::
:sorted: :sorted:
view grid
TODO This refers to a "table of data, with features" essentially.
Sometimes it may be displayed as a simple table with no features,
or sometimes it has sortable columns, search filters and other
tools.
See also the :class:`~wuttaweb.grids.base.Grid` base class.

View file

@ -474,6 +474,34 @@ class Form:
if self.schema and key in self.schema: if self.schema and key in self.schema:
self.schema[key].widget = widget self.schema[key].widget = widget
def set_grid(self, key, grid):
"""
Establish a :term:`grid` to be displayed for a field. This
uses a :class:`~wuttaweb.forms.widgets.GridWidget` to wrap the
rendered grid.
:param key: Name of field.
:param widget: :class:`~wuttaweb.grids.base.Grid` instance,
pre-configured and (usually) with data.
"""
from wuttaweb.forms.widgets import GridWidget
widget = GridWidget(self.request, grid)
self.set_widget(key, widget)
self.add_grid_vue_context(grid)
def add_grid_vue_context(self, grid):
""" """
if not grid.key:
raise ValueError("grid must have a key!")
if grid.key in self.grid_vue_context:
log.warning("grid data with key '%s' already registered, "
"but will be replaced", grid.key)
self.grid_vue_context[grid.key] = grid.get_vue_context()
def set_validator(self, key, validator): def set_validator(self, key, validator):
""" """
Set/override the validator for a field, or the form. Set/override the validator for a field, or the form.
@ -848,17 +876,6 @@ class Form:
output = render(template, context) output = render(template, context)
return HTML.literal(output) return HTML.literal(output)
def add_grid_vue_context(self, grid):
""" """
if not grid.key:
raise ValueError("grid must have a key!")
if grid.key in self.grid_vue_context:
log.warning("grid data with key '%s' already registered, "
"but will be replaced", grid.key)
self.grid_vue_context[grid.key] = grid.get_vue_context()
def render_vue_field( def render_vue_field(
self, self,
fieldname, fieldname,

View file

@ -214,7 +214,7 @@ class ObjectRef(colander.SchemaType):
node.model_instance = appstruct node.model_instance = appstruct
# serialize to uuid # serialize to uuid
return appstruct.uuid return appstruct.uuid.hex
def deserialize(self, node, cstruct): def deserialize(self, node, cstruct):
""" """ """ """
@ -296,7 +296,7 @@ class ObjectRef(colander.SchemaType):
if 'values' not in kwargs: if 'values' not in kwargs:
query = self.get_query() query = self.get_query()
objects = query.all() objects = query.all()
values = [(obj.uuid, str(obj)) values = [(obj.uuid.hex, str(obj))
for obj in objects] for obj in objects]
if self.empty_option: if self.empty_option:
values.insert(0, self.empty_option) values.insert(0, self.empty_option)

View file

@ -210,6 +210,44 @@ class FileDownloadWidget(Widget):
return humanize.naturalsize(size) return humanize.naturalsize(size)
class GridWidget(Widget):
"""
Widget for fields whose data is represented by a :term:`grid`.
This is a subclass of :class:`deform:deform.widget.Widget` but
does not use any Deform templates.
This widget only supports "readonly" mode, is not editable. It is
merely a convenience around the grid itself, which does the heavy
lifting.
Instead of creating this widget directly you probably should call
:meth:`~wuttaweb.forms.base.Form.set_grid()` on your form.
:param request: Current :term:`request` object.
:param grid: :class:`~wuttaweb.grids.base.Grid` instance, used to
display the field data.
"""
def __init__(self, request, grid, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.grid = grid
def serialize(self, field, cstruct, **kw):
"""
This widget simply calls
:meth:`~wuttaweb.grids.base.Grid.render_table_element()` on
the ``grid`` to serialize.
"""
readonly = kw.get('readonly', self.readonly)
if not readonly:
raise NotImplementedError("edit not allowed for this widget")
return self.grid.render_table_element()
class RoleRefsWidget(WuttaCheckboxChoiceWidget): class RoleRefsWidget(WuttaCheckboxChoiceWidget):
""" """
Widget for use with User Widget for use with User

View file

@ -56,7 +56,7 @@ Elements of :attr:`~Grid.sort_defaults` will be of this type.
class Grid: class Grid:
""" """
Base class for all grids. Base class for all :term:`grids <grid>`.
:param request: Reference to current :term:`request` object. :param request: Reference to current :term:`request` object.

View file

@ -142,6 +142,20 @@ class TestForm(TestCase):
self.assertIs(form.widgets['foo'], new_widget) self.assertIs(form.widgets['foo'], new_widget)
self.assertIs(schema['foo'].widget, new_widget) self.assertIs(schema['foo'].widget, new_widget)
def test_set_grid(self):
form = self.make_form(fields=['foo', 'bar'])
self.assertNotIn('foo', form.widgets)
self.assertNotIn('foogrid', form.grid_vue_context)
grid = Grid(self.request, key='foogrid',
columns=['a', 'b'],
data=[{'a': 1, 'b': 2}, {'a': 3, 'b': 4}])
form.set_grid('foo', grid)
self.assertIn('foo', form.widgets)
self.assertIsInstance(form.widgets['foo'], widgets.GridWidget)
self.assertIn('foogrid', form.grid_vue_context)
def test_set_validator(self): def test_set_validator(self):
form = self.make_form(fields=['foo', 'bar']) form = self.make_form(fields=['foo', 'bar'])
self.assertEqual(form.validators, {}) self.assertEqual(form.validators, {})

View file

@ -100,7 +100,7 @@ class TestObjectRef(DataTestCase):
self.assertIsNotNone(person.uuid) self.assertIsNotNone(person.uuid)
typ = mod.ObjectRef(self.request) typ = mod.ObjectRef(self.request)
value = typ.serialize(node, person) value = typ.serialize(node, person)
self.assertEqual(value, person.uuid) self.assertEqual(value, person.uuid.hex)
def test_deserialize(self): def test_deserialize(self):
model = self.app.model model = self.app.model

View file

@ -6,6 +6,7 @@ import colander
import deform import deform
from pyramid import testing from pyramid import testing
from wuttaweb import grids
from wuttaweb.forms import widgets as mod from wuttaweb.forms import widgets as mod
from wuttaweb.forms.schema import FileDownload, PersonRef, RoleRefs, UserRefs, Permissions from wuttaweb.forms.schema import FileDownload, PersonRef, RoleRefs, UserRefs, Permissions
from tests.util import WebTestCase from tests.util import WebTestCase
@ -117,6 +118,31 @@ class TestFileDownloadWidget(WebTestCase):
self.assertEqual(html2, html) self.assertEqual(html2, html)
class TestGridWidget(WebTestCase):
def make_field(self, node, **kwargs):
# TODO: not sure why default renderer is in use even though
# pyramid_deform was included in setup? but this works..
kwargs.setdefault('renderer', deform.Form.default_renderer)
return deform.Field(node, **kwargs)
def test_serialize(self):
grid = grids.Grid(self.request,
columns=['foo', 'bar'],
data=[{'foo': 1, 'bar': 2}, {'foo': 3, 'bar': 4}])
node = colander.SchemaNode(colander.String())
widget = mod.GridWidget(self.request, grid)
field = self.make_field(node)
# readonly works okay
html = widget.serialize(field, None, readonly=True)
self.assertIn('<b-table ', html)
# but otherwise, error
self.assertRaises(NotImplementedError, widget.serialize, field, None)
class TestRoleRefsWidget(WebTestCase): class TestRoleRefsWidget(WebTestCase):
def make_field(self, node, **kwargs): def make_field(self, node, **kwargs):