Studien-Semester-Select: parit\u00e4ts- und umfangsgerecht (WS ungerade, SS gerade; BSc 1-6, MSc 1-4) aus CIS-Endpoint
This commit is contained in:
+13
@@ -297,6 +297,19 @@ class CISClient:
|
|||||||
matrikel=mail.split("@")[0] if "@" in mail else ""))
|
matrikel=mail.split("@")[0] if "@" in mail else ""))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
# ---------- Studien-Semester pro StG ----------
|
||||||
|
@lru_cache(maxsize=64)
|
||||||
|
def study_semesters(self, kz: str) -> list[int]:
|
||||||
|
"""Von CIS bekannte Studien-Semester eines StG (z.B. [1..6] BSc, [1..4] MSc)."""
|
||||||
|
try:
|
||||||
|
r = self.s.post(f"{self.base}/private/lvplan/lvplan_autocomplete.php",
|
||||||
|
data={"autocomplete": "getSemester", "stg_kz": kz}, timeout=30)
|
||||||
|
data = r.json()
|
||||||
|
vals = data.values() if isinstance(data, dict) else data
|
||||||
|
return sorted({int(x) for x in vals if str(x).strip().isdigit()})
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
# ---------- Verbände pro StG/Semester ----------
|
# ---------- Verbände pro StG/Semester ----------
|
||||||
def verbands_for(self, kz: str, sem: str) -> list[str]:
|
def verbands_for(self, kz: str, sem: str) -> list[str]:
|
||||||
fallback = ["V", "B", "G", "A", "C", "D", "E", "F"]
|
fallback = ["V", "B", "G", "A", "C", "D", "E", "F"]
|
||||||
|
|||||||
+52
-29
@@ -44,6 +44,23 @@ def _meta():
|
|||||||
return client.meta()
|
return client.meta()
|
||||||
|
|
||||||
|
|
||||||
|
def _study_sem_options(kz: str, semester_label: str) -> list[int]:
|
||||||
|
"""Gültige Studien-Semester eines StG, gefiltert nach Plan-Semester
|
||||||
|
(Winter=ungerade, Sommer=gerade)."""
|
||||||
|
sems = client.study_semesters(kz)
|
||||||
|
if not sems:
|
||||||
|
code = next((s["code"] for s in _meta()["stgs"] if s["kz"] == kz), "") or ""
|
||||||
|
max_n = 4 if code[:1] in ("M", "L") else 6
|
||||||
|
sems = list(range(1, max_n + 1))
|
||||||
|
label = (semester_label or "").upper()
|
||||||
|
if label.startswith("WS"):
|
||||||
|
sems = [n for n in sems if n % 2 == 1]
|
||||||
|
elif label.startswith("SS"):
|
||||||
|
sems = [n for n in sems if n % 2 == 0]
|
||||||
|
sems = sorted(set(sems))
|
||||||
|
return sems or [1]
|
||||||
|
|
||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
def home(request: Request):
|
def home(request: Request):
|
||||||
meta = _meta()
|
meta = _meta()
|
||||||
@@ -60,13 +77,16 @@ def _plan_common(request, kind, uid, kz, sem, ver, grp, semester, week="", tab="
|
|||||||
semester = semester or current_semester(meta["semesters"])
|
semester = semester or current_semester(meta["semesters"])
|
||||||
s = next((x for x in meta["semesters"] if x["label"] == semester), meta["semesters"][0])
|
s = next((x for x in meta["semesters"] if x["label"] == semester), meta["semesters"][0])
|
||||||
person = None
|
person = None
|
||||||
|
sems: list[int] = []
|
||||||
if kind == "student":
|
if kind == "student":
|
||||||
uid = uid or OWN_UID
|
uid = uid or OWN_UID
|
||||||
person = client.person(uid)
|
person = client.person(uid)
|
||||||
events = client.student_events(uid, s["begin"], s["ende"])
|
events = client.student_events(uid, s["begin"], s["ende"])
|
||||||
else:
|
else:
|
||||||
kz = kz or "640"
|
kz = kz or "640"
|
||||||
sem = sem or "1"
|
sems = _study_sem_options(kz, semester)
|
||||||
|
if sem not in [str(n) for n in sems]:
|
||||||
|
sem = str(sems[0])
|
||||||
ver = ver or "V"
|
ver = ver or "V"
|
||||||
events = client.stg_events(kz, sem, ver, grp, s["begin"], s["ende"])
|
events = client.stg_events(kz, sem, ver, grp, s["begin"], s["ende"])
|
||||||
# initial week: explicit param, today if inside semester, else first event week
|
# initial week: explicit param, today if inside semester, else first event week
|
||||||
@@ -93,6 +113,7 @@ def _plan_common(request, kind, uid, kz, sem, ver, grp, semester, week="", tab="
|
|||||||
"uid": uid or "",
|
"uid": uid or "",
|
||||||
"kz": kz or "",
|
"kz": kz or "",
|
||||||
"sem": sem or "",
|
"sem": sem or "",
|
||||||
|
"sems": sems,
|
||||||
"ver": ver or "",
|
"ver": ver or "",
|
||||||
"grp": grp or "",
|
"grp": grp or "",
|
||||||
"stg": stg,
|
"stg": stg,
|
||||||
@@ -144,6 +165,9 @@ def liste(request: Request,
|
|||||||
sem: str = Query("1"),
|
sem: str = Query("1"),
|
||||||
ver: str = Query("V")):
|
ver: str = Query("V")):
|
||||||
meta = _meta()
|
meta = _meta()
|
||||||
|
sems = _study_sem_options(kz, current_semester(meta["semesters"]))
|
||||||
|
if sem not in [str(n) for n in sems]:
|
||||||
|
sem = str(sems[0])
|
||||||
students = client.namensliste(kz, sem, ver)
|
students = client.namensliste(kz, sem, ver)
|
||||||
stg = next((x for x in meta["stgs"] if x["kz"] == kz), None)
|
stg = next((x for x in meta["stgs"] if x["kz"] == kz), None)
|
||||||
verbands = client.verbands_for(kz, sem)
|
verbands = client.verbands_for(kz, sem)
|
||||||
@@ -155,6 +179,7 @@ def liste(request: Request,
|
|||||||
"meta": meta,
|
"meta": meta,
|
||||||
"kz": kz,
|
"kz": kz,
|
||||||
"sem": sem,
|
"sem": sem,
|
||||||
|
"sems": sems,
|
||||||
"ver": ver,
|
"ver": ver,
|
||||||
"verbands": verbands,
|
"verbands": verbands,
|
||||||
"stg": stg,
|
"stg": stg,
|
||||||
@@ -214,6 +239,9 @@ def fotos_page(request: Request,
|
|||||||
sem: str = Query("1"),
|
sem: str = Query("1"),
|
||||||
ver: str = Query("V")):
|
ver: str = Query("V")):
|
||||||
meta = _meta()
|
meta = _meta()
|
||||||
|
sems = _study_sem_options(kz, current_semester(meta["semesters"]))
|
||||||
|
if sem not in [str(n) for n in sems]:
|
||||||
|
sem = str(sems[0])
|
||||||
stg = next((x for x in meta["stgs"] if x["kz"] == kz), None)
|
stg = next((x for x in meta["stgs"] if x["kz"] == kz), None)
|
||||||
verbands = client.verbands_for(kz, sem)
|
verbands = client.verbands_for(kz, sem)
|
||||||
wall = _photo_wall(kz, sem, ver)
|
wall = _photo_wall(kz, sem, ver)
|
||||||
@@ -222,6 +250,7 @@ def fotos_page(request: Request,
|
|||||||
"meta": meta,
|
"meta": meta,
|
||||||
"kz": kz,
|
"kz": kz,
|
||||||
"sem": sem,
|
"sem": sem,
|
||||||
|
"sems": sems,
|
||||||
"ver": ver,
|
"ver": ver,
|
||||||
"verbands": verbands,
|
"verbands": verbands,
|
||||||
"stg": stg,
|
"stg": stg,
|
||||||
@@ -248,32 +277,32 @@ def fotos_suche(request: Request,
|
|||||||
sem: str = Form("1"),
|
sem: str = Form("1"),
|
||||||
ver: str = Form("V"),
|
ver: str = Form("V"),
|
||||||
file: UploadFile = File(...)):
|
file: UploadFile = File(...)):
|
||||||
|
meta = _meta()
|
||||||
|
sems = _study_sem_options(kz, current_semester(meta["semesters"]))
|
||||||
|
if sem not in [str(n) for n in sems]:
|
||||||
|
sem = str(sems[0])
|
||||||
|
base_ctx = {
|
||||||
|
"request": request,
|
||||||
|
"kz": kz, "sem": sem, "sems": sems, "ver": ver,
|
||||||
|
"results": [], "meta": meta,
|
||||||
|
"stg": next((x for x in meta["stgs"] if x["kz"] == kz), None),
|
||||||
|
"verbands": client.verbands_for(kz, sem),
|
||||||
|
"own_uid": OWN_UID,
|
||||||
|
}
|
||||||
data = file.file.read()
|
data = file.file.read()
|
||||||
if not data:
|
if not data:
|
||||||
return templates.TemplateResponse(request, "fotos_result.html", {
|
return templates.TemplateResponse(request, "fotos_result.html",
|
||||||
"request": request, "error": "Leere Datei hochgeladen.",
|
{**base_ctx, "error": "Leere Datei hochgeladen."},
|
||||||
"kz": kz, "sem": sem, "ver": ver, "results": [], "meta": _meta(),
|
status_code=400)
|
||||||
"stg": next((x for x in _meta()["stgs"] if x["kz"] == kz), None),
|
|
||||||
"verbands": client.verbands_for(kz, sem),
|
|
||||||
"own_uid": OWN_UID,
|
|
||||||
}, status_code=400)
|
|
||||||
if len(data) > MAX_UPLOAD:
|
if len(data) > MAX_UPLOAD:
|
||||||
return templates.TemplateResponse(request, "fotos_result.html", {
|
return templates.TemplateResponse(request, "fotos_result.html",
|
||||||
"request": request, "error": "Datei zu groß (max. 8 MB).",
|
{**base_ctx, "error": "Datei zu groß (max. 8 MB)."},
|
||||||
"kz": kz, "sem": sem, "ver": ver, "results": [], "meta": _meta(),
|
status_code=400)
|
||||||
"stg": next((x for x in _meta()["stgs"] if x["kz"] == kz), None),
|
|
||||||
"verbands": client.verbands_for(kz, sem),
|
|
||||||
"own_uid": OWN_UID,
|
|
||||||
}, status_code=400)
|
|
||||||
emb = engine.embed(data)
|
emb = engine.embed(data)
|
||||||
if emb is None:
|
if emb is None:
|
||||||
return templates.TemplateResponse(request, "fotos_result.html", {
|
return templates.TemplateResponse(request, "fotos_result.html",
|
||||||
"request": request, "error": "Kein Gesicht im hochgeladenen Bild gefunden.",
|
{**base_ctx, "error": "Kein Gesicht im hochgeladenen Bild gefunden."},
|
||||||
"kz": kz, "sem": sem, "ver": ver, "results": [], "meta": _meta(),
|
status_code=422)
|
||||||
"stg": next((x for x in _meta()["stgs"] if x["kz"] == kz), None),
|
|
||||||
"verbands": client.verbands_for(kz, sem),
|
|
||||||
"own_uid": OWN_UID,
|
|
||||||
}, status_code=422)
|
|
||||||
wall = _photo_wall(kz, sem, ver)
|
wall = _photo_wall(kz, sem, ver)
|
||||||
results = []
|
results = []
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
@@ -291,18 +320,12 @@ def fotos_suche(request: Request,
|
|||||||
results.append({**p, "sim": round(sim, 4),
|
results.append({**p, "sim": round(sim, 4),
|
||||||
"img": f"/fotos/img/{pid}"})
|
"img": f"/fotos/img/{pid}"})
|
||||||
results.sort(key=lambda r: r["sim"], reverse=True)
|
results.sort(key=lambda r: r["sim"], reverse=True)
|
||||||
meta = _meta()
|
|
||||||
return templates.TemplateResponse(request, "fotos_result.html", {
|
return templates.TemplateResponse(request, "fotos_result.html", {
|
||||||
"request": request,
|
**base_ctx,
|
||||||
"error": None,
|
"error": None,
|
||||||
"took": round(time.time() - t0, 1),
|
"took": round(time.time() - t0, 1),
|
||||||
"query": {"kz": kz, "sem": sem, "ver": ver},
|
"query": {"kz": kz, "sem": sem, "ver": ver},
|
||||||
"kz": kz, "sem": sem, "ver": ver,
|
|
||||||
"results": results[:30],
|
"results": results[:30],
|
||||||
"meta": meta,
|
|
||||||
"stg": next((x for x in meta["stgs"] if x["kz"] == kz), None),
|
|
||||||
"verbands": client.verbands_for(kz, sem),
|
|
||||||
"own_uid": OWN_UID,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+26
-1
@@ -380,7 +380,32 @@ function decodeMat() {
|
|||||||
box.classList.add("show");
|
box.classList.add("show");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= Verband-Selects dynamisch ================= */
|
/* ================= Seminar- + Verband-Selects dynamisch ================= */
|
||||||
|
async function reloadSemesters() {
|
||||||
|
const sel = $("#sem-select");
|
||||||
|
const stgSel = $("#stg-select");
|
||||||
|
if (!sel || !stgSel) return;
|
||||||
|
const kz = stgSel.value;
|
||||||
|
const planSem = (($('select[name="semester"]') || {}).value || "").toUpperCase();
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/semesters_for_stg?kz=${encodeURIComponent(kz)}`);
|
||||||
|
if (!r.ok) throw new Error("bad status");
|
||||||
|
let data = await r.json();
|
||||||
|
let arr = (Array.isArray(data) ? data : Object.values(data))
|
||||||
|
.map(Number).filter(n => Number.isInteger(n) && n > 0);
|
||||||
|
if (planSem.startsWith("WS")) arr = arr.filter(n => n % 2 === 1);
|
||||||
|
else if (planSem.startsWith("SS")) arr = arr.filter(n => n % 2 === 0);
|
||||||
|
arr = [...new Set(arr)].sort((a, b) => a - b);
|
||||||
|
if (arr.length) {
|
||||||
|
const current = sel.value;
|
||||||
|
sel.innerHTML = arr.map(n =>
|
||||||
|
`<option value="${n}"${n === Number(current) ? " selected" : ""}>${n}</option>`).join("");
|
||||||
|
if (current && !arr.includes(Number(current))) sel.selectedIndex = 0;
|
||||||
|
}
|
||||||
|
} catch (e) { /* Keep static options */ }
|
||||||
|
reloadVerbands();
|
||||||
|
}
|
||||||
|
|
||||||
async function reloadVerbands() {
|
async function reloadVerbands() {
|
||||||
const sel = $("#ver-select");
|
const sel = $("#ver-select");
|
||||||
if (!sel) return;
|
if (!sel) return;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Studien-Semester</label>
|
<label>Studien-Semester</label>
|
||||||
<select name="sem">
|
<select name="sem">
|
||||||
{% for n in range(1, 9) %}
|
{% for n in sems %}
|
||||||
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Studien-Semester</label>
|
<label>Studien-Semester</label>
|
||||||
<select name="sem">
|
<select name="sem">
|
||||||
{% for n in range(1, 9) %}
|
{% for n in sems %}
|
||||||
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Studien-Semester</label>
|
<label>Studien-Semester</label>
|
||||||
<select name="sem" id="sem-select" onchange="reloadVerbands()">
|
<select name="sem" id="sem-select" onchange="reloadVerbands()">
|
||||||
{% for n in range(1, 9) %}
|
{% for n in sems %}
|
||||||
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Studiengang</label>
|
<label>Studiengang</label>
|
||||||
<select name="kz" id="stg-select" required onchange="reloadVerbands()">
|
<select name="kz" id="stg-select" required onchange="reloadSemesters()">
|
||||||
{% for s in meta.stgs %}
|
{% for s in meta.stgs %}
|
||||||
<option value="{{ s.kz }}" {{ 'selected' if s.kz == kz }}>{{ s.code }} - {{ s.name }}</option>
|
<option value="{{ s.kz }}" {{ 'selected' if s.kz == kz }}>{{ s.code }} - {{ s.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Studien-Sem.</label>
|
<label>Studien-Sem.</label>
|
||||||
<select name="sem" id="sem-select" onchange="reloadVerbands()">
|
<select name="sem" id="sem-select" onchange="reloadVerbands()">
|
||||||
{% for n in range(1, 9) %}
|
{% for n in sems %}
|
||||||
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
Reference in New Issue
Block a user