3
0
Fork 0

fix: add WuttaDateWidget and associated logic

This commit is contained in:
Lance Edgar 2025-01-15 08:40:56 -06:00
parent e3c432aa37
commit 9e0e36d536
4 changed files with 103 additions and 7 deletions

View file

@ -546,10 +546,13 @@ class Form:
This is generally only possible if :attr:`model_class` is set This is generally only possible if :attr:`model_class` is set
to a valid SQLAlchemy mapped class. to a valid SQLAlchemy mapped class.
As of writing this only looks for This only checks for a couple of data types, with mapping as
:class:`sqlalchemy:sqlalchemy.types.DateTime` fields and if follows:
any are found, they are configured to use
:class:`~wuttaweb.forms.widgets.WuttaDateTimeWidget()`. * :class:`sqlalchemy:sqlalchemy.types.Date` ->
:class:`~wuttaweb.forms.widgets.WuttaDateWidget`
* :class:`sqlalchemy:sqlalchemy.types.DateTime` ->
:class:`~wuttaweb.forms.widgets.WuttaDateTimeWidget`
""" """
from wuttaweb.forms import widgets from wuttaweb.forms import widgets
@ -565,8 +568,9 @@ class Form:
prop = getattr(attr, 'prop', None) prop = getattr(attr, 'prop', None)
if prop and isinstance(prop, orm.ColumnProperty): if prop and isinstance(prop, orm.ColumnProperty):
column = prop.columns[0] column = prop.columns[0]
if isinstance(column.type, sa.DateTime): if isinstance(column.type, sa.Date):
# self.set_renderer(key, self.render_datetime) self.set_widget(key, widgets.WuttaDateWidget(self.request))
elif isinstance(column.type, sa.DateTime):
self.set_widget(key, widgets.WuttaDateTimeWidget(self.request)) self.set_widget(key, widgets.WuttaDateTimeWidget(self.request))
def set_grid(self, key, grid): def set_grid(self, key, grid):

View file

@ -36,6 +36,7 @@ in the namespace:
* :class:`deform:deform.widget.CheckboxWidget` * :class:`deform:deform.widget.CheckboxWidget`
* :class:`deform:deform.widget.SelectWidget` * :class:`deform:deform.widget.SelectWidget`
* :class:`deform:deform.widget.CheckboxChoiceWidget` * :class:`deform:deform.widget.CheckboxChoiceWidget`
* :class:`deform:deform.widget.DateInputWidget`
* :class:`deform:deform.widget.DateTimeInputWidget` * :class:`deform:deform.widget.DateTimeInputWidget`
* :class:`deform:deform.widget.MoneyInputWidget` * :class:`deform:deform.widget.MoneyInputWidget`
""" """
@ -49,7 +50,7 @@ import humanize
from deform.widget import (Widget, TextInputWidget, TextAreaWidget, from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
PasswordWidget, CheckedPasswordWidget, PasswordWidget, CheckedPasswordWidget,
CheckboxWidget, SelectWidget, CheckboxChoiceWidget, CheckboxWidget, SelectWidget, CheckboxChoiceWidget,
DateTimeInputWidget, MoneyInputWidget) DateInputWidget, DateTimeInputWidget, MoneyInputWidget)
from webhelpers2.html import HTML from webhelpers2.html import HTML
from wuttjamaican.conf import parse_list from wuttjamaican.conf import parse_list
@ -153,6 +154,43 @@ class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget):
self.app = self.config.get_app() self.app = self.config.get_app()
class WuttaDateWidget(DateInputWidget):
"""
Custom widget for :class:`python:datetime.date` fields.
The main purpose of this widget is to leverage
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_date()`
for the readonly display.
It is automatically used for SQLAlchemy mapped classes where the
field maps to a :class:`sqlalchemy:sqlalchemy.types.Date` column.
For other (non-mapped) date fields, or mapped datetime fields for
which a date widget is preferred, use
:meth:`~wuttaweb.forms.base.Form.set_widget()`.
This is a subclass of
:class:`deform:deform.widget.DateInputWidget` and uses these
Deform templates:
* ``dateinput``
"""
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.config = self.request.wutta_config
self.app = self.config.get_app()
def serialize(self, field, cstruct, **kw):
""" """
readonly = kw.get('readonly', self.readonly)
if readonly and cstruct:
dt = datetime.datetime.fromisoformat(cstruct)
return self.app.render_date(dt)
return super().serialize(field, cstruct, **kw)
class WuttaDateTimeWidget(DateTimeInputWidget): class WuttaDateTimeWidget(DateTimeInputWidget):
""" """
Custom widget for :class:`python:datetime.datetime` fields. Custom widget for :class:`python:datetime.datetime` fields.

View file

@ -3,6 +3,8 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import sqlalchemy as sa
import colander import colander
import deform import deform
from pyramid import testing from pyramid import testing
@ -188,6 +190,18 @@ class TestForm(TestCase):
self.assertIn('created', form.widgets) self.assertIn('created', form.widgets)
self.assertIsInstance(form.widgets['created'], MyWidget) self.assertIsInstance(form.widgets['created'], MyWidget)
# mock up a table with all relevant column types
class Whatever(model.Base):
__tablename__ = 'whatever'
id = sa.Column(sa.Integer(), primary_key=True)
date = sa.Column(sa.Date())
date_time = sa.Column(sa.DateTime())
# widget set for all known types
form = self.make_form(model_class=Whatever)
self.assertIsInstance(form.widgets['date'], widgets.WuttaDateWidget)
self.assertIsInstance(form.widgets['date_time'], widgets.WuttaDateTimeWidget)
def test_set_grid(self): def test_set_grid(self):
form = self.make_form(fields=['foo', 'bar']) form = self.make_form(fields=['foo', 'bar'])
self.assertNotIn('foo', form.widgets) self.assertNotIn('foo', form.widgets)

View file

@ -87,6 +87,46 @@ class TestObjectRefWidget(WebTestCase):
self.assertNotIn('url', values) self.assertNotIn('url', values)
class TestWuttaDateWidget(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 make_widget(self, **kwargs):
return mod.WuttaDateWidget(self.request, **kwargs)
def test_serialize(self):
node = colander.SchemaNode(colander.Date())
field = self.make_field(node)
# first try normal date
widget = self.make_widget()
dt = datetime.date(2025, 1, 15)
# editable widget has normal picker html
result = widget.serialize(field, str(dt))
self.assertIn('<wutta-datepicker', result)
# readonly is rendered per app convention
result = widget.serialize(field, str(dt), readonly=True)
self.assertEqual(result, '2025-01-15')
# now try again with datetime
widget = self.make_widget()
dt = datetime.datetime(2025, 1, 15, 8, 35)
# editable widget has normal picker html
result = widget.serialize(field, str(dt))
self.assertIn('<wutta-datepicker', result)
# readonly is rendered per app convention
result = widget.serialize(field, str(dt), readonly=True)
self.assertEqual(result, '2025-01-15')
class TestWuttaDateTimeWidget(WebTestCase): class TestWuttaDateTimeWidget(WebTestCase):
def make_field(self, node, **kwargs): def make_field(self, node, **kwargs):