Release - Upload
54
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Run and Debug Django Backend",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/backend/manage.py",
|
||||||
|
"args": [
|
||||||
|
"runserver",
|
||||||
|
"127.0.0.1:8000"
|
||||||
|
],
|
||||||
|
"django": true,
|
||||||
|
"autoStartBrowser": true,
|
||||||
|
"cwd": "${workspaceFolder}/backend",
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Debug React Frontend",
|
||||||
|
"type": "firefox",
|
||||||
|
"request": "launch",
|
||||||
|
"reAttach": true,
|
||||||
|
"url": "http://localhost:5173",
|
||||||
|
"webRoot": "${workspaceFolder}/Frontend",
|
||||||
|
// "runtimeExecutable": "yarn",
|
||||||
|
// "args": ["dev"],
|
||||||
|
// "cwd": "${workspaceFolder}/frontend",
|
||||||
|
// "console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Run React Frontend",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"runtimeExecutable": "yarn",
|
||||||
|
"args": ["dev"],
|
||||||
|
"cwd": "${workspaceFolder}/frontend",
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Start Django and React",
|
||||||
|
"configurations": [
|
||||||
|
"Run and Debug Django Backend",
|
||||||
|
"Run React Frontend"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
168
Backend/.gitignore
vendored
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### Python template
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# IPython Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
### VirtualEnv template
|
||||||
|
# Virtualenv
|
||||||
|
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
|
||||||
|
.Python
|
||||||
|
[Bb]in
|
||||||
|
[Ii]nclude
|
||||||
|
[Ll]ib
|
||||||
|
[Ll]ib64
|
||||||
|
[Ll]ocal
|
||||||
|
[Ss]cripts
|
||||||
|
pyvenv.cfg
|
||||||
|
.venv
|
||||||
|
pip-selfcheck.json
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/vcs.xml
|
||||||
|
.idea/jsLibraryMappings.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/dataSources.xml
|
||||||
|
.idea/dataSources.local.xml
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/mongoSettings.xml
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# DB
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# StudData
|
||||||
|
studis/xlsmodel/data/
|
||||||
|
|
||||||
|
# Logdateien
|
||||||
|
log/
|
||||||
|
|
||||||
|
# Medien
|
||||||
|
media/
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/launch.json
|
20
Backend/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
// Top-Bar
|
||||||
|
"titleBar.border": "#004F59", // Teal border
|
||||||
|
"titleBar.activeForeground": "#E0FFFF", // Light cyan text (sky blue feel)
|
||||||
|
"titleBar.activeBackground": "#007A91", // Vibrant teal background
|
||||||
|
|
||||||
|
// Left-Bar
|
||||||
|
"activityBar.border": "#004F59", // Teal border
|
||||||
|
"activityBar.activeForeground": "#E0FFFF", // Light cyan active icon
|
||||||
|
"activityBar.activeBackground": "#00A2C3", // Sky blue for active background
|
||||||
|
"activityBar.background": "#005F6B", // Deeper teal for overall background
|
||||||
|
|
||||||
|
// Bottom-Bar
|
||||||
|
"statusBar.background": "#004F59", // Teal background
|
||||||
|
"statusBar.foreground": "#E0FFFF", // Light cyan text
|
||||||
|
},
|
||||||
|
"debug.allowBreakpointsEverywhere": false
|
||||||
|
}
|
||||||
|
|
64
Backend/README.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Prüfplanviewer
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Nach dem laden des Repos und der Installation der Module
|
||||||
|
aus <code> requirements.txt </code> sollten noch folgende
|
||||||
|
<code> manage.py tasks </code> ausgeführt werden:
|
||||||
|
|
||||||
|
- <code> migrate </code> (Anlegen der Datenbanktabellen)
|
||||||
|
- Befehl aus READ.ME im Verzeichnis <code> fixtures </code>.
|
||||||
|
Dadurch werden Berechtigungsgruppen angelegt.
|
||||||
|
Für den Prüfungsplan wird aktuell nur die
|
||||||
|
Gruppe <code> PP_ADMIN </code> benötigt.
|
||||||
|
- <code> createsuperuser </code> (Anlage eines ersten Admin-Users)
|
||||||
|
|
||||||
|
Danach kann die Anwendung gestartet werden. Anmeldung mit dem
|
||||||
|
gerade generierten Superuser. Im Bereich "Administration" können
|
||||||
|
weitere User angelegt werden und ggf. der Gruppe <code> PP_ADMIN </code>
|
||||||
|
zugeordnet werden. Benutzer in dieser Gruppe können den
|
||||||
|
Prüfplan nicht nur sehen, sondern auch neue Daten laden.
|
||||||
|
|
||||||
|
## Daten
|
||||||
|
|
||||||
|
Die Daten werden von Prof. Mahr und Prof. Hopf generiert.
|
||||||
|
Bei Bedarf müssen wird dort nachfragen. Alte Daten gibt es
|
||||||
|
auch bei Prof. Hofmann.
|
||||||
|
|
||||||
|
## Neue Anforderungen
|
||||||
|
|
||||||
|
Nach dem Testbetrieb wurden einige neue Amforderungen
|
||||||
|
identifiziert:
|
||||||
|
|
||||||
|
- Anzeige von "Präsenz"-Pflicht in der
|
||||||
|
Dozentensicht (ob und wie dies in den Daten
|
||||||
|
enthalten ist, muss noch geklärt werden)
|
||||||
|
- Anzeige von "Pendler"-Eigenschaft in der
|
||||||
|
Dozentensicht (ob und wie dies in den Daten
|
||||||
|
enthalten ist, muss noch geklärt werden)
|
||||||
|
- Anzeige von ungeplanten Prüfungen
|
||||||
|
(also z.B. alternative Prüfungsformen während COVID)
|
||||||
|
(ob und wie dies in den Daten
|
||||||
|
enthalten ist, muss noch geklärt werden)
|
||||||
|
- Anzeige des Versionsstands der importierten Daten
|
||||||
|
(ob und wie dies in den Daten
|
||||||
|
enthalten ist, muss noch geklärt werden)
|
||||||
|
- Import der Daten auch von Kommandozeile
|
||||||
|
(nicht nur über Web-Upload)
|
||||||
|
- iCal-Export von Prüfungen
|
||||||
|
zum Import in den Kalender
|
||||||
|
- bei der Detailansicht einer Prüfung sollen
|
||||||
|
alle Prüfer, Räume und die jeweilig Anzahl
|
||||||
|
der Studierenden angezeigt werden.
|
||||||
|
(ob und wie dies in den Daten
|
||||||
|
enthalten ist, muss noch geklärt werden)
|
||||||
|
- Bessere Anzeige auf mobilen Geräten (responsive)
|
||||||
|
|
||||||
|
## ToDOs
|
||||||
|
|
||||||
|
- Klären der Datenschnittstelle
|
||||||
|
- Überarbeiten des Datenimports (neue Daten, Kommandozeile)
|
||||||
|
- Erweitern der Anzeige
|
||||||
|
* entweder in der bestehenden App
|
||||||
|
* oder in einer Single-Page-App
|
||||||
|
* oder beides
|
0
Backend/RestTest.http
Normal file
0
Backend/api/__init__.py
Normal file
576
Backend/api/serializer.py
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
import datetime
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||||||
|
from pruefplan_parser.models import JsonParser
|
||||||
|
from pruefplan_viewer.models import *
|
||||||
|
# from django.utils.encoding import smart_str
|
||||||
|
|
||||||
|
# Class to generate a UserToken with custom fields
|
||||||
|
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||||
|
@classmethod
|
||||||
|
def get_token(cls, user):
|
||||||
|
token = super().get_token(user) # Default Token
|
||||||
|
|
||||||
|
# Add custom informations to the Token
|
||||||
|
token["username"] = user.username
|
||||||
|
token["password"] = user.password
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
class API_ExamDataSet_ForStudentSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = API_ExamDataSet_ForStudent
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PruefPlan_JSON_Serializer(serializers.Serializer):
|
||||||
|
def create(self, validated_data):
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
if "array" not in data or not isinstance(data["array"], list):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"Das 'array'-Feld ist erforderlich und muss als Python-Liste verfügbar sein."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"dataArray": data["array"]} # Rückgabe des validierten Datenarrays
|
||||||
|
|
||||||
|
|
||||||
|
class Lecturer_JSON_Serializer(serializers.Serializer):
|
||||||
|
# Definiert die Felder, die der Serializer erwartet
|
||||||
|
revision = serializers.CharField(required=True)
|
||||||
|
array = serializers.ListField()
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
# Definieren der erwarteten Schlüssel im JSON-Daten
|
||||||
|
idKey = "kennung"
|
||||||
|
firstNameKey = "vorname"
|
||||||
|
lastNameKey = "nachname"
|
||||||
|
titleKey = "titel"
|
||||||
|
|
||||||
|
|
||||||
|
created_lecturers = [] # Liste zum Speichern der erstellten Lecturer-Objekte
|
||||||
|
try:
|
||||||
|
# Schleife über die Daten, um Lecturer-Objekte zu erstellen
|
||||||
|
for iteration_number, d in enumerate(validated_data["dataArray"]):
|
||||||
|
# Lecturer-Objekt erstellen und speichern
|
||||||
|
entry = Lecturer(
|
||||||
|
identification=d[idKey],
|
||||||
|
firstName=d[firstNameKey],
|
||||||
|
lastName=d[lastNameKey],
|
||||||
|
title=d[titleKey],
|
||||||
|
|
||||||
|
)
|
||||||
|
entry.save()
|
||||||
|
created_lecturers.append(entry) # Objekt der Liste hinzufügen
|
||||||
|
except KeyError as e:
|
||||||
|
# Fehlerbehandlung bei fehlenden Schlüsseln
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message) # Fehlermeldung speichern
|
||||||
|
except Exception as e:
|
||||||
|
# Allgemeine Fehlerbehandlung
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message) # Fehlermeldung speichern
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Serializer": "Lecturer",
|
||||||
|
"status": "success",
|
||||||
|
"created_count": len(created_lecturers),
|
||||||
|
} # Rückgabe des Erfolgsstatus
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
# Validierung, um sicherzustellen, dass 'revision' und 'array' Felder vorhanden sind
|
||||||
|
if "revision" not in data.keys():
|
||||||
|
raise serializers.ValidationError("Das 'revision'-Feld ist erforderlich.")
|
||||||
|
|
||||||
|
if "array" not in data or not isinstance(data["array"], list):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"Das 'array'-Feld ist erforderlich und muss als Python-Liste verfügbar sein."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"dataArray": data["array"]} # Rückgabe des validierten Datenarrays
|
||||||
|
|
||||||
|
|
||||||
|
class Attendance_JSON_Serializer(serializers.Serializer):
|
||||||
|
# Definiert die Felder, die der Serializer erwartet
|
||||||
|
revision = serializers.CharField(required=True)
|
||||||
|
array = serializers.ListField()
|
||||||
|
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
# Definieren der erwarteten Schlüssel im JSON-Daten
|
||||||
|
idKey = "dozentenkennung"
|
||||||
|
yearKey = "jahr"
|
||||||
|
monthKey = "monat"
|
||||||
|
dayKey = "tag"
|
||||||
|
hourKey = "stunde"
|
||||||
|
minuteKey = "minute"
|
||||||
|
weekdayKey = "wochentag"
|
||||||
|
dateKey = "termin"
|
||||||
|
|
||||||
|
created_attendences = (
|
||||||
|
[]
|
||||||
|
) # Liste zum Speichern der erstellten Attendance-Objekte
|
||||||
|
try:
|
||||||
|
# Schleife über die Daten, um Attendance-Objekte zu erstellen
|
||||||
|
for iteration_number, d in enumerate(validated_data["dataArray"]):
|
||||||
|
# Überprüfung, ob das 'termin'-Feld vorhanden ist
|
||||||
|
keyAvailable = "termin" in d
|
||||||
|
if not keyAvailable:
|
||||||
|
# Warnung ausgeben, wenn kein Termin zugewiesen wurde
|
||||||
|
template = "Warning. Following Exam was not assigned any examination date ['termin']: [{1}]"
|
||||||
|
message = template.format(d[idKey])
|
||||||
|
JsonParser.warningMessages.append(message)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Datum und Uhrzeit aus den Daten extrahieren
|
||||||
|
date = datetime.date(
|
||||||
|
d[dateKey][yearKey], d[dateKey][monthKey], d[dateKey][dayKey]
|
||||||
|
)
|
||||||
|
time = datetime.time(d[dateKey][hourKey], d[dateKey][minuteKey])
|
||||||
|
|
||||||
|
# Attendance-Objekt erstellen und speichern
|
||||||
|
entry = Attendance(
|
||||||
|
identification=d[idKey],
|
||||||
|
time=time,
|
||||||
|
date=date,
|
||||||
|
dateTimeText=d[dateKey]["datumText"],
|
||||||
|
weekday=d[dateKey]["wochentag"],
|
||||||
|
)
|
||||||
|
entry.save()
|
||||||
|
created_attendences.append(entry) # Objekt der Liste hinzufügen
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Dozent aus der Datenbank abrufen und mit der Anwesenheit verknüpfen
|
||||||
|
lecturer = Lecturer.objects.get(identification=d[idKey])
|
||||||
|
lecturer.attendance_set.add(
|
||||||
|
entry
|
||||||
|
) # Attendance dem Lecturer zuweisen
|
||||||
|
except Exception as e:
|
||||||
|
# Fehlerbehandlung, wenn der Dozent nicht gefunden wird
|
||||||
|
template = "Exception of type [{0}]. Arguments: {1!r}. Message: [{2}] entry might be missing"
|
||||||
|
message = template.format(type(e).__name__, e.args, idKey)
|
||||||
|
JsonParser.exceptionMessages.append(
|
||||||
|
message
|
||||||
|
) # Fehlermeldung speichern
|
||||||
|
|
||||||
|
except KeyError as e:
|
||||||
|
# Fehlerbehandlung bei fehlenden Schlüsseln
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message) # Fehlermeldung speichern
|
||||||
|
except Exception as e:
|
||||||
|
# Allgemeine Fehlerbehandlung
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message) # Fehlermeldung speichern
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Serializer": "Attendance",
|
||||||
|
"status": "success",
|
||||||
|
"created_count": len(created_attendences),
|
||||||
|
} # Rückgabe des Erfolgsstatus
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
# Validierung, um sicherzustellen, dass 'revision' und 'array' Felder vorhanden sind
|
||||||
|
if "revision" not in data.keys():
|
||||||
|
raise serializers.ValidationError("Das 'revision'-Feld ist erforderlich.")
|
||||||
|
|
||||||
|
if "array" not in data or not isinstance(data["array"], list):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"Das 'array'-Feld ist erforderlich und muss als Python-Liste verfügbar sein."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"dataArray": data["array"]} # Rückgabe des validierten Datenarrays
|
||||||
|
|
||||||
|
|
||||||
|
class Subject_JSON_Serializer(serializers.Serializer):
|
||||||
|
# Definiert die Felder, die der Serializer erwartet
|
||||||
|
revision = serializers.CharField(required=True)
|
||||||
|
array = serializers.ListField()
|
||||||
|
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
# Definieren der erwarteten Schlüssel im JSON-Daten
|
||||||
|
idKey = "kennung"
|
||||||
|
initialsKey = "kurztext"
|
||||||
|
nameKey = "langtext"
|
||||||
|
ohmIdKey = "fachkennungOhm"
|
||||||
|
|
||||||
|
created_subjects = [] # Liste zum Speichern der erstellten Subject-Objekte
|
||||||
|
try:
|
||||||
|
# Schleife über die Daten, um Subject-Objekte zu erstellen
|
||||||
|
for iteration_number, d in enumerate(validated_data["dataArray"]):
|
||||||
|
# Subject-Objekt erstellen und speichern
|
||||||
|
entry = Subject(
|
||||||
|
identification=d[idKey],
|
||||||
|
initials=d[ohmIdKey][initialsKey],
|
||||||
|
name=d[ohmIdKey][nameKey],
|
||||||
|
)
|
||||||
|
entry.save()
|
||||||
|
created_subjects.append(entry) # Objekt der Liste hinzufügen
|
||||||
|
|
||||||
|
except KeyError as e:
|
||||||
|
# Fehlerbehandlung bei fehlenden Schlüsseln
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message) # Fehlermeldung speichern
|
||||||
|
except Exception as e:
|
||||||
|
# Allgemeine Fehlerbehandlung
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message) # Fehlermeldung speichern
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Serializer": "Subject",
|
||||||
|
"status": "success",
|
||||||
|
"created_count": len(created_subjects),
|
||||||
|
} # Rückgabe des Erfolgsstatus
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
# Validierung, um sicherzustellen, dass 'revision' und 'array' Felder vorhanden sind
|
||||||
|
if "revision" not in data.keys():
|
||||||
|
raise serializers.ValidationError("Das 'revision'-Feld ist erforderlich.")
|
||||||
|
|
||||||
|
if "array" not in data or not isinstance(data["array"], list):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"Das 'array'-Feld ist erforderlich und muss als Python-Liste verfügbar sein."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"dataArray": data["array"]} # Rückgabe des validierten Datenarrays
|
||||||
|
|
||||||
|
|
||||||
|
class Exam_JSON_Serializer(serializers.Serializer):
|
||||||
|
# Define the fields expected by the serializer
|
||||||
|
revision = serializers.CharField(required=True)
|
||||||
|
array = serializers.ListField()
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
# Define the expected keys in the JSON data
|
||||||
|
idKey = "kennung"
|
||||||
|
yearKey = "jahr"
|
||||||
|
monthKey = "monat"
|
||||||
|
dayKey = "tag"
|
||||||
|
hourKey = "stunde"
|
||||||
|
minuteKey = "minute"
|
||||||
|
weekdayKey = "wochentag"
|
||||||
|
dateKey = "termin"
|
||||||
|
partialExamsKey = "teilpruefungen"
|
||||||
|
partialExamIdKey = "kennung"
|
||||||
|
numRegStudKey = "anzahlAnmeldungen"
|
||||||
|
subjectIdsKey = "fachkennungen"
|
||||||
|
examExecutionsKey = "pruefungsdurchfuehrungen"
|
||||||
|
lecturerIdKey = "dozentenkennung"
|
||||||
|
supervisorTypeKey = "typ"
|
||||||
|
locationKey = "raum"
|
||||||
|
|
||||||
|
created_exams = [] # List to store created Exam objects
|
||||||
|
created_partialexams = [] # List to store created Exam objects
|
||||||
|
ignored_Vorgezogen_exams = 0 # List to ignored Exam objects
|
||||||
|
ignored_KeinTermin_exams = 0 # List to ignored Exam objects
|
||||||
|
try:
|
||||||
|
# Iterate over the data to create Exam objects
|
||||||
|
for iteration_number, d in enumerate(validated_data["dataArray"]):
|
||||||
|
|
||||||
|
# Certain exam entries are ignored
|
||||||
|
if d["pruefungsart"] == "LN_VORGEZOGEN": # These entries can be ignored
|
||||||
|
ignored_Vorgezogen_exams += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
keyAvailable = "termin" in d
|
||||||
|
if not keyAvailable:
|
||||||
|
# Warning if no examination date is assigned
|
||||||
|
template = "Warning. Following Exam was not assigned any examination date ['termin']: [{0}]"
|
||||||
|
message = template.format(d[idKey])
|
||||||
|
JsonParser.warningMessages.append(message)
|
||||||
|
ignored_KeinTermin_exams += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Extract date and time for the exam
|
||||||
|
date = datetime.date(
|
||||||
|
d[dateKey][yearKey], d[dateKey][monthKey], d[dateKey][dayKey]
|
||||||
|
)
|
||||||
|
time = datetime.time(d[dateKey][hourKey], d[dateKey][minuteKey])
|
||||||
|
|
||||||
|
# Create and save a new Exam entry
|
||||||
|
examEntry = Exam(
|
||||||
|
identification=d[idKey],
|
||||||
|
date=date,
|
||||||
|
time=time,
|
||||||
|
weekday=d[dateKey][weekdayKey],
|
||||||
|
)
|
||||||
|
examEntry.save()
|
||||||
|
created_exams.append(examEntry)
|
||||||
|
|
||||||
|
# Create PartialExam entries and link them with the main exam
|
||||||
|
for teilpruef in d[partialExamsKey]:
|
||||||
|
partialExamId = teilpruef[partialExamIdKey]
|
||||||
|
regStudCount = teilpruef[numRegStudKey]
|
||||||
|
|
||||||
|
# Create and save a new PartialExam entry
|
||||||
|
partialExam = PartialExam(
|
||||||
|
identification=partialExamId, regStudCount=regStudCount
|
||||||
|
)
|
||||||
|
partialExam.save()
|
||||||
|
created_partialexams.append(partialExam)
|
||||||
|
|
||||||
|
# Link Exam and PartialExam (One-to-Many relationship)
|
||||||
|
examEntry.partialexam_set.add(partialExam)
|
||||||
|
|
||||||
|
# Link subjects to the PartialExams
|
||||||
|
for subId in teilpruef[subjectIdsKey]:
|
||||||
|
try:
|
||||||
|
# Check if the subject exists and link it
|
||||||
|
subject = Subject.objects.get(identification=subId)
|
||||||
|
partialExam.subjectIds.add(
|
||||||
|
subject
|
||||||
|
) # Many-to-Many relationship
|
||||||
|
except Exception as e:
|
||||||
|
# Log an error if the subject is not found
|
||||||
|
template = "Exception of type [{0}]. Arguments: {1!r}. Message: [{2}] entry might be missing"
|
||||||
|
message = template.format(type(e).__name__, e.args, subId)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
# Link exam executions to PartialExams
|
||||||
|
for pruefdurch in teilpruef[examExecutionsKey]:
|
||||||
|
lecId = pruefdurch[lecturerIdKey]
|
||||||
|
supervisorType = pruefdurch[supervisorTypeKey]
|
||||||
|
location = pruefdurch[locationKey]
|
||||||
|
|
||||||
|
# Create and save a new ExamExecution entry
|
||||||
|
examExecution = ExamExecution(
|
||||||
|
supervisorType=supervisorType, location=location
|
||||||
|
)
|
||||||
|
examExecution.save()
|
||||||
|
|
||||||
|
# Check if the lecturer exists and link the exam execution
|
||||||
|
try:
|
||||||
|
lecturer = Lecturer.objects.get(pk=lecId)
|
||||||
|
lecturer.examexecution_set.add(
|
||||||
|
examExecution
|
||||||
|
) # One-to-Many relationship
|
||||||
|
except Exception as e:
|
||||||
|
# Log an error if the lecturer is not found
|
||||||
|
template = "Exception of type [{0}]. Arguments: {1!r}. Message: [{2}] entry might be missing"
|
||||||
|
message = template.format(type(e).__name__, e.args, lecId)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
# Link subjects to the ExamExecution
|
||||||
|
for subId in pruefdurch[subjectIdsKey]:
|
||||||
|
try:
|
||||||
|
# Check if the subject exists and link it
|
||||||
|
subject = Subject.objects.get(identification=subId)
|
||||||
|
examExecution.subjectIds.add(
|
||||||
|
subject
|
||||||
|
) # Many-to-Many relationship
|
||||||
|
except Exception as e:
|
||||||
|
# Log an error if the subject is not found
|
||||||
|
template = "Exception of type [{0}]. Arguments: {1!r}. Message: [{2}] entry might be missing"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__, e.args, subId
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
# Link PartialExam and ExamExecution (One-to-Many relationship)
|
||||||
|
partialExam.examexecution_set.add(examExecution)
|
||||||
|
|
||||||
|
except KeyError as e:
|
||||||
|
# Error handling for missing keys in the dict
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
except Exception as e:
|
||||||
|
# General error handling
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Serializer": "Exam",
|
||||||
|
"status": "success",
|
||||||
|
"created_Exams": len(created_exams),
|
||||||
|
"created_PartialExams_count": len(created_partialexams),
|
||||||
|
"ignored_Exams -> Vorgezogen": ignored_Vorgezogen_exams,
|
||||||
|
"ignored_Exams -> Kein Termin": ignored_KeinTermin_exams,
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
# Validation for the 'revision' field
|
||||||
|
if "revision" not in data.keys():
|
||||||
|
raise serializers.ValidationError("The 'revision' field is required.")
|
||||||
|
|
||||||
|
# Validation for the 'array' field
|
||||||
|
if "array" not in data or not isinstance(data["array"], list):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"The 'array' field is required and must be a Python list."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"dataArray": data["array"]}
|
||||||
|
|
||||||
|
|
||||||
|
class RoomAllocation_JSON_Serializer(serializers.Serializer):
|
||||||
|
# Define the fields expected by the serializer
|
||||||
|
revision = serializers.CharField(required=True)
|
||||||
|
array = serializers.ListField()
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
# Define the expected keys in the JSON data
|
||||||
|
idKey = "kennungPruefung"
|
||||||
|
yearKey = "jahr"
|
||||||
|
monthKey = "monat"
|
||||||
|
dayKey = "tag"
|
||||||
|
hourKey = "stunde"
|
||||||
|
minuteKey = "minute"
|
||||||
|
weekdayKey = "wochentag"
|
||||||
|
locationKey = "raum"
|
||||||
|
dateKey = "termin"
|
||||||
|
matrikelKey = "matrikel"
|
||||||
|
|
||||||
|
created_roomAllocations = [] # List to store created StudentExam objects
|
||||||
|
try:
|
||||||
|
# Iterate over the data to create StudentExam objects
|
||||||
|
for iteration_number, d in enumerate(validated_data["dataArray"]):
|
||||||
|
# Extract date and time
|
||||||
|
date = datetime.date(
|
||||||
|
d[dateKey][yearKey], d[dateKey][monthKey], d[dateKey][dayKey]
|
||||||
|
)
|
||||||
|
time = datetime.time(d[dateKey][hourKey], d[dateKey][minuteKey])
|
||||||
|
|
||||||
|
# Find the corresponding Exam entry
|
||||||
|
exam = Exam.objects.filter(
|
||||||
|
identification=d[idKey], date=date, time=time
|
||||||
|
).first()
|
||||||
|
|
||||||
|
# Iterate through partial exams and their executions
|
||||||
|
partialExams = exam.partialexam_set.all()
|
||||||
|
for partialExam in partialExams:
|
||||||
|
examExecutions = partialExam.examexecution_set.all()
|
||||||
|
for examExecution in examExecutions:
|
||||||
|
# Check if the location matches and create StudentExam entry
|
||||||
|
if examExecution.location == d[locationKey]:
|
||||||
|
studentExamEntry = StudentExam(
|
||||||
|
examIdentification=d[idKey],
|
||||||
|
date=date,
|
||||||
|
time=time,
|
||||||
|
weekday=d[dateKey][weekdayKey],
|
||||||
|
location=d[locationKey],
|
||||||
|
examExecution=examExecution,
|
||||||
|
)
|
||||||
|
studentExamEntry.save()
|
||||||
|
created_roomAllocations.append(studentExamEntry)
|
||||||
|
|
||||||
|
# Create or update Student entries
|
||||||
|
for matrikel in d[matrikelKey]:
|
||||||
|
studentEntry = Student.objects.filter(matrikel=matrikel).exists()
|
||||||
|
if not studentEntry:
|
||||||
|
# Create a new Student entry if it doesn't exist
|
||||||
|
entry = Student(matrikel=matrikel)
|
||||||
|
entry.save()
|
||||||
|
else:
|
||||||
|
entry = Student.objects.all().filter(matrikel=matrikel).first()
|
||||||
|
entry.exams.add(studentExamEntry)
|
||||||
|
except KeyError as e:
|
||||||
|
# Error handling for missing keys in the dict
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
except Exception as e:
|
||||||
|
# General error handling
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Serializer": "RoomAllocation",
|
||||||
|
"status": "success",
|
||||||
|
"created_RoomAllocations": len(created_roomAllocations),
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
# Validation for the 'revision' field
|
||||||
|
if "revision" not in data.keys():
|
||||||
|
raise serializers.ValidationError("The 'revision' field is required.")
|
||||||
|
|
||||||
|
# Validation for the 'array' field
|
||||||
|
if "array" not in data or not isinstance(data["array"], list):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"The 'array' field is required and must be a Python list."
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"dataArray": data["array"]}
|
24
Backend/api/urls.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
from rest_framework_simplejwt.views import TokenRefreshView
|
||||||
|
from rest_framework.authtoken.views import obtain_auth_token
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
"user/token",
|
||||||
|
views.MyTokenObtainPairView.as_view(),
|
||||||
|
), # URL to classview fpr token interactions "as_view()"
|
||||||
|
path(
|
||||||
|
"user/token/refresh/", TokenRefreshView.as_view()
|
||||||
|
), # Lib view from rest_framework to refresh the given token
|
||||||
|
path("user/logoutUser", views.logoutUser),
|
||||||
|
path("user/checkUser", views.checkLoginStatus),
|
||||||
|
path("fetchExamData", views.getAPI_ExamDataSet_ForStudent),
|
||||||
|
path("pruefplanUpload/", views.PruefPlanUploader.as_view()),
|
||||||
|
path("api-token-auth/", obtain_auth_token),
|
||||||
|
path("addlecturer", views.addLecturer),
|
||||||
|
path("addAttendance", views.addAttendance),
|
||||||
|
path("addSubject", views.addSubject),
|
||||||
|
path("addExam", views.addExam),
|
||||||
|
path("addRoomAllocation", views.addRoomAllocation),
|
||||||
|
]
|
250
Backend/api/views.py
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
from datetime import date
|
||||||
|
import time
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from pruefplan_viewer.models import *
|
||||||
|
from .serializer import *
|
||||||
|
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.shortcuts import render
|
||||||
|
from api import serializer as api_serializer
|
||||||
|
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
|
||||||
|
# Create a REST API view class to interact with the custom token serializer class
|
||||||
|
class MyTokenObtainPairView(TokenObtainPairView):
|
||||||
|
# Setup the serializer -> using custom serializer
|
||||||
|
serializer_class = api_serializer.MyTokenObtainPairSerializer
|
||||||
|
|
||||||
|
# Func to handle a post request on this API endpoint
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
username = request.data.get("username")
|
||||||
|
password = request.data.get("password")
|
||||||
|
|
||||||
|
# User authentication using LADP agains the TH-Server -> Setup LADP in settings.py
|
||||||
|
user = authenticate(username=username, password=password)
|
||||||
|
if not user:
|
||||||
|
return Response({"error": "Invalid credentials"}, status=400)
|
||||||
|
|
||||||
|
# Login the user for Backend authentication
|
||||||
|
login(request=request, user=user)
|
||||||
|
|
||||||
|
# Delegate the post request to the TokenObptainPairView class
|
||||||
|
# Executing the standart jwt creation workflow -> using the custom serializer
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def logoutUser(request):
|
||||||
|
logout(request)
|
||||||
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
|
# Extract the location key and map it to a URL
|
||||||
|
def map_location(location_str):
|
||||||
|
URL_MAP = {
|
||||||
|
"B": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/b-standort-bahnhofstrasse/",
|
||||||
|
"F": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/f-standort-auf-aeg/",
|
||||||
|
"H": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/h-standort-hohfederstrasse/",
|
||||||
|
"K": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/k-standort-kesslerplatz/",
|
||||||
|
"NM": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/nm-standort-neumarkt-in-der-oberpfalz/",
|
||||||
|
"SC": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/sc-standort-innere-cramer-klett-strasse/",
|
||||||
|
"SG": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/sg-standort-karl-grillenberger-strasse/",
|
||||||
|
"SK": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/sk-standort-prof-ernst-nathan-strasse/",
|
||||||
|
"SP": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/sp-standort-kesslerstrasse/",
|
||||||
|
"SR": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/sr-standort-rednitzhembach/",
|
||||||
|
"W": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/w-standort-wassertorstrasse/",
|
||||||
|
"OL": "Online",
|
||||||
|
"MC": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/pruefungsstandort-mensa-insel-schuett/",
|
||||||
|
"Default": "https://www.th-nuernberg.de/wie-erreichen-sie-uns/anfahrt/",
|
||||||
|
}
|
||||||
|
if not location_str:
|
||||||
|
return URL_MAP["Default"] # Fallback URL if location is empty or None
|
||||||
|
|
||||||
|
location_key = location_str.split(".")[0] # Get the first part before '.'
|
||||||
|
|
||||||
|
# Find the matching key
|
||||||
|
for key in URL_MAP.keys():
|
||||||
|
if location_key.startswith(key):
|
||||||
|
return URL_MAP[key]
|
||||||
|
|
||||||
|
return URL_MAP["Default"] # If no match is found, return the default URL
|
||||||
|
|
||||||
|
|
||||||
|
def get_Subject(exam):
|
||||||
|
partialExam = PartialExam.objects.filter(identification=exam).first()
|
||||||
|
subjects = partialExam.subjectIds
|
||||||
|
result = [subject.name for subject in subjects]
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PruefPlanUploader(APIView):
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
timeStart = time.perf_counter()
|
||||||
|
JsonParser().clearTable()
|
||||||
|
dataSets = [dataSet for dataSet in request.data]
|
||||||
|
|
||||||
|
serializers = [
|
||||||
|
Lecturer_JSON_Serializer(data=dataSets[0]),
|
||||||
|
Attendance_JSON_Serializer(data=dataSets[1]),
|
||||||
|
Subject_JSON_Serializer(data=dataSets[2]),
|
||||||
|
Exam_JSON_Serializer(data=dataSets[3]),
|
||||||
|
RoomAllocation_JSON_Serializer(data=dataSets[4]),
|
||||||
|
]
|
||||||
|
|
||||||
|
respondList = [
|
||||||
|
serializer.save() for serializer in serializers if serializer.is_valid()
|
||||||
|
]
|
||||||
|
|
||||||
|
partial_delta = generate_ExamDataSet_ForStudent()
|
||||||
|
timeEnd = time.perf_counter()
|
||||||
|
|
||||||
|
total_delta = timeEnd - timeStart
|
||||||
|
respondList.append({"Uploadtime_in_Sekonds": {round(total_delta, ndigits=2)}})
|
||||||
|
respondList.append({"ExamDataSettime_in_Sekonds": {round(partial_delta, ndigits=2)}})
|
||||||
|
|
||||||
|
return Response(respondList)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["GET"])
|
||||||
|
def getAPI_ExamDataSet_ForStudent(request):
|
||||||
|
exam_data = API_ExamDataSet_ForStudent.objects.all()
|
||||||
|
|
||||||
|
# Serialize the data using the ModelSerializer
|
||||||
|
serializer = API_ExamDataSet_ForStudentSerializer(exam_data, many=True)
|
||||||
|
|
||||||
|
return Response(serializer.data) # This will return the data as JSON
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def pruefplanUpload(request):
|
||||||
|
|
||||||
|
timeStart = time.perf_counter()
|
||||||
|
dataSets = [dataSet for dataSet in request.data]
|
||||||
|
|
||||||
|
serializers = [
|
||||||
|
Lecturer_JSON_Serializer(data=dataSets[0]),
|
||||||
|
Attendance_JSON_Serializer(data=dataSets[1]),
|
||||||
|
Subject_JSON_Serializer(data=dataSets[2]),
|
||||||
|
Exam_JSON_Serializer(data=dataSets[3]),
|
||||||
|
RoomAllocation_JSON_Serializer(data=dataSets[4]),
|
||||||
|
]
|
||||||
|
respondList = [
|
||||||
|
serializer.save() for serializer in serializers if serializer.is_valid()
|
||||||
|
]
|
||||||
|
|
||||||
|
generate_ExamDataSet_ForStudent()
|
||||||
|
timeEnd = time.perf_counter()
|
||||||
|
|
||||||
|
delta = timeEnd - timeStart
|
||||||
|
print(f"Time for Upload: {round(delta)}s")
|
||||||
|
respondList.append(delta)
|
||||||
|
|
||||||
|
return Response(respondList)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ExamDataSet_ForStudent():
|
||||||
|
timeStart = time.perf_counter()
|
||||||
|
students = Student.objects.all()
|
||||||
|
|
||||||
|
for student in students:
|
||||||
|
for studentExam in student.exams.all():
|
||||||
|
|
||||||
|
examDataSet_ForStudent = API_ExamDataSet_ForStudent(
|
||||||
|
date=studentExam.date,
|
||||||
|
time=studentExam.time,
|
||||||
|
weekday=studentExam.weekday,
|
||||||
|
room=studentExam.location,
|
||||||
|
location=map_location(studentExam.location),
|
||||||
|
subjectIdent=list(
|
||||||
|
{
|
||||||
|
subject.name
|
||||||
|
for subject in studentExam.examExecution.partialExam.subjectIds.all()
|
||||||
|
}
|
||||||
|
)[0],
|
||||||
|
examIdent=studentExam.examIdentification,
|
||||||
|
examStudCount=studentExam.examExecution.partialExam.regStudCount,
|
||||||
|
supervisor=f"{studentExam.examExecution.lecturer.firstName} {studentExam.examExecution.lecturer.lastName}",
|
||||||
|
studMartNr=student.matrikel,
|
||||||
|
)
|
||||||
|
examDataSet_ForStudent.save()
|
||||||
|
|
||||||
|
timeEnd = time.perf_counter()
|
||||||
|
delta = timeEnd - timeStart
|
||||||
|
return delta
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def checkLoginStatus(request):
|
||||||
|
username = request.data["username"]
|
||||||
|
if User.objects.filter(username=username).exists():
|
||||||
|
usr = User.objects.get(username=username)
|
||||||
|
if usr.is_authenticated:
|
||||||
|
return Response(data="IsLoggedin")
|
||||||
|
else:
|
||||||
|
return Response(data="IsNotLoggedin")
|
||||||
|
else:
|
||||||
|
return Response(data="No user exist with this {} username.".format(username))
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def addLecturer(request):
|
||||||
|
serializer = Lecturer_JSON_Serializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
Lecturer_JASON_Serializer_Respond = (
|
||||||
|
serializer.save()
|
||||||
|
) # Hier wird der Rückgabewert von create() erfasst
|
||||||
|
# Erstelle eine Response basierend auf den erstellten Lecturer-Objekten
|
||||||
|
return Response(Lecturer_JASON_Serializer_Respond)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def addAttendance(request):
|
||||||
|
serializer = Attendance_JSON_Serializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
Attendance_JSON_Serializer_Respond = (
|
||||||
|
serializer.save()
|
||||||
|
) # Hier wird der Rückgabewert von create() erfasst
|
||||||
|
# Erstelle eine Response basierend auf den erstellten Lecturer-Objekten
|
||||||
|
return Response(Attendance_JSON_Serializer_Respond)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def addSubject(request):
|
||||||
|
serializer = Subject_JSON_Serializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
Subject_JASON_Serializer_Respond = (
|
||||||
|
serializer.save()
|
||||||
|
) # Hier wird der Rückgabewert von create() erfasst
|
||||||
|
# Erstelle eine Response basierend auf den erstellten Lecturer-Objekten
|
||||||
|
return Response(Subject_JASON_Serializer_Respond)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def addExam(request):
|
||||||
|
serializer = Exam_JSON_Serializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
Exam_JASON_Serializer_Respond = (
|
||||||
|
serializer.save()
|
||||||
|
) # Hier wird der Rückgabewert von create() erfasst
|
||||||
|
# Erstelle eine Response basierend auf den erstellten Lecturer-Objekten
|
||||||
|
return Response(Exam_JASON_Serializer_Respond)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def addRoomAllocation(request):
|
||||||
|
serializer = RoomAllocation_JSON_Serializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
RoomAllocation_JASON_Serializer_Respond = (
|
||||||
|
serializer.save()
|
||||||
|
) # Hier wird der Rückgabewert von create() erfasst
|
||||||
|
# Erstelle eine Response basierend auf den erstellten Lecturer-Objekten
|
||||||
|
return Response(RoomAllocation_JASON_Serializer_Respond)
|
0
Backend/authstuff/__init__.py
Normal file
3
Backend/authstuff/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
5
Backend/authstuff/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AuthStuffConfig(AppConfig):
|
||||||
|
name = 'authstuff'
|
167
Backend/authstuff/ldap_access.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import logging
|
||||||
|
import datetime
|
||||||
|
import pytz
|
||||||
|
import string
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from ldap3 import Server, Connection, ALL, NTLM, ALL_ATTRIBUTES
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils import timezone
|
||||||
|
from authstuff.models import ActiveDirectoryEntry
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
LOGIN_FIELD = "sAMAccountName"
|
||||||
|
EXPIRE_FIELD = "accountExpires"
|
||||||
|
DEPARTMENT_FIELD = "department"
|
||||||
|
GIVENNAME_FIELD = "givenName"
|
||||||
|
LASTNAME_FIELD = "sn"
|
||||||
|
ROLE_FIELD = "description"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateThread(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, connection, domain, delta):
|
||||||
|
super(UpdateThread, self).__init__(group=None)
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.connection = connection
|
||||||
|
self.domain = domain
|
||||||
|
self.delta = delta
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if has_to_update(self.delta):
|
||||||
|
try:
|
||||||
|
startchars = string.ascii_lowercase[:26] + "_"
|
||||||
|
for char in startchars:
|
||||||
|
self.logger.info('Update AD entries with leading {}'.format(char))
|
||||||
|
update_entries_with_leading_char(self.connection, self.domain, char)
|
||||||
|
time.sleep(5)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(str(e))
|
||||||
|
self.connection.unbind()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def last_update():
|
||||||
|
result = datetime.datetime.combine(datetime.date.min, datetime.time.min)
|
||||||
|
try:
|
||||||
|
entry = ActiveDirectoryEntry.objects.latest('modified')
|
||||||
|
result = entry.modified
|
||||||
|
except:
|
||||||
|
logger.debug('No last update found')
|
||||||
|
logger.info('Last update: {}'.format(result))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def has_to_update(delta):
|
||||||
|
deadline = timezone.now() - delta
|
||||||
|
deadline = deadline.replace(tzinfo=pytz.UTC)
|
||||||
|
last = last_update().replace(tzinfo=pytz.UTC)
|
||||||
|
result = settings.LDAP_FORCE_UPDATE or (last < deadline)
|
||||||
|
logger.info('Has to update: {}'.format(result))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def update_entries(user, pwd, delta):
|
||||||
|
dom = "ADS1"
|
||||||
|
conn = get_connection(user, pwd, dom)
|
||||||
|
update_entries_with_conn(conn, dom, delta)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def update_entries_with_conn(conn, dom, delta):
|
||||||
|
asyncThread = UpdateThread(conn, dom, delta)
|
||||||
|
asyncThread.start()
|
||||||
|
|
||||||
|
|
||||||
|
def update_entries_with_leading_char(conn, dom, char):
|
||||||
|
conn.search(
|
||||||
|
search_base='OU=users,OU=EFI,OU=Faculties,DC=' + dom + ',DC=fh-nuernberg,DC=de',
|
||||||
|
search_filter='(&(objectclass=user)(CN=' + char + '*))',
|
||||||
|
attributes=ALL_ATTRIBUTES)
|
||||||
|
logger.info('Found {} entries'.format(len(conn.entries)))
|
||||||
|
for entry in conn.entries:
|
||||||
|
try:
|
||||||
|
clear_and_update_entry(entry)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
|
||||||
|
def clear_and_update_entry(entry):
|
||||||
|
dic = parse_entry_dic(entry)
|
||||||
|
dic = clear_expire_date(dic)
|
||||||
|
dic = clear_department(dic)
|
||||||
|
dic = clear_description(dic)
|
||||||
|
update_entry(dic)
|
||||||
|
|
||||||
|
|
||||||
|
def update_entry(entry_dic):
|
||||||
|
login = entry_dic[LOGIN_FIELD]
|
||||||
|
try:
|
||||||
|
entry = ActiveDirectoryEntry.objects.get(canonicalName=login)
|
||||||
|
logger.debug('Update entry {}'.format(login))
|
||||||
|
except:
|
||||||
|
logger.debug('New entry {}'.format(login))
|
||||||
|
entry = ActiveDirectoryEntry()
|
||||||
|
entry.canonicalName = entry_dic[LOGIN_FIELD]
|
||||||
|
entry.accountExpires = entry_dic[EXPIRE_FIELD]
|
||||||
|
entry.department = entry_dic[DEPARTMENT_FIELD]
|
||||||
|
entry.givenName = entry_dic[GIVENNAME_FIELD]
|
||||||
|
entry.lastName = entry_dic[LASTNAME_FIELD]
|
||||||
|
entry.role = entry_dic[ROLE_FIELD]
|
||||||
|
entry.modified = timezone.now()
|
||||||
|
logger.debug(entry)
|
||||||
|
try:
|
||||||
|
entry.full_clean()
|
||||||
|
except ValidationError as e:
|
||||||
|
logger.warning('AD entry not valid: {}'.format(e.message_dict))
|
||||||
|
entry.save()
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection(user, pwd, dom):
|
||||||
|
server = Server('gso1.ads1.fh-nuernberg.de', get_info=ALL)
|
||||||
|
conn = Connection(
|
||||||
|
server,
|
||||||
|
user=dom + "\\" + user,
|
||||||
|
password=pwd,
|
||||||
|
authentication=NTLM)
|
||||||
|
conn.bind()
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def parse_entry_dic(entry):
|
||||||
|
def value_of_field(f):
|
||||||
|
try:
|
||||||
|
return entry[f].value
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
fieldNames = [LOGIN_FIELD, EXPIRE_FIELD, DEPARTMENT_FIELD, GIVENNAME_FIELD, LASTNAME_FIELD, ROLE_FIELD]
|
||||||
|
fields = map(value_of_field, fieldNames )
|
||||||
|
return dict(zip(fieldNames, fields))
|
||||||
|
|
||||||
|
|
||||||
|
def clear_expire_date(dic):
|
||||||
|
if dic[EXPIRE_FIELD] is not None:
|
||||||
|
expire_datetime = dic[EXPIRE_FIELD].replace(tzinfo=pytz.UTC)
|
||||||
|
else:
|
||||||
|
expire_datetime = datetime.datetime(datetime.MAXYEAR, 12, 31, tzinfo=pytz.UTC)
|
||||||
|
if expire_datetime.year == datetime.MAXYEAR:
|
||||||
|
expire_datetime = expire_datetime.replace(year=2099, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
dic[EXPIRE_FIELD] = expire_datetime
|
||||||
|
return dic
|
||||||
|
|
||||||
|
def clear_department(dic):
|
||||||
|
dic[DEPARTMENT_FIELD] = string_adjust(dic[DEPARTMENT_FIELD])
|
||||||
|
return dic
|
||||||
|
|
||||||
|
def clear_description(dic):
|
||||||
|
dic[ROLE_FIELD] = string_adjust(dic[ROLE_FIELD])
|
||||||
|
return dic
|
||||||
|
|
||||||
|
|
||||||
|
def string_adjust(s):
|
||||||
|
if s is not None:
|
||||||
|
return s[:32]
|
||||||
|
else:
|
||||||
|
return "N.N."
|
28
Backend/authstuff/migrations/0001_initial.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 2.1.4 on 2018-12-10 11:55
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ActiveDirectoryEntry',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(editable=False)),
|
||||||
|
('modified', models.DateTimeField()),
|
||||||
|
('accountExpires', models.DateTimeField()),
|
||||||
|
('canonicalName', models.CharField(max_length=32)),
|
||||||
|
('department', models.CharField(max_length=32)),
|
||||||
|
('givenName', models.CharField(max_length=32)),
|
||||||
|
('lastName', models.CharField(max_length=32)),
|
||||||
|
('role', models.CharField(max_length=32)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
Backend/authstuff/migrations/__init__.py
Normal file
25
Backend/authstuff/models.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
class ActiveDirectoryEntry(models.Model):
|
||||||
|
# id = models.CharField(primary_key=True, max_length=64)
|
||||||
|
# created = models.DateTimeField(auto_now_add=True)
|
||||||
|
# modified = models.DateTimeField(auto_now=True)
|
||||||
|
created = models.DateTimeField(editable=False)
|
||||||
|
modified = models.DateTimeField()
|
||||||
|
accountExpires = models.DateTimeField()
|
||||||
|
canonicalName = models.CharField(max_length=32) #sAMAccountName
|
||||||
|
department = models.CharField(max_length=32)
|
||||||
|
givenName = models.CharField(max_length=32)
|
||||||
|
lastName = models.CharField(max_length=32) #sn
|
||||||
|
role = models.CharField(max_length=32) #description
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
''' On save, update timestamps '''
|
||||||
|
if not self.id:
|
||||||
|
self.created = timezone.now()
|
||||||
|
self.modified = timezone.now()
|
||||||
|
return super(ActiveDirectoryEntry, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "AD-Entry %s (%s %s)" % (self.canonicalName, self.givenName, self.lastName)
|
0
Backend/authstuff/templatetags/__init__.py
Normal file
9
Backend/authstuff/templatetags/auth_tags.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django import template
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter(name='has_group')
|
||||||
|
def has_group(user, group_name):
|
||||||
|
group = Group.objects.get(name=group_name)
|
||||||
|
return True if group in user.groups.all() else False
|
17
Backend/authstuff/tests.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
import authstuff.ldap_access
|
||||||
|
import datetime
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveDirTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_ad(self):
|
||||||
|
delta = datetime.timedelta() # datetime.timedelta(days=2)
|
||||||
|
last_update = authstuff.ldap_access.last_update()
|
||||||
|
authstuff.ldap_access.update_entries(secrets.TEST_AD_USER, secrets.TEST_AD_PASSWORD, delta)
|
||||||
|
self.assertGreater(authstuff.ldap_access.last_update(), last_update)
|
||||||
|
|
20
Backend/authstuff/views.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import logging
|
||||||
|
from authstuff.models import ActiveDirectoryEntry
|
||||||
|
from django.http import JsonResponse
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def detail(request, login):
|
||||||
|
result = {'canonicalName' : login, 'found' : False}
|
||||||
|
try:
|
||||||
|
addata = ActiveDirectoryEntry.objects.get(canonicalName=login)
|
||||||
|
result['found'] = True
|
||||||
|
result['role'] = addata.role
|
||||||
|
result['updated'] = addata.modified
|
||||||
|
except ActiveDirectoryEntry.DoesNotExist:
|
||||||
|
pass
|
||||||
|
response = JsonResponse(result)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
22
Backend/manage.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "medinf.settings")
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError:
|
||||||
|
# The above import may fail for some other reason. Ensure that the
|
||||||
|
# issue is really that Django is missing to avoid masking other
|
||||||
|
# exceptions on Python 2.
|
||||||
|
try:
|
||||||
|
import django
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
execute_from_command_line(sys.argv)
|
36
Backend/medinf/ReadUserData.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import getpass
|
||||||
|
from ldap3 import Server, Connection, ALL, NTLM, ALL_ATTRIBUTES
|
||||||
|
|
||||||
|
print("Authentifizierung gegen ein Active Directory")
|
||||||
|
|
||||||
|
|
||||||
|
# Eingaben
|
||||||
|
dom = input("Domain [ADS1]:") or "ADS1"
|
||||||
|
user = input("User [hofmannol]:") or "hofmannol"
|
||||||
|
print('Password:', end='')
|
||||||
|
pwd = getpass.getpass()
|
||||||
|
|
||||||
|
# Binden an das AD
|
||||||
|
server = Server('gso1.ads1.fh-nuernberg.de', get_info=ALL)
|
||||||
|
conn = Connection(
|
||||||
|
server,
|
||||||
|
user=dom+"\\"+user,
|
||||||
|
password=pwd,
|
||||||
|
authentication=NTLM)
|
||||||
|
|
||||||
|
conn.bind()
|
||||||
|
print(conn)
|
||||||
|
print(conn.extend.standard.who_am_i())
|
||||||
|
print(conn.bound)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Suche nach dem gerade verbundenen User
|
||||||
|
conn.search(
|
||||||
|
search_base='DC='+dom+',DC=fh-nuernberg,DC=de',
|
||||||
|
search_filter='(&(objectclass=user)(CN='+user+'))',
|
||||||
|
attributes=ALL_ATTRIBUTES)
|
||||||
|
print(conn.entries[0])
|
||||||
|
|
||||||
|
|
||||||
|
conn.unbind()
|
0
Backend/medinf/__init__.py
Normal file
4
Backend/medinf/context_processors.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from django.conf import settings # import the settings file
|
||||||
|
|
||||||
|
def logout_redirect(request):
|
||||||
|
return {'LOGOUT_REDIRECT_URL': settings.LOGOUT_REDIRECT_URL}
|
1
Backend/medinf/fixtures/READ.ME
Normal file
@ -0,0 +1 @@
|
|||||||
|
loaddata medinf/fixtures/groups.json
|
30
Backend/medinf/fixtures/groups.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "auth.group",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"name": "PP_ADMIN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "auth.group",
|
||||||
|
"pk": 10,
|
||||||
|
"fields": {
|
||||||
|
"name": "STUD_VIEW"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "auth.group",
|
||||||
|
"pk": 11,
|
||||||
|
"fields": {
|
||||||
|
"name": "STUD_VIEW_EXTENDED"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "auth.group",
|
||||||
|
"pk": 12,
|
||||||
|
"fields": {
|
||||||
|
"name": "STUD_ADMIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
92
Backend/medinf/ldap_backend.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
import datetime
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from ldap3 import Server, Connection, ALL, NTLM, ALL_ATTRIBUTES
|
||||||
|
from ldap3.core.exceptions import LDAPSocketOpenError
|
||||||
|
from django.conf import settings
|
||||||
|
from authstuff.ldap_access import update_entries_with_conn
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LdapBackend(object):
|
||||||
|
"""
|
||||||
|
Authenticate against a LDAP directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def check_login(self, username, password):
|
||||||
|
self.log.info("check login {0}".format(username))
|
||||||
|
server = Server(settings.LDAP_SERVER, connect_timeout=8)
|
||||||
|
qualified_user = settings.LDAP_DOMAIN + '\\' + username
|
||||||
|
|
||||||
|
conn = Connection(server, qualified_user, password=password, authentication=NTLM)
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn.bind()
|
||||||
|
except LDAPSocketOpenError:
|
||||||
|
# LDAP Server nicht erreichbar
|
||||||
|
self.log.warning("LDAP check_login: Server " + settings.LDAP_SERVER + " not reachable.")
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
var = traceback.format_exc()
|
||||||
|
self.log.info("LDAP check_login(bind): Unexpected Error %s" % var)
|
||||||
|
return None
|
||||||
|
result = None
|
||||||
|
|
||||||
|
self.log.info("Bind successful")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if conn.extend.standard.who_am_i() != None:
|
||||||
|
conn.search(
|
||||||
|
search_base='DC=' + settings.LDAP_DOMAIN + ',DC=fh-nuernberg,DC=de',
|
||||||
|
search_filter='(&(objectclass=user)(CN=' + username + '))', attributes=ALL_ATTRIBUTES)
|
||||||
|
info = conn.entries[0]
|
||||||
|
result = {'lastname' : str(info.sn),
|
||||||
|
'givenname' : str(info.givenName),
|
||||||
|
'login' : str(info.cn),
|
||||||
|
'department' : str(info.department),
|
||||||
|
'role' : str(info.description)}
|
||||||
|
self.log.info("LDAP check_login: %s" % result)
|
||||||
|
except:
|
||||||
|
var = traceback.format_exc()
|
||||||
|
self.log.warning("LDAP check_login: Unexpected Error %s" % var)
|
||||||
|
|
||||||
|
try:
|
||||||
|
delta = datetime.timedelta(days=settings.LDAP_UPDATE_TIMEOUT_DAYS)
|
||||||
|
update_entries_with_conn(conn, settings.LDAP_DOMAIN, delta)
|
||||||
|
except:
|
||||||
|
var = traceback.format_exc()
|
||||||
|
self.log.warning("LDAP check_login: Update AD Entries failed %s" % var)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate(self, request, username=None, password=None):
|
||||||
|
ldap_user = self.check_login(username,password)
|
||||||
|
if ldap_user:
|
||||||
|
# {'lastname': 'Hofmann', 'givenname': 'Oliver', 'login': 'hofmannol', 'department': 'EFI', 'role': 'PF'}
|
||||||
|
# {'lastname': 'Wimmer', 'givenname': 'Martin', 'login': 'wimmerma', 'department': 'EFI', 'role': 'MA'}
|
||||||
|
# {'lastname': 'Mueller', 'givenname': 'Vincent', 'login': 'muellervi56608', 'department': 'EFI', 'role': 'ST'}
|
||||||
|
# {'lastname': 'Poehlau', 'givenname': 'Frank', 'login': 'poehlaufr', 'department': 'EFI', 'role': 'PF'}
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=ldap_user['login'])
|
||||||
|
except User.DoesNotExist:
|
||||||
|
self.log.info("LDAP authenticate: create new user %s" % ldap_user['login'])
|
||||||
|
user = User(username=ldap_user['login'])
|
||||||
|
user.first_name = ldap_user['givenname']
|
||||||
|
user.last_name = ldap_user['lastname']
|
||||||
|
user.is_staff = (ldap_user['role'] != 'ST')
|
||||||
|
user.is_superuser = False
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
try:
|
||||||
|
return User.objects.get(pk=user_id)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
306
Backend/medinf/settings.py
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
"""
|
||||||
|
Django settings for medinf project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 1.11.2.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.11/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/1.11/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# Development or Production
|
||||||
|
r = re.search(r"^172.17", socket.gethostbyname(socket.gethostname()))
|
||||||
|
DEVELOPMENT = r == None
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = DEVELOPMENT
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# Log Configuration
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
LOGLEVEL = "DEBUG"
|
||||||
|
else:
|
||||||
|
LOGLEVEL = "INFO"
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"formatters": {
|
||||||
|
"standard": {
|
||||||
|
"format": "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
|
||||||
|
"datefmt": "%d/%b/%Y %H:%M:%S",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"null": {
|
||||||
|
"level": "DEBUG",
|
||||||
|
"class": "logging.NullHandler",
|
||||||
|
},
|
||||||
|
"logfile": {
|
||||||
|
"level": "DEBUG",
|
||||||
|
"class": "logging.handlers.RotatingFileHandler",
|
||||||
|
"filename": os.path.join(BASE_DIR, "log/log.txt"),
|
||||||
|
"maxBytes": 1024 * 1024 * 5,
|
||||||
|
"backupCount": 5,
|
||||||
|
"formatter": "standard",
|
||||||
|
},
|
||||||
|
"console": {
|
||||||
|
"level": "DEBUG",
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "standard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"": {
|
||||||
|
"level": "WARNING",
|
||||||
|
"handlers": ["logfile"],
|
||||||
|
},
|
||||||
|
"django.db.backends": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": "INFO", # set DEBUG if needed
|
||||||
|
"propagate": False,
|
||||||
|
},
|
||||||
|
"medinf": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": LOGLEVEL,
|
||||||
|
"propagate": True,
|
||||||
|
},
|
||||||
|
"studis": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": LOGLEVEL,
|
||||||
|
"propagate": True,
|
||||||
|
},
|
||||||
|
"authstuff": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": "INFO",
|
||||||
|
"propagate": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "XXXXXn!i2lx(&)gq7l!art_pr	et*$r)r&ogu&j-+wm0^ni5"
|
||||||
|
|
||||||
|
|
||||||
|
USE_X_FORWARDED_HOST = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
"medinf.efi.th-nuernberg.de",
|
||||||
|
"api.efi.th-nuernberg.de",
|
||||||
|
"localhost",
|
||||||
|
"127.0.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
# Custom Apps
|
||||||
|
"authstuff.apps.AuthStuffConfig",
|
||||||
|
"pruefplan_viewer.apps.PruefplanViewerConfig",
|
||||||
|
"pruefplan_parser.apps.PruefplanParserConfig",
|
||||||
|
# Third Party Apps
|
||||||
|
"rest_framework",
|
||||||
|
"rest_framework_simplejwt.token_blacklist",
|
||||||
|
"rest_framework.authtoken",
|
||||||
|
"corsheaders",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "medinf.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
"medinf.context_processors.logout_redirect",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "medinf.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||||
|
|
||||||
|
if DEVELOPMENT:
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.mysql",
|
||||||
|
"NAME": "django-app",
|
||||||
|
"USER": "django-app",
|
||||||
|
"PASSWORD": "8TFXHv9X",
|
||||||
|
"HOST": "mysql",
|
||||||
|
"PORT": "3306",
|
||||||
|
"OPTIONS": {"init_command": "SET sql_mode='STRICT_TRANS_TABLES'"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "de-de"
|
||||||
|
|
||||||
|
TIME_ZONE = "Europe/Berlin"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||||
|
|
||||||
|
|
||||||
|
# Nach dem Deployment werden die statischen Inhalte von medinf geholt.
|
||||||
|
# Sie müssen in das Verzeichnis <HTMLROOT>/static kopiert werden
|
||||||
|
|
||||||
|
|
||||||
|
STATIC_ROOT = os.path.join("/tmp", "static")
|
||||||
|
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(BASE_DIR, "static"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Konfiguration des Auth-Systems
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
LDAP_DOMAIN = "ADS1"
|
||||||
|
LDAP_SERVER = "gso1.ads1.fh-nuernberg.de"
|
||||||
|
LDAP_FORCE_UPDATE = False
|
||||||
|
LDAP_UPDATE_TIMEOUT_DAYS = 2
|
||||||
|
|
||||||
|
if DEVELOPMENT:
|
||||||
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
LOGOUT_REDIRECT_URL = "/"
|
||||||
|
LOGIN_URL = "/accounts/login/"
|
||||||
|
else:
|
||||||
|
LOGIN_REDIRECT_URL = "/app/"
|
||||||
|
LOGOUT_REDIRECT_URL = "/app/"
|
||||||
|
LOGIN_URL = "/app/accounts/login/"
|
||||||
|
|
||||||
|
|
||||||
|
if DEVELOPMENT:
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
"medinf.ldap_backend.LdapBackend",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Config the JWT Token with static settings -> from docs
|
||||||
|
# https://django-rest-framework-simplejwt.readthedocs.io/en/latest/
|
||||||
|
SIMPLE_JWT = {
|
||||||
|
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=15),
|
||||||
|
"REFRESH_TOKEN_LIFETIME": timedelta(days=50),
|
||||||
|
"ROTATE_REFRESH_TOKENS": True,
|
||||||
|
"BLACKLIST_AFTER_ROTATION": True,
|
||||||
|
"UPDATE_LAST_LOGIN": False,
|
||||||
|
"ALGORITHM": "HS256",
|
||||||
|
"VERIFYING_KEY": None,
|
||||||
|
"AUDIENCE": None,
|
||||||
|
"ISSUER": None,
|
||||||
|
"JWK_URL": None,
|
||||||
|
"LEEWAY": 0,
|
||||||
|
"AUTH_HEADER_TYPES": ("Bearer",),
|
||||||
|
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
|
||||||
|
"USER_ID_FIELD": "id",
|
||||||
|
"USER_ID_CLAIM": "user_id",
|
||||||
|
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
|
||||||
|
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
|
||||||
|
"TOKEN_TYPE_CLAIM": "token_type",
|
||||||
|
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
|
||||||
|
"JTI_CLAIM": "jti",
|
||||||
|
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
|
||||||
|
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
|
||||||
|
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set coresheader to allow all origin
|
||||||
|
CORS_ALLOW_ALL_ORIGINS = True
|
||||||
|
|
||||||
|
# Konfiguration Prüfungsplan
|
||||||
|
PP_UPLOAD_DIR = os.path.join(BASE_DIR, "pruefplan_parser", "data")
|
||||||
|
PP_TEACHER_FILE = os.path.join(PP_UPLOAD_DIR, "Dozenten.json")
|
||||||
|
PP_SUBJECT_FILE = os.path.join(PP_UPLOAD_DIR, "Faecher.json")
|
||||||
|
PP_ATTENDANCE_FILE = os.path.join(PP_UPLOAD_DIR, "Praesenzen.json")
|
||||||
|
PP_EXAM_FILE = os.path.join(PP_UPLOAD_DIR, "Pruefungen.json")
|
||||||
|
PP_STUDENT_FILE = os.path.join(PP_UPLOAD_DIR, "Saaleinteilung.json")
|
37
Backend/medinf/urls.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
"""medinf URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/1.11/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.conf.urls import url, include
|
||||||
|
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf.urls import include
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
import medinf.views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", TemplateView.as_view(template_name="index.html")),
|
||||||
|
path("navlogin/", medinf.views.navlogin, name="navlogin"),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("accounts/", include("django.contrib.auth.urls")),
|
||||||
|
path("pruefplan_viewer/", include("pruefplan_viewer.urls")),
|
||||||
|
path("pruefplan_parser/", include("pruefplan_parser.urls")),
|
||||||
|
path("api/", include("api.urls")), # Link the api urls to the main access point
|
||||||
|
path(
|
||||||
|
"authenticate/",
|
||||||
|
medinf.views.AuthenticateView.as_view(),
|
||||||
|
name="authenticateView",
|
||||||
|
), # REST call to authenticate a user in the backend
|
||||||
|
]
|
59
Backend/medinf/views.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from yaml import serialize
|
||||||
|
from api.serializer import MyTokenObtainPairSerializer
|
||||||
|
from api.views import MyTokenObtainPairView
|
||||||
|
import medinf.settings
|
||||||
|
import logging
|
||||||
|
import medinf.ldap_backend
|
||||||
|
|
||||||
|
|
||||||
|
def navlogin(request):
|
||||||
|
log = logging.getLogger("medinf")
|
||||||
|
logout(request)
|
||||||
|
error = ""
|
||||||
|
if request.POST:
|
||||||
|
username = request.POST.get("username", "?")
|
||||||
|
password = request.POST.get("password", "?")
|
||||||
|
|
||||||
|
user = authenticate(username=username, password=password)
|
||||||
|
if user is not None:
|
||||||
|
if user.is_active:
|
||||||
|
login(request, user)
|
||||||
|
return redirect(medinf.settings.LOGIN_REDIRECT_URL)
|
||||||
|
else:
|
||||||
|
log.info("Inactive user {} tried to login".format(username))
|
||||||
|
error = "Ihre Benutzerkennung wurde deaktiviert."
|
||||||
|
else:
|
||||||
|
log.info("Login failed for {}".format(username))
|
||||||
|
error = "Benutzername oder Kennwort falsch."
|
||||||
|
context = {"error": error}
|
||||||
|
return render(request, "index.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
################ New REST Token Auth #########################################
|
||||||
|
|
||||||
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticateView(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
username = request.data.get("username")
|
||||||
|
password = request.data.get("password")
|
||||||
|
|
||||||
|
|
||||||
|
user = authenticate(request, username=username, password=password)
|
||||||
|
if user is not None:
|
||||||
|
login(request, user) #login the user in the backend
|
||||||
|
# refresh = RefreshToken.for_user(user)
|
||||||
|
serializer = MyTokenObtainPairSerializer(data={"username":username, "password":password})
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
token = serializer.validated_data
|
||||||
|
|
||||||
|
return Response(token)
|
||||||
|
|
||||||
|
return Response({"error": "Invalid credentials"}, status=400)
|
21
Backend/medinf/wsgi.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for medinf project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import medinf.settings
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "medinf.settings")
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.info("Starting in %s Environment" % ('DEV' if medinf.settings.DEVELOPMENT else 'PROD'))
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
|
|
0
Backend/pruefplan_parser/__init__.py
Normal file
3
Backend/pruefplan_parser/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
5
Backend/pruefplan_parser/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PruefplanParserConfig(AppConfig):
|
||||||
|
name = 'pruefplan_parser'
|
229
Backend/pruefplan_parser/data/Dozenten.json
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
{
|
||||||
|
"revision": "2648:2829M",
|
||||||
|
"array": [
|
||||||
|
{
|
||||||
|
"kennung": "259",
|
||||||
|
"vorname": "Christine",
|
||||||
|
"nachname": "Rademacher",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "AMP",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "315",
|
||||||
|
"vorname": "J\u00c3\u00b6rg",
|
||||||
|
"nachname": "Steinbach",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "AMP",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "385",
|
||||||
|
"vorname": "Peter",
|
||||||
|
"nachname": "Jonas",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "AMP",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "71655",
|
||||||
|
"vorname": "Melanie",
|
||||||
|
"nachname": "Basting",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "AMP",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "103",
|
||||||
|
"vorname": "Thomas",
|
||||||
|
"nachname": "Giesler",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "70901",
|
||||||
|
"vorname": "Sven",
|
||||||
|
"nachname": "Loquai",
|
||||||
|
"titel": "Prof. Dr.-Ing.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "166",
|
||||||
|
"vorname": "Bernd",
|
||||||
|
"nachname": "Klehn",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "382",
|
||||||
|
"vorname": "Michael",
|
||||||
|
"nachname": "Zwanger",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "74090",
|
||||||
|
"vorname": "Olga",
|
||||||
|
"nachname": "Battermann",
|
||||||
|
"titel": "Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "73247",
|
||||||
|
"vorname": "Michael",
|
||||||
|
"nachname": "Mayle",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "AMP",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "316",
|
||||||
|
"vorname": "Florian",
|
||||||
|
"nachname": "Steinmeyer",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "AMP",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "49",
|
||||||
|
"vorname": "Bernd",
|
||||||
|
"nachname": "Braun",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "AMP",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "73630",
|
||||||
|
"vorname": "Peter",
|
||||||
|
"nachname": "Eberhardt",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "576",
|
||||||
|
"vorname": "J\u00c3\u00bcrgen",
|
||||||
|
"nachname": "Krumm",
|
||||||
|
"titel": "Prof.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "219",
|
||||||
|
"vorname": "Stefan",
|
||||||
|
"nachname": "May",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "75398",
|
||||||
|
"vorname": "Christian",
|
||||||
|
"nachname": "Pfitzner",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "508",
|
||||||
|
"vorname": "Wolfgang",
|
||||||
|
"nachname": "M\u00c3\u00b6nch",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "585",
|
||||||
|
"vorname": "Rainer",
|
||||||
|
"nachname": "Engelbrecht",
|
||||||
|
"titel": "Prof. Dr.-Ing.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "73998",
|
||||||
|
"vorname": "Stefan",
|
||||||
|
"nachname": "Wenhardt",
|
||||||
|
"titel": "",
|
||||||
|
"fakultaet": "AMP",
|
||||||
|
"istLehrbeauftragter": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "74065",
|
||||||
|
"vorname": "Matthias",
|
||||||
|
"nachname": "Gleich",
|
||||||
|
"titel": "",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "376",
|
||||||
|
"vorname": "Olaf",
|
||||||
|
"nachname": "Ziemann",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "350",
|
||||||
|
"vorname": "Sebastian",
|
||||||
|
"nachname": "Walter",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "75807",
|
||||||
|
"vorname": "Andreas",
|
||||||
|
"nachname": "Stute",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "492",
|
||||||
|
"vorname": "G\u00c3\u00bcnter",
|
||||||
|
"nachname": "Kie\u00c3\u0178ling",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "84399",
|
||||||
|
"vorname": "Anja",
|
||||||
|
"nachname": "Freudenreich",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "38496",
|
||||||
|
"vorname": "Jan",
|
||||||
|
"nachname": "Paulus",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "62711",
|
||||||
|
"vorname": "Hritam",
|
||||||
|
"nachname": "Dutta",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "73323",
|
||||||
|
"vorname": "Enrico",
|
||||||
|
"nachname": "Schr\u00c3\u00b6der;",
|
||||||
|
"titel": "Prof. Dr.",
|
||||||
|
"fakultaet": "EFI",
|
||||||
|
"istLehrbeauftragter": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1121
Backend/pruefplan_parser/data/Faecher.json
Normal file
161
Backend/pruefplan_parser/data/Praesenzen.json
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
{
|
||||||
|
"revision": "2648:2829M",
|
||||||
|
"array": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "103",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "22.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 22,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "MONTAG",
|
||||||
|
"pruefungswoche": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "70901",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "11.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 11,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "DONNERSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "166",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "16.07.2024 16:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 16,
|
||||||
|
"stunde": 16,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "DIENSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "382",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "16.07.2024 14:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 16,
|
||||||
|
"stunde": 14,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DIENSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "576",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "03.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 3,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "75398",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "09.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 9,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DIENSTAG",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "508",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "13.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 13,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "SAMSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "585",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "13.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 13,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "SAMSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "492",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "19.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 19,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "FREITAG",
|
||||||
|
"pruefungswoche": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "84399",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "26.07.2024 16:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 26,
|
||||||
|
"stunde": 16,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "FREITAG",
|
||||||
|
"pruefungswoche": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "38496",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "05.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 5,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "FREITAG",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "62711",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "04.07.2024 14:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 4,
|
||||||
|
"stunde": 14,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DONNERSTAG",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
803
Backend/pruefplan_parser/data/Pruefungen.json
Normal file
@ -0,0 +1,803 @@
|
|||||||
|
{
|
||||||
|
"revision": "2648:2829M",
|
||||||
|
"array": [
|
||||||
|
{
|
||||||
|
"kennung": "M1:SU:BEI:1",
|
||||||
|
"pruefungsart": "LN_STUDIENBEGLEITEND",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "10.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 10,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "M1:SU:BEI:1",
|
||||||
|
"anzahlAnmeldungen": 63,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:1110"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "259",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "BB.103",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:1110"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "315",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.450",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:1110"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "385",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "BB.111",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:1110"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "71655",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "BB.103",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:1110"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "BMED:20141:5150:\"103,166\"_BMED:20141:5150:\"70901\"_BMED:20201:5150:\"103,166\"",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "11.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 11,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DONNERSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "BMED:20141:5150:\"103,166\"",
|
||||||
|
"anzahlAnmeldungen": 5,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5150"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "103",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.111",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5150"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "70901",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.213",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5150"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "166",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.111",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5150"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "BMED:20141:5150:\"70901\"",
|
||||||
|
"anzahlAnmeldungen": 0,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5150"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "103",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.111",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5150"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "70901",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.213",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5150"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "166",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.111",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5150"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "BMED:20201:5150:\"103,166\"",
|
||||||
|
"anzahlAnmeldungen": 41,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20201:5150"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "103",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.111",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20201:5150"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "70901",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.213",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20201:5150"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "166",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.111",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20201:5150"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "MZ1PH:SU:BMED:2_PH:SU:BMED:2",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "24.07.2024 14:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 24,
|
||||||
|
"stunde": 14,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 2
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "MZ1PH:SU:BMED:2",
|
||||||
|
"anzahlAnmeldungen": 2,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:1370"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "382",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.107",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:1370"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "74090",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.107",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:1370"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "PH:SU:BMED:2",
|
||||||
|
"anzahlAnmeldungen": 101,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20201:1600"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "73247",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "ML.034",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20201:1600"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "316",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.140",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20201:1600"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "49",
|
||||||
|
"typ": "AUFSICHT",
|
||||||
|
"raum": "ML.034",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20201:1600"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "126efi:WPF:EFI:0",
|
||||||
|
"pruefungsart": "LN_VORGEZOGEN",
|
||||||
|
"teilpruefungen": [],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "MUK:SU:BME:6",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "MUK:SU:BME:6",
|
||||||
|
"anzahlAnmeldungen": 0,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BME:20131:4520"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "ET1:SU:BMF:1",
|
||||||
|
"pruefungsart": "LN_STUDIENBEGLEITEND",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "03.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 3,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "ET1:SU:BMF:1",
|
||||||
|
"anzahlAnmeldungen": 12,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:1180"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "73630",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KH.108",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:1180"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "576",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KH.108",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:1180"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "mAUT4#AB:SU:MSY:1",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "30.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 30,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DIENSTAG",
|
||||||
|
"pruefungswoche": 3
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "mAUT4#AB:SU:MSY:1",
|
||||||
|
"anzahlAnmeldungen": 31,
|
||||||
|
"fachkennungen": [
|
||||||
|
"MAPR:20201:8425",
|
||||||
|
"MSY:20091:8425"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "219",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.213",
|
||||||
|
"fachkennungen": [
|
||||||
|
"MAPR:20201:8425",
|
||||||
|
"MSY:20091:8425"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "75398",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.213",
|
||||||
|
"fachkennungen": [
|
||||||
|
"MAPR:20201:8425",
|
||||||
|
"MSY:20091:8425"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "MAPR:20201:5020:\"393,159,355\"",
|
||||||
|
"pruefungsart": "LN_VORGEZOGEN",
|
||||||
|
"teilpruefungen": [],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "TO:SU:BMF:6_TO:SU:BMMF:4",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "13.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 13,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "SAMSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "TO:SU:BMMF:4",
|
||||||
|
"anzahlAnmeldungen": 30,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5350",
|
||||||
|
"BMED:20201:5350"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "508",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.440",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5350",
|
||||||
|
"BMED:20201:5350"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "585",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.107",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:5350",
|
||||||
|
"BMED:20201:5350"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "TO:SU:BMF:6",
|
||||||
|
"anzahlAnmeldungen": 29,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:4130"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "508",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.440",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:4130"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "585",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.107",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:4130"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "M1:SU:BMED:1",
|
||||||
|
"pruefungsart": "LN_STUDIENBEGLEITEND",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "10.07.2024 16:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 10,
|
||||||
|
"stunde": 16,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "M1:SU:BMED:1",
|
||||||
|
"anzahlAnmeldungen": 35,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:1210",
|
||||||
|
"BMED:20201:1510"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "73998",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "MC.100",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:1210",
|
||||||
|
"BMED:20201:1510"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "259",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "MC.100",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMED:20141:1210",
|
||||||
|
"BMED:20201:1510"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "FO:SU:BME:2",
|
||||||
|
"pruefungsart": "LN_VORGEZOGEN",
|
||||||
|
"teilpruefungen": [],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "BW:SU:BEI:5",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "11.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 11,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "DONNERSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "BW:SU:BEI:5",
|
||||||
|
"anzahlAnmeldungen": 49,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:3800"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "74065",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "MC.100",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:3800"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "376",
|
||||||
|
"typ": "AUFSICHT",
|
||||||
|
"raum": "KA.111",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:3800"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "MAPR:20201:5810:\"472,60104\"",
|
||||||
|
"pruefungsart": "LN_VORGEZOGEN",
|
||||||
|
"teilpruefungen": [],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "MK:SU:BMF:4",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "26.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 26,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "FREITAG",
|
||||||
|
"pruefungswoche": 3
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "MK:SU:BMF:4",
|
||||||
|
"anzahlAnmeldungen": 43,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:4070"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "350",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "MC.100",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:4070"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "024efi:WPF:EFI:0",
|
||||||
|
"pruefungsart": "LN_VORGEZOGEN",
|
||||||
|
"teilpruefungen": [],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "ELK1:SU:BEI:3_IBT:20141:6160:\"166\"",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "27.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 27,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "SAMSTAG",
|
||||||
|
"pruefungswoche": 3
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "ELK1:SU:BEI:3",
|
||||||
|
"anzahlAnmeldungen": 36,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:5120",
|
||||||
|
"BMED:20141:5120"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "166",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "MC.100",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:5120",
|
||||||
|
"BMED:20141:5120"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "IBT:20141:6160:\"166\"",
|
||||||
|
"anzahlAnmeldungen": 6,
|
||||||
|
"fachkennungen": [
|
||||||
|
"IBT:20141:6160"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "166",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "MC.100",
|
||||||
|
"fachkennungen": [
|
||||||
|
"IBT:20141:6160"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "mINF6/1:SU:MSY:2",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "mINF6/1:SU:MSY:2",
|
||||||
|
"anzahlAnmeldungen": 2,
|
||||||
|
"fachkennungen": [
|
||||||
|
"MAPR:20201:8835",
|
||||||
|
"MSY:20091:8835"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "MSY:20091:8361:\"49,75807\"",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "15.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 15,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "MONTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "MSY:20091:8361:\"49,75807\"",
|
||||||
|
"anzahlAnmeldungen": 8,
|
||||||
|
"fachkennungen": [
|
||||||
|
"MSY:20091:8361"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "75807",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.217",
|
||||||
|
"fachkennungen": [
|
||||||
|
"MSY:20091:8361"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "49",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.217",
|
||||||
|
"fachkennungen": [
|
||||||
|
"MSY:20091:8361"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "ET2:SU:BMF:2",
|
||||||
|
"pruefungsart": "PRUEFUNG",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "19.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 19,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "FREITAG",
|
||||||
|
"pruefungswoche": 2
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "ET2:SU:BMF:2",
|
||||||
|
"anzahlAnmeldungen": 61,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:1190"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "73630",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "BB.103",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:1190"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "492",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "SP.467",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BMF:20171:1190"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennung": "I2#AB:SU:BEI:3",
|
||||||
|
"pruefungsart": "LN_STUDIENBEGLEITEND",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "04.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 4,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DONNERSTAG",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
},
|
||||||
|
"teilpruefungen": [
|
||||||
|
{
|
||||||
|
"kennung": "I2#AB:SU:BEI:3",
|
||||||
|
"anzahlAnmeldungen": 63,
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BMED:20141:5160",
|
||||||
|
"BMED:20141:5160"
|
||||||
|
],
|
||||||
|
"pruefungsdurchfuehrungen": [
|
||||||
|
{
|
||||||
|
"dozentenkennung": "84399",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "BB.103",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BMED:20141:5160",
|
||||||
|
"BMED:20141:5160"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "38496",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "KA.450",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BMED:20141:5160",
|
||||||
|
"BMED:20141:5160"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "62711",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "BB.210",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BMED:20141:5160",
|
||||||
|
"BMED:20141:5160"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dozentenkennung": "73323",
|
||||||
|
"typ": "PRUEFER",
|
||||||
|
"raum": "BB.103",
|
||||||
|
"fachkennungen": [
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BMED:20141:5160",
|
||||||
|
"BMED:20141:5160"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dozentenKennungPendler": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1
Backend/pruefplan_parser/data/READ.ME
Normal file
@ -0,0 +1 @@
|
|||||||
|
Hier landen die hochgeladenen Dateien
|
577
Backend/pruefplan_parser/data/Saaleinteilung.json
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
{
|
||||||
|
"revision": "2648:2829M",
|
||||||
|
"array": [
|
||||||
|
{
|
||||||
|
"kennungPruefung": "M1:SU:BEI:1",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "10.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 10,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
},
|
||||||
|
"raum": "KA.450",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BEI:20141:1110"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3752511,
|
||||||
|
3743531,
|
||||||
|
3726002,
|
||||||
|
3756960,
|
||||||
|
3682959,
|
||||||
|
3747191,
|
||||||
|
3726312,
|
||||||
|
3727575,
|
||||||
|
3677443,
|
||||||
|
3724433,
|
||||||
|
3724165,
|
||||||
|
3748172,
|
||||||
|
3726338,
|
||||||
|
3671524,
|
||||||
|
3684924,
|
||||||
|
3571059,
|
||||||
|
3725229,
|
||||||
|
3678411,
|
||||||
|
3726268,
|
||||||
|
3303621,
|
||||||
|
3732643
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "BMED:20141:5150:\"103,166\"_BMED:20141:5150:\"70901\"_BMED:20201:5150:\"103,166\"",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "11.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 11,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DONNERSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
},
|
||||||
|
"raum": "KA.213",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BMED:20141:5150",
|
||||||
|
"BMED:20141:5150",
|
||||||
|
"BMED:20201:5150"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3691111,
|
||||||
|
3503148,
|
||||||
|
3680786,
|
||||||
|
3670499,
|
||||||
|
3634750,
|
||||||
|
3706613,
|
||||||
|
3527650,
|
||||||
|
3645338,
|
||||||
|
3706064,
|
||||||
|
3674877,
|
||||||
|
3291852,
|
||||||
|
3676053,
|
||||||
|
3578540,
|
||||||
|
3690651,
|
||||||
|
3621462,
|
||||||
|
3623453,
|
||||||
|
3623581,
|
||||||
|
3673432,
|
||||||
|
2747059,
|
||||||
|
3569726,
|
||||||
|
3673458,
|
||||||
|
3535000,
|
||||||
|
3623411
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "MZ1PH:SU:BMED:2_PH:SU:BMED:2",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "24.07.2024 14:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 24,
|
||||||
|
"stunde": 14,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 2
|
||||||
|
},
|
||||||
|
"raum": "KA.107",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BMED:20141:1370"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3504398,
|
||||||
|
3516845
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "ET1:SU:BMF:1",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "03.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 3,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
},
|
||||||
|
"raum": "KH.108",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BMF:20171:1180"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3584222,
|
||||||
|
3569599,
|
||||||
|
3565942,
|
||||||
|
3749645,
|
||||||
|
3756449,
|
||||||
|
3698347,
|
||||||
|
3628502,
|
||||||
|
3182142,
|
||||||
|
3673742,
|
||||||
|
3719352,
|
||||||
|
3693298,
|
||||||
|
3756564
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "mAUT4#AB:SU:MSY:1",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "30.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 30,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DIENSTAG",
|
||||||
|
"pruefungswoche": 3
|
||||||
|
},
|
||||||
|
"raum": "KA.213",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"MAPR:20201:8425",
|
||||||
|
"MSY:20091:8425"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3716009,
|
||||||
|
3518430,
|
||||||
|
3506909,
|
||||||
|
3513240,
|
||||||
|
3503119,
|
||||||
|
3724136,
|
||||||
|
3767142,
|
||||||
|
3767155,
|
||||||
|
3707610,
|
||||||
|
3508565,
|
||||||
|
3503429,
|
||||||
|
3581599,
|
||||||
|
3754052,
|
||||||
|
3516832,
|
||||||
|
3772463,
|
||||||
|
3508680,
|
||||||
|
3505960,
|
||||||
|
3569630,
|
||||||
|
3577853,
|
||||||
|
3244724,
|
||||||
|
3502563,
|
||||||
|
3503094,
|
||||||
|
3580352,
|
||||||
|
3164060,
|
||||||
|
3502873,
|
||||||
|
3559848,
|
||||||
|
3723960,
|
||||||
|
3758612,
|
||||||
|
3513224,
|
||||||
|
3289528,
|
||||||
|
3768800
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "TO:SU:BMF:6_TO:SU:BMMF:4",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "13.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 13,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "SAMSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
},
|
||||||
|
"raum": "KA.107",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BMED:20141:5350",
|
||||||
|
"BMED:20201:5350",
|
||||||
|
"BMF:20171:4130"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3622836,
|
||||||
|
3574261,
|
||||||
|
3529162,
|
||||||
|
3617023,
|
||||||
|
3616170,
|
||||||
|
3615636,
|
||||||
|
3629947,
|
||||||
|
3584699,
|
||||||
|
3624405,
|
||||||
|
3556537,
|
||||||
|
3616138,
|
||||||
|
3513422,
|
||||||
|
3619874,
|
||||||
|
3621178,
|
||||||
|
3623929,
|
||||||
|
2984098,
|
||||||
|
2931071,
|
||||||
|
3513266,
|
||||||
|
2267607
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "M1:SU:BMED:1",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "10.07.2024 16:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 10,
|
||||||
|
"stunde": 16,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "MITTWOCH",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
},
|
||||||
|
"raum": "MC.100",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BMED:20141:1210",
|
||||||
|
"BMED:20201:1510"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3733583,
|
||||||
|
3746008,
|
||||||
|
3751361,
|
||||||
|
3673445,
|
||||||
|
3739767,
|
||||||
|
3749629,
|
||||||
|
3719307,
|
||||||
|
3742617,
|
||||||
|
3740220,
|
||||||
|
3720860,
|
||||||
|
3721838,
|
||||||
|
3504653,
|
||||||
|
3754586,
|
||||||
|
3726479,
|
||||||
|
3561069,
|
||||||
|
3727869,
|
||||||
|
3740275,
|
||||||
|
3728051,
|
||||||
|
3724967,
|
||||||
|
3512920,
|
||||||
|
3581490,
|
||||||
|
3711062,
|
||||||
|
3759862,
|
||||||
|
3649154,
|
||||||
|
3756551,
|
||||||
|
3636093,
|
||||||
|
3719266,
|
||||||
|
3635348,
|
||||||
|
3719563,
|
||||||
|
3727393,
|
||||||
|
3761041,
|
||||||
|
3691393,
|
||||||
|
3638266,
|
||||||
|
3633414,
|
||||||
|
3622852
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "BW:SU:BEI:5",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "11.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 11,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "DONNERSTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
},
|
||||||
|
"raum": "MC.100",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BEI:20141:3800"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3616675,
|
||||||
|
3647725,
|
||||||
|
3627406,
|
||||||
|
3572030,
|
||||||
|
3235621,
|
||||||
|
3161199,
|
||||||
|
3512045,
|
||||||
|
3628672,
|
||||||
|
3308517,
|
||||||
|
3235250,
|
||||||
|
3634396,
|
||||||
|
3616703,
|
||||||
|
3627381,
|
||||||
|
3085850,
|
||||||
|
3620436,
|
||||||
|
3622159,
|
||||||
|
3569205,
|
||||||
|
3628205,
|
||||||
|
3263104,
|
||||||
|
3516957,
|
||||||
|
3628630,
|
||||||
|
3615850,
|
||||||
|
3647866,
|
||||||
|
3627419,
|
||||||
|
3641161,
|
||||||
|
3627831,
|
||||||
|
3622191,
|
||||||
|
3017956
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "MK:SU:BMF:4",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "26.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 26,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "FREITAG",
|
||||||
|
"pruefungswoche": 3
|
||||||
|
},
|
||||||
|
"raum": "MC.100",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BMF:20171:4070"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3544163,
|
||||||
|
3643800,
|
||||||
|
3621545,
|
||||||
|
3668827,
|
||||||
|
3569599,
|
||||||
|
3678479,
|
||||||
|
3246109,
|
||||||
|
3698350,
|
||||||
|
3583791,
|
||||||
|
3678437,
|
||||||
|
3693511,
|
||||||
|
3619902,
|
||||||
|
3699683,
|
||||||
|
3733244,
|
||||||
|
3627394,
|
||||||
|
3667523,
|
||||||
|
3700385,
|
||||||
|
3678226,
|
||||||
|
3698839,
|
||||||
|
3678495,
|
||||||
|
3036482,
|
||||||
|
3678440,
|
||||||
|
3684474,
|
||||||
|
3664621,
|
||||||
|
3698347,
|
||||||
|
3693243,
|
||||||
|
3564309,
|
||||||
|
3629947,
|
||||||
|
3584699,
|
||||||
|
3645721,
|
||||||
|
3687433,
|
||||||
|
3693269,
|
||||||
|
3678242,
|
||||||
|
3638914,
|
||||||
|
3619874,
|
||||||
|
3678424,
|
||||||
|
3234192,
|
||||||
|
3682438,
|
||||||
|
3669428,
|
||||||
|
3665967,
|
||||||
|
3693272,
|
||||||
|
3706626,
|
||||||
|
3665222
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "ELK1:SU:BEI:3_IBT:20141:6160:\"166\"",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "27.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 27,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "SAMSTAG",
|
||||||
|
"pruefungswoche": 3
|
||||||
|
},
|
||||||
|
"raum": "MC.100",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BEI:20141:5120",
|
||||||
|
"BMED:20141:5120",
|
||||||
|
"IBT:20141:6160"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3569979,
|
||||||
|
3647684,
|
||||||
|
3572030,
|
||||||
|
3633737,
|
||||||
|
3530133,
|
||||||
|
3509559,
|
||||||
|
3627381,
|
||||||
|
3085850,
|
||||||
|
3706121,
|
||||||
|
3670064,
|
||||||
|
3513381,
|
||||||
|
3263104,
|
||||||
|
3670402,
|
||||||
|
3670936,
|
||||||
|
3536119,
|
||||||
|
3692598,
|
||||||
|
3615272,
|
||||||
|
3692639,
|
||||||
|
3692668,
|
||||||
|
3671962,
|
||||||
|
3667932,
|
||||||
|
3672617,
|
||||||
|
3692837,
|
||||||
|
3674822,
|
||||||
|
3671805,
|
||||||
|
3513026,
|
||||||
|
3670051,
|
||||||
|
3693214,
|
||||||
|
3577712,
|
||||||
|
3668517,
|
||||||
|
3673586,
|
||||||
|
3672068,
|
||||||
|
3702392,
|
||||||
|
3722314,
|
||||||
|
3645792,
|
||||||
|
3534748,
|
||||||
|
3638394,
|
||||||
|
3305701,
|
||||||
|
3291335,
|
||||||
|
3637256,
|
||||||
|
3589933,
|
||||||
|
3589988
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "MSY:20091:8361:\"49,75807\"",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "15.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 15,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "MONTAG",
|
||||||
|
"pruefungswoche": 1
|
||||||
|
},
|
||||||
|
"raum": "KA.217",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"MSY:20091:8361"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3581599,
|
||||||
|
2761145,
|
||||||
|
3132088,
|
||||||
|
3757011,
|
||||||
|
2694152,
|
||||||
|
3768798,
|
||||||
|
3713545,
|
||||||
|
3506826
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "ET2:SU:BMF:2",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "19.07.2024 08:30 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 19,
|
||||||
|
"stunde": 8,
|
||||||
|
"minute": 30,
|
||||||
|
"wochentag": "FREITAG",
|
||||||
|
"pruefungswoche": 2
|
||||||
|
},
|
||||||
|
"raum": "SP.467",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BMF:20171:1190"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3726495,
|
||||||
|
3756449,
|
||||||
|
3721148,
|
||||||
|
3664621,
|
||||||
|
3721825,
|
||||||
|
3698347,
|
||||||
|
3628502,
|
||||||
|
3720831,
|
||||||
|
3182142,
|
||||||
|
3742505,
|
||||||
|
3250956,
|
||||||
|
3720901,
|
||||||
|
3688315,
|
||||||
|
3719253,
|
||||||
|
3720844,
|
||||||
|
3673742,
|
||||||
|
3513422,
|
||||||
|
3726239,
|
||||||
|
3719237,
|
||||||
|
3753899,
|
||||||
|
2984098,
|
||||||
|
3234192,
|
||||||
|
3719352,
|
||||||
|
3257531,
|
||||||
|
3693298,
|
||||||
|
3198761,
|
||||||
|
3756564,
|
||||||
|
3726099,
|
||||||
|
3719914
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kennungPruefung": "I2#AB:SU:BEI:3",
|
||||||
|
"termin": {
|
||||||
|
"datumText": "04.07.2024 11:00 MESZ",
|
||||||
|
"jahr": 2024,
|
||||||
|
"monat": 7,
|
||||||
|
"tag": 4,
|
||||||
|
"stunde": 11,
|
||||||
|
"minute": 0,
|
||||||
|
"wochentag": "DONNERSTAG",
|
||||||
|
"pruefungswoche": 0
|
||||||
|
},
|
||||||
|
"raum": "KA.450",
|
||||||
|
"kennungFaecher": [
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BEI:20141:5160",
|
||||||
|
"BMED:20141:5160",
|
||||||
|
"BMED:20141:5160"
|
||||||
|
],
|
||||||
|
"matrikel": [
|
||||||
|
3641161,
|
||||||
|
3670936,
|
||||||
|
3565575,
|
||||||
|
3536119,
|
||||||
|
3570982,
|
||||||
|
3615272,
|
||||||
|
3692613,
|
||||||
|
3570627,
|
||||||
|
3629017,
|
||||||
|
3590623,
|
||||||
|
3629033,
|
||||||
|
3667932,
|
||||||
|
3706118,
|
||||||
|
3702389,
|
||||||
|
3630286,
|
||||||
|
3283310,
|
||||||
|
3715284,
|
||||||
|
3671087,
|
||||||
|
3271030,
|
||||||
|
3674822,
|
||||||
|
3665277
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
9
Backend/pruefplan_parser/forms.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class UploadFileForm(forms.Form):
|
||||||
|
student_file = forms.FileField(required=False)
|
||||||
|
exams_file = forms.FileField(required=False)
|
||||||
|
lecturers_file = forms.FileField(required=False)
|
||||||
|
subjects_file = forms.FileField(required=False)
|
||||||
|
attendance_file = forms.FileField(required=False)
|
20
Backend/pruefplan_parser/migrations/0001_initial.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 3.2 on 2021-04-30 12:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='JsonParser',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
Backend/pruefplan_parser/migrations/__init__.py
Normal file
454
Backend/pruefplan_parser/models.py
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
import inspect
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from medinf.settings import (
|
||||||
|
PP_EXAM_FILE,
|
||||||
|
PP_ATTENDANCE_FILE,
|
||||||
|
PP_STUDENT_FILE,
|
||||||
|
PP_SUBJECT_FILE,
|
||||||
|
PP_TEACHER_FILE,
|
||||||
|
)
|
||||||
|
from pruefplan_viewer.models import (
|
||||||
|
API_ExamDataSet_ForStudent,
|
||||||
|
Attendance,
|
||||||
|
Subject,
|
||||||
|
Exam,
|
||||||
|
Lecturer,
|
||||||
|
PartialExam,
|
||||||
|
ExamExecution,
|
||||||
|
Student,
|
||||||
|
StudentExam,
|
||||||
|
)
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class JsonParser(models.Model):
|
||||||
|
exceptionMessages = []
|
||||||
|
warningMessages = []
|
||||||
|
|
||||||
|
def clearTable(self):
|
||||||
|
JsonParser.exceptionMessages.clear()
|
||||||
|
JsonParser.warningMessages.clear()
|
||||||
|
|
||||||
|
Subject.objects.all().delete()
|
||||||
|
Exam.objects.all().delete()
|
||||||
|
Attendance.objects.all().delete()
|
||||||
|
Lecturer.objects.all().delete()
|
||||||
|
Student.objects.all().delete()
|
||||||
|
PartialExam.objects.all().delete()
|
||||||
|
ExamExecution.objects.all().delete()
|
||||||
|
StudentExam.objects.all().delete()
|
||||||
|
API_ExamDataSet_ForStudent.objects.all().delete()
|
||||||
|
|
||||||
|
def fillLecturerTable(self):
|
||||||
|
idKey = "kennung"
|
||||||
|
firstNameKey = "vorname"
|
||||||
|
lastNameKey = "nachname"
|
||||||
|
titleKey = "titel"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(PP_TEACHER_FILE, encoding="utf-8") as json_file:
|
||||||
|
data = json.load(json_file)
|
||||||
|
revision = data["revision"] # TODO -> Do Something with Revision
|
||||||
|
for d in data["array"]:
|
||||||
|
entry = Lecturer(
|
||||||
|
identification=d[idKey],
|
||||||
|
firstName=d[firstNameKey],
|
||||||
|
lastName=d[lastNameKey],
|
||||||
|
title=d[titleKey],
|
||||||
|
) # Create new Lecturer entry
|
||||||
|
entry.save()
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
JsonParser.exceptionMessages.append(
|
||||||
|
"Exception of type [FileNotFound]. Message: " + str(e)
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing in {5!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
PP_TEACHER_FILE,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
except Exception as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
def fillAttendanceTable(self):
|
||||||
|
idKey = "dozentenkennung"
|
||||||
|
yearKey = "jahr"
|
||||||
|
monthKey = "monat"
|
||||||
|
dayKey = "tag"
|
||||||
|
hourKey = "stunde"
|
||||||
|
minuteKey = "minute"
|
||||||
|
weekdayKey = "wochentag"
|
||||||
|
dateKey = "termin"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(PP_ATTENDANCE_FILE, encoding="utf-8") as json_file:
|
||||||
|
data = json.load(json_file)
|
||||||
|
revision = data["revision"] # TODO -> Do Something with Revision
|
||||||
|
for d in data["array"]:
|
||||||
|
|
||||||
|
keyAvailable = "termin" in d
|
||||||
|
if (
|
||||||
|
not keyAvailable
|
||||||
|
): # These attendance entries were not assigned any examination date
|
||||||
|
template = "Warning. Location: {0!r}. Following Exam was not assigned any examination date ['termin']: [{1}]"
|
||||||
|
message = template.format(PP_EXAM_FILE, d[idKey])
|
||||||
|
JsonParser.warningMessages.append(message)
|
||||||
|
continue
|
||||||
|
|
||||||
|
date = datetime.date(
|
||||||
|
d[dateKey][yearKey], d[dateKey][monthKey], d[dateKey][dayKey]
|
||||||
|
)
|
||||||
|
time = datetime.time(d[dateKey][hourKey], d[dateKey][minuteKey])
|
||||||
|
|
||||||
|
entry = Attendance(
|
||||||
|
identification=d[idKey],
|
||||||
|
time=time,
|
||||||
|
date=date,
|
||||||
|
dateTimeText=d[dateKey]["datumText"],
|
||||||
|
weekday=d[dateKey]["wochentag"],
|
||||||
|
) # Create new Lecturer entry
|
||||||
|
entry.save()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the Lecturer with the "Dozentenkennung" of this attendance entry
|
||||||
|
lecturer = Lecturer.objects.get(identification=d[idKey])
|
||||||
|
|
||||||
|
# Link this attendance entry to the lecturer
|
||||||
|
lecturer.attendance_set.add(entry)
|
||||||
|
except Exception as e:
|
||||||
|
template = "Exception of type [{0}]. Arguments: {1!r}. Message: [{2}] entry might be missing in {3!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__, e.args, idKey, PP_TEACHER_FILE
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
JsonParser.exceptionMessages.append(
|
||||||
|
"Exception of type [FileNotFound]. Message: " + str(e)
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing in {5!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
PP_ATTENDANCE_FILE,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
except Exception as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
def fillSubjectTable(self):
|
||||||
|
idKey = "kennung"
|
||||||
|
initialsKey = "kurztext"
|
||||||
|
nameKey = "langtext"
|
||||||
|
ohmIdKey = "fachkennungOhm"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(PP_SUBJECT_FILE, encoding="utf-8") as json_file:
|
||||||
|
data = json.load(json_file)
|
||||||
|
revision = data["revision"] # TODO -> Do Something with Revision
|
||||||
|
for d in data["array"]:
|
||||||
|
|
||||||
|
entry = Subject(
|
||||||
|
identification=d[idKey],
|
||||||
|
initials=d[ohmIdKey][initialsKey],
|
||||||
|
name=d[ohmIdKey][nameKey],
|
||||||
|
) # Create new Subject entry
|
||||||
|
entry.save()
|
||||||
|
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
JsonParser.exceptionMessages.append(
|
||||||
|
"Exception of type [FileNotFound]. Message:" + str(e)
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing in {5!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
PP_SUBJECT_FILE,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
except Exception as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
def fillExamTable(self):
|
||||||
|
idKey = "kennung"
|
||||||
|
yearKey = "jahr"
|
||||||
|
monthKey = "monat"
|
||||||
|
dayKey = "tag"
|
||||||
|
hourKey = "stunde"
|
||||||
|
minuteKey = "minute"
|
||||||
|
weekdayKey = "wochentag"
|
||||||
|
dateKey = "termin"
|
||||||
|
partialExamsKey = "teilpruefungen"
|
||||||
|
partialExamIdKey = "kennung"
|
||||||
|
numRegStudKey = "anzahlAnmeldungen"
|
||||||
|
subjectIdsKey = "fachkennungen"
|
||||||
|
examExecutionsKey = "pruefungsdurchfuehrungen"
|
||||||
|
lecturerIdKey = "dozentenkennung"
|
||||||
|
supervisorTypeKey = "typ"
|
||||||
|
locationKey = "raum"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(PP_EXAM_FILE, encoding="utf-8") as json_file:
|
||||||
|
data = json.load(json_file)
|
||||||
|
revision = data["revision"] # TODO -> Do Something with Revision
|
||||||
|
for d in data["array"]:
|
||||||
|
|
||||||
|
if (
|
||||||
|
d["pruefungsart"] == "LN_VORGEZOGEN"
|
||||||
|
): # These entries can be ignored
|
||||||
|
continue
|
||||||
|
|
||||||
|
keyAvailable = "termin" in d
|
||||||
|
if (
|
||||||
|
not keyAvailable
|
||||||
|
): # These exam entries were not assigned any examination date
|
||||||
|
template = "Warning. Location: {0!r}. Following Exam was not assigned any examination date ['termin']: [{1}]"
|
||||||
|
message = template.format(PP_EXAM_FILE, d[idKey])
|
||||||
|
JsonParser.warningMessages.append(message)
|
||||||
|
continue
|
||||||
|
|
||||||
|
date = datetime.date(
|
||||||
|
d[dateKey][yearKey], d[dateKey][monthKey], d[dateKey][dayKey]
|
||||||
|
)
|
||||||
|
time = datetime.time(d[dateKey][hourKey], d[dateKey][minuteKey])
|
||||||
|
|
||||||
|
examEntry = Exam(
|
||||||
|
identification=d[idKey],
|
||||||
|
date=date,
|
||||||
|
time=time,
|
||||||
|
weekday=d[dateKey][weekdayKey],
|
||||||
|
) # Create new Exam entry
|
||||||
|
examEntry.save()
|
||||||
|
|
||||||
|
for teilpruef in d[partialExamsKey]:
|
||||||
|
partialExamId = teilpruef[partialExamIdKey]
|
||||||
|
regStudCount = teilpruef[numRegStudKey]
|
||||||
|
|
||||||
|
partialExam = PartialExam(
|
||||||
|
identification=partialExamId, regStudCount=regStudCount
|
||||||
|
) # Create new Partialexam entry
|
||||||
|
partialExam.save()
|
||||||
|
|
||||||
|
examEntry.partialexam_set.add(
|
||||||
|
partialExam
|
||||||
|
) # Add connection Exam(One) to PartialExam(Many)
|
||||||
|
|
||||||
|
for subId in teilpruef[subjectIdsKey]:
|
||||||
|
# check if subject exists
|
||||||
|
try:
|
||||||
|
subject = Subject.objects.get(identification=subId)
|
||||||
|
partialExam.subjectIds.add(
|
||||||
|
subject
|
||||||
|
) # Add connection PartialExam(Many) to Subject(Many)
|
||||||
|
except Exception as e:
|
||||||
|
template = "Exception of type [{0}]. Arguments: {1!r}. Message: [{2}] entry might be missing in {3!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__, e.args, subId, PP_SUBJECT_FILE
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
for pruefdurch in teilpruef[examExecutionsKey]:
|
||||||
|
lecId = pruefdurch[lecturerIdKey]
|
||||||
|
supervisorType = pruefdurch[supervisorTypeKey]
|
||||||
|
location = pruefdurch[locationKey]
|
||||||
|
|
||||||
|
examExecution = ExamExecution(
|
||||||
|
supervisorType=supervisorType, location=location
|
||||||
|
) # Create new ExamExecution entry
|
||||||
|
examExecution.save()
|
||||||
|
|
||||||
|
# check if lecturer exists
|
||||||
|
try:
|
||||||
|
lecturer = Lecturer.objects.get(pk=lecId)
|
||||||
|
lecturer.examexecution_set.add(
|
||||||
|
examExecution
|
||||||
|
) # Add connection Lecturer (One) to ExamExecution (Many)
|
||||||
|
except Exception as e:
|
||||||
|
template = "Exception of type [{0}]. Arguments: {1!r}. Message: [{2}] entry might be missing in {3!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__, e.args, lecId, PP_TEACHER_FILE
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
for subId in pruefdurch[subjectIdsKey]:
|
||||||
|
# check if subject exists
|
||||||
|
try:
|
||||||
|
subject = Subject.objects.get(identification=subId)
|
||||||
|
examExecution.subjectIds.add(
|
||||||
|
subject
|
||||||
|
) # Add connection ExamExecution(Many) to Subject(Many)
|
||||||
|
except Exception as e:
|
||||||
|
template = "Exception of type [{0}]. Arguments: {1!r}. Message: [{2}] entry might be missing in {3!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__, e.args, subId, PP_SUBJECT_FILE
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
partialExam.examexecution_set.add(
|
||||||
|
examExecution
|
||||||
|
) # Add connection PartialExam(One) to ExamExecution(Many)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
JsonParser.exceptionMessages.append(
|
||||||
|
"Exception of type [FileNotFound]. Message" + str(e)
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing in {5!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
PP_EXAM_FILE,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
except Exception as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
|
||||||
|
def fillStudentExamTable(self):
|
||||||
|
idKey = "kennungPruefung"
|
||||||
|
yearKey = "jahr"
|
||||||
|
monthKey = "monat"
|
||||||
|
dayKey = "tag"
|
||||||
|
hourKey = "stunde"
|
||||||
|
minuteKey = "minute"
|
||||||
|
weekdayKey = "wochentag"
|
||||||
|
locationKey = "raum"
|
||||||
|
dateKey = "termin"
|
||||||
|
matrikelKey = "matrikel"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(PP_STUDENT_FILE, encoding="utf-8") as json_file:
|
||||||
|
data = json.load(json_file)
|
||||||
|
revision = data["revision"] # TODO -> Do Something with Revision
|
||||||
|
for d in data["array"]:
|
||||||
|
|
||||||
|
date = datetime.date(
|
||||||
|
d[dateKey][yearKey], d[dateKey][monthKey], d[dateKey][dayKey]
|
||||||
|
)
|
||||||
|
time = datetime.time(d[dateKey][hourKey], d[dateKey][minuteKey])
|
||||||
|
|
||||||
|
exam = Exam.objects.filter(
|
||||||
|
identification=d[idKey], date=date, time=time
|
||||||
|
).first()
|
||||||
|
|
||||||
|
partialExams = exam.partialexam_set.all()
|
||||||
|
for partialExam in partialExams:
|
||||||
|
examExecutions = partialExam.examexecution_set.all()
|
||||||
|
for examExecution in examExecutions:
|
||||||
|
if examExecution.location == d[locationKey]:
|
||||||
|
studentExamEntry = StudentExam(
|
||||||
|
examIdentification=d[idKey],
|
||||||
|
date=date,
|
||||||
|
time=time,
|
||||||
|
weekday=d[dateKey][weekdayKey],
|
||||||
|
location=d[locationKey],
|
||||||
|
examExecution=examExecution,
|
||||||
|
) # Create new StudentExam entry
|
||||||
|
studentExamEntry.save()
|
||||||
|
|
||||||
|
for matrikel in d[matrikelKey]:
|
||||||
|
studentEntry = Student.objects.filter(
|
||||||
|
matrikel=matrikel
|
||||||
|
).exists()
|
||||||
|
if (
|
||||||
|
studentEntry is False
|
||||||
|
): # Create new Student entry if does not already exists
|
||||||
|
entry = Student(matrikel=matrikel)
|
||||||
|
entry.save()
|
||||||
|
else:
|
||||||
|
entry = (
|
||||||
|
Student.objects.all().filter(matrikel=matrikel).first()
|
||||||
|
)
|
||||||
|
entry.exams.add(studentExamEntry)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
JsonParser.exceptionMessages.append(
|
||||||
|
"Exception of type [FileNotFound]. Message:" + str(e)
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop.. Arguments: {3!r}. Message: [{4}] entry might be missing in {5!r}"
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e,
|
||||||
|
PP_STUDENT_FILE,
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
||||||
|
except Exception as e:
|
||||||
|
calling_function = inspect.stack()[0][3]
|
||||||
|
iteration_number = data["array"].index(d)
|
||||||
|
template = "Exception of type [{0}] occurred in function {1} in iteration {2} of the loop. Arguments: {3!r}. Message: {4}."
|
||||||
|
message = template.format(
|
||||||
|
type(e).__name__,
|
||||||
|
calling_function,
|
||||||
|
iteration_number,
|
||||||
|
e.args,
|
||||||
|
e.__str__(),
|
||||||
|
)
|
||||||
|
JsonParser.exceptionMessages.append(message)
|
10
Backend/pruefplan_parser/urls.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'pp_parser'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('parse', views.parse, name='parse'),
|
||||||
|
path('clearTable', views.clearTable, name='clearTable'),
|
||||||
|
path('upload_file', views.upload_file, name='upload_file'),
|
||||||
|
]
|
7
Backend/pruefplan_parser/utils.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
def handle_uploaded_file(f, location):
|
||||||
|
if f is not None:
|
||||||
|
with open(location, 'wb+') as destination:
|
||||||
|
for chunk in f.chunks():
|
||||||
|
destination.write(chunk)
|
65
Backend/pruefplan_parser/views.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from .models import JsonParser
|
||||||
|
from pruefplan_parser.models import JsonParser
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
|
from .forms import UploadFileForm
|
||||||
|
from pruefplan_parser.utils import handle_uploaded_file
|
||||||
|
from medinf.settings import (
|
||||||
|
PP_EXAM_FILE,
|
||||||
|
PP_ATTENDANCE_FILE,
|
||||||
|
PP_STUDENT_FILE,
|
||||||
|
PP_SUBJECT_FILE,
|
||||||
|
PP_TEACHER_FILE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_pp_admin(user):
|
||||||
|
return user.groups.filter(name="PP_ADMIN").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def parse():
|
||||||
|
parser = JsonParser()
|
||||||
|
parser.clearTable()
|
||||||
|
parser.fillLecturerTable()
|
||||||
|
parser.fillAttendanceTable()
|
||||||
|
parser.fillSubjectTable()
|
||||||
|
parser.fillExamTable()
|
||||||
|
parser.fillStudentExamTable()
|
||||||
|
|
||||||
|
|
||||||
|
@user_passes_test(is_pp_admin)
|
||||||
|
def clearTable(request):
|
||||||
|
parser = JsonParser()
|
||||||
|
parser.clearTable()
|
||||||
|
return render(request, "pruefplan/clearTable.html")
|
||||||
|
|
||||||
|
|
||||||
|
@user_passes_test(is_pp_admin)
|
||||||
|
def upload_file(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
form = UploadFileForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
print(request.FILES)
|
||||||
|
handle_uploaded_file(
|
||||||
|
request.FILES.get("attendance_file"), PP_ATTENDANCE_FILE
|
||||||
|
)
|
||||||
|
handle_uploaded_file(request.FILES.get("student_file"), PP_STUDENT_FILE)
|
||||||
|
handle_uploaded_file(request.FILES.get("exams_file"), PP_EXAM_FILE)
|
||||||
|
handle_uploaded_file(request.FILES.get("lecturers_file"), PP_TEACHER_FILE)
|
||||||
|
handle_uploaded_file(request.FILES.get("subjects_file"), PP_SUBJECT_FILE)
|
||||||
|
parse()
|
||||||
|
if (
|
||||||
|
len(JsonParser.exceptionMessages) != 0
|
||||||
|
or len(JsonParser.warningMessages) != 0
|
||||||
|
):
|
||||||
|
context = {
|
||||||
|
"exceptions": JsonParser.exceptionMessages,
|
||||||
|
"warnings": JsonParser.warningMessages,
|
||||||
|
}
|
||||||
|
return render(request, "pruefplan/exceptionHandling.html", context)
|
||||||
|
else:
|
||||||
|
return render(request, "pruefplan/parse.html")
|
||||||
|
else:
|
||||||
|
form = UploadFileForm()
|
||||||
|
return render(request, "pruefplan/upload.html", {"form": form})
|
0
Backend/pruefplan_viewer/__init__.py
Normal file
4
Backend/pruefplan_viewer/admin.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#from django.contrib import admin
|
||||||
|
#from .models import Lecturer
|
||||||
|
|
||||||
|
#admin.site.register(Lecturer)
|
5
Backend/pruefplan_viewer/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PruefplanViewerConfig(AppConfig):
|
||||||
|
name = 'pruefplan_viewer'
|
94
Backend/pruefplan_viewer/migrations/0001_initial.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Generated by Django 3.2 on 2021-04-30 12:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Exam',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('identification', models.CharField(max_length=200, null=True)),
|
||||||
|
('date', models.DateField(blank=True, null=True)),
|
||||||
|
('time', models.TimeField(blank=True, null=True)),
|
||||||
|
('weekday', models.CharField(max_length=200, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExamExecution',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('supervisorType', models.CharField(max_length=200, null=True)),
|
||||||
|
('location', models.CharField(max_length=200, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Lecturer',
|
||||||
|
fields=[
|
||||||
|
('identification', models.CharField(max_length=200, primary_key=True, serialize=False)),
|
||||||
|
('firstName', models.CharField(max_length=200, null=True)),
|
||||||
|
('lastName', models.CharField(max_length=200, null=True)),
|
||||||
|
('title', models.CharField(max_length=200, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Subject',
|
||||||
|
fields=[
|
||||||
|
('identification', models.CharField(max_length=200, primary_key=True, serialize=False)),
|
||||||
|
('initials', models.CharField(max_length=200, null=True)),
|
||||||
|
('name', models.CharField(max_length=200, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StudentExam',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('examIdentification', models.CharField(max_length=200, null=True)),
|
||||||
|
('date', models.DateField(blank=True, null=True)),
|
||||||
|
('time', models.TimeField(blank=True, null=True)),
|
||||||
|
('weekday', models.CharField(max_length=200, null=True)),
|
||||||
|
('location', models.CharField(max_length=200, null=True)),
|
||||||
|
('examExecution', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pruefplan_viewer.examexecution')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Student',
|
||||||
|
fields=[
|
||||||
|
('matrikel', models.CharField(max_length=200, primary_key=True, serialize=False)),
|
||||||
|
('exams', models.ManyToManyField(to='pruefplan_viewer.StudentExam')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PartialExam',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('identification', models.CharField(max_length=200, null=True)),
|
||||||
|
('regStudCount', models.CharField(max_length=200, null=True)),
|
||||||
|
('exam', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pruefplan_viewer.exam')),
|
||||||
|
('subjectIds', models.ManyToManyField(to='pruefplan_viewer.Subject')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='examexecution',
|
||||||
|
name='lecturer',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pruefplan_viewer.lecturer'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='examexecution',
|
||||||
|
name='partialExam',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pruefplan_viewer.partialexam'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='examexecution',
|
||||||
|
name='subjectIds',
|
||||||
|
field=models.ManyToManyField(to='pruefplan_viewer.Subject'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-02-26 17:04
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("pruefplan_viewer", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="examexecution",
|
||||||
|
options={
|
||||||
|
"ordering": ("partialExam__exam__date", "partialExam__exam__time")
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
19
Backend/pruefplan_viewer/migrations/0003_presences.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-04-11 13:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pruefplan_viewer', '0002_alter_examexecution_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Presences',
|
||||||
|
fields=[
|
||||||
|
('identification', models.CharField(max_length=200, primary_key=True, serialize=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-04-11 16:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pruefplan_viewer', '0003_presences'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='presences',
|
||||||
|
name='date',
|
||||||
|
field=models.DateField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='presences',
|
||||||
|
name='dateTimeText',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='presences',
|
||||||
|
name='time',
|
||||||
|
field=models.TimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='presences',
|
||||||
|
name='weekday',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.0.3 on 2024-04-24 09:13
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pruefplan_viewer', '0004_presences_date_presences_datetimetext_presences_time_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='presences',
|
||||||
|
name='lecturer',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pruefplan_viewer.lecturer'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.0.3 on 2024-05-17 10:43
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pruefplan_viewer', '0005_presences_lecturer'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Presences',
|
||||||
|
new_name='Attendance',
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-01-30 14:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pruefplan_viewer', '0006_rename_presences_attendance'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='API_ExamDataSet_ForStudent',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,63 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-01-30 14:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pruefplan_viewer', '0007_api_examdataset_forstudent'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='date',
|
||||||
|
field=models.DateField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='examIdent',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='examStudCount',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='location',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='room',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='studMartNr',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='subjectIdent',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='supervisor',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='time',
|
||||||
|
field=models.TimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='api_examdataset_forstudent',
|
||||||
|
name='weekday',
|
||||||
|
field=models.CharField(max_length=200, null=True),
|
||||||
|
),
|
||||||
|
]
|
0
Backend/pruefplan_viewer/migrations/__init__.py
Normal file
148
Backend/pruefplan_viewer/models.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
from __future__ import annotations # Python 3.10 (Production still on 3.9)
|
||||||
|
|
||||||
|
from dataclasses import asdict, dataclass, field
|
||||||
|
import datetime as dt
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Subject(models.Model):
|
||||||
|
identification = models.CharField(max_length=200, primary_key=True)
|
||||||
|
initials = models.CharField(max_length=200, null=True)
|
||||||
|
name = models.CharField(max_length=200, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Exam(models.Model):
|
||||||
|
identification = models.CharField(max_length=200, null=True)
|
||||||
|
date = models.DateField(blank=True, null=True)
|
||||||
|
time = models.TimeField(blank=True, null=True)
|
||||||
|
weekday = models.CharField(max_length=200, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PartialExam(models.Model):
|
||||||
|
identification = models.CharField(max_length=200, null=True)
|
||||||
|
regStudCount = models.CharField(max_length=200, null=True)
|
||||||
|
subjectIds = models.ManyToManyField(Subject)
|
||||||
|
exam = models.ForeignKey("Exam", blank=True, null=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
|
|
||||||
|
class Lecturer(models.Model):
|
||||||
|
identification = models.CharField(max_length=200, primary_key=True)
|
||||||
|
firstName = models.CharField(max_length=200, null=True)
|
||||||
|
lastName = models.CharField(max_length=200, null=True)
|
||||||
|
title = models.CharField(max_length=200, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Attendance(models.Model):
|
||||||
|
identification = models.CharField(max_length=200, primary_key=True)
|
||||||
|
date = models.DateField(blank=True, null=True)
|
||||||
|
time = models.TimeField(blank=True, null=True)
|
||||||
|
dateTimeText = models.CharField(max_length=200, null=True)
|
||||||
|
weekday = models.CharField(max_length=200, null=True)
|
||||||
|
lecturer = models.ForeignKey(
|
||||||
|
"Lecturer", blank=True, null=True, on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StudentExam(models.Model):
|
||||||
|
examIdentification = models.CharField(max_length=200, null=True)
|
||||||
|
date = models.DateField(blank=True, null=True)
|
||||||
|
time = models.TimeField(blank=True, null=True)
|
||||||
|
weekday = models.CharField(max_length=200, null=True)
|
||||||
|
location = models.CharField(max_length=200, null=True)
|
||||||
|
examExecution = models.ForeignKey(
|
||||||
|
"ExamExecution", blank=True, null=True, on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Student(models.Model):
|
||||||
|
matrikel = models.CharField(max_length=200, primary_key=True)
|
||||||
|
exams = models.ManyToManyField(StudentExam)
|
||||||
|
|
||||||
|
|
||||||
|
class ExamExecution(models.Model):
|
||||||
|
lecturer = models.ForeignKey(
|
||||||
|
"Lecturer", blank=True, null=True, on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
supervisorType = models.CharField(max_length=200, null=True)
|
||||||
|
location = models.CharField(max_length=200, null=True)
|
||||||
|
subjectIds = models.ManyToManyField(Subject)
|
||||||
|
partialExam = models.ForeignKey(
|
||||||
|
"PartialExam", blank=True, null=True, on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ("partialExam__exam__date", "partialExam__exam__time")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# @dataclass()
|
||||||
|
# class API_ExamDataSet_ForStudent(models.Model):
|
||||||
|
# date: dt.date
|
||||||
|
# time: dt.time
|
||||||
|
# weekday: str
|
||||||
|
# room: str | None
|
||||||
|
# location: str | None
|
||||||
|
# subjectIdent: str | None
|
||||||
|
# examIdent: str | None
|
||||||
|
# examStudCount: int | None
|
||||||
|
# supervisor: str
|
||||||
|
# studMartNr: str | None
|
||||||
|
|
||||||
|
# def to_dict(self):
|
||||||
|
# return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
class API_ExamDataSet_ForStudent(models.Model):
|
||||||
|
date = models.DateField(blank=True, null=True)
|
||||||
|
time = models.TimeField(blank=True, null=True)
|
||||||
|
weekday = models.CharField(max_length=200, null=True)
|
||||||
|
room = models.CharField(max_length=200, null=True)
|
||||||
|
location = models.CharField(max_length=200, null=True)
|
||||||
|
subjectIdent = models.CharField(max_length=200, null=True)
|
||||||
|
examIdent = models.CharField(max_length=200, null=True)
|
||||||
|
examStudCount = models.CharField(max_length=200, null=True)
|
||||||
|
supervisor = models.CharField(max_length=200, null=True)
|
||||||
|
studMartNr = models.CharField(max_length=200, null=True)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(order=True)
|
||||||
|
class ExamLines:
|
||||||
|
sort_index: dt.datetime = field(init=False)
|
||||||
|
date: dt.date
|
||||||
|
time: dt.time
|
||||||
|
weekday: str
|
||||||
|
location: str | None
|
||||||
|
subjectIds: list[Subject] | None
|
||||||
|
examIdent: str | None
|
||||||
|
examStudCound: int | None
|
||||||
|
supervisorType: str
|
||||||
|
executionSet: list | None
|
||||||
|
partialExamIdent: str | None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.sort_index = dt.datetime.combine(self.date, self.time)
|
||||||
|
|
||||||
|
|
||||||
|
def sort_and_group_datetime(examLines: list):
|
||||||
|
|
||||||
|
examLine_Datetime_Groups = {}
|
||||||
|
for examLine in examLines:
|
||||||
|
if examLine.sort_index not in examLine_Datetime_Groups:
|
||||||
|
examLine_Datetime_Groups[examLine.sort_index] = []
|
||||||
|
examLine_Datetime_Groups[examLine.sort_index].append(examLine)
|
||||||
|
|
||||||
|
sorted_groups = sorted(examLine_Datetime_Groups.items(), key=lambda x: x[0])
|
||||||
|
|
||||||
|
sorted_result = []
|
||||||
|
for _, group in sorted_groups:
|
||||||
|
sorted_result.extend(group)
|
||||||
|
|
||||||
|
return sorted_result
|
3
Backend/pruefplan_viewer/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
13
Backend/pruefplan_viewer/urls.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'pp_viewer'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('lecturer', views.lecturer, name='lecturer'),
|
||||||
|
path('subject', views.subject, name='subject'),
|
||||||
|
path('exam', views.exam, name='exam'),
|
||||||
|
path('student', views.student, name='student'),
|
||||||
|
path('studentExam/<int:student_id>/', views.examsForStudent, name='examForStudent'),
|
||||||
|
path('lecturerExam/<str:lecturer_id>/', views.examsForLecturer, name='examForLecturer'),
|
||||||
|
]
|
115
Backend/pruefplan_viewer/views.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from django.template.defaulttags import register
|
||||||
|
from django.http import Http404
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from .models import ExamLines, Lecturer, Subject, Exam, Student, sort_and_group_datetime
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def lecturer(request):
|
||||||
|
lecturers = Lecturer.objects.all()
|
||||||
|
context = {"lecturers": lecturers}
|
||||||
|
return render(request, "pruefplan/lecturer.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def subject(request):
|
||||||
|
subjects = Subject.objects.all()
|
||||||
|
context = {"subjects": subjects}
|
||||||
|
return render(request, "pruefplan/subject.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def student(request):
|
||||||
|
students = Student.objects.all()
|
||||||
|
context = {"students": students}
|
||||||
|
return render(request, "pruefplan/student.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def exam(request):
|
||||||
|
exams = Exam.objects.all()
|
||||||
|
supervisorSetDict = {}
|
||||||
|
for ex in exams:
|
||||||
|
examSupervisors = []
|
||||||
|
for partialExam in ex.partialexam_set.all():
|
||||||
|
for examExe in partialExam.examexecution_set.all():
|
||||||
|
supervisor = (
|
||||||
|
examExe.lecturer.firstName + " " + examExe.lecturer.lastName
|
||||||
|
)
|
||||||
|
if supervisor not in examSupervisors:
|
||||||
|
examSupervisors.append(supervisor)
|
||||||
|
supervisorSetDict[ex.identification] = examSupervisors
|
||||||
|
|
||||||
|
context = {"exams": exams, "supervisorSetDict": supervisorSetDict}
|
||||||
|
return render(request, "pruefplan/exam.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def get_item(dictionary, key):
|
||||||
|
return dictionary.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
def examsForLecturer(request, lecturer_id):
|
||||||
|
lecturer = Lecturer.objects.filter(pk=lecturer_id).first()
|
||||||
|
if lecturer is not None:
|
||||||
|
lecExamExes = lecturer.examexecution_set.all()
|
||||||
|
lecAttendance = lecturer.attendance_set.all()
|
||||||
|
lecExamLines = mergeModels(lecExamExes, lecAttendance)
|
||||||
|
context = {
|
||||||
|
"lecturer": lecturer,
|
||||||
|
"lecExamLines": lecExamLines,
|
||||||
|
"lecExamExes": lecExamExes,
|
||||||
|
}
|
||||||
|
return render(request, "pruefplan/lecturerExam.html", context)
|
||||||
|
else:
|
||||||
|
raise Http404("Dozentenkennung ungültig.")
|
||||||
|
|
||||||
|
|
||||||
|
def examsForStudent(request, student_id):
|
||||||
|
student = Student.objects.all().filter(matrikel=student_id).first()
|
||||||
|
if student is not None:
|
||||||
|
exams = student.exams.all()
|
||||||
|
context = {"student": student, "exams": exams}
|
||||||
|
return render(request, "pruefplan/studentExam.html", context)
|
||||||
|
else:
|
||||||
|
raise Http404("Matrikelnummer ungültig.")
|
||||||
|
|
||||||
|
|
||||||
|
def mergeModels(lecExamExes, lecAttendance):
|
||||||
|
result = [
|
||||||
|
ExamLines(
|
||||||
|
date=examExes.partialExam.exam.date,
|
||||||
|
time=examExes.partialExam.exam.time,
|
||||||
|
weekday=examExes.partialExam.exam.weekday,
|
||||||
|
location=examExes.location,
|
||||||
|
subjectIds=examExes.subjectIds,
|
||||||
|
examIdent=examExes.partialExam.identification,
|
||||||
|
examStudCound=examExes.partialExam.regStudCount,
|
||||||
|
supervisorType=examExes.supervisorType,
|
||||||
|
executionSet=list(examExes.partialExam.examexecution_set.all()),
|
||||||
|
partialExamIdent=examExes.partialExam.exam.identification,
|
||||||
|
)
|
||||||
|
for examExes in lecExamExes
|
||||||
|
]
|
||||||
|
|
||||||
|
result += [
|
||||||
|
ExamLines(
|
||||||
|
date=attendance.date,
|
||||||
|
time=attendance.time,
|
||||||
|
weekday=attendance.weekday,
|
||||||
|
location=None,
|
||||||
|
subjectIds=None,
|
||||||
|
examIdent=None,
|
||||||
|
examStudCound=None,
|
||||||
|
supervisorType="PRÄSENZ",
|
||||||
|
executionSet=None,
|
||||||
|
partialExamIdent=None,
|
||||||
|
)
|
||||||
|
for attendance in lecAttendance
|
||||||
|
]
|
||||||
|
|
||||||
|
result = sort_and_group_datetime(result)
|
||||||
|
|
||||||
|
return result
|
49
Backend/requirements.txt
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
pyasn1>=0.3.7
|
||||||
|
Django>=2
|
||||||
|
ldap3>=2.3
|
||||||
|
openpyxl>=2.4.8
|
||||||
|
pytz>=2018.7
|
||||||
|
asgiref>=3.7.2
|
||||||
|
boto3>=1.20.26
|
||||||
|
botocore>=1.23.54
|
||||||
|
certifi>=2023.11.17
|
||||||
|
cffi>=1.16.0
|
||||||
|
charset-normalizer>=3.3.2
|
||||||
|
cryptography>=41.0.7
|
||||||
|
dj-database-url>=2.1.0
|
||||||
|
Django>=4.2.7
|
||||||
|
django-anymail>=10.2
|
||||||
|
django-cors-headers>=3.14.0
|
||||||
|
django-jazzmin>=2.6.0
|
||||||
|
django-storages>=1.12.3
|
||||||
|
django-ckeditor-5
|
||||||
|
djangorestframework>=3.14.0
|
||||||
|
djangorestframework-simplejwt>=5.2.2
|
||||||
|
drf-yasg>=1.21.7
|
||||||
|
environs>=10.0.0
|
||||||
|
gunicorn>=21.2.0
|
||||||
|
idna>=3.6
|
||||||
|
inflection>=0.5.1
|
||||||
|
jmespath>=0.10.0
|
||||||
|
marshmallow>=3.20.1
|
||||||
|
packaging>=23.2
|
||||||
|
psycopg2>=2.9.9
|
||||||
|
pycparser>=2.21
|
||||||
|
PyJWT>=2.6.0
|
||||||
|
python-dateutil>=2.8.2
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
pytz>=2023.3.post1
|
||||||
|
PyYAML>=6.0.1
|
||||||
|
requests>=2.31.0
|
||||||
|
s3transfer>=0.5.2
|
||||||
|
shortuuid>=1.0.11
|
||||||
|
six>=1.16.0
|
||||||
|
sqlparse>=0.4.4
|
||||||
|
stripe>=7.9.0
|
||||||
|
typing_extensions>=4.9.0
|
||||||
|
tzdata>=2023.3
|
||||||
|
uritemplate>=4.1.1
|
||||||
|
urllib3>=1.26.18
|
||||||
|
|
||||||
|
requests
|
||||||
|
|
1567
Backend/static/bootstrap/css/bootstrap-grid.css
vendored
Normal file
1
Backend/static/bootstrap/css/bootstrap-grid.css.map
Normal file
7
Backend/static/bootstrap/css/bootstrap-grid.min.css
vendored
Normal file
1
Backend/static/bootstrap/css/bootstrap-grid.min.css.map
Normal file
342
Backend/static/bootstrap/css/bootstrap-reboot.css
vendored
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v4.0.0-beta.2 (https://getbootstrap.com)
|
||||||
|
* Copyright 2011-2017 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2017 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: sans-serif;
|
||||||
|
line-height: 1.15;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-ms-overflow-style: scrollbar;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-ms-viewport {
|
||||||
|
width: device-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[tabindex="-1"]:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title],
|
||||||
|
abbr[data-original-title] {
|
||||||
|
text-decoration: underline;
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
dfn {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: transparent;
|
||||||
|
-webkit-text-decoration-skip: objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #0056b3;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([tabindex]) {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([tabindex]):focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
-ms-overflow-style: scrollbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg:not(:root) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
area,
|
||||||
|
button,
|
||||||
|
[role="button"],
|
||||||
|
input:not([type="range"]),
|
||||||
|
label,
|
||||||
|
select,
|
||||||
|
summary,
|
||||||
|
textarea {
|
||||||
|
-ms-touch-action: manipulation;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
color: #868e96;
|
||||||
|
text-align: left;
|
||||||
|
caption-side: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
outline: 1px dotted;
|
||||||
|
outline: 5px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
html [type="button"],
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="date"],
|
||||||
|
input[type="time"],
|
||||||
|
input[type="datetime-local"],
|
||||||
|
input[type="month"] {
|
||||||
|
-webkit-appearance: listbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="search"] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="search"]::-webkit-search-cancel-button,
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
1
Backend/static/bootstrap/css/bootstrap-reboot.css.map
Normal file
8
Backend/static/bootstrap/css/bootstrap-reboot.min.css
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v4.0.0-beta.2 (https://getbootstrap.com)
|
||||||
|
* Copyright 2011-2017 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2017 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}[role=button],a,area,button,input:not([type=range]),label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#868e96;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
8374
Backend/static/bootstrap/css/bootstrap.css
vendored
Normal file
1
Backend/static/bootstrap/css/bootstrap.css.map
Normal file
7
Backend/static/bootstrap/css/bootstrap.min.css
vendored
Normal file
1
Backend/static/bootstrap/css/bootstrap.min.css.map
Normal file
6287
Backend/static/bootstrap/js/bootstrap.bundle.js
vendored
Normal file
1
Backend/static/bootstrap/js/bootstrap.bundle.js.map
Normal file
7
Backend/static/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
1
Backend/static/bootstrap/js/bootstrap.bundle.min.js.map
Normal file
3850
Backend/static/bootstrap/js/bootstrap.js
vendored
Normal file
1
Backend/static/bootstrap/js/bootstrap.js.map
Normal file
7
Backend/static/bootstrap/js/bootstrap.min.js
vendored
Normal file
1
Backend/static/bootstrap/js/bootstrap.min.js.map
Normal file
231
Backend/static/css/bots.css
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
@import url('http://fonts.googleapis.com/css?family=Kalam');
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
font-family: 'ChicagoFLFRegular';
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 90%;
|
||||||
|
color: white;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
height: 200px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sectionhash {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sectionwees {
|
||||||
|
height: 340px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sectionanswer {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#sectionform {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
height: 200px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
padding-right:10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
outline-color: #b4363b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
input.levelradio {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hashtags {
|
||||||
|
font-size: x-small;
|
||||||
|
max-width:400px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; bottom: 0; right: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
width: 50%;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
left: 0; bottom: 0; right: 0;
|
||||||
|
text-align: center;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerimg {
|
||||||
|
max-width:400px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wees {
|
||||||
|
max-width:400px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; bottom: 0; right: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#answer {
|
||||||
|
max-width:400px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; bottom: 0; right: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#applyform {
|
||||||
|
max-width:400px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; bottom: 0; right: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#apply {
|
||||||
|
background-color: #b4363b;
|
||||||
|
border: medium solid #b4363b;
|
||||||
|
font-family: 'ChicagoFLFRegular';
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
color: white;
|
||||||
|
font-size: medium;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
max-width:400px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; bottom: 0; right: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#planetdiv {
|
||||||
|
position: fixed;
|
||||||
|
padding: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 330px;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#planetcut {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
left: 0; bottom: 0; right: 0;
|
||||||
|
text-align: center;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
z-index: -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kurzlogo {
|
||||||
|
width: 150px;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
left: 30px; bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#thlogo {
|
||||||
|
width: 210px;
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
left: 210px; bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#skydiv {
|
||||||
|
position: fixed;
|
||||||
|
padding: 10px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 600px;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#skyl{
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#skyr {
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
1
Backend/static/css/dropzone.min.css
vendored
Normal file
58
Backend/static/css/medinf.css
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#navbar-efi {
|
||||||
|
background-color: #ffe240;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Login Dropdown */
|
||||||
|
|
||||||
|
#login-dp{
|
||||||
|
min-width: 250px;
|
||||||
|
padding: 14px 14px 0;
|
||||||
|
overflow:hidden;
|
||||||
|
background-color:rgba(255,255,255,.8);
|
||||||
|
}
|
||||||
|
#login-dp .bottom{
|
||||||
|
background-color:rgba(255,255,255,.8);
|
||||||
|
border-top:1px solid #ddd;
|
||||||
|
clear:both;
|
||||||
|
padding:14px;
|
||||||
|
}
|
||||||
|
#login-dp .form-group {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-button {
|
||||||
|
text-align: right;
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-button:focus {
|
||||||
|
border-color: rgba(0, 0, 0, 0.8);
|
||||||
|
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(126, 239, 104, 0.6);
|
||||||
|
outline: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconic-warning {
|
||||||
|
fill: #ffe240;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media(max-width:768px){
|
||||||
|
#login-dp{
|
||||||
|
background-color: inherit;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#login-dp .bottom{
|
||||||
|
background-color: inherit;
|
||||||
|
border-top:0 none;
|
||||||
|
}
|
||||||
|
}
|
3
Backend/static/css/pruefplan.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.table-nostriped tbody tr:nth-of-type(odd) {
|
||||||
|
background-color:transparent;
|
||||||
|
}
|
BIN
Backend/static/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
23
Backend/static/fileUpload/bootstrap.fd.css
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
.bfd-dropfield {
|
||||||
|
display: inline;
|
||||||
|
padding: 5px; }
|
||||||
|
|
||||||
|
.bfd-dropfield-inner {
|
||||||
|
border: 5px dashed #888;
|
||||||
|
color: #888;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 32px;
|
||||||
|
text-align: center; }
|
||||||
|
.bfd-dropfield-inner:hover, .bfd-dropfield-inner.bfd-dragover {
|
||||||
|
border-color: #bbb;
|
||||||
|
color: #bbb; }
|
||||||
|
|
||||||
|
.bfd-info {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap; }
|
||||||
|
|
||||||
|
.bfd-error-message {
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
|
.bfd-remove {
|
||||||
|
cursor: pointer; }
|
1
Backend/static/fileUpload/bootstrap.fd.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!function(e){"use strict";e.FileDialog=function(a){var o=e.extend(e.FileDialog.defaults,a),t=e(["<div class='modal fade'>"," <div class='modal-dialog'>"," <div class='modal-content'>"," <div class='modal-header'>"," <button type='button' class='close' data-dismiss='modal'>"," <span aria-hidden='true'>×</span>"," <span class='sr-only'>",o.cancel_button," </span>"," </button>"," <h4 class='modal-title'>",o.title," </h4>"," </div>"," <div class='modal-body'>"," <input type='file' />"," <div class='bfd-dropfield'>"," <div class='bfd-dropfield-inner'>",o.drag_message," </div>"," </div>"," <div class='container-fluid bfd-files'>"," </div>"," </div>"," <div class='modal-footer'>"," <button type='button' class='btn btn-primary bfd-ok'>",o.ok_button," </button>"," <button type='button' class='btn btn-default bfd-cancel'"," data-dismiss='modal'>",o.cancel_button," </button>"," </div>"," </div>"," </div>","</div>"].join("")),n=!1,r=e("input:file",t),i=e(".bfd-dropfield",t),s=e(".bfd-dropfield-inner",i);s.css({height:o.dropheight,"padding-top":o.dropheight/2-32}),r.attr({accept:o.accept,multiple:o.multiple}),i.on("click.bfd",function(){r.trigger("click")});var d=[],l=[],c=function(a){var n,r,i=new FileReader;l.push(i),i.onloadstart=function(){},i.onerror=function(e){e.target.error.code!==e.target.error.ABORT_ERR&&n.parent().html(["<div class='bg-danger bfd-error-message'>",o.error_message,"</div>"].join("\n"))},i.onprogress=function(a){var o=Math.round(100*a.loaded/a.total)+"%";n.attr("aria-valuenow",a.loaded),n.css("width",o),e(".sr-only",n).text(o)},i.onload=function(e){a.content=e.target.result,d.push(a),n.removeClass("active")};var s=e(["<div class='col-xs-7 col-sm-4 bfd-info'>"," <span class='glyphicon glyphicon-remove bfd-remove'></span> "," <span class='glyphicon glyphicon-file'></span> "+a.name,"</div>","<div class='col-xs-5 col-sm-8 bfd-progress'>"," <div class='progress'>"," <div class='progress-bar progress-bar-striped active' role='progressbar'"," aria-valuenow='0' aria-valuemin='0' aria-valuemax='"+a.size+"'>"," <span class='sr-only'>0%</span>"," </div>"," </div>","</div>"].join(""));n=e(".progress-bar",s),e(".bfd-remove",s).tooltip({container:"body",html:!0,placement:"top",title:o.remove_message}).on("click.bfd",function(){var e=d.indexOf(a);e>=0&&d.pop(e),r.fadeOut();try{i.abort()}catch(o){}}),r=e("<div class='row'></div>"),r.append(s),e(".bfd-files",t).append(r),i["readAs"+o.readAs](a)},f=function(e){Array.prototype.forEach.apply(e,[c])};return r.change(function(e){e=e.originalEvent;var a=e.target.files;f(a);var o=r.clone(!0);r.replaceWith(o),r=o}),i.on("dragenter.bfd",function(){s.addClass("bfd-dragover")}).on("dragover.bfd",function(e){e=e.originalEvent,e.stopPropagation(),e.preventDefault(),e.dataTransfer.dropEffect="copy"}).on("dragleave.bfd drop.bfd",function(){s.removeClass("bfd-dragover")}).on("drop.bfd",function(e){e=e.originalEvent,e.stopPropagation(),e.preventDefault();var a=e.dataTransfer.files;0===a.length,f(a)}),e(".bfd-ok",t).on("click.bfd",function(){var a=e.Event("files.bs.filedialog");a.files=d,t.trigger(a),n=!0,t.modal("hide")}),t.on("hidden.bs.modal",function(){if(l.forEach(function(e){try{e.abort()}catch(a){}}),!n){var a=e.Event("cancel.bs.filedialog");t.trigger(a)}t.remove()}),e(document.body).append(t),t.modal(),t},e.FileDialog.defaults={accept:"*",cancel_button:"Close",drag_message:"Drop files here",dropheight:400,error_message:"An error occured while loading file",multiple:!0,ok_button:"OK",readAs:"DataURL",remove_message:"Remove file",title:"Load file(s)"}}(jQuery);
|
BIN
Backend/static/images/BOTS.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
Backend/static/images/TH.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
Backend/static/images/efi.jpg
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
Backend/static/images/kurz.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
Backend/static/images/planetcut.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
Backend/static/images/skyleft.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Backend/static/images/skyright.png
Normal file
After Width: | Height: | Size: 11 KiB |