fix: event-log route handles missing location_id

The /event-log route now shows a location selector dropdown instead of
returning a 422 error when no location_id is provided. This follows the
same pattern used by the Eggs page.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 07:56:37 +00:00
parent 64bb99aa64
commit c8f026fb2a
3 changed files with 84 additions and 24 deletions

View File

@@ -11,6 +11,7 @@ from starlette.requests import Request
from starlette.responses import HTMLResponse
from animaltrack.repositories.locations import LocationRepository
from animaltrack.repositories.user_defaults import UserDefaultsRepository
from animaltrack.web.templates import page
from animaltrack.web.templates.events import event_log_list, event_log_panel
@@ -56,24 +57,35 @@ def event_log_index(request: Request):
"""GET /event-log - Event log for a location."""
db = request.app.state.db
# Get auth info
auth = request.scope.get("auth")
username = auth.username if auth else None
user_role = auth.role if auth else None
# Get location_id from query params
location_id = request.query_params.get("location_id")
if not location_id:
return HTMLResponse(
content="<p>Missing location_id parameter</p>",
status_code=422,
)
# Get location name
# If no query param, try user defaults
if not location_id and username:
defaults = UserDefaultsRepository(db).get(username, "event_log")
if defaults:
location_id = defaults.location_id
# Get all locations for selector
location_repo = LocationRepository(db)
locations = location_repo.list_active()
location_name = "Unknown"
# Find location name if we have a location_id
location_name = None
if location_id:
for loc in locations:
if loc.id == location_id:
location_name = loc.name
break
# Get event log
# Get event log if we have a valid location
events = []
if location_id and location_name:
events = get_event_log(db, location_id)
# Check if HTMX request
@@ -85,9 +97,11 @@ def event_log_index(request: Request):
# Full page render
return page(
event_log_panel(events, location_name),
title=f"Event Log - {location_name}",
active_nav=None,
event_log_panel(events, locations, location_id),
title="Event Log - AnimalTrack",
active_nav="event_log",
user_role=user_role,
username=username,
)

View File

@@ -4,7 +4,7 @@
from datetime import UTC, datetime
from typing import Any
from fasthtml.common import H3, Div, Li, P, Span, Ul
from fasthtml.common import H3, Div, Label, Li, Option, P, Select, Span, Ul
def format_timestamp(ts_utc: int) -> str:
@@ -120,11 +120,55 @@ def event_log_list(events: list[dict[str, Any]]) -> Any:
return Ul(*items, cls="divide-y divide-stone-200")
def event_log_panel(events: list[dict[str, Any]], location_name: str) -> Any:
"""Render the full event log panel."""
def location_selector(locations: list[Any], selected_location_id: str | None) -> Any:
"""Render location selector dropdown."""
options = [Option("Select a location...", value="", selected=not selected_location_id)]
for loc in locations:
options.append(Option(loc.name, value=loc.id, selected=loc.id == selected_location_id))
return Div(
H3(f"Event Log - {location_name}", cls="text-lg font-semibold mb-4"),
event_log_list(events),
Label("Location", cls="text-sm font-medium text-stone-700"),
Select(
*options,
name="location_id",
cls="mt-1 block w-full rounded-md border-stone-300 shadow-sm "
"focus:border-amber-500 focus:ring-amber-500 sm:text-sm",
hx_get="/event-log",
hx_trigger="change",
hx_target="#event-log-content",
hx_swap="innerHTML",
hx_include="this",
),
cls="mb-4 max-w-xs",
)
def event_log_panel(
events: list[dict[str, Any]], locations: list[Any], selected_location_id: str | None
) -> Any:
"""Render the full event log panel."""
# Find location name for header
location_name = None
if selected_location_id:
for loc in locations:
if loc.id == selected_location_id:
location_name = loc.name
break
header_text = f"Event Log - {location_name}" if location_name else "Event Log"
return Div(
H3(header_text, cls="text-lg font-semibold mb-4"),
location_selector(locations, selected_location_id),
Div(
event_log_list(events)
if selected_location_id
else P(
"Select a location to view events.",
cls="text-stone-500 text-sm text-center py-4",
),
id="event-log-content",
),
cls="bg-white rounded-lg shadow p-4",
id="event-log",
)

View File

@@ -107,10 +107,12 @@ def create_cohort(animal_service, location_id, count=3):
class TestEventLogRoute:
"""Tests for GET /event-log route."""
def test_event_log_requires_location_id(self, client):
"""Event log requires location_id parameter."""
def test_event_log_without_location_shows_selector(self, client):
"""Event log without location_id shows location selector."""
response = client.get("/event-log")
assert response.status_code == 422
assert response.status_code == 200
# Should show location selector prompt
assert "Select a location" in response.text
def test_event_log_returns_empty_for_new_location(self, client, valid_location_id):
"""Event log returns empty state for location with no events."""