Edward Betts
ce9faa654f
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.
205 lines
5.8 KiB
Python
Executable file
205 lines
5.8 KiB
Python
Executable file
#!/usr/bin/python3
|
|
|
|
"""Web page to show upcoming events."""
|
|
|
|
import inspect
|
|
import operator
|
|
import os.path
|
|
import sys
|
|
import traceback
|
|
from datetime import date, datetime
|
|
|
|
import flask
|
|
import werkzeug
|
|
import werkzeug.debug.tbtools
|
|
import yaml
|
|
|
|
import agenda.data
|
|
import agenda.error_mail
|
|
import agenda.thespacedevs
|
|
from agenda import format_list_with_ampersand, travel
|
|
from agenda.types import StrDict, Trip
|
|
|
|
app = flask.Flask(__name__)
|
|
app.debug = False
|
|
app.config.from_object("config.default")
|
|
|
|
agenda.error_mail.setup_error_mail(app)
|
|
|
|
|
|
@app.errorhandler(werkzeug.exceptions.InternalServerError)
|
|
def exception_handler(e: werkzeug.exceptions.InternalServerError) -> tuple[str, int]:
|
|
"""Handle exception."""
|
|
exec_type, exc_value, current_traceback = sys.exc_info()
|
|
assert exc_value
|
|
tb = werkzeug.debug.tbtools.DebugTraceback(exc_value)
|
|
|
|
summary = tb.render_traceback_html(include_title=False)
|
|
exc_lines = "".join(tb._te.format_exception_only())
|
|
|
|
last_frame = list(traceback.walk_tb(current_traceback))[-1][0]
|
|
last_frame_args = inspect.getargs(last_frame.f_code)
|
|
|
|
return (
|
|
flask.render_template(
|
|
"show_error.html",
|
|
plaintext=tb.render_traceback_text(),
|
|
exception=exc_lines,
|
|
exception_type=tb._te.exc_type.__name__,
|
|
summary=summary,
|
|
last_frame=last_frame,
|
|
last_frame_args=last_frame_args,
|
|
),
|
|
500,
|
|
)
|
|
|
|
|
|
@app.route("/")
|
|
async def index() -> str:
|
|
"""Index page."""
|
|
now = datetime.now()
|
|
data = await agenda.data.get_data(now, app.config)
|
|
|
|
return flask.render_template("index.html", today=now.date(), **data)
|
|
|
|
|
|
@app.route("/launches")
|
|
async def launch_list() -> str:
|
|
"""Web page showing List of space launches."""
|
|
now = datetime.now()
|
|
data_dir = app.config["DATA_DIR"]
|
|
rocket_dir = os.path.join(data_dir, "thespacedevs")
|
|
rockets = await agenda.thespacedevs.get_launches(rocket_dir, limit=100)
|
|
|
|
return flask.render_template("launches.html", rockets=rockets, now=now)
|
|
|
|
|
|
@app.route("/gaps")
|
|
async def gaps_page() -> str:
|
|
"""List of available gaps."""
|
|
now = datetime.now()
|
|
data = await agenda.data.get_data(now, app.config)
|
|
return flask.render_template("gaps.html", today=now.date(), gaps=data["gaps"])
|
|
|
|
|
|
@app.route("/travel")
|
|
def travel_list() -> str:
|
|
"""Page showing a list of upcoming travel."""
|
|
data_dir = app.config["PERSONAL_DATA"]
|
|
flights = travel.parse_yaml("flights", data_dir)
|
|
trains = travel.parse_yaml("trains", data_dir)
|
|
|
|
return flask.render_template("travel.html", flights=flights, trains=trains)
|
|
|
|
|
|
def as_date(d: date | datetime) -> date:
|
|
"""Date of event."""
|
|
return d.date() if isinstance(d, datetime) else d
|
|
|
|
|
|
@app.route("/conference")
|
|
def conference_list() -> str:
|
|
"""Page showing a list of conferences."""
|
|
data_dir = app.config["PERSONAL_DATA"]
|
|
filepath = os.path.join(data_dir, "conferences.yaml")
|
|
item_list = yaml.safe_load(open(filepath))
|
|
today = date.today()
|
|
for conf in item_list:
|
|
conf["start_date"] = as_date(conf["start"])
|
|
conf["end_date"] = as_date(conf["end"])
|
|
|
|
item_list.sort(key=operator.itemgetter("start_date"))
|
|
|
|
current = [
|
|
conf
|
|
for conf in item_list
|
|
if conf["start_date"] <= today and conf["end_date"] >= today
|
|
]
|
|
|
|
past = [conf for conf in item_list if conf["end_date"] < today]
|
|
future = [conf for conf in item_list if conf["start_date"] > today]
|
|
|
|
return flask.render_template(
|
|
"conference_list.html",
|
|
current=current,
|
|
past=past,
|
|
future=future,
|
|
today=today,
|
|
get_country=agenda.get_country,
|
|
)
|
|
|
|
|
|
@app.route("/accommodation")
|
|
def accommodation_list() -> str:
|
|
"""Page showing a list of past, present and future accommodation."""
|
|
data_dir = app.config["PERSONAL_DATA"]
|
|
items = travel.parse_yaml("accommodation", data_dir)
|
|
|
|
stays_in_2024 = [item for item in items if item["from"].year == 2024]
|
|
total_nights_2024 = sum(
|
|
(stay["to"].date() - stay["from"].date()).days for stay in stays_in_2024
|
|
)
|
|
|
|
nights_abroad_2024 = sum(
|
|
(stay["to"].date() - stay["from"].date()).days
|
|
for stay in stays_in_2024
|
|
if stay["country"] != "gb"
|
|
)
|
|
|
|
return flask.render_template(
|
|
"accommodation.html",
|
|
items=items,
|
|
total_nights_2024=total_nights_2024,
|
|
nights_abroad_2024=nights_abroad_2024,
|
|
get_country=agenda.get_country,
|
|
)
|
|
|
|
|
|
def load_travel(travel_type: str) -> list[StrDict]:
|
|
"""Read flight and train journeys."""
|
|
data_dir = app.config["PERSONAL_DATA"]
|
|
items = travel.parse_yaml(travel_type + "s", data_dir)
|
|
for item in items:
|
|
item["type"] = travel_type
|
|
return items
|
|
|
|
|
|
@app.route("/trip")
|
|
def trip_list() -> str:
|
|
"""Page showing a list of trips."""
|
|
trips: dict[date, Trip] = {}
|
|
|
|
data_dir = app.config["PERSONAL_DATA"]
|
|
|
|
travel_items = sorted(
|
|
load_travel("flight") + load_travel("train"), key=operator.itemgetter("depart")
|
|
)
|
|
|
|
data = {
|
|
"travel": travel_items,
|
|
"accommodation": travel.parse_yaml("accommodation", data_dir),
|
|
"conferences": travel.parse_yaml("conferences", data_dir),
|
|
}
|
|
|
|
for key, item_list in data.items():
|
|
assert isinstance(item_list, list)
|
|
for item in item_list:
|
|
if not (trip_id := item.get("trip")):
|
|
continue
|
|
if trip_id not in trips:
|
|
trips[trip_id] = Trip(date=trip_id)
|
|
getattr(trips[trip_id], key).append(item)
|
|
|
|
trip_list = [trip for _, trip in sorted(trips.items(), reverse=True)]
|
|
|
|
return flask.render_template(
|
|
"trips.html",
|
|
trips=trip_list,
|
|
get_country=agenda.get_country,
|
|
format_list_with_ampersand=format_list_with_ampersand,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0")
|