Feb 27 2026: add inspection delete button
This commit is contained in:
@@ -65,7 +65,7 @@ class Inspection(db.Model):
|
||||
completed_at = db.Column(db.DateTime)
|
||||
|
||||
results = db.relationship('InspectionResult', backref='inspection', lazy='dynamic', cascade='all, delete-orphan')
|
||||
issues = db.relationship('Issue', backref='inspection', lazy='dynamic')
|
||||
issues = db.relationship('Issue', backref='inspection', lazy='dynamic', cascade='all, delete-orphan')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Inspection {self.id} - {self.inspection_date}>'
|
||||
|
||||
@@ -404,7 +404,55 @@ def flag_issue(inspection_id):
|
||||
@supervisor_required
|
||||
def delete(inspection_id):
|
||||
inspection = Inspection.query.get_or_404(inspection_id)
|
||||
|
||||
# Capture identifiers for the log before deletion
|
||||
insp_id = inspection.id
|
||||
insp_date = inspection.inspection_date.strftime('%Y-%m-%d %H:%M')
|
||||
facility_name = inspection.facility.name
|
||||
template_name = inspection.template.name
|
||||
inspector_name = inspection.inspector.username
|
||||
|
||||
# ── Clean up uploaded photos from disk ────────────────────────────────
|
||||
# Collect photo paths from form data (inspection_photos) and issues
|
||||
photo_paths = []
|
||||
if inspection.notes:
|
||||
try:
|
||||
notes_data = json.loads(inspection.notes)
|
||||
form_data = notes_data.get('_form_data', {}) if isinstance(notes_data, dict) else {}
|
||||
for val in form_data.values():
|
||||
if isinstance(val, str) and val.startswith('uploads/'):
|
||||
photo_paths.append(val)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
for issue in inspection.issues.all():
|
||||
if issue.photo_path:
|
||||
photo_paths.append(issue.photo_path)
|
||||
|
||||
# Delete the inspection record (cascade removes results and issues)
|
||||
db.session.delete(inspection)
|
||||
db.session.commit()
|
||||
flash('Inspection deleted.', 'success')
|
||||
|
||||
# Remove photo files after successful DB commit
|
||||
for rel_path in photo_paths:
|
||||
abs_path = os.path.join(current_app.config['UPLOAD_FOLDER'], '..', 'static', rel_path)
|
||||
abs_path = os.path.normpath(abs_path)
|
||||
try:
|
||||
if os.path.isfile(abs_path):
|
||||
os.remove(abs_path)
|
||||
except OSError:
|
||||
pass # Non-fatal: log but don't block the response
|
||||
|
||||
current_app.logger.info(
|
||||
'INSPECTION DELETED | id=%s | facility="%s" | template="%s" | '
|
||||
'date=%s | inspector=%s | deleted_by=%s',
|
||||
insp_id, facility_name, template_name,
|
||||
insp_date, inspector_name, current_user.username
|
||||
)
|
||||
|
||||
flash(
|
||||
f'Inspection #{insp_id} ({template_name} — {facility_name}, {insp_date}) '
|
||||
f'has been permanently deleted.',
|
||||
'success'
|
||||
)
|
||||
return redirect(url_for('inspections.index'))
|
||||
@@ -70,12 +70,23 @@
|
||||
{{ ins.status|replace('_',' ')|title }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td class="text-nowrap">
|
||||
{% if ins.status == 'in_progress' or ins.status == 'flagged' %}
|
||||
<a href="{{ url_for('inspections.execute', inspection_id=ins.id) }}" class="btn btn-sm btn-outline-primary">Continue</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('inspections.view', inspection_id=ins.id) }}" class="btn btn-sm btn-outline-secondary">View</a>
|
||||
{% endif %}
|
||||
{% if current_user.role in ['admin', 'supervisor'] %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-danger ms-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#deleteInspectionModal"
|
||||
data-inspection-id="{{ ins.id }}"
|
||||
data-inspection-label="{{ ins.template.name }} — {{ ins.facility.name }} ({{ ins.inspection_date.strftime('%Y-%m-%d') }})"
|
||||
title="Delete inspection">
|
||||
<i class="bi bi-trash3"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -103,4 +114,52 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if current_user.role in ['admin', 'supervisor'] %}
|
||||
<!-- Delete Inspection Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteInspectionModal" tabindex="-1" aria-labelledby="deleteInspectionModalLabel" 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="deleteInspectionModalLabel">
|
||||
<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 the following inspection:</p>
|
||||
<p class="fw-bold" id="deleteInspectionLabel"></p>
|
||||
<p class="text-muted mb-0">This will also remove all associated results, flagged issues, and uploaded photos. This action is <strong>irreversible</strong>.</p>
|
||||
</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 id="deleteInspectionForm" method="POST" action="" class="d-inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash3-fill"></i> Delete Permanently
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% if current_user.role in ['admin', 'supervisor'] %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const modal = document.getElementById('deleteInspectionModal');
|
||||
modal.addEventListener('show.bs.modal', function (event) {
|
||||
const btn = event.relatedTarget;
|
||||
const id = btn.getAttribute('data-inspection-id');
|
||||
const label = btn.getAttribute('data-inspection-label');
|
||||
document.getElementById('deleteInspectionLabel').textContent = label;
|
||||
document.getElementById('deleteInspectionForm').action = '/inspections/' + id + '/delete';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user