This commit is contained in:
2024-05-20 20:36:46 +02:00
parent d6f7fa0661
commit 2153bb07b9
6 changed files with 373 additions and 306 deletions

View File

@ -7,7 +7,8 @@ from get_mysql import get_mysql
import atexit import atexit
from flask_apscheduler import APScheduler from flask_apscheduler import APScheduler
def create():
def create(testing: bool = False):
""" """
Erstellt die Flask-App inkl. Datenbank und Login-Manager. Erstellt die Flask-App inkl. Datenbank und Login-Manager.
:return app: :return app:
@ -25,6 +26,7 @@ def create():
login_manager.login_view = "login" login_manager.login_view = "login"
# Shut down the scheduler when exiting the app # Shut down the scheduler when exiting the app
if not testing:
atexit.register(lambda: scheduler.shutdown()) atexit.register(lambda: scheduler.shutdown())
@login_manager.user_loader @login_manager.user_loader

View File

@ -12,7 +12,7 @@ bs4~=0.0.1
pytz~=2023.3.post1 pytz~=2023.3.post1
flask_talisman flask_talisman
asyncio~=3.4.3 asyncio~=3.4.3
httpx~=1.0.0b0 httpx
celery~=5.4.0rc2 celery~=5.4.0rc2
flask[async] flask[async]
pymysql pymysql

View File

@ -18,18 +18,22 @@ from calendar_generation import getWeek
from init import * from init import *
@app.route("/") def init_routes(flask_app: Flask):
def index(): """
Initialisiert die App-Routen. Nötig für Tests.
"""
@flask_app.route("/")
def index():
""" """
Leitet den normalen Website-Aufruf zum Login weiter. Leitet den normalen Website-Aufruf zum Login weiter.
:return HTML: :return HTML:
""" """
return redirect(url_for("login")) return redirect(url_for("login"))
@flask_app.route("/dashboard")
@app.route("/dashboard") @login_required
@login_required def welcome():
def welcome():
""" """
Dashboard Dashboard
:return HTML: :return HTML:
@ -49,10 +53,9 @@ def welcome():
p = "" p = ""
return render_template('dashboard.html', kurs=kurs, name=name, theorie=t, praxis=p) return render_template('dashboard.html', kurs=kurs, name=name, theorie=t, praxis=p)
@flask_app.route("/theorie/noten", methods=["GET", "POST"])
@app.route("/theorie/noten", methods=["GET", "POST"]) @login_required
@login_required async def displayNoten():
async def displayNoten():
""" """
Zeigt die Noten aus Dualis an. Hierfür ist ein aktives Token nötig. Zeigt die Noten aus Dualis an. Hierfür ist ein aktives Token nötig.
:return HTML: :return HTML:
@ -73,10 +76,9 @@ async def displayNoten():
noten = await fetchDUALIS.getResults(t, c, chosensemester) noten = await fetchDUALIS.getResults(t, c, chosensemester)
return render_template("noten.html", noten=noten, semester=semester, sel=chosensemester, s="n", praxis="hidden") return render_template("noten.html", noten=noten, semester=semester, sel=chosensemester, s="n", praxis="hidden")
@flask_app.route("/plan", methods=["GET"])
@app.route("/plan", methods=["GET"]) @login_required
@login_required async def displayRapla():
async def displayRapla():
""" """
Zeigt den Stundenplan für eingeloggte User an. \n Zeigt den Stundenplan für eingeloggte User an. \n
TODO: Persönliche Filter, Notizen, Essensvorlieben etc. berücksichtigen. TODO: Persönliche Filter, Notizen, Essensvorlieben etc. berücksichtigen.
@ -94,12 +96,12 @@ async def displayRapla():
samstag = False samstag = False
events = await getWeek(week, fetchRAPLA.getIcal(current_user.kurs), samstag) events = await getWeek(week, fetchRAPLA.getIcal(current_user.kurs), samstag)
return render_template("plan-user.html", events=events[0], eventdays=events[1], return render_template("plan-user.html", events=events[0], eventdays=events[1],
name=current_user.name, prev=str(events[2])[:10], next=str(events[3])[:10], mon=events[4], name=current_user.name, prev=str(events[2])[:10], next=str(events[3])[:10],
mon=events[4],
s="p", praxis="hidden") s="p", praxis="hidden")
@flask_app.route("/plan/<string:kurs>")
@app.route("/plan/<string:kurs>") async def displayPlan(kurs):
async def displayPlan(kurs):
""" """
Zeigt den Stundenplan ohne Login an. \n Zeigt den Stundenplan ohne Login an. \n
Präferenzen werden nicht berücksichtigt. Präferenzen werden nicht berücksichtigt.
@ -128,19 +130,17 @@ async def displayPlan(kurs):
else: else:
return redirect(url_for("login")) return redirect(url_for("login"))
@flask_app.route("/set-up")
@app.route("/set-up") def redKurs():
def redKurs():
""" """
Setup beginnt mit Kurs. Setup beginnt mit Kurs.
:return HTML: :return HTML:
""" """
return redirect(url_for("getKurs")) return redirect(url_for("getKurs"))
@flask_app.route("/set-up/kurs")
@app.route("/set-up/kurs") @login_required
@login_required async def getKurs():
async def getKurs():
""" """
Automatische Kurs-Auswahl. \n Automatische Kurs-Auswahl. \n
Aktives Dualis-Token benötigt. Aktives Dualis-Token benötigt.
@ -157,7 +157,8 @@ async def getKurs():
kurs = await fetchDUALIS.getKurs(d.token, cookie) kurs = await fetchDUALIS.getKurs(d.token, cookie)
if kurs != 0: if kurs != 0:
if not fetchRAPLA.getIcal(kurs): if not fetchRAPLA.getIcal(kurs):
return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", praxis="hidden", return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden",
praxis="hidden",
file=False) file=False)
current_user.kurs = kurs current_user.kurs = kurs
db.session.commit() db.session.commit()
@ -172,10 +173,9 @@ async def getKurs():
kurs = "" kurs = ""
return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", praxis="hidden", file=True) return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", praxis="hidden", file=True)
@flask_app.route("/set-up/semester")
@app.route("/set-up/semester") @login_required
@login_required async def getSemester():
async def getSemester():
""" """
Manuelle Semester-Auswahl. Manuelle Semester-Auswahl.
:return HTML: :return HTML:
@ -186,17 +186,17 @@ async def getSemester():
if not semesterlist: if not semesterlist:
semester = await fetchDUALIS.getSem(t, c) semester = await fetchDUALIS.getSem(t, c)
for i in semester: for i in semester:
semitem = Semesterlist(semestername=i[0], semesterid=i[1], uid=current_user.id, itemid=current_user.id*int(i[1][-7:])//1000000) semitem = Semesterlist(semestername=i[0], semesterid=i[1], uid=current_user.id,
itemid=current_user.id * int(i[1][-7:]) // 1000000)
db.session.add(semitem) db.session.add(semitem)
db.session.commit() db.session.commit()
else: else:
semester = getSemesterList(current_user.id) semester = getSemesterList(current_user.id)
return render_template("semester.html", semester=semester, s="s", theorie="hidden", praxis="hidden") return render_template("semester.html", semester=semester, s="s", theorie="hidden", praxis="hidden")
@flask_app.route("/set-up/semester", methods=["POST"])
@app.route("/set-up/semester", methods=["POST"]) @login_required
@login_required def setSemester():
def setSemester():
""" """
Speichern der Semester-Auswahl. Speichern der Semester-Auswahl.
:return HTML: :return HTML:
@ -209,10 +209,9 @@ def setSemester():
db.session.commit() db.session.commit()
return redirect(n) return redirect(n)
@flask_app.route("/set-up/rapla")
@app.route("/set-up/rapla") @login_required
@login_required def chooseRaplas():
def chooseRaplas():
""" """
Manuelle Rapla-Auswahl. Manuelle Rapla-Auswahl.
:return HTML: :return HTML:
@ -220,10 +219,9 @@ def chooseRaplas():
r = getRaplas() r = getRaplas()
return render_template("rapla.html", raplas=r, s="s", theorie="hidden", praxis="hidden") return render_template("rapla.html", raplas=r, s="s", theorie="hidden", praxis="hidden")
@flask_app.route("/set-up/rapla", methods=["POST"])
@app.route("/set-up/rapla", methods=["POST"]) @login_required
@login_required async def getRapla():
async def getRapla():
""" """
Verarbeitet die Eingabe von chooseRaplas(). Verarbeitet die Eingabe von chooseRaplas().
:return HTML: :return HTML:
@ -244,18 +242,16 @@ async def getRapla():
return redirect(url_for("error", ecode=900)) return redirect(url_for("error", ecode=900))
return redirect(url_for("welcome")) return redirect(url_for("welcome"))
@flask_app.route("/log-in")
@app.route("/log-in") def login():
def login():
""" """
Login-Maske. Login-Maske.
:return HTML: :return HTML:
""" """
return render_template("login.html", theorie="hidden", praxis="hidden", s="s") return render_template("login.html", theorie="hidden", praxis="hidden", s="s")
@flask_app.route("/log-in", methods=["POST"])
@app.route("/log-in", methods=["POST"]) async def login_post():
async def login_post():
""" """
Verarbeitet die Eingabe von login(). \n Verarbeitet die Eingabe von login(). \n
Falls der User schon angelegt ist, wird das Passwort verglichen. \n Falls der User schon angelegt ist, wird das Passwort verglichen. \n
@ -304,10 +300,9 @@ async def login_post():
success.set_cookie("cnsc", value=cookie, httponly=True, secure=True) success.set_cookie("cnsc", value=cookie, httponly=True, secure=True)
return success return success
@flask_app.route("/log-out")
@app.route("/log-out") @login_required
@login_required async def logout():
async def logout():
""" """
Loggt den User aus. Loggt den User aus.
:return Empty Token: :return Empty Token:
@ -324,9 +319,8 @@ async def logout():
secure=True) secure=True)
return red return red
@flask_app.route("/error")
@app.route("/error") def error():
def error():
""" """
Error Page für custom-Errors. \n Error Page für custom-Errors. \n
TODO: Funktion depreciaten. Ersetzen durch Errors auf den entsprechenden Seiten. TODO: Funktion depreciaten. Ersetzen durch Errors auf den entsprechenden Seiten.
@ -341,10 +335,9 @@ def error():
msg = str(error) msg = str(error)
return render_template('display-message.html', message=msg) return render_template('display-message.html', message=msg)
@flask_app.route("/error")
@app.route("/error") @flask_app.errorhandler(HTTPException)
@app.errorhandler(HTTPException) def handle(e):
def handle(e):
"""" """"
HTTP-Exception-Handler HTTP-Exception-Handler
""" """
@ -352,4 +345,5 @@ def handle(e):
if __name__ == "__main__": if __name__ == "__main__":
init_routes(app)
app.run(host='0.0.0.0', port=2024, debug=True) app.run(host='0.0.0.0', port=2024, debug=True)

View File

@ -0,0 +1,2 @@
email = "EMAIL-GOES-HERE"
password = "PASSWORD-GOES-HERE"

View File

@ -0,0 +1,69 @@
import pytest
from bs4 import BeautifulSoup
import routing
import init
from tests_examples import login_data
@pytest.fixture()
def app():
app = init.create(testing=True)
app.config.update({
"TESTING": True,
})
routing.init_routes(app)
# other setup can go here
yield app
# clean up / reset resources here
@pytest.fixture()
def client(app):
return app.test_client()
@pytest.fixture()
def runner(app):
return app.test_cli_runner()
def login(client):
client.post('/log-in', data=dict(email=login_data.email, password=login_data.password),
follow_redirects=True)
cookie = client.get_cookie("cnsc")
return len(cookie.value) == 32
def test_login(client):
loginpage = client.get("/log-in", follow_redirects=True)
assert b"Einloggen" in loginpage.data
assert loginpage.status_code == 200
loginpage_html = BeautifulSoup(loginpage.text, "lxml")
login_form = loginpage_html.find("form")
login_action = login_form.get("action")
login_request = client.post(login_action, data=dict(email=login_data.email, password=login_data.password),
follow_redirects=True)
assert login_request.status_code == 200
assert b"Willkommen, " in login_request.data
def test_noten(client):
if login(client):
notenpage = client.get("/theorie/noten", follow_redirects=True)
assert notenpage.status_code == 200
assert b"Deine Noten im" in notenpage.data
def test_logout(client):
if login(client):
loginpage = client.get("/log-out", follow_redirects=True)
assert loginpage.status_code == 200
assert b"Einloggen" in loginpage.data
cookie = client.get_cookie("cnsc")
assert len(cookie.value) != 32 # CNSC-Länge: 32 → CNSC darf ausgeloggt nicht gesetzt sein

View File

@ -8,7 +8,7 @@ from bs4 import BeautifulSoup
from celery import Celery from celery import Celery
import fetchDUALIS import fetchDUALIS
from login_data import passwort, email #CREATE LOCAL login_data.py!!! from login_data import password, email #CREATE LOCAL login_data.py!!!
app = Flask(__name__) app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0' app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
@ -26,7 +26,7 @@ headers = {
} }
url = "https://dualis.dhbw.de/scripts/mgrqispi.dll" url = "https://dualis.dhbw.de/scripts/mgrqispi.dll"
fpw = urllib.parse.quote(passwort, safe='', encoding=None, errors=None) fpw = urllib.parse.quote(password, safe='', encoding=None, errors=None)
fmail = urllib.parse.quote(email, safe='', encoding=None, errors=None) fmail = urllib.parse.quote(email, safe='', encoding=None, errors=None)