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 ""))
|
||||
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 ----------
|
||||
def verbands_for(self, kz: str, sem: str) -> list[str]:
|
||||
fallback = ["V", "B", "G", "A", "C", "D", "E", "F"]
|
||||
|
||||
+52
-29
@@ -44,6 +44,23 @@ def _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)
|
||||
def home(request: Request):
|
||||
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"])
|
||||
s = next((x for x in meta["semesters"] if x["label"] == semester), meta["semesters"][0])
|
||||
person = None
|
||||
sems: list[int] = []
|
||||
if kind == "student":
|
||||
uid = uid or OWN_UID
|
||||
person = client.person(uid)
|
||||
events = client.student_events(uid, s["begin"], s["ende"])
|
||||
else:
|
||||
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"
|
||||
events = client.stg_events(kz, sem, ver, grp, s["begin"], s["ende"])
|
||||
# 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 "",
|
||||
"kz": kz or "",
|
||||
"sem": sem or "",
|
||||
"sems": sems,
|
||||
"ver": ver or "",
|
||||
"grp": grp or "",
|
||||
"stg": stg,
|
||||
@@ -144,6 +165,9 @@ def liste(request: Request,
|
||||
sem: str = Query("1"),
|
||||
ver: str = Query("V")):
|
||||
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)
|
||||
stg = next((x for x in meta["stgs"] if x["kz"] == kz), None)
|
||||
verbands = client.verbands_for(kz, sem)
|
||||
@@ -155,6 +179,7 @@ def liste(request: Request,
|
||||
"meta": meta,
|
||||
"kz": kz,
|
||||
"sem": sem,
|
||||
"sems": sems,
|
||||
"ver": ver,
|
||||
"verbands": verbands,
|
||||
"stg": stg,
|
||||
@@ -214,6 +239,9 @@ def fotos_page(request: Request,
|
||||
sem: str = Query("1"),
|
||||
ver: str = Query("V")):
|
||||
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)
|
||||
verbands = client.verbands_for(kz, sem)
|
||||
wall = _photo_wall(kz, sem, ver)
|
||||
@@ -222,6 +250,7 @@ def fotos_page(request: Request,
|
||||
"meta": meta,
|
||||
"kz": kz,
|
||||
"sem": sem,
|
||||
"sems": sems,
|
||||
"ver": ver,
|
||||
"verbands": verbands,
|
||||
"stg": stg,
|
||||
@@ -248,32 +277,32 @@ def fotos_suche(request: Request,
|
||||
sem: str = Form("1"),
|
||||
ver: str = Form("V"),
|
||||
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()
|
||||
if not data:
|
||||
return templates.TemplateResponse(request, "fotos_result.html", {
|
||||
"request": request, "error": "Leere Datei hochgeladen.",
|
||||
"kz": kz, "sem": sem, "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,
|
||||
}, status_code=400)
|
||||
return templates.TemplateResponse(request, "fotos_result.html",
|
||||
{**base_ctx, "error": "Leere Datei hochgeladen."},
|
||||
status_code=400)
|
||||
if len(data) > MAX_UPLOAD:
|
||||
return templates.TemplateResponse(request, "fotos_result.html", {
|
||||
"request": request, "error": "Datei zu groß (max. 8 MB).",
|
||||
"kz": kz, "sem": sem, "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,
|
||||
}, status_code=400)
|
||||
return templates.TemplateResponse(request, "fotos_result.html",
|
||||
{**base_ctx, "error": "Datei zu groß (max. 8 MB)."},
|
||||
status_code=400)
|
||||
emb = engine.embed(data)
|
||||
if emb is None:
|
||||
return templates.TemplateResponse(request, "fotos_result.html", {
|
||||
"request": request, "error": "Kein Gesicht im hochgeladenen Bild gefunden.",
|
||||
"kz": kz, "sem": sem, "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,
|
||||
}, status_code=422)
|
||||
return templates.TemplateResponse(request, "fotos_result.html",
|
||||
{**base_ctx, "error": "Kein Gesicht im hochgeladenen Bild gefunden."},
|
||||
status_code=422)
|
||||
wall = _photo_wall(kz, sem, ver)
|
||||
results = []
|
||||
t0 = time.time()
|
||||
@@ -291,18 +320,12 @@ def fotos_suche(request: Request,
|
||||
results.append({**p, "sim": round(sim, 4),
|
||||
"img": f"/fotos/img/{pid}"})
|
||||
results.sort(key=lambda r: r["sim"], reverse=True)
|
||||
meta = _meta()
|
||||
return templates.TemplateResponse(request, "fotos_result.html", {
|
||||
"request": request,
|
||||
**base_ctx,
|
||||
"error": None,
|
||||
"took": round(time.time() - t0, 1),
|
||||
"query": {"kz": kz, "sem": sem, "ver": ver},
|
||||
"kz": kz, "sem": sem, "ver": ver,
|
||||
"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");
|
||||
}
|
||||
|
||||
/* ================= 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() {
|
||||
const sel = $("#ver-select");
|
||||
if (!sel) return;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="field">
|
||||
<label>Studien-Semester</label>
|
||||
<select name="sem">
|
||||
{% for n in range(1, 9) %}
|
||||
{% for n in sems %}
|
||||
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="field">
|
||||
<label>Studien-Semester</label>
|
||||
<select name="sem">
|
||||
{% for n in range(1, 9) %}
|
||||
{% for n in sems %}
|
||||
<option value="{{ n }}" {{ 'selected' if (n | string) == sem }}>{{ n }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="field">
|
||||
<label>Studien-Semester</label>
|
||||
<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>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
{% else %}
|
||||
<div class="field">
|
||||
<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 %}
|
||||
<option value="{{ s.kz }}" {{ 'selected' if s.kz == kz }}>{{ s.code }} - {{ s.name }}</option>
|
||||
{% endfor %}
|
||||
@@ -44,7 +44,7 @@
|
||||
<div class="field">
|
||||
<label>Studien-Sem.</label>
|
||||
<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>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user