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 |