From 96d82b6f865e68397279b210b8d9dcd0247329d7 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 22 Mar 2026 01:28:56 +0100 Subject: [PATCH] Add admin blueprint routes, inspection blueprint routes, inspection templates, and PDF generator utility --- app/routes/admin.py | 78 ++++++++++++++++++++++ app/routes/inspections.py | 102 +++++++++++++++++++++++++++++ app/templates/inspection_form.html | 54 +++++++++++++++ app/templates/inspection_view.html | 40 +++++++++++ app/utils/pdf_generator.py | 24 +++++++ 5 files changed, 298 insertions(+) create mode 100644 app/routes/admin.py create mode 100644 app/routes/inspections.py create mode 100644 app/templates/inspection_form.html create mode 100644 app/templates/inspection_view.html create mode 100644 app/utils/pdf_generator.py diff --git a/app/routes/admin.py b/app/routes/admin.py new file mode 100644 index 0000000..08ad1c8 --- /dev/null +++ b/app/routes/admin.py @@ -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("//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("//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")) diff --git a/app/routes/inspections.py b/app/routes/inspections.py new file mode 100644 index 0000000..47bc62b --- /dev/null +++ b/app/routes/inspections.py @@ -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("/", 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("//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("//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")) diff --git a/app/templates/inspection_form.html b/app/templates/inspection_form.html new file mode 100644 index 0000000..ca6897b --- /dev/null +++ b/app/templates/inspection_form.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% block title %}New Inspection{% endblock %} + +{% block content %} +

New Inspection

+
+ {{ form.hidden_tag() }} +
+ Installation Details +
+ {{ form.installation_name.label(class="form-label") }} + {{ form.installation_name(class="form-control") }} +
+
+ {{ form.location.label(class="form-label") }} + {{ form.location(class="form-control") }} +
+
+ {{ form.inspection_date.label(class="form-label") }} + {{ form.inspection_date(class="form-control") }} +
+
+ {{ form.reference_number.label(class="form-label") }} + {{ form.reference_number(class="form-control") }} +
+
+
+ Observations + {{ form.observations.label(class="form-label") }} + {{ form.observations(class="form-control", rows=6) }} +
+
+ Conclusion + {{ 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") }} +
+
+ Inspectors +
+ {{ form.inspectors_form|safe }} +
+
+
+ Photos +
+ {{ form.photos_form|safe }} +
+
+ +
+{% endblock %} \ No newline at end of file diff --git a/app/templates/inspection_view.html b/app/templates/inspection_view.html new file mode 100644 index 0000000..5d40005 --- /dev/null +++ b/app/templates/inspection_view.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% block title %}Inspection Report{% endblock %} + +{% block content %} +

Inspection Report

+ +

Installation Name: {{ inspection.installation_name }}

+

Location: {{ inspection.location }}

+

Inspection Date: {{ inspection.inspection_date }}

+

Reference Number: {{ inspection.reference_number }}

+

Observations: {{ inspection.observations }}

+

Conclusion Text: {{ inspection.conclusion_text }}

+

Conclusion Status: {{ inspection.conclusion_status }}

+

Created By: {{ inspection.creator.username }}

+

Created At: {{ inspection.created_at }}

+ +

Inspectors

+
    + {% for inspector in inspectors %} +
  • {{ inspector.get_full_name_or_name() }}
  • + {% endfor %} +
+ +

Photos

+
+ {% for photo in photos %} +
+ {{ photo.caption }} + {{ photo.caption }} + {% if photo.action_required != 'none' %} +
Action Required: {{ photo.action_required }} + {% endif %} +
+ {% endfor %} +
+ +Edit Report +Delete Report +{% endblock %} \ No newline at end of file diff --git a/app/utils/pdf_generator.py b/app/utils/pdf_generator.py new file mode 100644 index 0000000..0dbd92b --- /dev/null +++ b/app/utils/pdf_generator.py @@ -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