diff --git a/app.py b/app.py index 2de615c..7753751 100644 --- a/app.py +++ b/app.py @@ -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), ) diff --git a/config/default.py b/config/default.py index aff1369..dfdb4fa 100644 --- a/config/default.py +++ b/config/default.py @@ -8,3 +8,7 @@ CACHE_DIR = os.path.expanduser('~/lib/data/tfl/cache') # TransXChange timetable file for the Circle Line CIRCLE_LINE_XML = os.path.join(TFL_DATA_DIR, 'output_txc_01CIR_.xml') + +# Default connection window (minutes) between Paddington arrival and St Pancras departure +DEFAULT_MIN_CONNECTION = 70 +DEFAULT_MAX_CONNECTION = 150 diff --git a/templates/base.html b/templates/base.html index c98c83e..1ee3279 100644 --- a/templates/base.html +++ b/templates/base.html @@ -192,7 +192,6 @@ /* Card helpers */ .card > h2:first-child { margin-top: 0; } - .card-scroll { overflow-x: auto; } /* Form groups */ .form-group { margin-bottom: 1.2rem; } diff --git a/templates/index.html b/templates/index.html index 57a9afc..4e7140b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -49,7 +49,7 @@ @@ -60,7 +60,7 @@ diff --git a/templates/results.html b/templates/results.html index 183bc3e..4884ee5 100644 --- a/templates/results.html +++ b/templates/results.html @@ -15,10 +15,10 @@ {{ departure_station_name }} → {{ destination }}
| {{ departure_station_name }} | -Paddington | -GWR Fare | +National Rail | Transfer | -Depart STP | -{{ destination }} | -ES Std | +Eurostar | Total | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
- {{ row.depart_bristol }}
- {% if row.headcode %} {{ row.headcode }}{% endif %} - |
- {{ row.arrive_paddington }}
+ {{ row.depart_bristol }} → {{ row.arrive_paddington }}
({{ row.gwr_duration }})
- {% if row.arrive_platform %} Plat {{ row.arrive_platform }}{% endif %} - |
-
+ {% if row.headcode or row.arrive_platform %}
+ + {%- if row.headcode %}{{ row.headcode }}{% endif %} + {%- if row.headcode and row.arrive_platform %} · {% endif %} + {%- if row.arrive_platform %}Plat {{ row.arrive_platform }}{% endif %} + + {% endif %} {% if row.ticket_price is not none %} - £{{ "%.2f"|format(row.ticket_price) }} - {{ row.ticket_name }} + £{{ "%.2f"|format(row.ticket_price) }} + {{ row.ticket_name }} {% else %} - – + – {% endif %} |
- - {{ row.connection_duration }}{% if row.connection_minutes < 80 %} ⚠️{% endif %} + |
+ {{ row.connection_duration }}{% if row.connection_minutes < 80 %} ⚠️{% endif %}
{% if row.circle_services %}
{% set c = row.circle_services[0] %}
- Circle {{ c.depart }} → KX {{ c.arrive_kx }} + Circle {{ c.depart }} → KX {{ c.arrive_kx }} {% if row.circle_services | length > 1 %} {% set c2 = row.circle_services[1] %} - next {{ c2.depart }} → KX {{ c2.arrive_kx }} + next {{ c2.depart }} → KX {{ c2.arrive_kx }} {% endif %} {% endif %} |
-
- {{ row.depart_st_pancras }}
- {% if row.train_number %} {% for part in row.train_number.split(' + ') %}{{ part }}{% if not loop.last %} + {% endif %}{% endfor %}{% endif %} - |
- {{ row.arrive_destination }}
- (CET)
- {% if row.eurostar_duration %} ({{ row.eurostar_duration }}){% endif %} - |
-
+ {{ row.depart_st_pancras }} → {{ row.arrive_destination }} (CET)
+ {% if row.eurostar_duration or row.train_number %}
+ + {%- if row.eurostar_duration %}({{ row.eurostar_duration }}){% endif %} + {%- if row.eurostar_duration and row.train_number %} · {% endif %} + {%- if row.train_number %}{% for part in row.train_number.split(' + ') %}{{ part }}{% if not loop.last %} + {% endif %}{% endfor %}{% endif %} + + {% endif %} {% if row.eurostar_price is not none %} - £{{ "%.2f"|format(row.eurostar_price) }} + £{{ "%.2f"|format(row.eurostar_price) }} {% if row.eurostar_seats is not none %} - {{ row.eurostar_seats }} at this price + {{ row.eurostar_seats }} at this price {% endif %} {% else %} - – + – {% endif %} |
@@ -188,32 +187,29 @@ {% endif %} | {% else %} -— | -— | -— | -— | -
- {{ row.depart_st_pancras }}
- {% if row.train_number %} {% for part in row.train_number.split(' + ') %}{{ part }}{% if not loop.last %} + {% endif %}{% endfor %}{% endif %} - |
- {{ row.arrive_destination }}
- (CET)
- {% if row.eurostar_duration %} ({{ row.eurostar_duration }}){% endif %} + Too early |
- + | — | +
+ {{ row.depart_st_pancras }} → {{ row.arrive_destination }} (CET)
+ {% if row.eurostar_duration or row.train_number %}
+ + {%- if row.eurostar_duration %}({{ row.eurostar_duration }}){% endif %} + {%- if row.eurostar_duration and row.train_number %} · {% endif %} + {%- if row.train_number %}{% for part in row.train_number.split(' + ') %}{{ part }}{% if not loop.last %} + {% endif %}{% endfor %}{% endif %} + + {% endif %} {% if row.eurostar_price is not none %} - £{{ "%.2f"|format(row.eurostar_price) }} + £{{ "%.2f"|format(row.eurostar_price) }} {% if row.eurostar_seats is not none %} - {{ row.eurostar_seats }} at this price + {{ row.eurostar_seats }} at this price {% endif %} {% else %} - – + – {% endif %} |
- - Too early - | +— | {% endif %}