Download phase/Teacher_view/studentlist.py from FinEDProj/FInFront: direct link, hf CLI and curl.
- Browser
- Download file 8.53 kB
-
https://huggingface.co/spaces/FinEDProj/FInFront/resolve/main/phase/Teacher_view/studentlist.py
- Command line
-
hf download hf://spaces/FinEDProj/FInFront/phase/Teacher_view/studentlist.py
-
curl -L -o studentlist.py https://huggingface.co/spaces/FinEDProj/FInFront/resolve/main/phase/Teacher_view/studentlist.py
8.53 kB
| # phase/Teacher_view/studentlist.py | |
| import os | |
| import streamlit as st | |
| from utils import db as dbapi | |
| import utils.api as api # backend Space client | |
| # Use local DB only when DISABLE_DB != "1" | |
| USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1" | |
| # ---------- tiny helpers ---------- | |
| def _avatar(name: str) -> str: | |
| return "π§βπ" if hash(name) % 2 else "π©βπ" | |
| def _avg_pct_from_row(r) -> int: | |
| """ | |
| Accepts either: | |
| - r['avg_pct'] in [0, 100] | |
| - r['avg_score'] in [0, 1] or [0, 100] | |
| Returns an int 0..100. | |
| """ | |
| v = r.get("avg_pct", r.get("avg_score", 0)) or 0 | |
| try: | |
| f = float(v) | |
| if f <= 1.0: # treat as 0..1 | |
| f *= 100.0 | |
| return max(0, min(100, int(round(f)))) | |
| except Exception: | |
| return 0 | |
| def _level_from_xp(total_xp: int) -> int: | |
| try: | |
| xp = int(total_xp or 0) | |
| except Exception: | |
| xp = 0 | |
| return 1 + xp // 500 | |
| def _report_text(r, level, avg_pct): | |
| return ( | |
| "STUDENT PROGRESS REPORT\n" | |
| "======================\n" | |
| f"Student: {r.get('name','')}\n" | |
| f"Email: {r.get('email','')}\n" | |
| f"Joined: {str(r.get('joined_at',''))[:10]}\n\n" | |
| "PROGRESS OVERVIEW\n" | |
| "-----------------\n" | |
| f"Lessons Completed: {int(r.get('lessons_completed') or 0)}/{int(r.get('total_assigned_lessons') or 0)}\n" | |
| f"Average Quiz Score: {avg_pct}%\n" | |
| f"Total XP: {int(r.get('total_xp') or 0)}\n" | |
| f"Current Level: {level}\n" | |
| f"Study Streak: {int(r.get('streak_days') or 0)} days\n" | |
| ) | |
| ROW_CSS = """ | |
| <style> | |
| .sm-chip{display:inline-block;padding:4px 10px;border-radius:999px;background:#eef7f1;color:#0b8f5d;font-weight:600;font-size:.80rem;margin-left:8px} | |
| .sm-pill{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid #e6e6e6;border-radius:8px;background:#fff} | |
| .sm-row{border:1px solid #eee;border-radius:12px;padding:16px 16px;margin:10px 0;background:#fff} | |
| .sm-row:hover{box-shadow:0 2px 10px rgba(0,0,0,.04)} | |
| .sm-right{display:flex;gap:16px;align-items:center;justify-content:flex-end} | |
| .sm-metric{min-width:90px;text-align:right} | |
| .sm-metric .label{color:#777;font-size:.75rem} | |
| .sm-metric .value{font-weight:700;font-size:1.1rem} | |
| .sm-name{font-size:1.05rem;font-weight:700} | |
| .sm-sub{color:#6c6c6c;font-size:.85rem} | |
| </style> | |
| """ | |
| # ---------- data access (DB or Backend) ---------- | |
| def _list_classes_by_teacher(teacher_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "list_classes_by_teacher"): | |
| return dbapi.list_classes_by_teacher(teacher_id) or [] | |
| try: | |
| return api.list_classes_by_teacher(teacher_id) or [] | |
| except Exception: | |
| return [] | |
| def _get_class(class_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "get_class"): | |
| return dbapi.get_class(class_id) or {} | |
| try: | |
| return api.get_class(class_id) or {} | |
| except Exception: | |
| return {} | |
| def _class_student_metrics(class_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "class_student_metrics"): | |
| return dbapi.class_student_metrics(class_id) or [] | |
| try: | |
| return api.class_student_metrics(class_id) or [] | |
| except Exception: | |
| return [] | |
| def _list_assignments_for_student(student_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "list_assignments_for_student"): | |
| return dbapi.list_assignments_for_student(student_id) or [] | |
| try: | |
| return api.list_assignments_for_student(student_id) or [] | |
| except Exception: | |
| return [] | |
| # ---------- page ---------- | |
| def show_page(): | |
| st.title("π Student Management") | |
| st.caption("Monitor and manage your students' progress") | |
| st.markdown(ROW_CSS, unsafe_allow_html=True) | |
| teacher = st.session_state.get("user") | |
| if not teacher: | |
| st.error("Please log in.") | |
| return | |
| teacher_id = teacher["user_id"] | |
| classes = _list_classes_by_teacher(teacher_id) | |
| if not classes: | |
| st.info("No classes yet. Create one in Classroom Management.") | |
| return | |
| # class selector | |
| idx = st.selectbox( | |
| "Choose a class", | |
| list(range(len(classes))), | |
| index=0, | |
| format_func=lambda i: f"{classes[i].get('name','(unnamed)')}" | |
| ) | |
| selected = classes[idx] | |
| class_id = selected.get("class_id") or selected.get("id") # be tolerant to backend naming | |
| if class_id is None: | |
| st.error("Selected class is missing an ID.") | |
| return | |
| code_row = _get_class(class_id) | |
| # get students before drawing chips | |
| rows = _class_student_metrics(class_id) | |
| # code + student chip row | |
| chip1, chip2 = st.columns([1, 1]) | |
| with chip1: | |
| st.markdown( | |
| f'<div class="sm-chip">Code: {code_row.get("code","")}</div>', | |
| unsafe_allow_html=True | |
| ) | |
| with chip2: | |
| st.markdown( | |
| f'<div class="sm-chip">π₯ {len(rows)} Students</div>', | |
| unsafe_allow_html=True | |
| ) | |
| st.markdown("---") | |
| # search line | |
| query = st.text_input( | |
| "Search students by name or email", | |
| placeholder="Type a name or email..." | |
| ).strip().lower() | |
| if query: | |
| rows = [ | |
| r for r in rows | |
| if query in (r.get("name","").lower()) or query in (r.get("email","").lower()) | |
| ] | |
| # student rows | |
| for r in rows: | |
| name = r.get("name", "Unknown") | |
| email = r.get("email", "") | |
| joined = str(r.get("joined_at", ""))[:10] | |
| total_xp = int(r.get("total_xp") or 0) | |
| level = _level_from_xp(total_xp) | |
| lessons_completed = int(r.get("lessons_completed") or 0) | |
| total_assigned = int(r.get("total_assigned_lessons") or 0) | |
| avg_pct = _avg_pct_from_row(r) | |
| streak = int(r.get("streak_days") or 0) | |
| student_id = r.get("student_id") or r.get("id") | |
| with st.container(): | |
| st.markdown('<div class="sm-row">', unsafe_allow_html=True) | |
| # top bar: avatar + name/email + right metrics | |
| a, b, c = st.columns([0.7, 4, 3]) | |
| with a: | |
| st.markdown(f"### {_avatar(name)}") | |
| with b: | |
| st.markdown(f'<div class="sm-name">{name}</div>', unsafe_allow_html=True) | |
| st.markdown(f'<div class="sm-sub">{email} Β· Joined {joined}</div>', unsafe_allow_html=True) | |
| with c: | |
| st.markdown( | |
| '<div class="sm-right">' | |
| f'<div class="sm-metric"><div class="value">{level}</div><div class="label">Level</div></div>' | |
| f'<div class="sm-metric"><div class="value">{avg_pct}%</div><div class="label">Avg Score</div></div>' | |
| f'<div class="sm-metric"><div class="value">{streak}</div><div class="label">Streak</div></div>' | |
| "</div>", | |
| unsafe_allow_html=True | |
| ) | |
| # progress bar | |
| st.caption("Overall Progress") | |
| frac = (lessons_completed / total_assigned) if total_assigned > 0 else 0.0 | |
| st.progress(min(1.0, frac)) | |
| st.caption(f"{lessons_completed}/{total_assigned} lessons") | |
| # actions row | |
| d1, d2, spacer = st.columns([2, 1.3, 5]) | |
| with d1: | |
| with st.popover("ποΈ View Details"): | |
| if student_id is not None: | |
| items = _list_assignments_for_student(int(student_id)) | |
| else: | |
| items = [] | |
| if items: | |
| for it in items[:25]: | |
| tag = " + Quiz" if it.get("quiz_id") else "" | |
| st.markdown( | |
| f"- **{it.get('title','Untitled')}** Β· {it.get('subject','General')} Β· " | |
| f"{it.get('level','')} {tag} Β· Status: {it.get('status','unknown')}" | |
| ) | |
| else: | |
| st.info("No assignments yet.") | |
| with d2: | |
| rep = _report_text(r, level, avg_pct) | |
| st.download_button( | |
| "β¬οΈ Export", | |
| data=rep, | |
| file_name=f"{str(name).replace(' ','_')}_report.txt", | |
| mime="text/plain", | |
| key=f"dl_{student_id or name}" | |
| ) | |
| st.markdown('</div>', unsafe_allow_html=True) | |