"""Rename the study plans away from the vendor's programme name. "PREP" is the American Academy of Pediatrics' trademark for its own product. The plans here are our own sets of questions grouped by year, so they get names that describe what they are: "Board Review 2021", and "Mixed Review" for the plan that draws from every year at once. Slugs change with them, which is safe because a study plan is reached by id and the slug is not a public address. Quizzes a learner already generated from a block are renamed too, so a title in their history matches the plan it came from rather than referring to something that no longer exists. So are the year tags, which appear in the question bank's filters and are as visible as the plans. docker compose exec backend python -m scripts.rename_study_plans docker compose exec backend python -m scripts.rename_study_plans --apply """ import re import sys from sqlalchemy import text as sa_text from app.database import SessionLocal YEAR_NAME = "Board Review {year}" YEAR_SLUG = "board-review-{year}" MIXED_NAME = "Mixed Review" MIXED_SLUG = "mixed-review" def planned(db): """(id, old name, new name, old slug, new slug) for everything to rename.""" rows = db.execute(sa_text( "SELECT id, slug, name, kind FROM study_plans ORDER BY sort_order, name")).fetchall() changes = [] for plan in rows: year = re.search(r"(\d{4})", plan.name) if plan.kind == "mixed" or "mixed" in plan.name.lower(): new_name, new_slug = MIXED_NAME, MIXED_SLUG elif year: new_name = YEAR_NAME.format(year=year.group(1)) new_slug = YEAR_SLUG.format(year=year.group(1)) else: continue if (new_name, new_slug) != (plan.name, plan.slug): changes.append((plan.id, plan.name, new_name, plan.slug, new_slug)) return changes def main(): apply_changes = "--apply" in sys.argv db = SessionLocal() try: # No early return when the plans are already done: the quizzes and the # year tags are renamed by the same pass, and a second run has to be able # to finish what a first one left. changes = planned(db) print(f" plans to rename: {len(changes)}\n") for _pid, old_name, new_name, old_slug, new_slug in changes: print(f" {old_name:<16} -> {new_name:<22} ({old_slug} -> {new_slug})") quizzes = db.execute(sa_text( "SELECT id, title FROM quizzes WHERE title LIKE '%PREP%'")).fetchall() print(f"\n quizzes already generated from a block: {len(quizzes)}") tags = db.execute(sa_text( "SELECT id, name FROM question_tags WHERE name ~ '^PREP [0-9]{4}$'")).fetchall() print(f" year tags shown in the bank's filters : {len(tags)}") if not apply_changes: print("\n Re-run with --apply to rename them.") return 0 for plan_id, _old_name, new_name, _old_slug, new_slug in changes: db.execute(sa_text("UPDATE study_plans SET name = :n, slug = :s WHERE id = :i"), {"n": new_name, "s": new_slug, "i": plan_id}) renamed = 0 for quiz in quizzes: title = quiz.title year = re.search(r"PREP (\d{4})", title) if year: title = title.replace(f"PREP {year.group(1)}", YEAR_NAME.format(year=year.group(1))) else: title = title.replace("PREP Mixed", MIXED_NAME).replace("PREP", "Board Review") if title != quiz.title: db.execute(sa_text("UPDATE quizzes SET title = :t WHERE id = :i"), {"t": title, "i": quiz.id}) renamed += 1 retagged = 0 for tag in tags: year = re.search(r"(\d{4})", tag.name) if not year: continue db.execute(sa_text("UPDATE question_tags SET name = :n WHERE id = :i"), {"n": YEAR_NAME.format(year=year.group(1)), "i": tag.id}) retagged += 1 db.commit() print(f"\n plans renamed : {len(changes)}") print(f" quizzes renamed : {renamed}") print(f" tags renamed : {retagged}") finally: db.close() return 0 if __name__ == "__main__": sys.exit(main())