DB review safe-fix: naive-UTC timestamp defaults (drop db.func.now)
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / frontend (push) Has been cancelled

DB review found four DateTime columns defaulting to db.func.now() (MySQL
session-timezone wall clock) while the rest of the schema stores naive UTC, so
one schema mixed two clocks and to_dict() labelled the local values UTC with a
'Z' suffix. Switch application.dateadded, computers.installeddate,
knowledgebase.lastupdated (default + onupdate), and slides.uploadeddate to the
module-level naive-UTC _utcnow callable already used elsewhere (apitoken.py).
ORM-side default only - no column-type change, no data migration; affects
new/updated rows.

Targeted tests pass (154); naming + pyflakes green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-13 09:09:29 -04:00
parent cd02cd20f4
commit 9c8b2c9c9e
4 changed files with 32 additions and 4 deletions

View File

@@ -1,8 +1,15 @@
"""Computer plugin models.""" """Computer plugin models."""
from datetime import datetime, timezone
from shopdb.api import db, BaseModel from shopdb.api import db, BaseModel
def _utcnow():
# naive UTC to match the other DB DateTime columns (stored without tzinfo)
return datetime.now(timezone.utc).replace(tzinfo=None)
class ComputerType(BaseModel): class ComputerType(BaseModel):
""" """
Computer type classification. Computer type classification.
@@ -170,7 +177,7 @@ class ComputerInstalledApp(db.Model):
# Raw version string from automated collection (when no curated AppVersion) # Raw version string from automated collection (when no curated AppVersion)
installedversion = db.Column(db.String(100), nullable=True) installedversion = db.Column(db.String(100), nullable=True)
isactive = db.Column(db.Boolean, default=True, nullable=False) isactive = db.Column(db.Boolean, default=True, nullable=False)
installeddate = db.Column(db.DateTime, default=db.func.now()) installeddate = db.Column(db.DateTime, default=_utcnow)
# Relationships # Relationships
computer = db.relationship('Computer', back_populates='installedapps') computer = db.relationship('Computer', back_populates='installedapps')

View File

@@ -7,9 +7,16 @@ references the core applications table by name, which resolves at mapper config
time without importing the core model. time without importing the core model.
""" """
from datetime import datetime, timezone
from shopdb.api import db, BaseModel from shopdb.api import db, BaseModel
def _utcnow():
# naive UTC to match the other DB DateTime columns (stored without tzinfo)
return datetime.now(timezone.utc).replace(tzinfo=None)
class KnowledgeBase(BaseModel): class KnowledgeBase(BaseModel):
"""Knowledge Base article linking to external resources.""" """Knowledge Base article linking to external resources."""
__tablename__ = 'knowledgebase' __tablename__ = 'knowledgebase'
@@ -20,7 +27,7 @@ class KnowledgeBase(BaseModel):
linkurl = db.Column(db.String(2000)) linkurl = db.Column(db.String(2000))
keywords = db.Column(db.String(500)) keywords = db.Column(db.String(500))
clicks = db.Column(db.Integer, default=0) clicks = db.Column(db.Integer, default=0)
lastupdated = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now()) lastupdated = db.Column(db.DateTime, default=_utcnow, onupdate=_utcnow)
# Relationship to the core Application model (resolved by class name). # Relationship to the core Application model (resolved by class name).
application = db.relationship('Application', backref=db.backref('knowledgebase_articles', lazy='dynamic')) application = db.relationship('Application', backref=db.backref('knowledgebase_articles', lazy='dynamic'))

View File

@@ -1,8 +1,15 @@
"""Slide model for the TV dashboard / screensaver slideshows.""" """Slide model for the TV dashboard / screensaver slideshows."""
from datetime import datetime, timezone
from shopdb.api import db from shopdb.api import db
def _utcnow():
# naive UTC to match the other DB DateTime columns (stored without tzinfo)
return datetime.now(timezone.utc).replace(tzinfo=None)
class TvSlide(db.Model): class TvSlide(db.Model):
"""One slide image in a surface's playlist. """One slide image in a surface's playlist.
@@ -17,7 +24,7 @@ class TvSlide(db.Model):
filename = db.Column(db.String(255), nullable=False) filename = db.Column(db.String(255), nullable=False)
sortorder = db.Column(db.Integer, default=0, nullable=False) sortorder = db.Column(db.Integer, default=0, nullable=False)
seconds = db.Column(db.Integer, default=0, nullable=False) # 0 = use default interval seconds = db.Column(db.Integer, default=0, nullable=False) # 0 = use default interval
uploadeddate = db.Column(db.DateTime, default=db.func.now()) uploadeddate = db.Column(db.DateTime, default=_utcnow)
# No DB-level unique on (surface, filename): utf8mb4 pushes that index past # No DB-level unique on (surface, filename): utf8mb4 pushes that index past
# MySQL 5.6's 767-byte limit. Upload unique-renames, so dupes can't occur. # MySQL 5.6's 767-byte limit. Upload unique-renames, so dupes can't occur.

View File

@@ -1,7 +1,14 @@
"""Application tracking models.""" """Application tracking models."""
from datetime import datetime, timezone
from shopdb.extensions import db from shopdb.extensions import db
from .base import BaseModel from .base import BaseModel
def _utcnow():
# naive UTC to match the other DB DateTime columns (stored without tzinfo)
return datetime.now(timezone.utc).replace(tzinfo=None)
# SupportTeam / SupportTeamContact live in supportteam.py; imported by the # SupportTeam / SupportTeamContact live in supportteam.py; imported by the
# models package so the Application.supportteam relationship resolves. # models package so the Application.supportteam relationship resolves.
@@ -61,7 +68,7 @@ class AppVersion(db.Model):
version = db.Column(db.String(50), nullable=False) version = db.Column(db.String(50), nullable=False)
releasedate = db.Column(db.Date) releasedate = db.Column(db.Date)
notes = db.Column(db.String(255)) notes = db.Column(db.String(255))
dateadded = db.Column(db.DateTime, default=db.func.now()) dateadded = db.Column(db.DateTime, default=_utcnow)
isactive = db.Column(db.Boolean, default=True) isactive = db.Column(db.Boolean, default=True)
# Relationships # Relationships