Add OpenWeatherMap weather forecasts. Closes #48

Show 8-day Bristol home weather on the index and weekends pages.
Show destination weather per day on the trip list and trip page.
Cache forecasts in ~/lib/data/weather/ and refresh via update.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2026-02-21 20:27:25 +00:00
parent 61e17d9c96
commit b4f0a5bf5d
7 changed files with 217 additions and 0 deletions

View file

@ -70,6 +70,19 @@
Sunset: {{ sunset.strftime("%H:%M:%S") }}</li>
</ul>
{% if home_weather %}
<div class="mb-2">
<strong>Bristol weather:</strong>
{% for day in home_weather %}
<span class="me-3 text-nowrap">
<small class="text-muted">{{ day.date_obj.strftime("%-d %b") }}</small>
<img src="https://openweathermap.org/img/wn/{{ day.icon }}.png" alt="{{ day.status }}" title="{{ day.detailed_status }}" width="25" height="25">
{{ day.temp_min }}{{ day.temp_max }}°C
</span>
{% endfor %}
</div>
{% endif %}
{% if errors %}
{% for error in errors %}
<div class="alert alert-danger" role="alert">

View file

@ -474,8 +474,16 @@ https://www.flightradar24.com/data/flights/{{ flight.airline_detail.iata | lower
{{ conference_list(trip) }}
{% set trip_weather = trip_weather_map.get(trip.start.isoformat(), {}) if trip_weather_map is defined else {} %}
{% for day, elements in trip.elements_grouped_by_day() %}
{% set weather = trip_weather.get(day.isoformat()) %}
<h4>{{ display_date_no_year(day) }}
{% if weather %}
<small class="text-muted">
<img src="https://openweathermap.org/img/wn/{{ weather.icon }}.png" alt="{{ weather.status }}" title="{{ weather.detailed_status }}" width="25" height="25">
{{ weather.temp_min }}{{ weather.temp_max }}°C {{ weather.detailed_status }}
</small>
{% endif %}
{% if g.user.is_authenticated and day <= today %}
<span class="lead">
<a href="https://photos.4angle.com/search?query=%7B%22takenAfter%22%3A%22{{day}}T00%3A00%3A00.000Z%22%2C%22takenBefore%22%3A%22{{day}}T23%3A59%3A59.999Z%22%7D">photos</a>

View file

@ -377,6 +377,30 @@
</div>
{% endfor %}
{% if trip_weather %}
<div class="mt-3">
<h4>Weather forecast</h4>
{% set ns = namespace(has_rows=false) %}
<table class="table table-hover w-auto">
{% for day, elements in trip.elements_grouped_by_day() %}
{% set weather = trip_weather.get(day.isoformat()) %}
{% if weather %}
{% set ns.has_rows = true %}
<tr>
<td class="text-end text-nowrap">{{ display_date(day) }}</td>
<td><img src="https://openweathermap.org/img/wn/{{ weather.icon }}.png" alt="{{ weather.status }}" title="{{ weather.detailed_status }}" width="25" height="25"></td>
<td>{{ weather.temp_min }}{{ weather.temp_max }}°C</td>
<td>{{ weather.detailed_status }}</td>
</tr>
{% endif %}
{% endfor %}
</table>
{% if not ns.has_rows %}
<p class="text-muted">Forecast not yet available (available up to 8 days ahead).</p>
{% endif %}
</div>
{% endif %}
<div class="mt-3">
<h4>Holidays</h4>
{% if holidays %}

View file

@ -12,8 +12,10 @@
<th class="text-end">Date</th>
<th>Saturday</th>
<th>Saturday Location</th>
<th>Saturday Weather</th>
<th>Sunday</th>
<th>Sunday Location</th>
<th>Sunday Weather</th>
</tr>
</thead>
<tbody>
@ -51,6 +53,13 @@
{{ city }}, {{ country.flag }} {{ country.name }}
{% endif %}
</td>
{% if extra_class %}<td class="{{ extra_class|trim }}">{% else %}<td>{% endif %}
{% set w = weekend[day + '_weather'] %}
{% if w %}
<img src="https://openweathermap.org/img/wn/{{ w.icon }}.png" alt="{{ w.status }}" title="{{ w.detailed_status }}" width="25" height="25">
{{ w.temp_min }}{{ w.temp_max }}°C
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}