From 2ccfe29553f4fb45493e76ecb90e17a0cd2fda0e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 17 Dec 2025 17:46:31 -0600 Subject: [PATCH] fix: prevent error in DateTime schema type if no widget/request set when we use this intentionally, the widget/request should be set as expected. but apparently this gets instantiated sometimes (by ColanderAlchemy?) without a widget. so this adds sane fallback logic, instead of outright error --- src/wuttaweb/forms/schema.py | 30 +++++++++++++++++++++--------- tests/forms/test_schema.py | 7 +++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/wuttaweb/forms/schema.py b/src/wuttaweb/forms/schema.py index 37db3e9..e002c0b 100644 --- a/src/wuttaweb/forms/schema.py +++ b/src/wuttaweb/forms/schema.py @@ -31,6 +31,7 @@ import colander import sqlalchemy as sa from wuttjamaican.conf import parse_list +from wuttjamaican.util import localtime from wuttaweb.db import Session from wuttaweb.forms import widgets @@ -38,28 +39,38 @@ from wuttaweb.forms import widgets class WuttaDateTime(colander.DateTime): """ - Custom schema type for ``datetime`` fields. + Custom schema type for :class:`~python:datetime.datetime` fields. This should be used automatically for - :class:`sqlalchemy:sqlalchemy.types.DateTime` columns unless you - register another default. + :class:`~sqlalchemy:sqlalchemy.types.DateTime` ORM columns unless + you register another default. This schema type exists for sake of convenience, when working with the Buefy datepicker + timepicker widgets. + + It also follows the datetime handling "rules" as outlined in + :doc:`wuttjamaican:narr/datetime`. On the Python side, values + should be naive/UTC datetime objects. On the HTTP side, values + will be ISO-format strings representing aware/local time. """ def serialize(self, node, appstruct): if not appstruct: return colander.null - request = node.widget.request - config = request.wutta_config - app = config.get_app() + # nb. request should be present when it matters + if node.widget and node.widget.request: + request = node.widget.request + config = request.wutta_config + app = config.get_app() + appstruct = app.localtime(appstruct) + else: + # but if not, fallback to config-less logic + appstruct = localtime(appstruct) - dt = app.localtime(appstruct) if self.format: - return dt.strftime(self.format) - return dt.isoformat() + return appstruct.strftime(self.format) + return appstruct.isoformat() def deserialize( # pylint: disable=inconsistent-return-statements self, node, cstruct @@ -72,6 +83,7 @@ class WuttaDateTime(colander.DateTime): "%Y-%m-%dT%I:%M %p", ] + # nb. request is always assumed to be present here request = node.widget.request config = request.wutta_config app = config.get_app() diff --git a/tests/forms/test_schema.py b/tests/forms/test_schema.py index a01ce35..6c587fb 100644 --- a/tests/forms/test_schema.py +++ b/tests/forms/test_schema.py @@ -62,6 +62,13 @@ class TestWuttaDateTime(WebTestCase): ) self.assertEqual(result, "2024-12-11 02:33 PM") + # missing widget/request/config + typ = mod.WuttaDateTime() + node = colander.SchemaNode(typ) + result = typ.serialize(node, datetime.datetime(2024, 12, 11, 22, 33)) + # nb. not possible to know which timezone is system-local + self.assertTrue(result.startswith("2024-12-")) + def test_deserialize(self): tzlocal = get_timezone_by_name("America/Los_Angeles") with patch.object(self.app, "get_timezone", return_value=tzlocal):