Files
NateMan/nateman/blueprints/klausuren.py
T

359 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# NateMan Nachschreibtermin-Manager
# blueprints/klausuren.py
# Copyright © 2020 Niklas Elsbrock
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
Klausurlisten-Blueprint
"""
from datetime import datetime
from flask import Blueprint, abort, current_app, flash, g, redirect, render_template, request, url_for, Response
from .auth import login_required, beratungslehrer_required
from ..config_manager import config
from ..models import Klausur, Klausurteilnahme, Lehrer, Schueler, db, get_next_new_schueler_id, Stufe
bp = Blueprint("klausuren", __name__, url_prefix="/klausuren")
@bp.route("/")
@login_required
def mine():
""" Ansicht der eigenen Klausuren (Seite *Meine Klausuren*) """
klausur_query = Klausur.query.filter(Klausur.lehrer == g.lehrer).order_by(Klausur.date)
klausuren_anstehend = klausur_query.filter(Klausur.date > datetime.now().date()).all()
klausuren_vergangen = klausur_query.filter(Klausur.date <= datetime.now().date()).all()
return render_template("klausuren/mine.html.j2", klausuren_anstehend=klausuren_anstehend,
klausuren_vergangen=klausuren_vergangen)
@bp.route("/schueler", endpoint="schueler")
def schueler_():
schueler_id = request.args.get("id")
schueler_nachname = request.args.get("nachname")
if schueler_id is None or schueler_nachname is None:
flash("Fehlende Schülerdaten.", "error")
return redirect(url_for("index.index"), code=303)
schueler = Schueler.query.filter_by(id=schueler_id, nachname=schueler_nachname).first()
if schueler is None:
flash("Ungültige Schülerdaten.", "error")
return redirect(url_for("index.index"), code=303)
kt_query = Klausurteilnahme.query.join(Schueler).join(Klausur) \
.filter(Schueler.id == schueler_id) \
.order_by(Klausur.date)
kt_anstehend = kt_query.filter(Klausur.date > datetime.now().date()).all()
kt_vergangen = kt_query.filter(Klausur.date <= datetime.now().date()).all()
return render_template("klausuren/schueler.html.j2", schueler=schueler, kt_vergangen=kt_vergangen,
kt_anstehend=kt_anstehend)
@bp.route("/<stufe_name>/", endpoint="stufe")
def stufe_(stufe_name):
""" Ansicht der Klausuren einer Stufe (Seite *Klausuren [Stufe]*) """
stufe = Stufe.query.filter_by(name=stufe_name).first()
if stufe is None:
abort(404)
return
desc_order = request.args.get("order") == "desc"
if desc_order:
order = Klausur.date.desc()
else:
order = Klausur.date.asc()
dates_query = db.session.query(Klausur.date).filter_by(stufe=stufe).order_by(order)
dates_anstehend = [t.date for t in dates_query.filter(
(Klausur.date > datetime.now().date()) if desc_order else (Klausur.date >= datetime.now().date())
).distinct()]
dates_vergangen = [t.date for t in dates_query.filter(
(Klausur.date <= datetime.now().date()) if desc_order else (Klausur.date < datetime.now().date())
).distinct()]
return render_template("klausuren/stufe.html.j2", stufe=stufe, dates_anstehend=dates_anstehend,
dates_vergangen=dates_vergangen, desc_order=desc_order)
@bp.route("/<stufe_name>/add", methods=("GET", "POST"))
@beratungslehrer_required
def add(stufe_name):
""" Klausurhinzufügung """
reload_resp = redirect(url_for(".add", stufe_name=stufe_name), code=303)
stufe = Stufe.query.filter_by(name=stufe_name).first()
if stufe is None:
abort(404)
return
if not g.lehrer.can_access(stufe):
abort(403)
return
if request.method != "POST":
return render_template("klausuren/add.html.j2", stufe=stufe)
# ELSE
kursname = request.form["kursname"]
lehrer_id = request.form["lehrer"]
date_str = request.form["date"]
try:
startperiod = int(request.form["startperiod"])
endperiod = int(request.form["endperiod"])
except ValueError:
abort(400)
return
if startperiod < 0 or startperiod > endperiod or endperiod.bit_length() > 32:
flash("Ungültiger Zeitraum", "error")
return reload_resp
if not kursname:
flash("Bitte geben Sie einen Kursnamen an.", "error")
return reload_resp
try:
date = datetime.strptime(date_str, "%Y-%m-%d").date()
except ValueError:
abort(400)
return
lehrer = Lehrer.query.filter_by(id=lehrer_id).first()
if lehrer is None:
abort(400)
return
new_klausur = Klausur(kursname=kursname, date=date, startperiod=startperiod, endperiod=endperiod, stufe=stufe,
lehrer=lehrer)
db.session.add(new_klausur)
db.session.commit()
flash("Die Klausur wurde erfolgreich hinzugefügt.", "success")
current_app.logger.info(f"{g.lehrer} hat die Klausur {new_klausur} hinzugefügt.")
return redirect(url_for(".edit", klausur_id=new_klausur.id), code=303)
@bp.route("/<int:klausur_id>", methods=("GET", "POST"))
@login_required
def edit(klausur_id):
""" Klausurbearbeitung """
klausur = Klausur.query.filter_by(id=klausur_id).first()
if klausur is None:
abort(404)
return
if not g.lehrer.can_access(klausur.stufe) and klausur.lehrer != g.lehrer:
abort(403)
return
# ELSE
if request.method != "POST":
kt_list = Klausurteilnahme.query \
.filter_by(klausur=klausur) \
.join(Schueler) \
.order_by(Schueler.nachname, Schueler.vorname) \
.all()
not_in_klausur_list = Schueler.query \
.filter_by(stufe=klausur.stufe) \
.filter(Schueler.id.notin_(db.session.query(Klausurteilnahme.schueler_id).filter_by(klausur=klausur))) \
.order_by(Schueler.nachname, Schueler.vorname) \
.all()
return render_template("klausuren/edit.html.j2", klausur=klausur, kt_list=kt_list,
not_in_klausur_list=not_in_klausur_list)
# ELSE
reload_resp = redirect(url_for(".edit", klausur_id=klausur_id), code=303)
exit_resp: Response
if klausur.lehrer == g.lehrer:
exit_resp = redirect(url_for(".mine"), code=303)
else:
exit_resp = redirect(url_for(".stufe", stufe_name=klausur.stufe.name), code=303)
if klausur.edited and not g.lehrer.can_access(klausur.stufe):
abort(400)
return
action = request.form["action"]
if action == "edit":
lehrer_can_access = g.lehrer.can_access(klausur.stufe)
annotation = request.form["annotation"].strip()
flag_as_edited = ("flag-as-edited" in request.form)
klausur_laenge_str = request.form.get("laenge", None)
if lehrer_can_access and not klausur_laenge_str:
klausur_laenge = None
else:
try:
klausur_laenge = int(klausur_laenge_str)
except ValueError:
abort(400)
return
klausur.laenge = klausur_laenge
for kt in Klausurteilnahme.query.filter_by(klausur=klausur).all():
kt.versaeumt = (f"s_{kt.schueler.id}" in request.form)
if annotation:
if len(annotation) > config["klausuren"]["max-annotation-length"]:
abort(400)
return
klausur.annotation = annotation
else:
klausur.annotation = None
if lehrer_can_access:
klausur.edited = flag_as_edited
else:
klausur.edited = True
current_app.logger.info(f"{g.lehrer} hat die Klausur {klausur} bearbeitet.")
flash("Klausur erfolgreich bearbeitet.", "success")
db.session.commit()
return exit_resp
elif action == "edit-advanced":
if not g.lehrer.can_access(klausur.stufe):
abort(400)
return
new_lehrer_id = request.form["new-lehrer"]
new_date_str = request.form["new-date"]
try:
new_startperiod = int(request.form["new-startperiod"])
new_endperiod = int(request.form["new-endperiod"])
except ValueError:
abort(400)
return
if new_startperiod < 0 or new_startperiod > new_endperiod or new_endperiod.bit_length() > 32:
flash("Ungültiger Zeitraum", "error")
return reload_resp
try:
new_date = datetime.strptime(new_date_str, "%Y-%m-%d").date()
except ValueError:
abort(400)
return
new_lehrer = Lehrer.query.filter_by(id=new_lehrer_id).first()
if new_lehrer is None:
abort(400)
return
klausur.lehrer = new_lehrer
klausur.date = new_date
klausur.startperiod = new_startperiod
klausur.endperiod = new_endperiod
current_app.logger.info(f"{g.lehrer} hat die Klausur {klausur} erweitert bearbeitet.")
flash("Klausur erfolgreich bearbeitet.", "success")
db.session.commit()
return reload_resp
elif action == "delete":
if not g.lehrer.can_access(klausur.stufe):
abort(400)
return
current_app.logger.info(f"{g.lehrer} hat die Klausur {klausur} gelöscht.")
flash("Die Klausur wurde erfolgreich gelöscht.", "success")
db.session.delete(klausur)
db.session.commit()
return exit_resp
elif action == "add-schueler":
schueler_id = request.form.get("added-schueler", None)
add_to = request.form["add-to"]
if schueler_id is None:
flash("Bitte wählen Sie einen Schüler zum Hinzufügen aus.", "error")
return reload_resp
is_new = False
if schueler_id == "new-schueler":
is_new = True
new_schueler_nachname = request.form["new-schueler-nachname"].strip()
new_schueler_vorname = request.form["new-schueler-vorname"].strip()
new_schueler_koop = ("new-schueler-koop" in request.form)
if not new_schueler_nachname or not new_schueler_vorname:
flash("Bitte geben Sie einen vollständigen Namen für den neuen Schüler an.", "error")
return reload_resp
schueler = Schueler(id=get_next_new_schueler_id(), nachname=new_schueler_nachname,
vorname=new_schueler_vorname, stufe=klausur.stufe,
koop=new_schueler_koop)
db.session.add(schueler)
else:
schueler = Schueler.query.filter_by(id=schueler_id).first()
if schueler is None:
abort(400)
return
if Klausurteilnahme.query.filter_by(klausur=klausur, schueler=schueler).first() is not None:
abort(400)
return
if add_to == "klausur":
klausuren = [klausur]
elif add_to == "kurs":
klausuren = Klausur.query.filter_by(stufe=klausur.stufe).filter_by(kursname=klausur.kursname).all()
else:
abort(400)
return
for k in klausuren:
kt_entity = Klausurteilnahme(klausur=k, schueler=schueler)
db.session.add(kt_entity)
current_app.logger.info(f"{g.lehrer} hat den{' neuen' if is_new else ''} Schüler {schueler} "
f"zur Klausur {klausur} hinzugefügt.")
db.session.commit()
return reload_resp
elif action == "remove-schueler":
for kt in Klausurteilnahme.query.join(Klausur)\
.filter_by(stufe=klausur.stufe)\
.filter_by(kursname=klausur.kursname)\
.all():
if f"r_{kt.schueler.id}" in request.form:
db.session.delete(kt)
current_app.logger.info(f"{g.lehrer} hat den Schüler {kt.schueler} aus der Klausur "
f"{kt.klausur} entfernt.")
db.session.commit()
return reload_resp
else:
# Bad Request, wenn ungültige action
abort(400)