85 lines
3.0 KiB
Python
85 lines
3.0 KiB
Python
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("/<int:person_id>")
|
|
@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("/<int:person_id>/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"))
|