Add conference country filter

This commit is contained in:
Edward Betts 2026-07-09 10:13:49 +01:00
parent 442ae98463
commit 38dbb2b4e7
3 changed files with 178 additions and 1 deletions

View file

@ -212,6 +212,28 @@ tr.conf-hl > td {
<div class="container-fluid mt-2"> <div class="container-fluid mt-2">
<h1>Conferences</h1> <h1>Conferences</h1>
{% if country_options %}
<form method="get" class="row g-2 align-items-end mb-3">
<div class="col-auto">
<label for="country-filter" class="form-label mb-1">Country</label>
<select id="country-filter" name="country" class="form-select form-select-sm" onchange="this.form.submit()">
<option value="">All countries</option>
{% for option in country_options %}
<option value="{{ option.code }}"{% if option.code == selected_country %} selected{% endif %}>
{{ option.flag }} {{ option.name }}
</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-sm btn-primary">Filter</button>
{% if selected_country %}
<a class="btn btn-sm btn-outline-secondary" href="{{ url_for(request.endpoint) }}">Clear</a>
{% endif %}
</div>
</form>
{% endif %}
{{ render_timeline(timeline) }} {{ render_timeline(timeline) }}
<table class="table table-sm table-hover align-middle"> <table class="table table-sm table-hover align-middle">

View file

@ -6,6 +6,7 @@ from types import SimpleNamespace
import yaml import yaml
import agenda.fx
import agenda.trip import agenda.trip
import web_view import web_view
@ -121,3 +122,84 @@ def test_conference_series_pages(tmp_path: typing.Any, monkeypatch: typing.Any)
assert b"attended" in index_response.data assert b"attended" in index_response.data
assert detail_response.status_code == 200 assert detail_response.status_code == 200
assert b"trip: Seattle Python trip" in detail_response.data assert b"trip: Seattle Python trip" in detail_response.data
def test_conference_page_filters_by_country(
tmp_path: typing.Any, monkeypatch: typing.Any
) -> None:
"""Conference page should filter upcoming conferences by country code."""
conferences = [
{
"name": "UK Mapping Conf 2099",
"topic": "Maps",
"location": "London",
"country": "GB",
"start": date(2099, 5, 1),
"end": date(2099, 5, 2),
},
{
"name": "US Python Conf 2099",
"topic": "Python",
"location": "Pittsburgh",
"country": "US",
"start": date(2099, 6, 1),
"end": date(2099, 6, 2),
},
]
(tmp_path / "conferences.yaml").write_text(
yaml.safe_dump(conferences), encoding="utf-8"
)
monkeypatch.setitem(web_view.app.config, "PERSONAL_DATA", str(tmp_path))
monkeypatch.setattr(agenda.trip, "build_trip_list", lambda: [])
monkeypatch.setattr(agenda.fx, "get_rates", lambda config: {})
web_view.app.config["TESTING"] = True
with web_view.app.test_client() as client:
response = client.get("/conference?country=gb")
assert response.status_code == 200
assert b"UK Mapping Conf 2099" in response.data
assert b"US Python Conf 2099" not in response.data
assert b'<option value="gb" selected>' in response.data
assert b'href="/conference"' in response.data
def test_past_conference_page_filters_by_country(
tmp_path: typing.Any, monkeypatch: typing.Any
) -> None:
"""Past conference page should filter conferences by country code."""
conferences = [
{
"name": "Past UK Conf",
"topic": "Maps",
"location": "London",
"country": "GB",
"start": date(2001, 5, 1),
"end": date(2001, 5, 2),
},
{
"name": "Past US Conf",
"topic": "Python",
"location": "Pittsburgh",
"country": "US",
"start": date(2001, 6, 1),
"end": date(2001, 6, 2),
},
]
(tmp_path / "conferences.yaml").write_text(
yaml.safe_dump(conferences), encoding="utf-8"
)
monkeypatch.setitem(web_view.app.config, "PERSONAL_DATA", str(tmp_path))
monkeypatch.setattr(agenda.trip, "build_trip_list", lambda: [])
monkeypatch.setattr(agenda.fx, "get_rates", lambda config: {})
web_view.app.config["TESTING"] = True
with web_view.app.test_client() as client:
response = client.get("/conference/past?country=us")
assert response.status_code == 200
assert b"Past US Conf" in response.data
assert b"Past UK Conf" not in response.data
assert b'href="/conference/past"' in response.data

View file

@ -400,6 +400,64 @@ def build_conference_list() -> list[StrDict]:
return items return items
def conference_country_code(conf: StrDict) -> str | None:
"""Return normalized alpha-2 country code for a conference."""
country = conf.get("country")
if not isinstance(country, str):
return None
code = country.strip().lower()
return code if len(code) == 2 else None
def normalize_country_filter(value: str | None) -> str | None:
"""Normalize and validate a country query parameter."""
if not value:
return None
code = value.strip().lower()
if len(code) != 2:
return None
return code if agenda.get_country(code) else None
def filter_conferences_by_country(
items: list[StrDict], country_code: str | None
) -> list[StrDict]:
"""Filter conferences by country when a country code is provided."""
if not country_code:
return items
return [item for item in items if conference_country_code(item) == country_code]
def conference_country_options(items: list[StrDict]) -> list[StrDict]:
"""Return country options for the conference country filter."""
counts: defaultdict[str, int] = defaultdict(int)
for item in items:
code = conference_country_code(item)
if code and agenda.get_country(code):
counts[code] += 1
options: list[StrDict] = []
for code, count in counts.items():
country = agenda.get_country(code)
if not country:
continue
options.append(
{
"code": code,
"name": country.name,
"flag": country.flag,
"count": count,
}
)
options.sort(key=lambda item: str(item["name"]))
return options
def build_conference_series_list() -> list[StrDict]: def build_conference_series_list() -> list[StrDict]:
"""Build conference series list with conference counts.""" """Build conference series list with conference counts."""
data_dir = app.config["PERSONAL_DATA"] data_dir = app.config["PERSONAL_DATA"]
@ -591,6 +649,11 @@ def conference_list() -> str:
"""Page showing a list of conferences.""" """Page showing a list of conferences."""
today = date.today() today = date.today()
items = build_conference_list() items = build_conference_list()
country_filter = normalize_country_filter(flask.request.args.get("country"))
country_options = conference_country_options(
[conf for conf in items if conf["latest_date"] >= today]
)
items = filter_conferences_by_country(items, country_filter)
current = [ current = [
conf conf
@ -612,6 +675,8 @@ def conference_list() -> str:
timeline=timeline, timeline=timeline,
today=today, today=today,
get_country=agenda.get_country, get_country=agenda.get_country,
selected_country=country_filter,
country_options=country_options,
fx_rate=agenda.fx.get_rates(app.config), fx_rate=agenda.fx.get_rates(app.config),
) )
@ -620,11 +685,19 @@ def conference_list() -> str:
def past_conference_list() -> str: def past_conference_list() -> str:
"""Page showing a list of conferences.""" """Page showing a list of conferences."""
today = date.today() today = date.today()
items = build_conference_list()
country_filter = normalize_country_filter(flask.request.args.get("country"))
country_options = conference_country_options(
[conf for conf in items if conf["latest_date"] < today]
)
items = filter_conferences_by_country(items, country_filter)
return flask.render_template( return flask.render_template(
"conference_list.html", "conference_list.html",
past=[conf for conf in build_conference_list() if conf["latest_date"] < today], past=[conf for conf in items if conf["latest_date"] < today],
today=today, today=today,
get_country=agenda.get_country, get_country=agenda.get_country,
selected_country=country_filter,
country_options=country_options,
fx_rate=agenda.fx.get_rates(app.config), fx_rate=agenda.fx.get_rates(app.config),
) )