Add admin blueprint routes, inspection blueprint routes, inspection templates, and PDF generator utility
This commit is contained in:
parent
bfd320c5e9
commit
96d82b6f86
5 changed files with 298 additions and 0 deletions
78
app/routes/admin.py
Normal file
78
app/routes/admin.py
Normal 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
102
app/routes/inspections.py
Normal 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"))
|
||||||
54
app/templates/inspection_form.html
Normal file
54
app/templates/inspection_form.html
Normal 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 %}
|
||||||
40
app/templates/inspection_view.html
Normal file
40
app/templates/inspection_view.html
Normal 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 %}
|
||||||
24
app/utils/pdf_generator.py
Normal file
24
app/utils/pdf_generator.py
Normal 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
|
||||||
Loading…
Reference in a new issue