Phase 2: add Facility delete button

This commit is contained in:
2026-02-19 14:52:25 -05:00
parent 47a49a7438
commit 225ec9000a
3 changed files with 151 additions and 14 deletions
+7 -9
View File
@@ -1,9 +1,10 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_required from flask_login import login_required
from flask_wtf.csrf import generate_csrf
from app import db from app import db
from app.models.facility import Facility, Area from app.models.facility import Facility, Area
from app.utils.forms import FacilityForm, AreaForm from app.utils.forms import FacilityForm, AreaForm
from app.utils.decorators import supervisor_required from app.utils.decorators import supervisor_required, admin_required
bp = Blueprint('facilities', __name__, url_prefix='/facilities') bp = Blueprint('facilities', __name__, url_prefix='/facilities')
@@ -11,7 +12,7 @@ bp = Blueprint('facilities', __name__, url_prefix='/facilities')
@login_required @login_required
def list_facilities(): def list_facilities():
facilities = Facility.query.order_by(Facility.name).all() facilities = Facility.query.order_by(Facility.name).all()
return render_template('facilities/list.html', facilities=facilities) return render_template('facilities/list.html', facilities=facilities, csrf_token=generate_csrf())
@bp.route('/new', methods=['GET', 'POST']) @bp.route('/new', methods=['GET', 'POST'])
@login_required @login_required
@@ -41,7 +42,7 @@ def create_facility():
def view_facility(facility_id): def view_facility(facility_id):
facility = Facility.query.get_or_404(facility_id) facility = Facility.query.get_or_404(facility_id)
areas = facility.areas.order_by(Area.name).all() areas = facility.areas.order_by(Area.name).all()
return render_template('facilities/view.html', facility=facility, areas=areas) return render_template('facilities/view.html', facility=facility, areas=areas, csrf_token=generate_csrf())
@bp.route('/<int:facility_id>/edit', methods=['GET', 'POST']) @bp.route('/<int:facility_id>/edit', methods=['GET', 'POST'])
@login_required @login_required
@@ -65,20 +66,19 @@ def edit_facility(facility_id):
@bp.route('/<int:facility_id>/delete', methods=['POST']) @bp.route('/<int:facility_id>/delete', methods=['POST'])
@login_required @login_required
@supervisor_required @admin_required
def delete_facility(facility_id): def delete_facility(facility_id):
facility = Facility.query.get_or_404(facility_id) facility = Facility.query.get_or_404(facility_id)
# Check if facility has inspections
if facility.inspections.count() > 0: if facility.inspections.count() > 0:
flash('Cannot delete facility with existing inspections.', 'danger') flash(f'Cannot delete "{facility.name}" — it has existing inspection records.', 'danger')
return redirect(url_for('facilities.view_facility', facility_id=facility.id)) return redirect(url_for('facilities.view_facility', facility_id=facility.id))
facility_name = facility.name facility_name = facility.name
db.session.delete(facility) db.session.delete(facility)
db.session.commit() db.session.commit()
flash(f'Facility "{facility_name}" deleted successfully.', 'success') flash(f'Facility "{facility_name}" has been permanently deleted.', 'success')
return redirect(url_for('facilities.list_facilities')) return redirect(url_for('facilities.list_facilities'))
# Area Management Routes # Area Management Routes
@@ -114,7 +114,6 @@ def edit_area(area_id):
area = Area.query.get_or_404(area_id) area = Area.query.get_or_404(area_id)
form = AreaForm(obj=area) form = AreaForm(obj=area)
# Populate facility choices
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all() facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
form.facility_id.choices = [(f.id, f.name) for f in facilities] form.facility_id.choices = [(f.id, f.name) for f in facilities]
@@ -136,7 +135,6 @@ def delete_area(area_id):
area = Area.query.get_or_404(area_id) area = Area.query.get_or_404(area_id)
facility_id = area.facility_id facility_id = area.facility_id
# Check if area has inspections
if area.inspections.count() > 0: if area.inspections.count() > 0:
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))
+84 -1
View File
@@ -42,10 +42,21 @@
</small> </small>
</div> </div>
</div> </div>
<div class="card-footer bg-transparent"> <div class="card-footer bg-transparent d-flex gap-2">
<a href="{{ url_for('facilities.view_facility', facility_id=facility.id) }}" class="btn btn-sm btn-outline-primary"> <a href="{{ url_for('facilities.view_facility', facility_id=facility.id) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> View Details <i class="bi bi-eye"></i> View Details
</a> </a>
{% if current_user.role == 'admin' %}
<button type="button"
class="btn btn-sm btn-outline-danger ms-auto"
data-bs-toggle="modal"
data-bs-target="#deleteModal"
data-facility-id="{{ facility.id }}"
data-facility-name="{{ facility.name }}"
data-inspection-count="{{ facility.inspections.count() }}">
<i class="bi bi-trash"></i> Delete
</button>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@@ -57,4 +68,76 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% if current_user.role == 'admin' %}
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" 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="deleteModalLabel">
<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 fs-5" id="modalFacilityName"></p>
<div id="modalWarningBlock" class="alert alert-danger d-none">
<i class="bi bi-x-circle-fill"></i>
<strong>Cannot delete this facility.</strong> It has existing inspection records.
Please remove all associated inspections first.
</div>
<div id="modalConfirmBlock">
<p class="text-muted mb-0">This action is <strong>irreversible</strong>. All areas associated with this facility will also be deleted.</p>
</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 id="deleteFacilityForm" method="POST" action="" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" id="confirmDeleteBtn" class="btn btn-danger">
<i class="bi bi-trash-fill"></i> Delete Permanently
</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
{% if current_user.role == 'admin' %}
<script>
document.addEventListener('DOMContentLoaded', function () {
const deleteModal = document.getElementById('deleteModal');
deleteModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget;
const facilityId = button.getAttribute('data-facility-id');
const facilityName = button.getAttribute('data-facility-name');
const inspectionCount = parseInt(button.getAttribute('data-inspection-count'));
document.getElementById('modalFacilityName').textContent = facilityName;
document.getElementById('deleteFacilityForm').action = '/facilities/' + facilityId + '/delete';
const warningBlock = document.getElementById('modalWarningBlock');
const confirmBlock = document.getElementById('modalConfirmBlock');
const confirmBtn = document.getElementById('confirmDeleteBtn');
if (inspectionCount > 0) {
warningBlock.classList.remove('d-none');
confirmBlock.classList.add('d-none');
confirmBtn.disabled = true;
} else {
warningBlock.classList.add('d-none');
confirmBlock.classList.remove('d-none');
confirmBtn.disabled = false;
}
});
});
</script>
{% endif %}
{% endblock %} {% endblock %}
+57 -1
View File
@@ -7,7 +7,7 @@
<div class="col-md-8"> <div class="col-md-8">
<h2><i class="bi bi-building"></i> {{ facility.name }}</h2> <h2><i class="bi bi-building"></i> {{ facility.name }}</h2>
</div> </div>
<div class="col-md-4 text-end"> <div class="col-md-4 text-end d-flex gap-2 justify-content-end align-items-start">
{% if current_user.role in ['admin', 'supervisor'] %} {% if current_user.role in ['admin', 'supervisor'] %}
<a href="{{ url_for('facilities.edit_facility', facility_id=facility.id) }}" class="btn btn-outline-primary"> <a href="{{ url_for('facilities.edit_facility', facility_id=facility.id) }}" class="btn btn-outline-primary">
<i class="bi bi-pencil"></i> Edit <i class="bi bi-pencil"></i> Edit
@@ -16,6 +16,14 @@
<i class="bi bi-plus-circle"></i> Add Area <i class="bi bi-plus-circle"></i> Add Area
</a> </a>
{% endif %} {% endif %}
{% if current_user.role == 'admin' %}
<button type="button"
class="btn btn-danger"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
<i class="bi bi-trash-fill"></i> Delete Facility
</button>
{% endif %}
</div> </div>
</div> </div>
@@ -103,6 +111,7 @@
<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?');"> <form method="POST" action="{{ url_for('facilities.delete_area', area_id=area.id) }}" class="d-inline" onsubmit="return confirm('Delete this area?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" class="btn btn-sm btn-outline-danger"> <button type="submit" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i> <i class="bi bi-trash"></i>
</button> </button>
@@ -121,4 +130,51 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% if current_user.role == 'admin' %}
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" 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="deleteModalLabel">
<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 fs-5">{{ facility.name }}</p>
{% if facility.inspections.count() > 0 %}
<div class="alert alert-danger mb-0">
<i class="bi bi-x-circle-fill"></i>
<strong>Cannot delete this facility.</strong> It has
<strong>{{ facility.inspections.count() }} inspection record(s)</strong> on file.
Please remove all associated inspections first.
</div>
{% else %}
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle-fill"></i>
This action is <strong>irreversible</strong>. All
<strong>{{ areas|length }} area(s)</strong> associated with this facility will also be deleted.
</div>
{% endif %}
</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('facilities.delete_facility', facility_id=facility.id) }}" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit"
class="btn btn-danger"
{% if facility.inspections.count() > 0 %}disabled{% endif %}>
<i class="bi bi-trash-fill"></i> Delete Permanently
</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}