Add conference country filter
This commit is contained in:
parent
442ae98463
commit
38dbb2b4e7
3 changed files with 178 additions and 1 deletions
|
|
@ -212,6 +212,28 @@ tr.conf-hl > td {
|
|||
<div class="container-fluid mt-2">
|
||||
<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) }}
|
||||
|
||||
<table class="table table-sm table-hover align-middle">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from types import SimpleNamespace
|
|||
|
||||
import yaml
|
||||
|
||||
import agenda.fx
|
||||
import agenda.trip
|
||||
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 detail_response.status_code == 200
|
||||
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
|
||||
|
|
|
|||
75
web_view.py
75
web_view.py
|
|
@ -400,6 +400,64 @@ def build_conference_list() -> list[StrDict]:
|
|||
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]:
|
||||
"""Build conference series list with conference counts."""
|
||||
data_dir = app.config["PERSONAL_DATA"]
|
||||
|
|
@ -591,6 +649,11 @@ def conference_list() -> str:
|
|||
"""Page showing a list of conferences."""
|
||||
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)
|
||||
|
||||
current = [
|
||||
conf
|
||||
|
|
@ -612,6 +675,8 @@ def conference_list() -> str:
|
|||
timeline=timeline,
|
||||
today=today,
|
||||
get_country=agenda.get_country,
|
||||
selected_country=country_filter,
|
||||
country_options=country_options,
|
||||
fx_rate=agenda.fx.get_rates(app.config),
|
||||
)
|
||||
|
||||
|
|
@ -620,11 +685,19 @@ def conference_list() -> str:
|
|||
def past_conference_list() -> str:
|
||||
"""Page showing a list of conferences."""
|
||||
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(
|
||||
"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,
|
||||
get_country=agenda.get_country,
|
||||
selected_country=country_filter,
|
||||
country_options=country_options,
|
||||
fx_rate=agenda.fx.get_rates(app.config),
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue