From ab2d5546b496036266e928deae49a12066d57930 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Sat, 28 Feb 2026 12:37:20 -0500 Subject: [PATCH] Feb 02 2026: implement remembering logged in, correct timezone --- app/models/inspection.py | 6 +++--- app/models/issue.py | 6 +++--- app/models/user.py | 4 ++-- app/routes/auth.py | 2 +- app/routes/dashboard.py | 5 +++-- app/routes/inspections.py | 7 ++++--- app/routes/issues.py | 6 +++--- app/routes/reports.py | 3 ++- app/templates/auth/login.html | 5 +++++ app/utils/forms.py | 5 +++-- app/utils/time_utils.py | 24 ++++++++++++++++++++++++ 11 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 app/utils/time_utils.py diff --git a/app/models/inspection.py b/app/models/inspection.py index 5485376..3835061 100644 --- a/app/models/inspection.py +++ b/app/models/inspection.py @@ -1,5 +1,5 @@ from app import db -from datetime import datetime +from app.utils.time_utils import now_eastern import json @@ -11,7 +11,7 @@ class InspectionTemplate(db.Model): description = db.Column(db.Text) frequency = db.Column(db.Enum('daily', 'weekly', 'monthly', 'quarterly')) created_by = db.Column(db.Integer, db.ForeignKey('users.id')) - created_at = db.Column(db.DateTime, default=datetime.utcnow) + created_at = db.Column(db.DateTime, default=now_eastern) form_schema = db.Column(db.JSON, nullable=True) checklist_items = db.relationship('ChecklistItem', backref='template', lazy='dynamic', cascade='all, delete-orphan') @@ -57,7 +57,7 @@ class Inspection(db.Model): facility_id = db.Column(db.Integer, db.ForeignKey('facilities.id'), nullable=False) area_id = db.Column(db.Integer, db.ForeignKey('areas.id')) inspector_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) - inspection_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + inspection_date = db.Column(db.DateTime, nullable=False, default=now_eastern) overall_score = db.Column(db.Numeric(5, 2)) status = db.Column(db.Enum('in_progress', 'completed', 'flagged'), default='in_progress') notes = db.Column(db.Text) # inspector free-text notes diff --git a/app/models/issue.py b/app/models/issue.py index c810a3e..6714322 100644 --- a/app/models/issue.py +++ b/app/models/issue.py @@ -1,5 +1,5 @@ from app import db -from datetime import datetime +from app.utils.time_utils import now_eastern class IssueComment(db.Model): @@ -10,7 +10,7 @@ class IssueComment(db.Model): user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) status_at_time = db.Column(db.String(20)) # snapshot of issue status when comment was made body = db.Column(db.Text, nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) + created_at = db.Column(db.DateTime, default=now_eastern, nullable=False) # Relationships author = db.relationship('User', foreign_keys=[user_id]) @@ -30,7 +30,7 @@ class Issue(db.Model): photo_path = db.Column(db.String(255)) status = db.Column(db.Enum('open', 'in_progress', 'resolved'), default='open') assigned_to = db.Column(db.Integer, db.ForeignKey('users.id')) - reported_at = db.Column(db.DateTime, default=datetime.utcnow) + reported_at = db.Column(db.DateTime, default=now_eastern) resolved_at = db.Column(db.DateTime) result_notes = db.Column(db.Text) result_photos = db.Column(db.JSON) # list of relative paths e.g. ["uploads/issue_photos/abc.jpg"] diff --git a/app/models/user.py b/app/models/user.py index 747042b..c39aa6d 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,7 +1,7 @@ from app import db, login_manager from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash -from datetime import datetime +from app.utils.time_utils import now_eastern @login_manager.user_loader def load_user(user_id): @@ -15,7 +15,7 @@ class User(UserMixin, db.Model): email = db.Column(db.String(255), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(255), nullable=False) role = db.Column(db.Enum('admin', 'supervisor', 'inspector'), nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow) + created_at = db.Column(db.DateTime, default=now_eastern) # Relationships inspections = db.relationship('Inspection', backref='inspector', lazy='dynamic') diff --git a/app/routes/auth.py b/app/routes/auth.py index ee8a804..fd3e4ac 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -35,7 +35,7 @@ def login(): user = User.query.filter_by(username=form.username.data).first() if user and user.check_password(form.password.data): - login_user(user) + login_user(user, remember=form.remember_me.data) # Use validated next URL — never redirect blindly to request.args['next'] next_page = _safe_next(request.args.get('next')) flash(f'Welcome back, {user.username}!', 'success') diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index 148ba97..2d323fd 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -7,6 +7,7 @@ from app.models.issue import Issue from app.models.user import User from sqlalchemy import func from datetime import datetime, timedelta +from app.utils.time_utils import now_eastern bp = Blueprint('dashboard', __name__) @@ -15,7 +16,7 @@ bp = Blueprint('dashboard', __name__) @login_required def index(): # Get today's date range - today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + today_start = now_eastern().replace(hour=0, minute=0, second=0, microsecond=0) today_end = today_start + timedelta(days=1) # Statistics for today @@ -55,7 +56,7 @@ def index(): ).count() # Calculate average score (last 30 days) - thirty_days_ago = datetime.utcnow() - timedelta(days=30) + thirty_days_ago = now_eastern() - timedelta(days=30) avg_score_query = Inspection.query.filter( Inspection.status == 'completed', Inspection.overall_score.isnot(None), diff --git a/app/routes/inspections.py b/app/routes/inspections.py index 5f814e5..8279665 100644 --- a/app/routes/inspections.py +++ b/app/routes/inspections.py @@ -2,6 +2,7 @@ import os import json import uuid from datetime import datetime +from app.utils.time_utils import now_eastern from flask import (Blueprint, render_template, redirect, url_for, flash, request, current_app, jsonify) from flask_login import login_required, current_user @@ -223,7 +224,7 @@ def start(): facility_id = form.facility_id.data, area_id = form.area_id.data or None, inspector_id = current_user.id, - inspection_date = datetime.utcnow(), + inspection_date = now_eastern(), status = 'in_progress', notes = form.notes.data or None, ) @@ -300,7 +301,7 @@ def execute(inspection_id): score = _compute_score_from_form(form_fields, responses) inspection.overall_score = score inspection.status = 'completed' - inspection.completed_at = datetime.utcnow() + inspection.completed_at = now_eastern() # Persist the final form data alongside any inspector notes _save_responses(inspection, responses) @@ -401,7 +402,7 @@ def flag_issue(inspection_id): photo_path = photo_path, status = 'open', assigned_to = form.assigned_to.data or None, - reported_at = datetime.utcnow(), + reported_at = now_eastern(), ) db.session.add(issue) diff --git a/app/routes/issues.py b/app/routes/issues.py index 9773811..c4efe97 100644 --- a/app/routes/issues.py +++ b/app/routes/issues.py @@ -1,4 +1,4 @@ -from datetime import datetime +from app.utils.time_utils import now_eastern from flask import (Blueprint, render_template, redirect, url_for, flash, request, current_app) from flask_login import login_required, current_user @@ -66,7 +66,7 @@ def view(issue_id): issue.assigned_to = form.assigned_to.data or None if form.status.data == 'resolved' and not issue.resolved_at: - issue.resolved_at = datetime.utcnow() + issue.resolved_at = now_eastern() elif form.status.data != 'resolved': issue.resolved_at = None @@ -130,7 +130,7 @@ def create(): photo_path = photo_path, status = 'open', assigned_to = form.assigned_to.data or None, - reported_at = datetime.utcnow(), + reported_at = now_eastern(), ) db.session.add(issue) db.session.commit() diff --git a/app/routes/reports.py b/app/routes/reports.py index 94c0f92..2798b11 100644 --- a/app/routes/reports.py +++ b/app/routes/reports.py @@ -1,6 +1,7 @@ import csv import io from datetime import datetime, timedelta +from app.utils.time_utils import now_eastern from flask import (Blueprint, render_template, request, Response, stream_with_context) from flask_login import login_required, current_user @@ -17,7 +18,7 @@ bp = Blueprint('reports', __name__, url_prefix='/reports') def _date_range(): """Parse ?start= and ?end= query params; default to last 30 days.""" - end_default = datetime.utcnow() + end_default = now_eastern() start_default = end_default - timedelta(days=30) try: start = datetime.strptime(request.args.get('start', ''), '%Y-%m-%d') diff --git a/app/templates/auth/login.html b/app/templates/auth/login.html index 571350a..b1140e6 100644 --- a/app/templates/auth/login.html +++ b/app/templates/auth/login.html @@ -33,6 +33,11 @@ {% endif %} + +
+ {{ form.remember_me(class="form-check-input") }} + {{ form.remember_me.label(class="form-check-label text-muted") }} +