3
0
Fork 0

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
This commit is contained in:
Lance Edgar 2025-12-17 17:46:31 -06:00
parent 131eb22580
commit 2ccfe29553
2 changed files with 28 additions and 9 deletions

View file

@ -31,6 +31,7 @@ import colander
import sqlalchemy as sa import sqlalchemy as sa
from wuttjamaican.conf import parse_list from wuttjamaican.conf import parse_list
from wuttjamaican.util import localtime
from wuttaweb.db import Session from wuttaweb.db import Session
from wuttaweb.forms import widgets from wuttaweb.forms import widgets
@ -38,28 +39,38 @@ from wuttaweb.forms import widgets
class WuttaDateTime(colander.DateTime): 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 This should be used automatically for
:class:`sqlalchemy:sqlalchemy.types.DateTime` columns unless you :class:`~sqlalchemy:sqlalchemy.types.DateTime` ORM columns unless
register another default. you register another default.
This schema type exists for sake of convenience, when working with This schema type exists for sake of convenience, when working with
the Buefy datepicker + timepicker widgets. 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): def serialize(self, node, appstruct):
if not appstruct: if not appstruct:
return colander.null return colander.null
request = node.widget.request # nb. request should be present when it matters
config = request.wutta_config if node.widget and node.widget.request:
app = config.get_app() 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: if self.format:
return dt.strftime(self.format) return appstruct.strftime(self.format)
return dt.isoformat() return appstruct.isoformat()
def deserialize( # pylint: disable=inconsistent-return-statements def deserialize( # pylint: disable=inconsistent-return-statements
self, node, cstruct self, node, cstruct
@ -72,6 +83,7 @@ class WuttaDateTime(colander.DateTime):
"%Y-%m-%dT%I:%M %p", "%Y-%m-%dT%I:%M %p",
] ]
# nb. request is always assumed to be present here
request = node.widget.request request = node.widget.request
config = request.wutta_config config = request.wutta_config
app = config.get_app() app = config.get_app()

View file

@ -62,6 +62,13 @@ class TestWuttaDateTime(WebTestCase):
) )
self.assertEqual(result, "2024-12-11 02:33 PM") 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): def test_deserialize(self):
tzlocal = get_timezone_by_name("America/Los_Angeles") tzlocal = get_timezone_by_name("America/Los_Angeles")
with patch.object(self.app, "get_timezone", return_value=tzlocal): with patch.object(self.app, "get_timezone", return_value=tzlocal):