From 57b2db205d6ce863c4c85c90d05205328a14556e Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Mon, 22 Jun 2026 10:25:55 +0100 Subject: [PATCH] Add conference series pages --- agenda/conference.py | 28 +++++++++ docs/personal-data-yaml.md | 45 ++++++++++++++ templates/conference_list.html | 13 +++- templates/conference_series.html | 85 +++++++++++++++++++++++++++ templates/conference_series_list.html | 44 ++++++++++++++ templates/navbar.html | 3 +- tests/test_conference_list.py | 67 +++++++++++++++++++++ validate_yaml.py | 22 +++++++ web_view.py | 63 ++++++++++++++++++++ 9 files changed, 367 insertions(+), 3 deletions(-) create mode 100644 templates/conference_series.html create mode 100644 templates/conference_series_list.html diff --git a/agenda/conference.py b/agenda/conference.py index f9e15b4..89f43fe 100644 --- a/agenda/conference.py +++ b/agenda/conference.py @@ -2,6 +2,7 @@ import dataclasses import decimal +import os import typing from datetime import date, datetime @@ -16,6 +17,18 @@ DATE_STATUSES = {"exact", "approximate", "tentative"} DATED_STATUSES = {"exact", "tentative"} +class ConferenceSeries(typing.TypedDict, total=False): + """Conference series metadata.""" + + name: str + topic: str + url: str + notes: str + cadence: str + usual_location: str + country: str + + @dataclasses.dataclass class Conference: """Conference.""" @@ -23,6 +36,7 @@ class Conference: name: str topic: str location: str + series: str | None = None start: date | datetime | None = None end: date | datetime | None = None trip: date | None = None @@ -166,6 +180,20 @@ def validate_conference_date_fields(item: StrDict) -> StrDict: return fields +def load_series(data_dir: str) -> dict[str, ConferenceSeries]: + """Load conference series metadata.""" + filepath = os.path.join(data_dir, "conference_series.yaml") + if not os.path.exists(filepath): + return {} + + loaded = yaml.safe_load(open(filepath, "r")) + if loaded is None: + return {} + if not isinstance(loaded, dict): + raise ValueError("conference_series.yaml must be a mapping") + return typing.cast(dict[str, ConferenceSeries], loaded) + + def get_list(filepath: str) -> list[Event]: """Read conferences from a YAML file and return a list of Event objects.""" events: list[Event] = [] diff --git a/docs/personal-data-yaml.md b/docs/personal-data-yaml.md index a538fff..08c391e 100644 --- a/docs/personal-data-yaml.md +++ b/docs/personal-data-yaml.md @@ -283,6 +283,7 @@ Legacy fields: Common optional fields: +- Series: `series`, a key from `conference_series.yaml`. - Trip/location: `trip`, `country`, `venue`, `address`, `latitude`, `longitude`. - Attendance: `going`, `registered`, `speaking`, `online`, `accommodation_booked`, `transport_booked`. - Partial attendance: `attend_start`, `attend_end`. These may be dates or timezone-aware datetimes and are used on trip pages instead of official dates. @@ -294,6 +295,7 @@ Exact example: ```yaml - name: FOSDEM + series: fosdem topic: FOSDEM location: Brussels country: be @@ -319,6 +321,7 @@ Tentative example: ```yaml - name: FOSDEM + series: fosdem topic: FOSDEM location: Brussels country: be @@ -335,6 +338,7 @@ Approximate examples: ```yaml - name: Wikimedia Hackathon 2027 + series: wikimedia-hackathon topic: Wikimedia location: Albania country: al @@ -346,6 +350,7 @@ Approximate examples: hackathon: true - name: PyCascades 2027 + series: pycascades topic: Python location: TBC dates: @@ -355,6 +360,46 @@ Approximate examples: latest: 2027-03-31 ``` +## `conference_series.yaml` + +Top-level shape: mapping from stable series ID to series metadata. + +Used by: conference list pages, conference series index/detail pages, and validation of `conferences.yaml` `series` references. + +Required fields for each series: + +- `name`: display name for the series. + +Common optional fields: + +- `topic`: default topic/category. +- `cadence`: for example `annual` or `recurring`. +- `usual_location`: common city/place when the event usually stays in one place. +- `country`: common lowercase country code when stable. +- `url`: series homepage. +- `notes`: free-text generation or scheduling notes. + +Example: + +```yaml +fosdem: + name: FOSDEM + topic: FOSDEM + cadence: annual + usual_location: Brussels + country: be + url: https://fosdem.org/ + notes: Usually the weekend where Sunday is the first Sunday in February. + +geomob-london: + name: Geomob London + topic: Maps + cadence: recurring + usual_location: London + country: gb + url: https://thegeomob.com/ +``` + ## `entities.yaml` Top-level shape: list of people/entities. diff --git a/templates/conference_list.html b/templates/conference_list.html index 6529b22..e20b154 100644 --- a/templates/conference_list.html +++ b/templates/conference_list.html @@ -134,7 +134,7 @@ tr.conf-hl > td { {% if item_list %} {% set count = item_list | length %} - {{ heading }} {{ count }} conference{{ "" if count == 1 else "s" }} + {{ heading }} {{ count }} conference{{ "" if count == 1 else "s" }} {% set ns = namespace(prev_month="") %} {% for item in item_list %} @@ -142,7 +142,7 @@ tr.conf-hl > td { {% if month_label != ns.prev_month %} {% set ns.prev_month = month_label %} - {{ month_label }} + {{ month_label }} {% endif %} @@ -176,6 +176,13 @@ tr.conf-hl > td { {% endif %} {{ item.topic }} + + {% if item.series and item.series_detail %} + {{ item.series_detail.name }} + {% elif item.series %} + {{ item.series }} + {% endif %} + {% set country = get_country(item.country) if item.country else None %} {% if country %}{{ country.flag }} {{ item.location }} @@ -213,6 +220,7 @@ tr.conf-hl > td { + @@ -221,6 +229,7 @@ tr.conf-hl > td { Dates Conference Topic + Series Location CFP ends Price diff --git a/templates/conference_series.html b/templates/conference_series.html new file mode 100644 index 0000000..10bfdb7 --- /dev/null +++ b/templates/conference_series.html @@ -0,0 +1,85 @@ +{% extends "base.html" %} + +{% block title %}{{ series.name }} - Edward Betts{% endblock %} + +{% block content %} +
+

{{ series.name }}

+ +
+ {% if series.topic %} +
Topic
+
{{ series.topic }}
+ {% endif %} + {% if series.usual_location or series.country %} +
Usual location
+
+ {% set country = get_country(series.country) if series.country else None %} + {% if country %}{{ country.flag }} {% endif %}{{ series.usual_location or "" }} +
+ {% endif %} + {% if series.cadence %} +
Cadence
+
{{ series.cadence }}
+ {% endif %} + {% if series.url %} +
Website
+
{{ series.url }}
+ {% endif %} + {% if series.notes %} +
Notes
+
{{ series.notes }}
+ {% endif %} +
+ +

Editions

+ + + + + + + + + + + {% for item in conferences %} + + + + + + + {% endfor %} + +
DatesConferenceLocationAttendance
+ {{ item.display_date }} + {% if item.date_status == "tentative" %} + tentative + {% elif item.date_status == "approximate" %} + approximate + {% endif %} + + {% if item.url %}{{ item.name }} + {% else %}{{ item.name }}{% endif %} + + {% set country = get_country(item.country) if item.country else None %} + {% if country %}{{ country.flag }} {% endif %}{{ item.location }} + + {% if item.going %} + going + {% endif %} + {% if item.registered %} + registered + {% endif %} + {% if item.linked_trip %} + {% set trip = item.linked_trip %} + + trip{% if trip.title != item.name %}: {{ trip.title }}{% endif %} + + {% endif %} +
+
+{% endblock %} diff --git a/templates/conference_series_list.html b/templates/conference_series_list.html new file mode 100644 index 0000000..a39890d --- /dev/null +++ b/templates/conference_series_list.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} + +{% block title %}Conference Series - Edward Betts{% endblock %} + +{% block content %} +
+

Conference Series

+ + + + + + + + + + + + + {% for item in series_list %} + + + + + + + + {% endfor %} + +
SeriesTopicUsual locationConferencesNext
+ {{ item.name }} + {% if item.url %} + + {% endif %} + {{ item.topic or "" }} + {% set country = get_country(item.country) if item.country else None %} + {% if country %}{{ country.flag }} {% endif %}{{ item.usual_location or "" }} + {{ item.count }} + {% if item.next_conf %} + {{ item.next_conf.display_date }} · {{ item.next_conf.name }} + {% endif %} +
+
+{% endblock %} diff --git a/templates/navbar.html b/templates/navbar.html index fe5b98a..373c053 100644 --- a/templates/navbar.html +++ b/templates/navbar.html @@ -28,6 +28,7 @@ {% set conference_pages = [ {"endpoint": "conference_list", "label": "Conferences" }, {"endpoint": "past_conference_list", "label": "Past conferences" }, + {"endpoint": "conference_series_list", "label": "Conference series" }, ] %} @@ -63,7 +64,7 @@ - {% set conference_active = request.endpoint in ['conference_list', 'past_conference_list'] %} + {% set conference_active = request.endpoint in ['conference_list', 'past_conference_list', 'conference_series_list', 'conference_series_page'] %}