Refactor and optimize: deduplicate, batch queries, fix bugs
- Remove unused DATABASE_PATH import from games.py - Extract _new_game_context(db) helper to deduplicate GET/POST error path - Extract _parse_hole_form() helper to deduplicate hole validation in create/edit course - Batch N+1 player handicap and tee queries in create_game into two IN() queries - Fix hole count import bug: use max() across all tees instead of first match - Fix migration exception handling: re-raise unless "duplicate column name" - Cache calcSkins() result in _skinsCache; invalidate on score/skip changes - Collapse two identical Next buttons into one in new_game.html - Fix stale skinDoubleBackNine checkbox: reset on mode change, use x-model Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+37
-55
@@ -51,6 +51,36 @@ 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)
|
||||
@@ -91,30 +121,8 @@ async def create_course(request: Request,
|
||||
errors["holes"] = "Holes must be 9 or 18."
|
||||
|
||||
hole_count = holes if holes in (9, 18) else 18
|
||||
pars = {}
|
||||
stroke_indices = {}
|
||||
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
|
||||
pars, stroke_indices, hole_errors = _parse_hole_form(form, hole_count)
|
||||
errors.update(hole_errors)
|
||||
|
||||
if errors:
|
||||
return templates.TemplateResponse(
|
||||
@@ -187,16 +195,12 @@ async def import_course(api_id: int, request: Request,
|
||||
else:
|
||||
location = (loc.get("address") or "").strip()
|
||||
|
||||
# Determine hole count from tees
|
||||
# Determine hole count from tees — use max so a course with any 18-hole tee stays 18
|
||||
tees_male = data.get("tees", {}).get("male") or []
|
||||
tees_female = data.get("tees", {}).get("female") or []
|
||||
all_tees = tees_male + tees_female
|
||||
holes_count = 18
|
||||
for t in all_tees:
|
||||
n = t.get("number_of_holes")
|
||||
if n in (9, 18):
|
||||
holes_count = n
|
||||
break
|
||||
valid_counts = [t.get("number_of_holes") for t in all_tees if t.get("number_of_holes") in (9, 18)]
|
||||
holes_count = max(valid_counts, default=18)
|
||||
|
||||
# Hole par + course-level stroke index from first male tee with hole data (fallback for display)
|
||||
hole_pars: dict[int, int] = {}
|
||||
@@ -286,30 +290,8 @@ 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 = {}
|
||||
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
|
||||
pars, stroke_indices, hole_errors = _parse_hole_form(form, hole_count)
|
||||
errors.update(hole_errors)
|
||||
|
||||
if errors:
|
||||
return templates.TemplateResponse(
|
||||
|
||||
Reference in New Issue
Block a user