Store and serve a maintenance date as a date

The machine maintenance panel showed every date one calendar day early on this
site, and would show it a day late east of UTC.

lastmaintenancedate and nextmaintenancedate were DATETIME columns holding
midnight, and BaseModel.to_dict serialises a datetime as isoformat + 'Z', so
2026-12-15 00:00:00 went out as "2026-12-15T00:00:00Z" and the browser
converted it into local time. There is no time in a maintenance date: the form
offers a date picker, the CSV importer takes '2026-06-01', and the value is only
ever compared against other dates. Every sibling already models it correctly -
measuringtools.lastcalibrationdate, warranties.startdate and enddate are all
db.Date - so machines were the odd one out.

Migration 7d40 converts both columns; MySQL truncates a midnight time so no
data moves, and SQLite neither enforces nor needs it.

That exposed two more things. The route was assigning the raw request string to
the column, which a DATE column rejects outright - it now goes through the
strict parser from wave 2, so a mistyped maintenance date is refused with a 400
rather than stored or nulled. And BaseModel.to_dict had no branch for a plain
date, so one reached Flask's encoder and came out as an HTTP-date
("Thu, 15 Jan 2026 00:00:00 GMT") - which a browser parses back into a moment
and can still render as the day before. Dates now serialise as "2026-01-15",
with datetime checked first because it is a subclass of date.

Verified against MySQL on a live instance: the column is DATE, the API serves
2026-01-15, a mistyped date is refused with 400, and a rename on a machine that
has maintenance dates saves - the 500 from the audit-log fix in 5d81f50c, now
confirmed end to end rather than only in tests.

Scope: product
This commit is contained in:
cproudlock
2026-09-03 15:51:22 -04:00
parent fb63ef9d30
commit f6e5f6ca47
5 changed files with 152 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
"""Base model class with common fields."""
from datetime import datetime, timezone
from datetime import date, datetime, timezone
from shopdb.extensions import db
@@ -63,6 +63,13 @@ class BaseModel(db.Model):
value = getattr(self, c.name)
if isinstance(value, datetime):
value = value.isoformat() + 'Z'
elif isinstance(value, date):
# A date, not an instant: no time and no timezone marker. Left
# to Flask's encoder it became an HTTP-date
# ("Thu, 15 Jan 2026 00:00:00 GMT"), which a browser parses
# back into a moment and can render as the day before.
# datetime is a subclass of date, so this must stay second.
value = value.isoformat()
result[c.name] = value
return result