The page at /quizzes showed the same fifteen rows twice — once under a "Sessions" tab as a list, once under a "Library" tab as cards — with nothing distinguishing them. The navbar carried the duplication too, with "Sessions" and "History" both pointing at the same page. Board Review I-XII already exist as study plans. The Library tab was showing the bulk quizzes those plans were built from, so the same twelve titles appeared in both systems. Those quizzes are now origin='plan': still real, still the parent of their questions via source_quiz_id, but no longer offered as something to pick off a list. Once a learner has actually sat one it is history, so the session list keeps it. - QuizzesPage deleted; /sessions is the only listing - /quizzes/* redirects to /sessions/*, preserving path and query - submitting a session lands on its analysis, not the old score page - the answer review drops its score hero, which the analysis owns and stated differently; a course quiz keeps its card, having no analysis - delete-attempt moves to the analysis page, where the session lives Backend 208/208, frontend 246/246. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
47 lines
1.6 KiB
Python
47 lines
1.6 KiB
Python
"""Mark study-plan source quizzes with origin='plan'
|
||
|
||
Board Review I–XII exist twice over: once as a study plan the learner works
|
||
through block by block, and once as the bulk quiz the plan was built from. The
|
||
session list showed both, so the same twelve titles appeared under "Sessions"
|
||
and again under "Library" with nothing to tell them apart.
|
||
|
||
Tagging the bulk quizzes keeps them intact — the questions still hang off them
|
||
via `source_quiz_id` — while taking them out of the list of things a learner
|
||
picks from. A quiz already sat stays in the history regardless; that filtering
|
||
lives in the endpoint, not here.
|
||
|
||
Revision ID: b2c3d4e5f6a7
|
||
Revises: a1b2c3d4e5f6
|
||
"""
|
||
import sqlalchemy as sa
|
||
from alembic import op
|
||
|
||
revision = "b2c3d4e5f6a7"
|
||
down_revision = "a1b2c3d4e5f6"
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
|
||
def _has(table: str) -> bool:
|
||
return table in sa.inspect(op.get_bind()).get_table_names()
|
||
|
||
|
||
def upgrade() -> None:
|
||
# Guarded because create_all() may have built these tables on a fresh
|
||
# deploy before Alembic ran.
|
||
if not (_has("quizzes") and _has("study_plans")):
|
||
return
|
||
# Matched on the trimmed title: one of the imports carries a trailing
|
||
# space ("Board Review VII ").
|
||
op.execute(sa.text("""
|
||
UPDATE quizzes SET origin = 'plan'
|
||
WHERE course_id IS NULL
|
||
AND origin <> 'plan'
|
||
AND btrim(title) IN (SELECT btrim(name) FROM study_plans)
|
||
"""))
|
||
|
||
|
||
def downgrade() -> None:
|
||
if not _has("quizzes"):
|
||
return
|
||
op.execute(sa.text("UPDATE quizzes SET origin = 'bank' WHERE origin = 'plan'"))
|