Tidy results table layout and centralise connection defaults

- Redesign results table from 8 columns to 4 (National Rail, Transfer,
  Eurostar, Total), making GWR and Eurostar legs consistent with each other
- Move CET label next to Paris arrival time; show duration · train number
  on one line below
- Move "Too early" label into the National Rail column for unreachable rows
- Remove horizontal scrollbar (drop card-scroll / overflow-x: auto)
- Add DEFAULT_MIN_CONNECTION / DEFAULT_MAX_CONNECTION to config/default.py
  (70 / 150 min); remove all hardcoded fallback values from app.py and
  templates
- Redirect to clean URL when both connection params equal their defaults;
  omit params from all generated links when at default values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2026-04-10 13:11:26 +01:00
parent 6ab4534051
commit 8433252cae
5 changed files with 109 additions and 87 deletions

75
app.py
View file

@ -65,11 +65,14 @@ DESTINATIONS = {
@app.route("/")
def index():
today = date.today().isoformat()
default_min, default_max = _get_defaults()
return render_template(
"index.html",
destinations=DESTINATIONS,
today=today,
stations=STATIONS,
default_min_connection=default_min,
default_max_connection=default_max,
valid_min_connections=sorted(VALID_MIN_CONNECTIONS),
valid_max_connections=sorted(VALID_MAX_CONNECTIONS),
)
@ -79,6 +82,21 @@ VALID_MIN_CONNECTIONS = {45, 50, 60, 70, 80, 90, 100, 110, 120}
VALID_MAX_CONNECTIONS = {60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180}
def _get_defaults():
return (
app.config["DEFAULT_MIN_CONNECTION"],
app.config["DEFAULT_MAX_CONNECTION"],
)
def _parse_connection(raw, default, valid_set):
try:
val = int(raw)
except (TypeError, ValueError):
return default
return val if val in valid_set else default
@app.route("/search")
def search():
slug = request.args.get("destination", "")
@ -86,18 +104,13 @@ def search():
station_crs = request.args.get("station_crs", "BRI")
if station_crs not in STATION_BY_CRS:
station_crs = "BRI"
try:
min_conn = int(request.args.get("min_connection", 50))
except ValueError:
min_conn = 50
if min_conn not in VALID_MIN_CONNECTIONS:
min_conn = 50
try:
max_conn = int(request.args.get("max_connection", 110))
except ValueError:
max_conn = 110
if max_conn not in VALID_MAX_CONNECTIONS:
max_conn = 110
default_min, default_max = _get_defaults()
min_conn = _parse_connection(
request.args.get("min_connection"), default_min, VALID_MIN_CONNECTIONS
)
max_conn = _parse_connection(
request.args.get("max_connection"), default_max, VALID_MAX_CONNECTIONS
)
if slug in DESTINATIONS and travel_date:
return redirect(
url_for(
@ -105,8 +118,8 @@ def search():
station_crs=station_crs,
slug=slug,
travel_date=travel_date,
min_connection=min_conn,
max_connection=max_conn,
min_connection=None if min_conn == default_min else min_conn,
max_connection=None if max_conn == default_max else max_conn,
)
)
return redirect(url_for("index"))
@ -121,18 +134,21 @@ def results(station_crs, slug, travel_date):
if not destination or not travel_date:
return redirect(url_for("index"))
try:
min_connection = int(request.args.get("min_connection", 50))
except ValueError:
min_connection = 70
if min_connection not in VALID_MIN_CONNECTIONS:
min_connection = 70
try:
max_connection = int(request.args.get("max_connection", 110))
except ValueError:
max_connection = 150
if max_connection not in VALID_MAX_CONNECTIONS:
max_connection = 150
default_min, default_max = _get_defaults()
min_connection = _parse_connection(
request.args.get("min_connection"), default_min, VALID_MIN_CONNECTIONS
)
max_connection = _parse_connection(
request.args.get("max_connection"), default_max, VALID_MAX_CONNECTIONS
)
# Redirect to clean URL when both params are at their defaults
if (
"min_connection" in request.args or "max_connection" in request.args
) and min_connection == default_min and max_connection == default_max:
return redirect(
url_for("results", station_crs=station_crs, slug=slug, travel_date=travel_date)
)
user_agent = request.headers.get("User-Agent", rtt_scraper.DEFAULT_UA)
@ -254,6 +270,9 @@ def results(station_crs, slug, travel_date):
rtt_url = RTT_PADDINGTON_URL.format(crs=station_crs, date=travel_date)
rtt_station_url = RTT_STATION_URL.format(crs=station_crs, date=travel_date)
url_min = None if min_connection == default_min else min_connection
url_max = None if max_connection == default_max else max_connection
return render_template(
"results.html",
trips=trips,
@ -278,6 +297,10 @@ def results(station_crs, slug, travel_date):
rtt_station_url=rtt_station_url,
min_connection=min_connection,
max_connection=max_connection,
default_min_connection=default_min,
default_max_connection=default_max,
url_min_connection=url_min,
url_max_connection=url_max,
valid_min_connections=sorted(VALID_MIN_CONNECTIONS),
valid_max_connections=sorted(VALID_MAX_CONNECTIONS),
)