1
0
Fork 0
mirror of https://codeberg.org/Mo8it/AdvLabDB.git synced 2024-11-06 21:17:43 +00:00
AdvLabDB/advlabdb/database_import.py
2023-11-02 20:04:09 +01:00

334 lines
12 KiB
Python

from datetime import datetime
from pathlib import Path
from flask import flash
from sqlalchemy import select
from .actions import backup
from .exceptions import DatabaseImportException
from .models import (
Appointment,
Assistant,
Experiment,
Group,
GroupExperiment,
Part,
PartStudent,
Program,
Semester,
SemesterExperiment,
Student,
User,
db,
get_first,
)
def is_null(entry):
return entry in {"NULL", ""}
def nullable(entry):
if is_null(entry):
return None
return entry.strip()
def not_nullable(entry):
if is_null(entry):
raise DatabaseImportException("Unnullable entry is NULL!")
return entry.strip()
def importFromFile(filePath: Path):
if filePath.name[-4:] != ".txt":
raise DatabaseImportException(
"The import file has to be a text file with txt extension (.txt at the end of the filename)!"
)
semesters = {}
parts = {}
students = {}
groups = {}
partStudents = {}
experiments = {}
groupExperiments = {}
appointments = {}
with filePath.open() as f:
flash("Reading file...")
expectingTable = True
readHeader = False
for line in f:
line = line[:-1]
if line == "":
expectingTable = True
continue
if expectingTable:
if line[0] != "#":
raise DatabaseImportException(f"Expected a Table name starting with # but got this line: {line}")
expectingTable = False
tableName = line[1:]
if tableName == "Semester":
activeDict = semesters
elif tableName == "Part":
activeDict = parts
elif tableName == "Student":
activeDict = students
elif tableName == "Group":
activeDict = groups
elif tableName == "PartStudent":
activeDict = partStudents
elif tableName == "Experiment":
activeDict = experiments
elif tableName == "GroupExperiment":
activeDict = groupExperiments
elif tableName == "Appointment":
activeDict = appointments
else:
raise DatabaseImportException(f"{tableName} is not a valid table name!")
readHeader = True
continue
cells = line.split("\t")
if readHeader:
readHeader = False
activeDict["_header"] = cells
for cell in cells:
activeDict[cell] = []
continue
cellsLen = len(cells)
if cellsLen != len(activeDict["_header"]):
raise DatabaseImportException(
f"The number of header cells is not equal to the number of row cells in row {cells}!"
)
for i in range(cellsLen):
activeDict[activeDict["_header"][i]].append(cells[i])
try:
# Semester
flash("Semester...")
if len(semesters["label"]) != 1:
raise DatabaseImportException("Only one semester is allowed in an import file!")
semesterLabel = not_nullable(semesters["label"][0])
semesterYear = int(not_nullable(semesters["year"][0]))
dbSemester = get_first(select(Semester).where(Semester.label == semesterLabel, Semester.year == semesterYear))
if dbSemester is None:
raise DatabaseImportException(
f"{semesterLabel}{semesterYear} does not exist in the database! Please make sure that you create the semester from the web interface first."
)
# Part
flash("Part...")
dbParts = {}
for i, id in enumerate(parts["id"]):
id = int(not_nullable(id))
partNumber = int(not_nullable(parts["number"][i]))
partProgramLabel = not_nullable(parts["program_label"][i])
dbPart = get_first(
select(Part)
.join(Program)
.where(
Part.number == partNumber,
Program.label == partProgramLabel,
Part.semester == dbSemester,
)
)
if dbPart is None:
raise DatabaseImportException(
f"Part with number {partNumber} and program label {partProgramLabel} does not exist in the database! Please make sure that you create parts from the web interface first."
)
dbParts[id] = dbPart
# Student
flash("Student...")
dbStudents = {}
for i, student_number in enumerate(students["student_number"]):
student_number = int(not_nullable(student_number))
first_name = not_nullable(students["first_name"][i])
last_name = not_nullable(students["last_name"][i])
uni_email = not_nullable(students["uni_email"][i]).lower()
contact_email = nullable(students["contact_email"][i])
if contact_email is not None:
contact_email = contact_email.lower()
bachelor_thesis = nullable(students["bachelor_thesis"][i])
bachelor_thesis_work_group = nullable(students["bachelor_thesis_work_group"][i])
note = nullable(students["note"][i])
dbStudent = get_first(select(Student).where(Student.student_number == student_number))
if dbStudent is None:
dbStudent = Student(
student_number=student_number,
first_name=first_name,
last_name=last_name,
uni_email=uni_email,
contact_email=contact_email,
bachelor_thesis=bachelor_thesis,
bachelor_thesis_work_group=bachelor_thesis_work_group,
note=note,
)
db.session.add(dbStudent)
else:
# Check if columns that should not change match
if dbStudent.first_name != first_name:
flash(
f'First name "{dbStudent.first_name}" in the database does not match with the first name "{first_name}" provided in the import file for the student number {student_number}.',
"warning",
)
if dbStudent.last_name != last_name:
flash(
f'Last name "{dbStudent.last_name}" in the database does not match with the last name "{last_name}" provided in the import file for the student number {student_number}.',
"warning",
)
if dbStudent.uni_email != uni_email:
flash(
f'University email "{dbStudent.uni_email}" in the database does not match with the university email "{last_name}" provided in the import file for the student number {student_number}.',
"warning",
)
dbStudent.contact_email = contact_email
# Only overwrite if set
if bachelor_thesis is not None:
dbStudent.bachelor_thesis = bachelor_thesis
if bachelor_thesis_work_group is not None:
dbStudent.bachelor_thesis_work_group = bachelor_thesis_work_group
# Append to note instead of overwriting
if note is not None:
if dbStudent.note is None:
dbStudent.note = note
dbStudent.note += "\n" + note
dbStudents[student_number] = dbStudent
# Group
flash("Group...")
dbGroups = {}
for i, id in enumerate(groups["id"]):
id = int(not_nullable(id))
program = get_first(select(Program).where(Program.label == not_nullable(groups["program_label"][i])))
dbGroup = Group(
number=int(not_nullable(groups["number"][i])),
program=program,
semester=dbSemester,
)
db.session.add(dbGroup)
dbGroups[id] = dbGroup
# PartStudent
flash("PartStudent...")
for i, student_number in enumerate(partStudents["student_number"]):
student_number = int(not_nullable(student_number))
dbPartStudent = PartStudent(
student=dbStudents[student_number],
part=dbParts[int(not_nullable(partStudents["part_id"][i]))],
group=dbGroups[int(not_nullable(partStudents["group_id"][i]))],
)
db.session.add(dbPartStudent)
# Experiment
flash("Experiment...")
dbSemesterExperiments = {}
for i, id in enumerate(experiments["id"]):
id = int(not_nullable(id))
experimentNumber = int(not_nullable(experiments["number"][i]))
experimentProgram = get_first(
select(Program).where(Program.label == not_nullable(experiments["program_label"][i]))
)
dbExperiment = get_first(
select(Experiment).where(Experiment.number == experimentNumber, Experiment.program == experimentProgram)
)
if dbExperiment is None:
# TODO: Check if experimentProgram is None
raise DatabaseImportException(
f"Experiment with number {experimentNumber} and program {experimentProgram} does not exist in the database. Please make sure that experiments are created from the web interface."
)
dbSemesterExperiment = get_first(
select(SemesterExperiment).where(
SemesterExperiment.experiment == dbExperiment, SemesterExperiment.semester == dbSemester
)
)
if dbSemesterExperiment is None:
raise DatabaseImportException(
f"No semester experiment exists in the database to the experiment with number {experimentNumber} and program {experimentProgram}. Please make sure that semester experiments are created in the web interface. The problem might be that the experiment was not active while creating a new semester"
)
dbSemesterExperiments[id] = dbSemesterExperiment
# GroupExperiment
flash("GroupExperiment...")
dbGroupExperiments = {}
for i, id in enumerate(groupExperiments["id"]):
id = int(not_nullable(id))
dbGroupExperiment = GroupExperiment(
semester_experiment=dbSemesterExperiments[int(not_nullable(groupExperiments["experiment_id"][i]))],
group=dbGroups[int(not_nullable(groupExperiments["group_id"][i]))],
)
db.session.add(dbGroupExperiment)
dbGroupExperiments[id] = dbGroupExperiment
# Appointment
flash("Appointment...")
for i, date in enumerate(appointments["date"]):
date = not_nullable(date)
assistantEmail = not_nullable(appointments["assistant_email"][i]).lower()
assistant = get_first(select(Assistant).join(User).where(User.email == assistantEmail))
if assistant is None:
raise DatabaseImportException(
f"Assistant with email {assistantEmail} does not exist in the database! Please make sure that you create assistants in the web interface."
)
dbAppointment = Appointment(
date=datetime.strptime(date, "%d.%m.%Y").date(),
special=bool(int(not_nullable(appointments["special"][i]))),
group_experiment=dbGroupExperiments[int(not_nullable(appointments["group_experiment_id"][i]))],
assistant=assistant,
)
db.session.add(dbAppointment)
backup(f"before_{dbSemester}_import")
db.session.commit()
except Exception as ex:
db.session.rollback()
raise ex
backup(f"after_{dbSemester}_import")
flash("\nImport done!", "success")
flash("Please check that everything worked as expected. Restore the database backup otherwise!", "warning")