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 }}
- ← Prev {{ travel_date_display }} - Next →
@@ -30,7 +30,7 @@ {% else %} {{ destination_name }} {% endif %} {% endfor %} @@ -64,9 +64,13 @@

@@ -90,17 +94,13 @@ {% if trips or unreachable_morning_services %} -

+
- - - + - - - + @@ -128,51 +128,50 @@ {% endif %} {% if row.row_type == 'trip' %} - - - - - {% else %} - - - - - - + - + {% endif %} {% endfor %}
{{ departure_station_name }}PaddingtonGWR FareNational Rail TransferDepart STP{{ destination }}ES StdEurostar 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 %} - {{ 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 -