3
0
Fork 0

fix: render human-friendly datetime for such fields in version diff

still render the "raw" value but include human-friendly for convenience
This commit is contained in:
Lance Edgar 2026-03-14 16:19:17 -05:00
parent 6f26120640
commit d08ba5fe51
2 changed files with 119 additions and 5 deletions

View file

@ -25,6 +25,7 @@ Tools for displaying simple data diffs
""" """
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm
from pyramid.renderers import render from pyramid.renderers import render
from webhelpers2.html import HTML from webhelpers2.html import HTML
@ -150,6 +151,25 @@ class VersionDiff(WebDiff):
# be embedded within a more complex result. # be embedded within a more complex result.
text = HTML.tag("span", c=[repr(value)], style="font-family: monospace;") text = HTML.tag("span", c=[repr(value)], style="font-family: monospace;")
# style to apply for bold human-friendly text (if applicable)
bold = "margin-left: 2rem; font-style: italic; font-weight: bold;"
# check for standard datetime field
if self.mapper.has_property(field):
prop = self.mapper.get_property(field)
if isinstance(prop, orm.ColumnProperty):
if len(prop.columns) == 1:
col = prop.columns[0]
if isinstance(col.type, sa.DateTime):
if value:
# render as local datetime w/ "time since" tooltip
display = HTML.tag(
"span",
c=self.app.render_datetime(value, html=True),
style=bold,
)
return HTML.tag("span", c=[text, display])
# loop thru all mapped relationship props # loop thru all mapped relationship props
for prop in self.mapper.relationships: for prop in self.mapper.relationships:
@ -176,12 +196,9 @@ class VersionDiff(WebDiff):
if ref := getattr(ref, "version_parent", None): if ref := getattr(ref, "version_parent", None):
# render text w/ related object as bold string # render text w/ related object as bold string
style = (
"margin-left: 2rem; font-style: italic; font-weight: bold;"
)
return HTML.tag( return HTML.tag(
"span", "span",
c=[text, HTML.tag("span", c=[str(ref)], style=style)], c=[text, HTML.tag("span", c=[str(ref)], style=bold)],
) )
return text return text

View file

@ -1,5 +1,14 @@
# -*- coding: utf-8; -*- # -*- coding: utf-8; -*-
import datetime
import re
from unittest.mock import patch
import sqlalchemy as sa
from sqlalchemy import orm
from wuttjamaican.util import get_timezone_by_name
from wuttaweb import diffs as mod from wuttaweb import diffs as mod
from wuttaweb.testing import WebTestCase, VersionWebTestCase from wuttaweb.testing import WebTestCase, VersionWebTestCase
@ -70,7 +79,7 @@ class TestVersionDiff(VersionWebTestCase):
["active", "person_uuid", "prevent_edit", "username", "uuid"], ["active", "person_uuid", "prevent_edit", "username", "uuid"],
) )
def test_render_version_value(self): def test_render_version_value_with_objref(self):
import sqlalchemy_continuum as continuum import sqlalchemy_continuum as continuum
model = self.app.model model = self.app.model
@ -130,3 +139,91 @@ class TestVersionDiff(VersionWebTestCase):
self.assertIn(str(person.uuid), html) self.assertIn(str(person.uuid), html)
self.assertIn("Fred Flintstone", html) self.assertIn("Fred Flintstone", html)
self.assertEqual(diff.render_new_value("person_uuid"), "") self.assertEqual(diff.render_new_value("person_uuid"), "")
def test_render_version_value_with_datetime(self):
import sqlalchemy_continuum as continuum
tzlocal = get_timezone_by_name("America/Los_Angeles")
with patch.object(self.app, "get_timezone", return_value=tzlocal):
# make one person
model = self.app.model
person = model.Person(full_name="Fred Flintstone")
self.session.add(person)
self.session.commit()
# get its one version record
txncls = continuum.transaction_class(model.Person)
vercls = continuum.version_class(model.Person)
version = self.session.query(vercls).order_by(vercls.transaction_id).one()
# make a diff, but we have to mock some things up for the test
# coverage, since we don't currently have a versioned datetime
# field in the base model.
diff = self.make_diff(version)
mock_column = sa.Column("timestamp", sa.DateTime())
mock_property = orm.ColumnProperty(column=mock_column)
class MockMapper:
def __init__(self, mapper):
self.mapper = mapper
def __getattr__(self, name):
if hasattr(self.mapper, name):
return getattr(self.mapper, name)
raise AttributeError(f"attr not found: {name}")
def has_property(self, field):
if field == "timestamp":
return True
return self.mapper.has_property(field)
def get_property(self, field):
if field == "timestamp":
return mock_property
return self.mapper.get_property(field)
mock_mapper = MockMapper(diff.mapper)
with patch.object(diff, "mapper", new=mock_mapper):
# zone-aware local time
dt = datetime.datetime(2026, 3, 14, 14, 0, tzinfo=tzlocal)
html = diff.render_version_value(version, "timestamp", dt)
self.assertTrue(
re.search(
r'<span style="[^"]+"><span title="[^"]+">\d{4}-\d{2}-\d{2} \d{2}:\d{2}-\d{4}</span></span>',
html,
)
)
self.assertTrue(
re.search(
r'<span style="[^"]+"><span title="[^"]+">2026-03-14 14:00-0700</span></span>',
html,
)
)
# zone-naive (presumed UTC)
dt = datetime.datetime(2026, 3, 14, 21, 0)
html = diff.render_version_value(version, "timestamp", dt)
self.assertTrue(
re.search(
r'<span style="[^"]+"><span title="[^"]+">\d{4}-\d{2}-\d{2} \d{2}:\d{2}-\d{4}</span></span>',
html,
)
)
self.assertTrue(
re.search(
r'<span style="[^"]+"><span title="[^"]+">2026-03-14 14:00-0700</span></span>',
html,
)
)
# null
html = diff.render_version_value(version, "timestamp", None)
self.assertFalse(
re.search(
r'<span style="[^"]+"><span title="[^"]+">\d{4}-\d{2}-\d{2} \d{2}:\d{2}-\d{4}</span></span>',
html,
)
)