# 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 . """ 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("//", 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("//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("/", 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)