from __future__ import annotations from flask import Blueprint, abort, flash, redirect, render_template, request, url_for from app.extensions import db from app.models import FaceEncoding, Person, Role from app.people.forms import PersonForm from app.services.attendance import last_presence_summary from app.services.recognition import RecognitionUnavailable, encode_uploaded_image from app.services.security import roles_required bp = Blueprint("people", __name__, url_prefix="/people") @bp.get("/") @roles_required(Role.ADMIN, Role.OPERATOR, Role.VIEWER) def index(): people = Person.query.order_by(Person.full_name.asc()).all() return render_template("people/index.html", people=people) @bp.route("/new", methods=["GET", "POST"]) @roles_required(Role.ADMIN, Role.OPERATOR) def new_person(): form = PersonForm() if form.validate_on_submit(): files = [field.data for field in (form.photo_1, form.photo_2, form.photo_3, form.photo_4) if field.data] files = [file for file in files if file.filename] if not files: flash("Upload at least one reference photo.", "danger") return render_template("people/form.html", form=form) try: vectors = [encode_uploaded_image(file) for file in files] except RecognitionUnavailable as exc: flash(str(exc), "danger") return render_template("people/form.html", form=form), 503 except ValueError as exc: flash(str(exc), "danger") return render_template("people/form.html", form=form) person = Person( full_name=form.full_name.data.strip(), employee_id=form.employee_id.data.strip(), notes=form.notes.data.strip() or None, ) for slot, vector in enumerate(vectors, start=1): encoding = FaceEncoding(image_slot=slot) encoding.vector = vector person.face_encodings.append(encoding) db.session.add(person) db.session.commit() flash(f"{person.full_name} has been enrolled.", "success") return redirect(url_for("people.detail", person_id=person.id)) return render_template("people/form.html", form=form) @bp.get("/") @roles_required(Role.ADMIN, Role.OPERATOR, Role.VIEWER) def detail(person_id: int): person = db.session.get(Person, person_id) if not person: abort(404) events = person.attendance_events[:] events.sort(key=lambda event: event.occurred_at, reverse=True) return render_template( "people/detail.html", person=person, events=events[:20], presence_summary=last_presence_summary(person), ) @bp.post("//toggle") @roles_required(Role.ADMIN, Role.OPERATOR) def toggle_person(person_id: int): person = db.session.get(Person, person_id) if not person: abort(404) person.active = not person.active db.session.commit() flash(f"{person.full_name} is now {'active' if person.active else 'inactive'}.", "success") return redirect(request.referrer or url_for("people.index"))