Files
NateMan/nateman/blueprints/fileio.py
T
2021-08-10 17:53:30 +02:00

164 lines
6.3 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/fileio.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/>.
"""
Export- und Import-Blueprint
"""
import os
import tempfile
from datetime import datetime
from pyexpat import ExpatError
from flask import abort, Blueprint, current_app, g, redirect, url_for, flash, request, render_template
from openpyxl.utils.exceptions import InvalidFileException
from .auth import beratungslehrer_required, admin_required
from .. import exporter, util
from ..importer import KlausurplanImportError, import_plan, excelimport, KoopSchuelerImportError
from ..models import db, Stufe, Klausur, Schueler
bp = Blueprint("fileio", __name__)
@bp.route("/export")
@beratungslehrer_required
def export():
""" Export (Seite *Nachschreibplan exportieren*)"""
with tempfile.NamedTemporaryFile(prefix="nateman_export_") as fp:
exporter.excelexport(fp.name, g.lehrer.accessible_stufen())
# XLSX-Datei wird in ``filebytes`` geladen, damit die temporäre Datei gelöscht werden kann
fp.seek(0)
filebytes = fp.read()
download_name = "NateMan-Export " + datetime.now().strftime("%Y-%m-%d") + ".xlsx"
xlsx_mimetype = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
response = current_app.response_class(filebytes, mimetype=xlsx_mimetype)
response.headers.set("Content-Disposition", "attachment", filename=download_name)
return response
@bp.route("/import", methods=("GET", "POST"), endpoint="import")
@admin_required
def import_():
""" Import (Seite *Klausurpläne importieren*) """
if request.method != "POST":
return render_template("fileio/import.html.j2")
# ELSE
all_stufen = Stufe.query.all()
for stufe in all_stufen:
if "del_" + stufe.name in request.form:
if not g.lehrer.can_access(stufe):
abort(400)
return
_del_plan(stufe)
db.session.commit()
flash("Klausurplan erfolgreich gelöscht.", "success")
current_app.logger.info(f"{g.lehrer} hat den Klausurplan für die {stufe.name} gelöscht.")
return redirect(url_for(".import"), code=303)
new_lehrer_password = request.form["new-lehrer-password"]
error_msg = None
if not new_lehrer_password:
error_msg = "Bitte geben Sie das Passwort für neu registrierte Lehrerkonten an."
elif not util.validate_bcrypt_password(new_lehrer_password):
error_msg = "Das Passwort für neue Lehrerkonten darf nicht mehr als 72 Zeichen enthalten."
if error_msg is not None:
flash(error_msg, "error")
return redirect(url_for(".import"), code=303)
plan_given = []
for stufe in all_stufen:
plan = request.files.get("plan_" + stufe.name, None)
# Klausurplan importieren
if plan and plan.filename:
plan_given.append(stufe.name)
_del_plan(stufe)
try:
import_plan(plan, stufe, new_lehrer_password)
except (KeyError, ValueError, ExpatError, KlausurplanImportError) as exc:
db.session.rollback()
flash(f"Beim Importieren des Klausurplans für die {stufe.name} ist ein Fehler aufgetreten.\n"
f"Wahrscheinlich ist die Klausurplandatei ungültig oder es gibt einen Konflikt mit den "
f"bisherigen Daten.\n\nFehlerbeschreibung: {type(exc).__name__}\n{exc}", "error")
current_app.logger.warning(f"Beim Versuch von {g.lehrer}, einen Klausurplan zu importieren, "
f"ist ein Fehler aufgetreten.", exc_info=exc)
return redirect(url_for(".import"), code=303)
ks_file = request.files.get("koopschueler", None)
ks_file_given = False
# Koopschülerdatei importieren
ks_import_failures = []
if ks_file and ks_file.filename:
ks_file_given = True
_del_koopschueler()
filename_ext = os.path.splitext(ks_file.filename)[1]
with tempfile.NamedTemporaryFile(prefix="nateman_ks_import_", suffix=filename_ext) as fp:
ks_file.save(fp.name)
try:
ks_import_failures = excelimport(fp.name)
except (InvalidFileException, KoopSchuelerImportError) as exc:
db.session.rollback()
flash(f"Beim Importieren der Koopschülerliste ist ein Fehler aufgetreten.\n\n"
f"Fehlerbeschreibung: {type(exc).__name__}\n{exc}", "error")
current_app.logger.warning(f"Beim Versuch von {g.lehrer}, eine Koopschülerliste zu importieren, "
f"ist ein Fehler aufgetreten.", exc_info=exc)
return redirect(url_for(".import"), code=303)
if len(plan_given) == 0 and not ks_file_given:
flash("Es wurden keine Pläne angegeben.", "error")
else:
if len(ks_import_failures) == 0:
flash("Pläne importiert.", "success")
else:
flash("Pläne importiert.\n\nKoopschülerimport: Folgende SuS konnten nicht importiert werden:\n"
+ "; ".join(f"{failure[0]}, {failure[1]}" for failure in ks_import_failures) + ".\n"
"Die restlichen KoopSuS wurden trotzdem importiert.", "warning")
current_app.logger.info(f"{g.lehrer} hat Pläne importiert.")
db.session.commit()
return redirect(url_for(".import"), code=303)
def _del_plan(stufe: Stufe):
"""Löscht alle Klausuren und Schüler der angegebenen Stufe"""
stufe.import_date = None
Klausur.query.filter_by(stufe=stufe).delete()
Schueler.query.filter_by(stufe=stufe).delete()
def _del_koopschueler():
"""Löscht alle Koopschüler"""
Schueler.query.filter(Schueler.koop).delete()