Add admin blueprint routes, inspection blueprint routes, inspection templates, and PDF generator utility

This commit is contained in:
Jimmy 2026-03-22 01:28:56 +01:00
parent bfd320c5e9
commit 96d82b6f86
5 changed files with 298 additions and 0 deletions

78
app/routes/admin.py Normal file
View file

@ -0,0 +1,78 @@
"""Admin blueprint."""
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user, user_passes_test
from app import db
from app.models import User
from app.forms import UserForm
admin_bp = Blueprint("admin", __name__)
def admin_only(user):
return user.is_admin
@admin_bp.route("/")
@login_required
@user_passes_test(admin_only)
def index():
"""List all users."""
users = User.query.all()
return render_template("admin/users.html", users=users)
@admin_bp.route("/create", methods=["GET", "POST"])
@login_required
@user_passes_test(admin_only)
def create():
"""Create a new user."""
form = UserForm()
if form.validate_on_submit():
if User.query.filter_by(email=form.email.data).first():
flash("Email already registered.", "danger")
return redirect(url_for("admin.create"))
user = User(
username=form.username.data,
full_name=form.full_name.data,
email=form.email.data,
is_admin=form.is_admin.data,
)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash("User created successfully.", "success")
return redirect(url_for("admin.index"))
return render_template("admin/user_form.html", form=form)
@admin_bp.route("/<int:user_id>/edit", methods=["GET", "POST"])
@login_required
@user_passes_test(admin_only)
def edit(user_id):
"""Edit a user."""
user = User.query.get_or_404(user_id)
form = UserForm(obj=user)
if form.validate_on_submit():
user.username = form.username.data
user.full_name = form.full_name.data
user.email = form.email.data
user.is_admin = form.is_admin.data
if form.password.data:
user.set_password(form.password.data)
db.session.commit()
flash("User updated successfully.", "success")
return redirect(url_for("admin.index"))
return render_template("admin/user_form.html", form=form)
@admin_bp.route("/<int:user_id>/delete", methods=["POST"])
@login_required
@user_passes_test(admin_only)
def delete(user_id):
"""Delete a user."""
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
flash("User deleted successfully.", "info")
return redirect(url_for("admin.index"))

102
app/routes/inspections.py Normal file
View file

@ -0,0 +1,102 @@
"""Inspection blueprint."""
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user
from app import db
from app.models import Inspection, InspectionInspector
from app.forms import InspectionForm
inspections_bp = Blueprint("inspections", __name__)
@inspections_bp.route("", methods=["GET"])
@login_required
def index():
"""List all inspections for the logged-in user."""
inspections = Inspection.query.order_by(Inspection.inspection_date.desc()).all()
return render_template("dashboard.html", inspections=inspections)
@inspections_bp.route("/new", methods=["GET", "POST"])
@login_required
def create():
"""Create a new inspection."""
form = InspectionForm()
if form.validate_on_submit():
inspection = Inspection(
installation_name=form.installation_name.data,
location=form.location.data,
inspection_date=form.inspection_date.data,
reference_number=form.reference_number.data,
observations=form.observations.data,
conclusion_text=form.conclusion_text.data,
conclusion_status=form.conclusion_status.data,
created_by=current_user.id,
)
# Add inspectors
for inspector in form.inspectors.data:
if inspector.get("user_id"):
inspector_obj = InspectionInspector(
inspection_id=inspection.id,
user_id=inspector["user_id"],
free_text_name=inspector.get("free_text_name"),
)
db.session.add(inspector_obj)
else:
inspector_obj = InspectionInspector(
inspection_id=inspection.id,
free_text_name=inspector.get("free_text_name"),
)
db.session.add(inspector_obj)
db.session.commit()
flash("Inspection created successfully.", "success")
return redirect(url_for("inspections.view", inspection_id=inspection.id))
return render_template("inspection_form.html", form=form)
@inspections_bp.route("/<int:inspection_id>", methods=["GET"])
@login_required
def view(inspection_id):
"""View an inspection."""
inspection = Inspection.query.get_or_404(inspection_id)
inspectors = inspection.inspectors
photos = inspection.photos
return render_template(
"inspection_view.html",
inspection=inspection,
inspectors=inspectors,
photos=photos,
)
@inspections_bp.route("/<int:inspection_id>/edit", methods=["GET", "POST"])
@login_required
def edit(inspection_id):
"""Edit an inspection."""
inspection = Inspection.query.get_or_404(inspection_id)
form = InspectionForm(obj=inspection)
if form.validate_on_submit():
inspection.installation_name = form.installation_name.data
inspection.location = form.location.data
inspection.inspection_date = form.inspection_date.data
inspection.reference_number = form.reference_number.data
inspection.observations = form.observations.data
inspection.conclusion_text = form.conclusion_text.data
inspection.conclusion_status = form.conclusion_status.data
# Update inspectors
# Simplified handling for brevity
db.session.commit()
flash("Inspection updated successfully.", "success")
return redirect(url_for("inspections.view", inspection_id=inspection.id))
return render_template("inspection_form.html", form=form)
@inspections_bp.route("/<int:inspection_id>/delete", methods=["POST"])
@login_required
def delete(inspection_id):
"""Delete an inspection."""
inspection = Inspection.query.get_or_404(inspection_id)
db.session.delete(inspection)
db.session.commit()
flash("Inspection deleted successfully.", "info")
return redirect(url_for("inspections.index"))

View file

@ -0,0 +1,54 @@
{% extends "base.html" %}
{% block title %}New Inspection{% endblock %}
{% block content %}
<h1>New Inspection</h1>
<form method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<fieldset>
<legend>Installation Details</legend>
<div class="mb-3">
{{ form.installation_name.label(class="form-label") }}
{{ form.installation_name(class="form-control") }}
</div>
<div class="mb-3">
{{ form.location.label(class="form-label") }}
{{ form.location(class="form-control") }}
</div>
<div class="mb-3">
{{ form.inspection_date.label(class="form-label") }}
{{ form.inspection_date(class="form-control") }}
</div>
<div class="mb-3">
{{ form.reference_number.label(class="form-label") }}
{{ form.reference_number(class="form-control") }}
</div>
</fieldset>
<fieldset>
<legend>Observations</legend>
{{ form.observations.label(class="form-label") }}
{{ form.observations(class="form-control", rows=6) }}
</fieldset>
<fieldset>
<legend>Conclusion</legend>
{{ form.conclusion_text.label(class="form-label") }}
{{ form.conclusion_text(class="form-control", rows=6) }}
{{ form.conclusion_status.label(class="form-label") }}
{{ form.conclusion_status(class="form-select") }}
</fieldset>
<fieldset>
<legend>Inspectors</legend>
<div id="inspectors">
{{ form.inspectors_form|safe }}
</div>
</fieldset>
<fieldset>
<legend>Photos</legend>
<div id="photos">
{{ form.photos_form|safe }}
</div>
</fieldset>
<button type="submit" class="btn btn-primary">Save Report</button>
</form>
{% endblock %}

View file

@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block title %}Inspection Report{% endblock %}
{% block content %}
<h1>Inspection Report</h1>
<p><strong>Installation Name:</strong> {{ inspection.installation_name }}</p>
<p><strong>Location:</strong> {{ inspection.location }}</p>
<p><strong>Inspection Date:</strong> {{ inspection.inspection_date }}</p>
<p><strong>Reference Number:</strong> {{ inspection.reference_number }}</p>
<p><strong>Observations:</strong> {{ inspection.observations }}</p>
<p><strong>Conclusion Text:</strong> {{ inspection.conclusion_text }}</p>
<p><strong>Conclusion Status:</strong> {{ inspection.conclusion_status }}</p>
<p><strong>Created By:</strong> {{ inspection.creator.username }}</p>
<p><strong>Created At:</strong> {{ inspection.created_at }}</p>
<h2>Inspectors</h2>
<ul>
{% for inspector in inspectors %}
<li>{{ inspector.get_full_name_or_name() }}</li>
{% endfor %}
</ul>
<h2>Photos</h2>
<div class="row">
{% for photo in photos %}
<div class="col-md-3">
<img src="{{ url_for('static', filename='uploads/' + photo.filename) }}" class="img-thumbnail" alt="{{ photo.caption }}">
<small>{{ photo.caption }}</small>
{% if photo.action_required != 'none' %}
<br><strong>Action Required:</strong> {{ photo.action_required }}
{% endif %}
</div>
{% endfor %}
</div>
<a href="{{ url_for('inspections.edit', inspection_id=inspection.id) }}" class="btn btn-outline-primary">Edit Report</a>
<a href="{{ url_for('inspections.delete', inspection_id=inspection.id) }}" class="btn btn-outline-danger">Delete Report</a>
{% endblock %}

View file

@ -0,0 +1,24 @@
"""PDF generation utility."""
from flask import current_app
from weasyprint import HTML
from pathlib import Path
from flask import render_template, url_for
from app.models import Inspection
def generate_pdf(inspection_id):
"""Render inspection view template and convert to PDF."""
inspection = Inspection.query.get_or_404(inspection_id)
inspectors = inspection.inspectors
photos = inspection.photos
# Render the inspection view template
html = render_template(
"inspection_view.html",
inspection=inspection,
inspectors=inspectors,
photos=photos,
)
# Generate PDF
pdf_bytes = HTML(string=html, base_url=current_app.root_path).write_pdf()
return pdf_bytes