Apr 1 2026: update issues delete button

This commit is contained in:
2026-04-01 13:55:26 -04:00
parent 093d3a0043
commit 812ce9ce26
7 changed files with 137 additions and 13 deletions
+9
View File
@@ -167,6 +167,15 @@ def delete_area(area_id):
flash('Cannot delete area with existing inspections.', 'danger') flash('Cannot delete area with existing inspections.', 'danger')
return redirect(url_for('facilities.view_facility', facility_id=facility_id)) return redirect(url_for('facilities.view_facility', facility_id=facility_id))
issue_count = area.issues.count()
if issue_count > 0:
flash(
f'Cannot delete area "{area.name}" — it has {issue_count} issue record(s) on file. '
f'Resolve or reassign those issues first.',
'danger'
)
return redirect(url_for('facilities.view_facility', facility_id=facility_id))
area_name = area.name area_name = area.name
area_id_snap = area.id area_id_snap = area.id
db.session.delete(area) db.session.delete(area)
+52
View File
@@ -1,3 +1,4 @@
import os
from app.utils.time_utils import now_eastern from app.utils.time_utils import now_eastern
from flask import (Blueprint, render_template, redirect, url_for, from flask import (Blueprint, render_template, redirect, url_for,
flash, request, current_app, jsonify) flash, request, current_app, jsonify)
@@ -550,3 +551,54 @@ def verification_queue():
sla_status = sla_status, sla_status = sla_status,
sla_hours_remaining = sla_hours_remaining, sla_hours_remaining = sla_hours_remaining,
) )
# ── Delete ────────────────────────────────────────────────────────────────────
@bp.route('/<int:issue_id>/delete', methods=['POST'])
@login_required
@supervisor_required
def delete(issue_id):
"""Permanently delete an issue and its associated photos.
Restricted to admin and supervisor roles. The deletion is recorded in
the audit log before the record is removed so there is always a trace.
"""
issue = Issue.query.get_or_404(issue_id)
# Snapshot fields needed for logging before deletion
issue_id_snap = issue.id
issue_desc = issue.description[:80]
area_name = issue.area.name if issue.area else f'area_id={issue.area_id}'
facility_name = issue.area.facility.name if issue.area else ''
severity = issue.severity
# Collect photo paths to clean up from disk after DB delete
photo_paths = []
if issue.photo_path:
photo_paths.append(issue.photo_path)
if issue.result_photos:
photo_paths.extend(issue.result_photos)
db.session.delete(issue)
db.session.commit()
# Remove orphaned photo files — best-effort, never block on failure
static_folder = current_app.root_path
for rel_path in photo_paths:
abs_path = os.path.normpath(os.path.join(static_folder, 'static', rel_path))
try:
if os.path.isfile(abs_path):
os.remove(abs_path)
except OSError:
pass
current_app.logger.info(
'ISSUE DELETED | id=%s | severity=%s | area=%s | facility=%s | deleted_by=%s',
issue_id_snap, severity, area_name, facility_name, current_user.username,
)
log_action(ACTION_DELETE, 'Issue', issue_id_snap,
f'#{issue_id_snap} {severity} in {area_name}',
f'facility={facility_name}; description={issue_desc}')
flash(f'Issue #{issue_id_snap} has been permanently deleted.', 'success')
return redirect(url_for('issues.index'))
+1 -1
View File
@@ -97,7 +97,7 @@
<i class="bi bi-x-circle"></i> Cancel <i class="bi bi-x-circle"></i> Cancel
</button> </button>
<form id="deleteFacilityForm" method="POST" action="" class="d-inline"> <form id="deleteFacilityForm" method="POST" action="" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" id="confirmDeleteBtn" class="btn btn-danger"> <button type="submit" id="confirmDeleteBtn" class="btn btn-danger">
<i class="bi bi-trash-fill"></i> Delete Permanently <i class="bi bi-trash-fill"></i> Delete Permanently
</button> </button>
+11 -4
View File
@@ -130,9 +130,16 @@
<a href="{{ url_for('facilities.edit_area', area_id=area.id) }}" class="btn btn-sm btn-outline-primary"> <a href="{{ url_for('facilities.edit_area', area_id=area.id) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i> <i class="bi bi-pencil"></i>
</a> </a>
<form method="POST" action="{{ url_for('facilities.delete_area', area_id=area.id) }}" class="d-inline" onsubmit="return confirm('Delete this area?');"> {% set area_issue_count = area.issues.count() %}
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> {% set area_insp_count = area.inspections.count() %}
<button type="submit" class="btn btn-sm btn-outline-danger"> <form method="POST" action="{{ url_for('facilities.delete_area', area_id=area.id) }}" class="d-inline"
onsubmit="return confirm('Delete area '{{ area.name }}'? This cannot be undone.');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger"
{% if area_insp_count > 0 or area_issue_count > 0 %}
disabled
title="Cannot delete — {{ area_insp_count }} inspection(s) and {{ area_issue_count }} issue(s) on file"
{% endif %}>
<i class="bi bi-trash"></i> <i class="bi bi-trash"></i>
</button> </button>
</form> </form>
@@ -185,7 +192,7 @@
<i class="bi bi-x-circle"></i> Cancel <i class="bi bi-x-circle"></i> Cancel
</button> </button>
<form method="POST" action="{{ url_for('facilities.delete_facility', facility_id=facility.id) }}" class="d-inline"> <form method="POST" action="{{ url_for('facilities.delete_facility', facility_id=facility.id) }}" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" <button type="submit"
class="btn btn-danger" class="btn btn-danger"
{% if facility.inspections.count() > 0 %}disabled{% endif %}> {% if facility.inspections.count() > 0 %}disabled{% endif %}>
+11
View File
@@ -106,6 +106,17 @@
<i class="bi bi-eye"></i> View <i class="bi bi-eye"></i> View
{% endif %} {% endif %}
</a> </a>
{% if current_user.role in ['admin', 'supervisor'] %}
<form method="POST" action="{{ url_for('issues.delete', issue_id=issue.id) }}"
class="d-inline"
onsubmit="return confirm('Permanently delete Issue #{{ issue.id }}? This cannot be undone.');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger"
title="Delete Issue #{{ issue.id }}">
<i class="bi bi-trash"></i>
</button>
</form>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
+48 -3
View File
@@ -220,9 +220,54 @@
</div> </div>
</div> </div>
<a href="{{ url_for('issues.index') }}" class="btn btn-outline-secondary btn-sm"> <div class="d-flex align-items-center gap-2 mt-2">
<i class="bi bi-arrow-left"></i> Back to Issues <a href="{{ url_for('issues.index') }}" class="btn btn-outline-secondary btn-sm">
</a> <i class="bi bi-arrow-left"></i> Back to Issues
</a>
{% if current_user.role in ['admin', 'supervisor'] %}
<button type="button" class="btn btn-outline-danger btn-sm"
data-bs-toggle="modal" data-bs-target="#deleteIssueModal">
<i class="bi bi-trash"></i> Delete Issue
</button>
{% endif %}
</div>
{% if current_user.role in ['admin', 'supervisor'] %}
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteIssueModal" tabindex="-1" aria-labelledby="deleteIssueModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteIssueModalLabel">
<i class="bi bi-exclamation-triangle-fill"></i> Confirm Deletion
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>You are about to permanently delete:</p>
<p class="fw-bold">Issue #{{ issue.id }} — {{ issue.severity|title }} severity in {{ issue.area.name }}</p>
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-triangle-fill"></i>
This action is <strong>irreversible</strong>. All comments, photos, and
follower records associated with this issue will also be deleted.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Cancel
</button>
<form method="POST" action="{{ url_for('issues.delete', issue_id=issue.id) }}" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash-fill"></i> Delete Permanently
</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
{% block extra_js %} {% block extra_js %}
<script> <script>
(function () { (function () {
+3 -3
View File
@@ -59,7 +59,7 @@
<form method="POST" <form method="POST"
action="{{ url_for('templates.duplicate_template', template_id=template.id) }}" action="{{ url_for('templates.duplicate_template', template_id=template.id) }}"
class="d-inline"> class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-secondary" <button type="submit" class="btn btn-sm btn-outline-secondary"
title="Duplicate template"> title="Duplicate template">
<i class="bi bi-copy"></i> Duplicate <i class="bi bi-copy"></i> Duplicate
@@ -103,7 +103,7 @@
</div> </div>
<form id="renameTemplateForm" method="POST" action=""> <form id="renameTemplateForm" method="POST" action="">
<div class="modal-body"> <div class="modal-body">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3"> <div class="mb-3">
<label for="renameInput" class="form-label fw-semibold">Name <span class="text-danger">*</span></label> <label for="renameInput" class="form-label fw-semibold">Name <span class="text-danger">*</span></label>
@@ -177,7 +177,7 @@
<i class="bi bi-x-circle"></i> Cancel <i class="bi bi-x-circle"></i> Cancel
</button> </button>
<form id="deleteTemplateForm" method="POST" action="" class="d-inline"> <form id="deleteTemplateForm" method="POST" action="" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" id="confirmDeleteBtn" class="btn btn-danger"> <button type="submit" id="confirmDeleteBtn" class="btn btn-danger">
<i class="bi bi-trash-fill"></i> Delete Permanently <i class="bi bi-trash-fill"></i> Delete Permanently
</button> </button>