From 06995c56682a8ffcc5652f8affcfc6e1ad477038 Mon Sep 17 00:00:00 2001 From: Rolf Date: Tue, 24 Mar 2026 20:03:19 +0100 Subject: [PATCH] Remove manual course creation; import-only with per-tee stroke indices Courses can now only be added via Golf Course API import, which provides the full data model: per-tee slope/rating/gender/SI (stroke_indices JSON) and front/back split ratings. Manual creation required admins to enter SI by hand and didn't match the API model. Changes: - Remove GET/POST /admin/courses/new routes and new_course.html - Remove _parse_hole_form helper (no longer needed) - Edit course: par-only editing; stroke_index preserved from import, not exposed or overwritten in the admin form - courses.html: replace "+ New course" with "+ Import course" button - course_search.html: remove "+ Manual" shortcut - edit_course.html: remove SI column from hole grid Co-Authored-By: Claude Sonnet 4.6 --- app/routers/courses.py | 105 ++++--------------------- app/templates/admin/course_search.html | 1 - app/templates/admin/courses.html | 5 +- app/templates/admin/edit_course.html | 6 -- app/templates/admin/new_course.html | 81 ------------------- 5 files changed, 16 insertions(+), 182 deletions(-) delete mode 100644 app/templates/admin/new_course.html diff --git a/app/routers/courses.py b/app/routers/courses.py index 3d8c945..974aafc 100644 --- a/app/routers/courses.py +++ b/app/routers/courses.py @@ -51,36 +51,6 @@ async def _get_course_detail(cid: str, db: aiosqlite.Connection): return dict(course), holes, tees -def _parse_hole_form(form, hole_count: int) -> tuple[dict, dict, dict]: - """Parse par/SI fields from a form. Returns (pars, stroke_indices, errors).""" - pars: dict[int, int] = {} - stroke_indices: dict[int, int | None] = {} - errors: dict[str, str] = {} - for i in range(1, hole_count + 1): - raw = form.get(f"par_{i}", "4") - try: - p = int(raw) - if p < 3 or p > 5: - errors[f"par_{i}"] = f"Hole {i}: par must be 3–5." - else: - pars[i] = p - except (ValueError, TypeError): - errors[f"par_{i}"] = f"Hole {i}: invalid par." - raw_si = form.get(f"si_{i}", "") - if raw_si: - try: - si = int(raw_si) - if si < 1 or si > hole_count: - errors[f"si_{i}"] = f"Hole {i}: SI must be 1–{hole_count}." - else: - stroke_indices[i] = si - except (ValueError, TypeError): - errors[f"si_{i}"] = f"Hole {i}: invalid SI." - else: - stroke_indices[i] = None - return pars, stroke_indices, errors - - # ── List ────────────────────────────────────────────────────────────────────── @router.get("/admin/courses", response_class=HTMLResponse) @@ -96,61 +66,6 @@ async def courses_page(request: Request, user: dict = Depends(require_admin), ) -# ── Create ──────────────────────────────────────────────────────────────────── - -@router.get("/admin/courses/new", response_class=HTMLResponse) -async def new_course_page(request: Request, user: dict = Depends(require_admin)): - return templates.TemplateResponse( - "admin/new_course.html", {"request": request, "user": user} - ) - - -@router.post("/admin/courses/new") -async def create_course(request: Request, - name: str = Form(...), holes: int = Form(...), - location: str = Form(""), - user: dict = Depends(require_admin), - db: aiosqlite.Connection = Depends(get_db)): - form = await request.form() - name = name.strip() - location = location.strip() - errors = {} - if not name: - errors["name"] = "Name is required." - if holes not in (9, 18): - errors["holes"] = "Holes must be 9 or 18." - - hole_count = holes if holes in (9, 18) else 18 - pars, stroke_indices, hole_errors = _parse_hole_form(form, hole_count) - errors.update(hole_errors) - - if errors: - return templates.TemplateResponse( - "admin/new_course.html", - {"request": request, "user": user, "errors": errors, - "form": {"name": name, "holes": holes, "location": location, - "pars": pars, "stroke_indices": stroke_indices}}, - status_code=422, - ) - - cid = secrets.token_urlsafe(10) - now = datetime.now(timezone.utc).isoformat() - await db.execute( - "INSERT INTO courses (id, name, holes, location, created_at) VALUES (?,?,?,?,?)", - (cid, name, holes, location or None, now), - ) - for hole_num, par in pars.items(): - await db.execute( - "INSERT INTO course_holes (id, course_id, hole_number, par, stroke_index) VALUES (?,?,?,?,?)", - (secrets.token_urlsafe(10), cid, hole_num, par, stroke_indices.get(hole_num)), - ) - await db.commit() - return RedirectResponse( - f"/admin/courses/{cid}/edit?success=Course+created.+Add+tees+below.", - status_code=302, - ) - - # ── API Search & Import ─────────────────────────────────────────────────────── @router.get("/admin/courses/search", response_class=HTMLResponse) @@ -290,8 +205,17 @@ async def edit_course_submit(cid: str, request: Request, errors["holes"] = "Holes must be 9 or 18." hole_count = holes if holes in (9, 18) else 18 - pars, stroke_indices, hole_errors = _parse_hole_form(form, hole_count) - errors.update(hole_errors) + pars: dict[int, int] = {} + for i in range(1, hole_count + 1): + raw = form.get(f"par_{i}", "4") + try: + p = int(raw) + if p < 3 or p > 5: + errors[f"par_{i}"] = f"Hole {i}: par must be 3–5." + else: + pars[i] = p + except (ValueError, TypeError): + errors[f"par_{i}"] = f"Hole {i}: invalid par." if errors: return templates.TemplateResponse( @@ -307,10 +231,9 @@ async def edit_course_submit(cid: str, request: Request, ) for hole_num, par in pars.items(): await db.execute( - """INSERT INTO course_holes (id, course_id, hole_number, par, stroke_index) VALUES (?,?,?,?,?) - ON CONFLICT(course_id, hole_number) DO UPDATE SET - par=excluded.par, stroke_index=excluded.stroke_index""", - (secrets.token_urlsafe(10), cid, hole_num, par, stroke_indices.get(hole_num)), + """INSERT INTO course_holes (id, course_id, hole_number, par) VALUES (?,?,?,?) + ON CONFLICT(course_id, hole_number) DO UPDATE SET par = excluded.par""", + (secrets.token_urlsafe(10), cid, hole_num, par), ) # Remove holes beyond new count if holes was reduced await db.execute( diff --git a/app/templates/admin/course_search.html b/app/templates/admin/course_search.html index 525e03b..6c82a85 100644 --- a/app/templates/admin/course_search.html +++ b/app/templates/admin/course_search.html @@ -15,7 +15,6 @@
← Courses - + Manual
{% if not api_configured %} diff --git a/app/templates/admin/courses.html b/app/templates/admin/courses.html index cb7658f..037c4ae 100644 --- a/app/templates/admin/courses.html +++ b/app/templates/admin/courses.html @@ -18,8 +18,7 @@ {% endif %}
- Search & Import - + New course + + Import course
{% if courses %} @@ -50,7 +49,7 @@

No courses yet.

- Add a course + Import a course
{% endif %} diff --git a/app/templates/admin/edit_course.html b/app/templates/admin/edit_course.html index dc88ed2..520235b 100644 --- a/app/templates/admin/edit_course.html +++ b/app/templates/admin/edit_course.html @@ -58,12 +58,6 @@ - {% endfor %} diff --git a/app/templates/admin/new_course.html b/app/templates/admin/new_course.html deleted file mode 100644 index 972db8c..0000000 --- a/app/templates/admin/new_course.html +++ /dev/null @@ -1,81 +0,0 @@ -{% extends "base.html" %} -{% block title %}New Course – Skins & Pins{% endblock %} - -{% block content %} -
- - -
-
-
- - {% if errors and errors.name %}
{{ errors.name }}
{% endif %} - -
- -
- - -
- -
- - {% if errors and errors.holes %}
{{ errors.holes }}
{% endif %} -
- - -
-
-
- -
-

- Hole details - - — Course par: - -

-
- -
-
- - -
-
-{% endblock %}