mirror of
https://github.com/nelsbrock/NateMan.git
synced 2026-08-14 21:36:49 +02:00
CSV-Importer implementiert
This commit is contained in:
@@ -28,7 +28,7 @@ from openpyxl.utils.exceptions import InvalidFileException
|
|||||||
|
|
||||||
from .auth import beratungslehrer_required, admin_required
|
from .auth import beratungslehrer_required, admin_required
|
||||||
from .. import exporter, util
|
from .. import exporter, util
|
||||||
from ..importer import KlausurplanImportError, import_plan, excelimport, KoopSchuelerImportError
|
from ..importer import KlausurplanImportError, import_kurs42, import_excel_koop, KoopSchuelerImportError, import_csv
|
||||||
from ..models import db, Stufe, Klausur, Schueler
|
from ..models import db, Stufe, Klausur, Schueler
|
||||||
|
|
||||||
bp = Blueprint("fileio", __name__)
|
bp = Blueprint("fileio", __name__)
|
||||||
@@ -98,8 +98,13 @@ def import_():
|
|||||||
|
|
||||||
_del_plan(stufe)
|
_del_plan(stufe)
|
||||||
|
|
||||||
|
if plan.filename.endswith(".xml"):
|
||||||
|
import_func = import_kurs42
|
||||||
|
else:
|
||||||
|
import_func = import_csv
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import_plan(plan, stufe, new_lehrer_password)
|
import_func(plan, stufe, new_lehrer_password)
|
||||||
except (KeyError, ValueError, ExpatError, KlausurplanImportError) as exc:
|
except (KeyError, ValueError, ExpatError, KlausurplanImportError) as exc:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
|
|
||||||
@@ -125,7 +130,7 @@ def import_():
|
|||||||
ks_file.save(fp.name)
|
ks_file.save(fp.name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ks_import_failures = excelimport(fp.name)
|
ks_import_failures = import_excel_koop(fp.name)
|
||||||
except (InvalidFileException, KoopSchuelerImportError) as exc:
|
except (InvalidFileException, KoopSchuelerImportError) as exc:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
|
|
||||||
|
|||||||
+108
-3
@@ -18,7 +18,8 @@
|
|||||||
"""
|
"""
|
||||||
Enthält Funktionen zum Import von Daten.
|
Enthält Funktionen zum Import von Daten.
|
||||||
"""
|
"""
|
||||||
|
import io
|
||||||
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TextIO, SupportsInt, Union
|
from typing import TextIO, SupportsInt, Union
|
||||||
|
|
||||||
@@ -50,7 +51,111 @@ def _nbit_int(x: Union[str, bytes, SupportsInt], base=10, maxbits=32):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def import_plan(xml_file: TextIO, stufe: Stufe, new_lehrer_pwd: str) -> None:
|
def import_csv(file: TextIO, stufe: Stufe, new_lehrer_pwd: str) -> None:
|
||||||
|
"""
|
||||||
|
Importiert eine Klausurliste aus der gegebenen CSV-Klausurplandatei für die angegebene Stufe.
|
||||||
|
Registriert noch nicht registrierte Lehrer mit dem angegebenen Passwort.
|
||||||
|
von Niklas Elsbrock
|
||||||
|
|
||||||
|
:param file: zu importierende XML-Datei
|
||||||
|
:param stufe: Stufe, für die der Plan importiert werden soll
|
||||||
|
:param new_lehrer_pwd: Passwort für neu registrierte Lehrerkonten
|
||||||
|
"""
|
||||||
|
col_kursname = 0
|
||||||
|
col_lehrer_kuerzel = 1
|
||||||
|
col_date = 2
|
||||||
|
col_startperiod = 3
|
||||||
|
col_endperiod = 4
|
||||||
|
col_teilnehmer = 5
|
||||||
|
|
||||||
|
row_len = 6
|
||||||
|
|
||||||
|
delim_regex = r"[,;]"
|
||||||
|
|
||||||
|
assert util.validate_bcrypt_password(new_lehrer_pwd)
|
||||||
|
|
||||||
|
# Importdatum setzen
|
||||||
|
stufe.import_date = datetime.now().date()
|
||||||
|
|
||||||
|
new_lehrer_pwd_hash = bcrypt.hashpw(new_lehrer_pwd.encode("utf-8"), bcrypt.gensalt())
|
||||||
|
|
||||||
|
rows = [tuple(re.split(delim_regex, row)) for row in io.TextIOWrapper(file).read().splitlines()]
|
||||||
|
|
||||||
|
for rownum, row in enumerate(rows[1:], start=2):
|
||||||
|
if len(row) != row_len:
|
||||||
|
raise KlausurplanImportError(f"Zu importierender Plan für die Stufe {stufe.name} enthält in Zeile {rownum} "
|
||||||
|
f"nur {len(row)} statt der erwarteten {row_len} Werte.")
|
||||||
|
|
||||||
|
kursname = row[col_kursname]
|
||||||
|
lehrer_kuerzel = row[col_lehrer_kuerzel]
|
||||||
|
date = datetime.strptime(row[col_date], "%Y%m%d").date()
|
||||||
|
startperiod = _nbit_int(row[col_startperiod])
|
||||||
|
endperiod = _nbit_int(row[col_endperiod])
|
||||||
|
|
||||||
|
if startperiod < 0 or startperiod > endperiod:
|
||||||
|
raise KlausurplanImportError(f"Zu importierender Plan für die Stufe {stufe.name} enthält ungültigen "
|
||||||
|
f"Zeitraum (Zeile: {rownum}; VonStd: {startperiod} BisStd: {endperiod})")
|
||||||
|
|
||||||
|
# eventuell bereits registrierten Lehrer bekommen
|
||||||
|
kurs_lehrer_entity = Lehrer.query.filter_by(kuerzel=lehrer_kuerzel).first()
|
||||||
|
|
||||||
|
# Falls der Lehrer noch nicht registriert ist, registrieren
|
||||||
|
if kurs_lehrer_entity is None:
|
||||||
|
kurs_lehrer_entity = Lehrer(kuerzel=lehrer_kuerzel, is_confirmed=True, pwd_hash=new_lehrer_pwd_hash)
|
||||||
|
# Falls ein E-Mailadressenformat konfiguriert ist, dieses nutzen.
|
||||||
|
email_address_format = config.get("email-address-format", None)
|
||||||
|
if email_address_format is not None:
|
||||||
|
kurs_lehrer_entity.set_default_email_address(email_address_format)
|
||||||
|
|
||||||
|
db.session.add(kurs_lehrer_entity)
|
||||||
|
|
||||||
|
klausur_entity = Klausur(kursname=kursname, date=date, startperiod=startperiod, endperiod=endperiod,
|
||||||
|
stufe=stufe, lehrer=kurs_lehrer_entity)
|
||||||
|
db.session.add(klausur_entity)
|
||||||
|
|
||||||
|
for schueler_entry in row[col_teilnehmer].split('~'):
|
||||||
|
schueler_data = schueler_entry.rsplit('_', maxsplit=2)
|
||||||
|
|
||||||
|
if len(schueler_data) != 3:
|
||||||
|
schueler_entity = Schueler(id=get_next_new_schueler_id(), nachname=schueler_entry, vorname="???",
|
||||||
|
stufe=stufe, koop=True)
|
||||||
|
else:
|
||||||
|
(nachname, vorname, birthdate) = schueler_data
|
||||||
|
birthdate = int(birthdate)
|
||||||
|
for rownum in range(100):
|
||||||
|
test_id = birthdate * 100 + rownum
|
||||||
|
test_schueler = Schueler.query.filter_by(id=test_id).first()
|
||||||
|
if test_schueler is None:
|
||||||
|
schueler_entity = Schueler(id=test_id, nachname=nachname, vorname=vorname, stufe=stufe)
|
||||||
|
break
|
||||||
|
elif test_schueler.nachname == nachname and test_schueler.vorname == vorname:
|
||||||
|
# Ist dieser Schüler bereits für eine andere Stufe eingetragen? -> Fehler
|
||||||
|
if test_schueler.stufe != stufe:
|
||||||
|
raise KlausurplanImportError(f"Zu importierender Plan für die {stufe.name} enthält Schüler "
|
||||||
|
f"({vorname} {nachname}, {birthdate}), der bereits für die "
|
||||||
|
f"{test_schueler.stufe.name} eingetragen ist "
|
||||||
|
f"(Zeile: {rownum}).")
|
||||||
|
schueler_entity = test_schueler
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise KlausurplanImportError(f"Zu importierender Plan für Stufe {stufe.name} enthält Schüler "
|
||||||
|
f"({vorname} {nachname}, {birthdate}; Zeile: {rownum}), dem keine ID "
|
||||||
|
f"gegeben werden konnte.")
|
||||||
|
|
||||||
|
# Ist derselbe Schüler bereits in dieser Klausur? -> Fehler
|
||||||
|
if db.session.query(exists().where(and_(Klausurteilnahme.schueler == schueler_entity,
|
||||||
|
Klausurteilnahme.klausur == klausur_entity))).scalar():
|
||||||
|
raise KlausurplanImportError(f"Zu importierender Plan für die {stufe.name} enthält selben "
|
||||||
|
f"Schüler ({schueler_entity.vorname} {schueler_entity.nachname}, "
|
||||||
|
f"{schueler_entity.birthdate}, Zeile: {rownum}) mehrfach in derselben "
|
||||||
|
f"Klausur ({kursname}).")
|
||||||
|
|
||||||
|
# Schüler zur Klausur hinzufügen
|
||||||
|
klausurteilnahme = Klausurteilnahme(klausur=klausur_entity, schueler=schueler_entity)
|
||||||
|
db.session.add(klausurteilnahme)
|
||||||
|
|
||||||
|
|
||||||
|
def import_kurs42(xml_file: TextIO, stufe: Stufe, new_lehrer_pwd: str) -> None:
|
||||||
"""
|
"""
|
||||||
Importiert eine Klausurliste aus dem gegebenen XML-Klausurexport für die angegebene Stufe.
|
Importiert eine Klausurliste aus dem gegebenen XML-Klausurexport für die angegebene Stufe.
|
||||||
Registriert noch nicht registrierte Lehrer mit dem angegebenen Passwort.
|
Registriert noch nicht registrierte Lehrer mit dem angegebenen Passwort.
|
||||||
@@ -146,7 +251,7 @@ def import_plan(xml_file: TextIO, stufe: Stufe, new_lehrer_pwd: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
# Von Johannes
|
# Von Johannes
|
||||||
def excelimport(filepath: str):
|
def import_excel_koop(filepath: str):
|
||||||
# Initialisiert Startdaten
|
# Initialisiert Startdaten
|
||||||
if Stufe.import_date is not None:
|
if Stufe.import_date is not None:
|
||||||
workbook = load_workbook(filepath)
|
workbook = load_workbook(filepath)
|
||||||
|
|||||||
@@ -54,7 +54,9 @@
|
|||||||
{% for stufe in stufen_list %}
|
{% for stufe in stufen_list %}
|
||||||
<div>
|
<div>
|
||||||
<div><label for="plan_{{ stufe.name }}-input">Plan für {{ stufe.name }}:</label></div>
|
<div><label for="plan_{{ stufe.name }}-input">Plan für {{ stufe.name }}:</label></div>
|
||||||
<div><input type="file" name="plan_{{ stufe.name }}" id="plan_{{ stufe.name }}-input" accept="text/xml"></div>
|
<div>
|
||||||
|
<input type="file" name="plan_{{ stufe.name }}" id="plan_{{ stufe.name }}-input" accept="text/csv,text/xml">
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" name="del_{{ stufe.name }}" value="Plan löschen"
|
<input type="submit" name="del_{{ stufe.name }}" value="Plan löschen"
|
||||||
onclick="return confirm(plan_delete_warn)" {% if stufe.import_date is none %}disabled{% endif %}>
|
onclick="return confirm(plan_delete_warn)" {% if stufe.import_date is none %}disabled{% endif %}>
|
||||||
|
|||||||
Reference in New Issue
Block a user