Apr 1 2026: clean up code

This commit is contained in:
2026-04-01 13:15:05 -04:00
parent f399537246
commit 093d3a0043
11 changed files with 52 additions and 29 deletions
+1 -1
View File
@@ -170,7 +170,7 @@ def refresh():
request.remote_addr) request.remote_addr)
return api_error('Refresh token is invalid or expired', 401) return api_error('Refresh token is invalid or expired', 401)
user = User.query.get(rt_row.user_id) user = db.session.get(User, rt_row.user_id)
if user is None or not user.active: if user is None or not user.active:
rt_row.revoke() rt_row.revoke()
db.session.commit() db.session.commit()
+2 -1
View File
@@ -34,6 +34,7 @@ from flask import request, g, abort
from app.api.jwt_utils import decode_access_token from app.api.jwt_utils import decode_access_token
from app.api.errors import api_error from app.api.errors import api_error
from app import db
from app.models.user import User from app.models.user import User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -62,7 +63,7 @@ def jwt_required(f):
return api_error('Access token is invalid or expired', 401) return api_error('Access token is invalid or expired', 401)
user_id = int(payload.get('sub', 0)) user_id = int(payload.get('sub', 0))
user = User.query.get(user_id) user = db.session.get(User, user_id)
if user is None: if user is None:
return api_error('User not found', 401) return api_error('User not found', 401)
+3 -6
View File
@@ -2,6 +2,7 @@ import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import Blueprint, render_template, request, redirect, url_for, flash from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_login import login_required from flask_login import login_required
from app import db
from app.models.audit import AuditLog from app.models.audit import AuditLog
from app.models.user import User from app.models.user import User
from app.utils.decorators import admin_required from app.utils.decorators import admin_required
@@ -55,13 +56,13 @@ def index():
# Distinct action and entity_type values for the filter dropdowns # Distinct action and entity_type values for the filter dropdowns
distinct_actions = ( distinct_actions = (
db.session.query(AuditLog.action) AuditLog.query.with_entities(AuditLog.action)
.distinct() .distinct()
.order_by(AuditLog.action) .order_by(AuditLog.action)
.all() .all()
) )
distinct_entity_types = ( distinct_entity_types = (
db.session.query(AuditLog.entity_type) AuditLog.query.with_entities(AuditLog.entity_type)
.distinct() .distinct()
.order_by(AuditLog.entity_type) .order_by(AuditLog.entity_type)
.all() .all()
@@ -145,7 +146,3 @@ def purge():
'success' if deleted else 'info', 'success' if deleted else 'info',
) )
return redirect(url_for('audit.index')) return redirect(url_for('audit.index'))
# Avoid circular import — imported after function definitions
from app import db # noqa: E402
+2 -2
View File
@@ -249,8 +249,8 @@ def add_assignment(customer_id):
def remove_assignment(assignment_id): def remove_assignment(assignment_id):
assignment = CustomerAssignment.query.get_or_404(assignment_id) assignment = CustomerAssignment.query.get_or_404(assignment_id)
customer_id = assignment.user_id customer_id = assignment.user_id
customer = User.query.get(customer_id) customer = db.session.get(User, customer_id)
project = Project.query.get(assignment.project_id) project = db.session.get(Project, assignment.project_id)
username = customer.username if customer else f'user_id={customer_id}' username = customer.username if customer else f'user_id={customer_id}'
project_name = project.name if project else f'project_id={assignment.project_id}' project_name = project.name if project else f'project_id={assignment.project_id}'
+1 -1
View File
@@ -241,7 +241,7 @@ def facility_trend():
if facility_id not in cids: if facility_id not in cids:
return jsonify({'labels': [], 'data': [], 'facility': ''}), 403 return jsonify({'labels': [], 'data': [], 'facility': ''}), 403
facility = Facility.query.get(facility_id) facility = db.session.get(Facility, facility_id)
if not facility: if not facility:
return jsonify({'labels': [], 'data': [], 'facility': ''}) return jsonify({'labels': [], 'data': [], 'facility': ''})
+2 -2
View File
@@ -318,7 +318,7 @@ def execute(inspection_id):
# fields (rating, pass_fail) so the inspector doesn't re-enter static # fields (rating, pass_fail) so the inspector doesn't re-enter static
# data but must re-evaluate every scoreable item fresh. # data but must re-evaluate every scoreable item fresh.
if not saved_responses and inspection.parent_inspection_id: if not saved_responses and inspection.parent_inspection_id:
parent = Inspection.query.get(inspection.parent_inspection_id) parent = db.session.get(Inspection, inspection.parent_inspection_id)
if parent and parent.notes: if parent and parent.notes:
try: try:
parent_parsed = json.loads(parent.notes) parent_parsed = json.loads(parent.notes)
@@ -717,7 +717,7 @@ def flag_issue(inspection_id):
) )
if issue.assigned_to: if issue.assigned_to:
assignee = User.query.get(issue.assigned_to) assignee = db.session.get(User, issue.assigned_to)
if assignee and assignee.id != current_user.id: if assignee and assignee.id != current_user.id:
notify( notify(
recipient = assignee, recipient = assignee,
+9 -8
View File
@@ -107,7 +107,7 @@ def view(issue_id):
if current_user.role == 'customer': if current_user.role == 'customer':
from app.models.facility import Area from app.models.facility import Area
cids = get_customer_scope(current_user) or [] cids = get_customer_scope(current_user) or []
area = Area.query.get(issue.area_id) area = db.session.get(Area, issue.area_id)
if not area or area.facility_id not in cids: if not area or area.facility_id not in cids:
flash('Access denied.', 'danger') flash('Access denied.', 'danger')
return redirect(url_for('issues.index')) return redirect(url_for('issues.index'))
@@ -167,7 +167,7 @@ def view(issue_id):
# 1. Status changed — notify assignee # 1. Status changed — notify assignee
if old_status != issue.status and new_assigned_to: if old_status != issue.status and new_assigned_to:
assignee = User.query.get(new_assigned_to) assignee = db.session.get(User, new_assigned_to)
if assignee and assignee.id != actor_id: if assignee and assignee.id != actor_id:
notify( notify(
recipient = assignee, recipient = assignee,
@@ -186,7 +186,7 @@ def view(issue_id):
# 2. Reassigned — notify new assignee # 2. Reassigned — notify new assignee
if (old_assigned_to != new_assigned_to) and new_assigned_to: if (old_assigned_to != new_assigned_to) and new_assigned_to:
new_assignee = User.query.get(new_assigned_to) new_assignee = db.session.get(User, new_assigned_to)
if new_assignee and new_assignee.id != actor_id: if new_assignee and new_assignee.id != actor_id:
notify( notify(
recipient = new_assignee, recipient = new_assignee,
@@ -204,7 +204,7 @@ def view(issue_id):
# 3. Unassigned — notify previous assignee # 3. Unassigned — notify previous assignee
if old_assigned_to and old_assigned_to != new_assigned_to: if old_assigned_to and old_assigned_to != new_assigned_to:
old_assignee = User.query.get(old_assigned_to) old_assignee = db.session.get(User, old_assigned_to)
if old_assignee and old_assignee.id != actor_id: if old_assignee and old_assignee.id != actor_id:
notify( notify(
recipient = old_assignee, recipient = old_assignee,
@@ -221,7 +221,7 @@ def view(issue_id):
# 4. Comment added — notify assignee # 4. Comment added — notify assignee
if comment_body and new_assigned_to: if comment_body and new_assigned_to:
commentee = User.query.get(new_assigned_to) commentee = db.session.get(User, new_assigned_to)
if commentee and commentee.id != actor_id: if commentee and commentee.id != actor_id:
notify( notify(
recipient = commentee, recipient = commentee,
@@ -250,7 +250,8 @@ def view(issue_id):
f'to "{issue.status.replace("_"," ").title()}"' f'to "{issue.status.replace("_"," ").title()}"'
) )
if old_assigned_to != new_assigned_to: if old_assigned_to != new_assigned_to:
new_name = User.query.get(new_assigned_to).username if new_assigned_to else 'Unassigned' _new_assignee_obj = db.session.get(User, new_assigned_to) if new_assigned_to else None
new_name = _new_assignee_obj.username if _new_assignee_obj else 'Unassigned'
changes.append(f'reassigned to {new_name}') changes.append(f'reassigned to {new_name}')
if comment_body: if comment_body:
changes.append(f'new comment added by {current_user.username}') changes.append(f'new comment added by {current_user.username}')
@@ -378,7 +379,7 @@ def create():
f'severity={issue.severity}; assigned_to={issue.assigned_to}') f'severity={issue.severity}; assigned_to={issue.assigned_to}')
if issue.assigned_to: if issue.assigned_to:
assignee = User.query.get(issue.assigned_to) assignee = db.session.get(User, issue.assigned_to)
if assignee and assignee.id != current_user.id: if assignee and assignee.id != current_user.id:
notify( notify(
recipient = assignee, recipient = assignee,
@@ -397,7 +398,7 @@ def create():
db.session.commit() db.session.commit()
# ── Notify customer portal users for this facility ────────── # ── Notify customer portal users for this facility ──────────
area = Area.query.get(issue.area_id) area = db.session.get(Area, issue.area_id)
if area: if area:
notify_customers_for_facility( notify_customers_for_facility(
facility_id = area.facility_id, facility_id = area.facility_id,
+3 -1
View File
@@ -3,7 +3,7 @@ import logging
from flask import (Blueprint, jsonify, request, abort, from flask import (Blueprint, jsonify, request, abort,
render_template, redirect, url_for, flash, current_app) render_template, redirect, url_for, flash, current_app)
from flask_login import login_required, current_user from flask_login import login_required, current_user
from app import db from app import db, csrf
from app.models.notification import ( from app.models.notification import (
Notification, NotificationPreference, ALL_EVENT_TYPES Notification, NotificationPreference, ALL_EVENT_TYPES
) )
@@ -166,6 +166,7 @@ def preferences():
# ── Digest trigger (called by cron) ─────────────────────────────────────────── # ── Digest trigger (called by cron) ───────────────────────────────────────────
@bp.route('/send-digest', methods=['POST']) @bp.route('/send-digest', methods=['POST'])
@csrf.exempt
def send_digest(): def send_digest():
"""Trigger digest email delivery. Protected by a shared secret token. """Trigger digest email delivery. Protected by a shared secret token.
@@ -198,6 +199,7 @@ def send_digest():
# ── SLA alert trigger (called by cron) ──────────────────────────────────────── # ── SLA alert trigger (called by cron) ────────────────────────────────────────
@bp.route('/check-sla', methods=['POST']) @bp.route('/check-sla', methods=['POST'])
@csrf.exempt
def check_sla(): def check_sla():
"""Scan all open issues for SLA breaches and dispatch alerts. """Scan all open issues for SLA breaches and dispatch alerts.
+2 -2
View File
@@ -181,7 +181,7 @@ def add_assignment(project_id):
db.session.add(assignment) db.session.add(assignment)
db.session.commit() db.session.commit()
user = User.query.get(form.user_id.data) user = db.session.get(User, form.user_id.data)
scope_label = f'facility_id={facility_id}' if facility_id else 'all facilities' scope_label = f'facility_id={facility_id}' if facility_id else 'all facilities'
logger.info('PROJECTS | assignment_add | admin=%s customer=%s project_id=%s scope=%s', logger.info('PROJECTS | assignment_add | admin=%s customer=%s project_id=%s scope=%s',
current_user.username, user.username, project_id, scope_label) current_user.username, user.username, project_id, scope_label)
@@ -208,7 +208,7 @@ def remove_assignment(assignment_id):
assignment = CustomerAssignment.query.get_or_404(assignment_id) assignment = CustomerAssignment.query.get_or_404(assignment_id)
project_id = assignment.project_id project_id = assignment.project_id
project = Project.query.get_or_404(project_id) project = Project.query.get_or_404(project_id)
user = User.query.get(assignment.user_id) user = db.session.get(User, assignment.user_id)
username = user.username if user else f'user_id={assignment.user_id}' username = user.username if user else f'user_id={assignment.user_id}'
assignment_id_snap = assignment.id assignment_id_snap = assignment.id
+14 -2
View File
@@ -32,7 +32,7 @@ from flask_login import login_required, current_user
from flask_mail import Message from flask_mail import Message
from sqlalchemy import func from sqlalchemy import func
from app import db, mail from app import db, mail, csrf
from app.models.scheduled_report import ScheduledReport from app.models.scheduled_report import ScheduledReport
from app.models.inspection import Inspection, InspectionTemplate from app.models.inspection import Inspection, InspectionTemplate
from app.models.facility import Facility, Area from app.models.facility import Facility, Area
@@ -419,6 +419,9 @@ def send_now(report_id):
if ok: if ok:
report.last_sent_at = now_eastern() report.last_sent_at = now_eastern()
db.session.commit() db.session.commit()
log_action(ACTION_UPDATE, 'ScheduledReport', report.id, report.name,
f'manual send_now by {current_user.username}; '
f'frequency={report.frequency}; recipients={len(report.recipient_list())}')
flash(f'Report "{report.name}" sent successfully.', 'success') flash(f'Report "{report.name}" sent successfully.', 'success')
else: else:
flash(f'Failed to send report "{report.name}". Check application logs.', 'danger') flash(f'Failed to send report "{report.name}". Check application logs.', 'danger')
@@ -428,6 +431,7 @@ def send_now(report_id):
# ── Cron endpoint ───────────────────────────────────────────────────────────── # ── Cron endpoint ─────────────────────────────────────────────────────────────
@bp.route('/send', methods=['POST']) @bp.route('/send', methods=['POST'])
@csrf.exempt
def send(): def send():
"""Token-protected endpoint called by cron to dispatch due reports. """Token-protected endpoint called by cron to dispatch due reports.
@@ -452,12 +456,20 @@ def send():
sent, failed = 0, 0 sent, failed = 0, 0
for report in due: for report in due:
ok = _send_report(report) ok = _send_report(report)
# Always advance next_send_at so a failed report does not get
# retried on every subsequent cron run. last_sent_at is only
# updated on a successful delivery so the UI accurately reflects
# when the last good email was dispatched.
report.next_send_at = _compute_next_send(frequency, now)
if ok: if ok:
report.last_sent_at = now report.last_sent_at = now
report.next_send_at = _compute_next_send(frequency, now)
sent += 1 sent += 1
else: else:
failed += 1 failed += 1
logger.error(
'SCHEDULED REPORT FAILED | id=%s | name=%r | frequency=%s',
report.id, report.name, frequency,
)
db.session.commit() db.session.commit()
logger.info('SCHEDULED REPORTS CRON | frequency=%s | due=%s | sent=%s | failed=%s', logger.info('SCHEDULED REPORTS CRON | frequency=%s | due=%s | sent=%s | failed=%s',
+11 -1
View File
@@ -305,6 +305,9 @@ def create_checklist_item(template_id):
) )
db.session.add(item) db.session.add(item)
db.session.commit() db.session.commit()
log_action(ACTION_CREATE, 'ChecklistItem', item.id, item.item_description[:80],
f'template_id={template.id}; category={item.category or ""}; '
f'scoring_type={item.scoring_type}')
flash('Checklist item added successfully.', 'success') flash('Checklist item added successfully.', 'success')
return redirect(url_for('templates.edit_template', template_id=template.id)) return redirect(url_for('templates.edit_template', template_id=template.id))
@@ -331,6 +334,9 @@ def edit_checklist_item(item_id):
item.weight = form.weight.data item.weight = form.weight.data
item.requires_photo = form.requires_photo.data item.requires_photo = form.requires_photo.data
db.session.commit() db.session.commit()
log_action(ACTION_UPDATE, 'ChecklistItem', item.id, item.item_description[:80],
f'template_id={item.template_id}; category={item.category or ""}; '
f'scoring_type={item.scoring_type}')
flash('Checklist item updated successfully.', 'success') flash('Checklist item updated successfully.', 'success')
return redirect(url_for('templates.edit_template', template_id=item.template_id)) return redirect(url_for('templates.edit_template', template_id=item.template_id))
@@ -349,8 +355,12 @@ def edit_checklist_item(item_id):
def delete_checklist_item(item_id): def delete_checklist_item(item_id):
item = ChecklistItem.query.get_or_404(item_id) item = ChecklistItem.query.get_or_404(item_id)
template_id = item.template_id template_id = item.template_id
item_desc = item.item_description[:80]
item_id_snap = item.id
db.session.delete(item) db.session.delete(item)
db.session.commit() db.session.commit()
log_action(ACTION_DELETE, 'ChecklistItem', item_id_snap, item_desc,
f'template_id={template_id}')
flash('Checklist item deleted successfully.', 'success') flash('Checklist item deleted successfully.', 'success')
return redirect(url_for('templates.edit_template', template_id=template_id)) return redirect(url_for('templates.edit_template', template_id=template_id))
@@ -363,7 +373,7 @@ def reorder_items(template_id):
item_order = request.json.get('item_order', []) item_order = request.json.get('item_order', [])
for index, item_id in enumerate(item_order): for index, item_id in enumerate(item_order):
item = ChecklistItem.query.get(item_id) item = db.session.get(ChecklistItem, item_id)
if item and item.template_id == template.id: if item and item.template_id == template.id:
item.display_order = index item.display_order = index