Download phase/Student_view/teacherlink.py from FinEDProj/FInFront: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/spaces/FinEDProj/FInFront/resolve/main/phase/Student_view/teacherlink.py
- Command line
-
hf download hf://spaces/FinEDProj/FInFront/phase/Student_view/teacherlink.py
-
curl -L -o teacherlink.py https://huggingface.co/spaces/FinEDProj/FInFront/resolve/main/phase/Student_view/teacherlink.py
10.5 kB
| # phase/Student_view/teacherlink.py | |
| import os | |
| import streamlit as st | |
| from utils import db as dbapi | |
| import utils.api as api # <-- backend Space client | |
| USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1" # DB only when DISABLE_DB=0 | |
| def load_css(file_name: str): | |
| try: | |
| with open(file_name, "r", encoding="utf-8") as f: | |
| st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True) | |
| except FileNotFoundError: | |
| pass | |
| def _progress_0_1(v): | |
| try: | |
| f = float(v) | |
| except Exception: | |
| return 0.0 | |
| # accept 0..1 or 0..100 | |
| return max(0.0, min(1.0, f if f <= 1.0 else f / 100.0)) | |
| # --- Small wrappers to switch between DB and Backend --- | |
| def _join_class_by_code(student_id: int, code: str): | |
| if USE_LOCAL_DB and hasattr(dbapi, "join_class_by_code"): | |
| return dbapi.join_class_by_code(student_id, code) | |
| return api.join_class_by_code(student_id, code) | |
| def _list_classes_for_student(student_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "list_classes_for_student"): | |
| return dbapi.list_classes_for_student(student_id) | |
| try: | |
| return api.list_classes_for_student(student_id) | |
| except Exception: | |
| return [] | |
| def _class_content_counts(class_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "class_content_counts"): | |
| return dbapi.class_content_counts(class_id) | |
| try: | |
| return api.class_content_counts(class_id) | |
| except Exception: | |
| return {"lessons": 0, "quizzes": 0} | |
| def _student_class_progress(student_id: int, class_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "student_class_progress"): | |
| return dbapi.student_class_progress(student_id, class_id) | |
| try: | |
| return api.student_class_progress(student_id, class_id) | |
| except Exception: | |
| return { | |
| "overall_progress": 0, | |
| "lessons_completed": 0, | |
| "total_assigned_lessons": 0, | |
| "avg_score": 0, | |
| } | |
| def _leave_class(student_id: int, class_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "leave_class"): | |
| return dbapi.leave_class(student_id, class_id) | |
| return api.leave_class(student_id, class_id) | |
| def _student_assignments_for_class(student_id: int, class_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "student_assignments_for_class"): | |
| return dbapi.student_assignments_for_class(student_id, class_id) | |
| try: | |
| return api.student_assignments_for_class(student_id, class_id) | |
| except Exception: | |
| return [] | |
| def _mark_assignment_started(student_id: int, assignment_id: int): | |
| if USE_LOCAL_DB and hasattr(dbapi, "mark_assignment_started"): | |
| return dbapi.mark_assignment_started(student_id, assignment_id) | |
| return api.mark_assignment_started(student_id, assignment_id) | |
| def _set_assignment_progress(student_id: int, assignment_id: int, current_pos: int = 1, progress: float = 0.05): | |
| # optional nudge so the bar is not 0 | |
| if USE_LOCAL_DB and hasattr(dbapi, "set_assignment_progress"): | |
| return dbapi.set_assignment_progress(student_id, assignment_id, current_pos, progress) | |
| return api.set_assignment_progress(student_id, assignment_id, current_pos, progress) | |
| # --- UI --- | |
| def show_code(): | |
| load_css(os.path.join("assets", "styles.css")) | |
| if "user" not in st.session_state or not st.session_state.user: | |
| st.error("Please log in as a student.") | |
| return | |
| if st.session_state.user["role"] != "Student": | |
| st.error("This page is for students.") | |
| return | |
| student_id = st.session_state.user["user_id"] | |
| st.markdown("## π₯ Join a Class") | |
| st.caption("Enter class code from your teacher") | |
| raw = st.text_input( | |
| label="Class Code", | |
| placeholder="e.g. FIN5A2024", | |
| key="class_code_input", | |
| label_visibility="collapsed" | |
| ) | |
| # custom button style | |
| st.markdown( | |
| """ | |
| <style> | |
| .stButton>button#join_class_btn { | |
| background-color: #28a745; /* Bootstrap green */ | |
| color: white; | |
| border-radius: 5px; | |
| padding: 10px 16px; | |
| font-weight: 600; | |
| } | |
| .stButton>button#join_class_btn:hover { | |
| background-color: #218838; | |
| color: white; | |
| } | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| if st.button("Join Class", key="join_class_btn"): | |
| code = (raw or "").strip().upper() | |
| if not code: | |
| st.error("Enter a class code.") | |
| else: | |
| try: | |
| _join_class_by_code(student_id, code) | |
| st.success("π Joined the class!") | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(str(e)) | |
| except Exception as e: | |
| st.error(f"Could not join class: {e}") | |
| st.markdown("---") | |
| st.markdown("## Your Classes") | |
| classes = _list_classes_for_student(student_id) | |
| if not classes: | |
| st.info("You havenβt joined any classes yet. Ask your teacher for a class code.") | |
| return | |
| # one card per class | |
| for c in classes: | |
| class_id = c["class_id"] | |
| counts = _class_content_counts(class_id) # lessons/quizzes count | |
| prog = _student_class_progress(student_id, class_id) | |
| st.markdown(f"### {c.get('name', 'Untitled Class')}") | |
| st.caption( | |
| f"Teacher: {c.get('teacher_name','β')} β’ " | |
| f"Code: {c.get('code','β')} β’ " | |
| f"Joined: {str(c.get('joined_at',''))[:10] if c.get('joined_at') else 'β'}" | |
| ) | |
| st.progress(_progress_0_1(prog.get("overall_progress", 0))) | |
| avg_pct = int(round(100 * _progress_0_1(prog.get("avg_score", 0)))) | |
| st.caption( | |
| f"{prog.get('lessons_completed', 0)}/{prog.get('total_assigned_lessons', 0)} lessons completed β’ " | |
| f"Avg quiz: {avg_pct}%" | |
| ) | |
| # top metrics | |
| m1, m2, m3, m4 = st.columns(4) | |
| m1.metric("Lessons", counts.get("lessons", 0)) | |
| m2.metric("Quizzes", counts.get("quizzes", 0)) | |
| m3.metric("Overall", f"{int(round(100 * _progress_0_1(prog.get('overall_progress', 0))))}%") | |
| m4.metric("Avg Quiz", f"{avg_pct}%") | |
| # Leave class | |
| leave_col, _ = st.columns([1, 3]) | |
| with leave_col: | |
| if st.button("πͺ Leave Class", key=f"leave_{class_id}"): | |
| try: | |
| _leave_class(student_id, class_id) | |
| st.toast("Left class.", icon="π") | |
| st.rerun() | |
| except Exception as e: | |
| st.error(f"Could not leave class: {e}") | |
| # Assignments for THIS class with THIS student's progress | |
| st.markdown("#### Teacher Lessons & Quizzes") | |
| rows = _student_assignments_for_class(student_id, class_id) | |
| if not rows: | |
| st.info("No assignments yet.") | |
| else: | |
| lessons_tab, quizzes_tab = st.tabs(["π Lessons", "π Quizzes"]) | |
| with lessons_tab: | |
| for r in rows: | |
| if r.get("lesson_id") is None: | |
| continue | |
| status = r.get("status") or "not_started" | |
| pos = r.get("current_pos") or 0 | |
| # if backend returns explicit progress % or 0..1, keep it sane: | |
| pct = r.get("progress") | |
| if pct is None: | |
| # fallback: estimate from position | |
| pct = 1.0 if status == "completed" else min(0.95, float(pos or 0) * 0.1) | |
| st.subheader(r.get("title", "Untitled")) | |
| due = r.get("due_at") | |
| due_txt = f"Due: {str(due)[:10]}" if due else "β" | |
| st.caption(f"{r.get('subject','General')} β’ {r.get('level','Beginner')} β’ {due_txt}") | |
| st.progress(_progress_0_1(pct)) | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| if st.button("βΆοΈ Start Lesson", key=f"start_lesson_{r.get('assignment_id')}"): | |
| lesson_id = int(r.get("lesson_id")) | |
| assignment_id = int(r.get("assignment_id")) | |
| try: | |
| _mark_assignment_started(student_id, assignment_id) | |
| _set_assignment_progress(student_id, assignment_id, current_pos=1, progress=0.05) | |
| except Exception as e: | |
| st.warning(f"Could not mark as started yet. Continuing anyway. {e}") | |
| st.session_state.selected_lesson = lesson_id | |
| st.session_state.selected_assignment = assignment_id | |
| st.session_state.lesson_route = { | |
| "source": "teacher", | |
| "lesson_id": lesson_id, | |
| "assignment_id": assignment_id, | |
| } | |
| st.session_state.current_page = "Lessons" | |
| st.rerun() | |
| with c2: | |
| st.write(f"Status: **{status}**") | |
| with quizzes_tab: | |
| any_quiz = False | |
| for r in rows: | |
| quiz_id = r.get("quiz_id") | |
| if not quiz_id: | |
| continue | |
| any_quiz = True | |
| st.subheader(r.get("title", "Untitled")) | |
| score, total = r.get("score"), r.get("total") | |
| if score is not None and total: | |
| try: | |
| pct = int(round(100 * float(score) / float(total))) | |
| st.caption(f"Last score: {pct}%") | |
| except Exception: | |
| st.caption("Last score: β") | |
| else: | |
| st.caption("No submission yet") | |
| # pass quiz & assignment to the Quiz page | |
| if st.button("π Start Quiz", key=f"start_quiz_{class_id}_{quiz_id}"): | |
| st.session_state.selected_quiz = quiz_id | |
| st.session_state.current_assignment = r.get("assignment_id") | |
| st.session_state.current_page = "Quiz" | |
| st.rerun() | |
| if not any_quiz: | |
| st.info("No quizzes yet for this class.") | |
| st.markdown("---") | |