#!/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 import agenda.trip from agenda import format_list_with_ampersand, travel 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, ) @app.route("/trip") def trip_list() -> str: """Page showing a list of trips.""" trip_list = agenda.trip.build_trip_list() today = date.today() current = [ item for item in trip_list if item.start <= today and (item.end or item.start) >= today ] past = [item for item in trip_list if (item.end or item.start) < today] future = [item for item in trip_list if item.start > today] future_coordinates = [] seen_future_coordinates: set[tuple[str, str]] = set() future_routes = [] seen_future_routes: set[str] = set() for trip in future: for stop in agenda.trip.collect_trip_coordinates(trip): key = (stop["type"], stop["name"]) if key in seen_future_coordinates: continue future_coordinates.append(stop) seen_future_coordinates.add(key) for route in agenda.trip.get_trip_routes(trip): if route["key"] in seen_future_routes: continue future_routes.append(route) seen_future_routes.add(route["key"]) for route in future_routes: if "geojson_filename" in route: route["geojson"] = agenda.trip.read_geojson(route.pop("geojson_filename")) return flask.render_template( "trip_list.html", current=current, past=past, future=future, future_coordinates=future_coordinates, future_routes=future_routes, today=today, get_country=agenda.get_country, format_list_with_ampersand=format_list_with_ampersand, ) @app.route("/trip/") def trip_page(start: str) -> str: """Individual trip page.""" trip_list = agenda.trip.build_trip_list() today = date.today() trip = next((trip for trip in trip_list if trip.start.isoformat() == start), None) if not trip: flask.abort(404) coordinates = agenda.trip.collect_trip_coordinates(trip) routes = agenda.trip.get_trip_routes(trip) for route in routes: if "geojson_filename" in route: route["geojson"] = agenda.trip.read_geojson(route.pop("geojson_filename")) return flask.render_template( "trip_page.html", trip=trip, today=today, coordinates=coordinates, routes=routes, get_country=agenda.get_country, format_list_with_ampersand=format_list_with_ampersand, ) if __name__ == "__main__": app.run(host="0.0.0.0")