From ce9faa654fbd530fe4a8360ef1c52e30ed0ec69e Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Thu, 4 Jan 2024 22:55:19 +0000 Subject: [PATCH] Add trips page Creating a new entity called a trip. This will group together any travel accommodation and conferences that happen together on one trip. A trip is assumed to start when leaving home and finish when returning home. The start date of a trip in is the trip ID. The date is written in ISO format. This assumes there cannot be multiple trips one one day. This assumption might be wrong, for example a morning day trip by rail, then another trip starts in the afternoon. I can change my choice of using dates as trip IDs if that happens. Sometimes during the planning of a trip the start date is unknown. For now we make up a start date, we can always change it later. If we use the start date in URLs then the URLs will change. Might need to keep a file of redirects, or could think of a different style of identifier. Trip ID have been added to accommodation, conferences, trains and flights. Later there will be a trips.yaml with notes about each trip. --- agenda/__init__.py | 21 ++++++++ agenda/conference.py | 1 + agenda/data.py | 4 +- agenda/types.py | 47 +++++++++++++++++ templates/accommodation.html | 29 +--------- templates/conference_list.html | 49 +++-------------- templates/macros.html | 96 ++++++++++++++++++++++++++++++++++ templates/navbar.html | 1 + templates/travel.html | 39 +++----------- web_view.py | 67 ++++++++++++++++++------ 10 files changed, 234 insertions(+), 120 deletions(-) create mode 100644 templates/macros.html diff --git a/agenda/__init__.py b/agenda/__init__.py index baf8813..63b6e7d 100644 --- a/agenda/__init__.py +++ b/agenda/__init__.py @@ -2,6 +2,7 @@ from datetime import date, datetime, time +import pycountry import pytz uk_tz = pytz.timezone("Europe/London") @@ -10,3 +11,23 @@ uk_tz = pytz.timezone("Europe/London") def uk_time(d: date, t: time) -> datetime: """Combine time and date for UK timezone.""" return uk_tz.localize(datetime.combine(d, t)) + + +def format_list_with_ampersand(items: list[str]) -> str: + """Join a list of strings with commas and an ampersand.""" + if len(items) > 1: + return ", ".join(items[:-1]) + " & " + items[-1] + elif items: + return items[0] + return "" + + +def get_country(alpha_2: str) -> pycountry.db.Country | None: + """Lookup country by alpha-2 country code.""" + if not alpha_2: + return None + if alpha_2 == "xk": + return pycountry.db.Country(flag="\U0001F1FD\U0001F1F0", name="Kosovo") + + country: pycountry.db.Country = pycountry.countries.get(alpha_2=alpha_2.upper()) + return country diff --git a/agenda/conference.py b/agenda/conference.py index 13d6a42..513a225 100644 --- a/agenda/conference.py +++ b/agenda/conference.py @@ -18,6 +18,7 @@ class Conference: location: str start: date | datetime end: date | datetime + trip: date | None = None country: str | None = None venue: str | None = None address: str | None = None diff --git a/agenda/data.py b/agenda/data.py index a508c39..04de858 100644 --- a/agenda/data.py +++ b/agenda/data.py @@ -36,9 +36,7 @@ from . import ( uk_tz, waste_schedule, ) -from .types import Event, Holiday - -StrDict = dict[str, typing.Any] +from .types import Event, Holiday, StrDict here = dateutil.tz.tzlocal() diff --git a/agenda/types.py b/agenda/types.py index ce58d3a..e3d5801 100644 --- a/agenda/types.py +++ b/agenda/types.py @@ -2,6 +2,53 @@ import dataclasses import datetime +import typing + +from pycountry.db import Country + +import agenda +from agenda import format_list_with_ampersand + +StrDict = dict[str, typing.Any] + + +@dataclasses.dataclass +class Trip: + """Trip.""" + + date: datetime.date + travel: list[StrDict] = dataclasses.field(default_factory=list) + accommodation: list[StrDict] = dataclasses.field(default_factory=list) + conferences: list[StrDict] = dataclasses.field(default_factory=list) + + @property + def title(self) -> str: + """Trip title.""" + names = ( + format_list_with_ampersand([conf["name"] for conf in self.conferences]) + or "[no conferences in trip]" + ) + return f'{names} ({self.date.strftime("%b %Y")})' + + @property + def countries(self) -> set[Country]: + """Trip countries.""" + found: set[Country] = set() + for item in self.conferences + self.accommodation: + if "country" not in item: + continue + country = agenda.get_country(item["country"]) + assert country + found.add(country) + + return found + + @property + def countries_str(self) -> str: + """List of countries visited on this trip.""" + return format_list_with_ampersand( + [f"{c.flag} {c.name}" for c in self.countries] + ) @dataclasses.dataclass diff --git a/templates/accommodation.html b/templates/accommodation.html index 29e5bfa..5d85bc4 100644 --- a/templates/accommodation.html +++ b/templates/accommodation.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "macros.html" import accommodation_row with context %} {% block style %} {% set column_count = 7 %} {% endblock %} -{% macro row(item, badge) %} -{% set country = get_country(item.country) %} -{% set nights = (item.to.date() - item.from.date()).days %} -
{{ item.from.strftime("%a, %d %b %Y") }}
-
{{ item.to.strftime("%a, %d %b") }}
-
{% if nights == 1 %}1 night{% else %}{{ nights }} nights{% endif %}
-
{{ item.operator }}
-
{{ item.location }}
-
- {% if country %} - {{ country.flag }} {{ country.name }} - {% else %} - - country code {{ item.country }} not found - - {% endif %} -
-
- {% if item.url %} - {{ item.name }} - {% else %} - {{ item.name }} - {% endif %} -
-{% endmacro %} - {% macro section(heading, item_list, badge) %} {% if item_list %}

{{heading}}

-{% for item in item_list %}{{ row(item, badge) }}{% endfor %} +{% for item in item_list %}{{ accommodation_row(item, badge) }}{% endfor %} {% endif %} {% endmacro %} diff --git a/templates/conference_list.html b/templates/conference_list.html index 74fa7eb..3465f01 100644 --- a/templates/conference_list.html +++ b/templates/conference_list.html @@ -1,5 +1,7 @@ {% extends "base.html" %} +{% from "macros.html" import conference_row with context %} + {% block style %} {% set column_count = 6 %} {% endblock %} -{% macro row(item, badge) %} -{% set country = get_country(item.country) if item.country else None %} -
{{ item.start.strftime("%a, %d %b %Y") }}
-
{{ item.end.strftime("%a, %d %b") }}
-
- {% if item.url %} - {{ item.name }} - {% else %} - {{ item.name }} - {% endif %} - {% if item.going and not (item.accommodation_booked or item.travel_booked) %} - - {{ badge }} - - {% endif %} - {% if item.accommodation_booked %} - accommodation - {% endif %} - {% if item.transport_booked %} - transport - {% endif %} -
-
{{ item.topic }}
-
{{ item.location }}
-
- {% if country %} - {{ country.flag }} {{ country.name }} - {% elif item.online %} - 💻 Online - {% else %} - - country code {{ item.country }} not found - - {% endif %} -
-{% endmacro %} - {% macro section(heading, item_list, badge) %} -{% if item_list %} -

{{heading}}

-{% for item in item_list %}{{ row(item, badge) }}{% endfor %} -{% endif %} + {% if item_list %} +

{{ heading }}

+ {% for item in item_list %}{{ conference_row(item, badge) }}{% endfor %} + {% endif %} {% endmacro %} {% block content %}
-

Conferences

-
{{ section("Current", current, "attending") }} {{ section("Future", future, "going") }} diff --git a/templates/macros.html b/templates/macros.html new file mode 100644 index 0000000..b279470 --- /dev/null +++ b/templates/macros.html @@ -0,0 +1,96 @@ +{% macro display_datetime(dt) %}{{ dt.strftime("%a, %d, %b %Y %H:%M %z") }}{% endmacro %} +{% macro display_time(dt) %}{{ dt.strftime("%H:%M %z") }}{% endmacro %} + +{% macro conference_row(item, badge) %} + {% set country = get_country(item.country) if item.country else None %} +
{{ item.start.strftime("%a, %d %b %Y") }}
+
{{ item.end.strftime("%a, %d %b") }}
+
+ {% if item.url %} + {{ item.name }} + {% else %} + {{ item.name }} + {% endif %} + {% if item.going and not (item.accommodation_booked or item.travel_booked) %} + + {{ badge }} + + {% endif %} + {% if item.accommodation_booked %} + accommodation + {% endif %} + {% if item.transport_booked %} + transport + {% endif %} +
+
{{ item.topic }}
+
{{ item.location }}
+
+ {% if country %} + {{ country.flag }} {{ country.name }} + {% elif item.online %} + 💻 Online + {% else %} + + country code {{ item.country }} not found + + {% endif %} +
+{% endmacro %} + +{% macro accommodation_row(item, badge) %} + {% set country = get_country(item.country) %} + + {% set nights = (item.to.date() - item.from.date()).days %} +
{{ item.from.strftime("%a, %d %b %Y") }}
+
{{ item.to.strftime("%a, %d %b") }}
+
{% if nights == 1 %}1 night{% else %}{{ nights }} nights{% endif %}
+
{{ item.operator }}
+
{{ item.location }}
+
+ {% if country %} + {{ country.flag }} {{ country.name }} + {% else %} + + country code {{ item.country }} not found + + {% endif %} +
+
+ {% if item.url %} + {{ item.name }} + {% else %} + {{ item.name }} + {% endif %} +
+{% endmacro %} + +{% macro flight_row(item) %} +
{{ item.depart.strftime("%a, %d %b %Y") }}
+
{{ item.from }} → {{ item.to }}
+
{{ item.depart.strftime("%H:%M") }}
+
+ {% if item.arrive %} + {{ item.arrive.strftime("%H:%M") }} + {% if item.arrive.date() != item.depart.date() %}+1 day{% endif %} + {% endif %} +
+
{{ item.duration }}
+
{{ item.airline }}{{ item.flight_number }}
+
{{ item.booking_reference }}
+{% endmacro %} + +{% macro train_row(item) %} +
{{ item.depart.strftime("%a, %d %b %Y") }}
+
{{ item.from }} → {{ item.to }}
+
{{ item.depart.strftime("%H:%M") }}
+
+ {% if item.arrive %} + {{ item.arrive.strftime("%H:%M") }} + {% if item.arrive.date() != item.depart.date() %}+1 day{% endif %} + {% endif %} +
+
{{ ((item.arrive - item.depart).total_seconds() // 60) | int }} mins
+
{{ item.operator }}
+
{{ item.booking_reference }}
+{% endmacro %} diff --git a/templates/navbar.html b/templates/navbar.html index 444fa14..198b666 100644 --- a/templates/navbar.html +++ b/templates/navbar.html @@ -2,6 +2,7 @@ {% set pages = [ {"endpoint": "index", "label": "Home" }, + {"endpoint": "trip_list", "label": "Trips" }, {"endpoint": "conference_list", "label": "Conference" }, {"endpoint": "travel_list", "label": "Travel" }, {"endpoint": "accommodation_list", "label": "Accommodation" }, diff --git a/templates/travel.html b/templates/travel.html index 86ae8bc..a762ebb 100644 --- a/templates/travel.html +++ b/templates/travel.html @@ -1,10 +1,5 @@ {% extends "base.html" %} - -{% block travel %} -{% endblock %} - -{% macro display_datetime(dt) %}{{ dt.strftime("%a, %d, %b %Y %H:%M %z") }}{% endmacro %} -{% macro display_time(dt) %}{{ dt.strftime("%H:%M %z") }}{% endmacro %} +{% from "macros.html" import flight_row, train_row with context %} {% block style %}