Skip to content

Commit ea8ba56

Browse files
committed
Improve holidays config form and naming
The holidays library is improving over time, let's make use of their data for a more user-friendly experience.
1 parent ad7a334 commit ea8ba56

File tree

2 files changed

+95
-15
lines changed

2 files changed

+95
-15
lines changed

homeassistant/components/holiday/config_flow.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from homeassistant.helpers.selector import (
2020
CountrySelector,
2121
CountrySelectorConfig,
22+
SelectOptionDict,
2223
SelectSelector,
2324
SelectSelectorConfig,
2425
SelectSelectorMode,
@@ -30,6 +31,29 @@
3031
SUPPORTED_COUNTRIES = list_supported_countries(include_aliases=False)
3132

3233

34+
def get_optional_provinces(country: str) -> list[Any]:
35+
"""Return the country provinces (territories).
36+
37+
Some territories have extra or different holidays
38+
from another within the same country.
39+
"""
40+
province_options: list[Any] = []
41+
42+
if provinces := SUPPORTED_COUNTRIES[country]:
43+
country_data = country_holidays(country, years=dt_util.utcnow().year)
44+
if country_data.subdivisions_aliases and (
45+
subdiv_aliases := country_data.get_subdivision_aliases()
46+
):
47+
province_options = [
48+
SelectOptionDict(value=k, label=", ".join(v))
49+
for k, v in subdiv_aliases.items()
50+
]
51+
else:
52+
province_options = provinces
53+
54+
return province_options
55+
56+
3357
def get_optional_categories(country: str) -> list[str]:
3458
"""Return the country categories.
3559
@@ -45,7 +69,7 @@ def get_optional_categories(country: str) -> list[str]:
4569
def get_options_schema(country: str) -> vol.Schema:
4670
"""Return the options schema."""
4771
schema = {}
48-
if provinces := SUPPORTED_COUNTRIES[country]:
72+
if provinces := get_optional_provinces(country):
4973
schema[vol.Optional(CONF_PROVINCE)] = SelectSelector(
5074
SelectSelectorConfig(
5175
options=provinces,
@@ -64,6 +88,15 @@ def get_options_schema(country: str) -> vol.Schema:
6488
return vol.Schema(schema)
6589

6690

91+
def get_province_alias(country: str, province: str) -> str:
92+
"""Return the friendly alias for a given province (territory) code, if any."""
93+
country_data = country_holidays(country, province, years=dt_util.utcnow().year)
94+
# holidays API doesn't make it straightforward
95+
# revisit this after their API is enriched
96+
code_to_alias = {v: k for k, v in country_data.subdivisions_aliases.items()}
97+
return code_to_alias.get(province, province)
98+
99+
67100
class HolidayConfigFlow(ConfigFlow, domain=DOMAIN):
68101
"""Handle a config flow for Holiday."""
69102

@@ -140,8 +173,11 @@ async def async_step_options(
140173
# Default to (US) English if language not recognized by babel
141174
# Mainly an issue with English flavors such as "en-GB"
142175
locale = Locale("en")
143-
province_str = f", {province}" if province else ""
144-
name = f"{locale.territories[country]}{province_str}"
176+
name = locale.territories[country]
177+
178+
if province:
179+
alias = get_province_alias(country, province)
180+
name = f"{name}, {alias}"
145181

146182
return self.async_create_entry(title=name, data=data, options=options)
147183

@@ -177,8 +213,11 @@ async def async_step_reconfigure(
177213
# Default to (US) English if language not recognized by babel
178214
# Mainly an issue with English flavors such as "en-GB"
179215
locale = Locale("en")
180-
province_str = f", {province}" if province else ""
181-
name = f"{locale.territories[country]}{province_str}"
216+
name = locale.territories[country]
217+
218+
if province:
219+
alias = get_province_alias(country, province)
220+
name = f"{name}, {alias}"
182221

183222
if options:
184223
return self.async_update_reload_and_abort(

tests/components/holiday/test_config_flow.py

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
4747
await hass.async_block_till_done()
4848

4949
assert result3["type"] is FlowResultType.CREATE_ENTRY
50-
assert result3["title"] == "Germany, BW"
50+
assert result3["title"] == "Germany, Baden-Württemberg"
5151
assert result3["data"] == {
5252
"country": "DE",
5353
"province": "BW",
@@ -178,7 +178,7 @@ async def test_form_babel_unresolved_language(hass: HomeAssistant) -> None:
178178
await hass.async_block_till_done()
179179

180180
assert result["type"] is FlowResultType.CREATE_ENTRY
181-
assert result["title"] == "Germany, BW"
181+
assert result["title"] == "Germany, Baden-Württemberg"
182182
assert result["data"] == {
183183
"country": "DE",
184184
"province": "BW",
@@ -225,7 +225,7 @@ async def test_form_babel_replace_dash_with_underscore(hass: HomeAssistant) -> N
225225
await hass.async_block_till_done()
226226

227227
assert result["type"] is FlowResultType.CREATE_ENTRY
228-
assert result["title"] == "Germany, BW"
228+
assert result["title"] == "Germany, Baden-Württemberg"
229229
assert result["data"] == {
230230
"country": "DE",
231231
"province": "BW",
@@ -237,7 +237,7 @@ async def test_reconfigure(hass: HomeAssistant) -> None:
237237
"""Test reconfigure flow."""
238238
entry = MockConfigEntry(
239239
domain=DOMAIN,
240-
title="Germany, BW",
240+
title="Germany, Baden-Württemberg",
241241
data={"country": "DE", "province": "BW"},
242242
)
243243
entry.add_to_hass(hass)
@@ -256,7 +256,7 @@ async def test_reconfigure(hass: HomeAssistant) -> None:
256256
assert result["type"] is FlowResultType.ABORT
257257
assert result["reason"] == "reconfigure_successful"
258258
entry = hass.config_entries.async_get_entry(entry.entry_id)
259-
assert entry.title == "Germany, NW"
259+
assert entry.title == "Germany, Nordrhein-Westfalen"
260260
assert entry.data == {"country": "DE", "province": "NW"}
261261

262262

@@ -297,7 +297,7 @@ async def test_reconfigure_incorrect_language(hass: HomeAssistant) -> None:
297297

298298
entry = MockConfigEntry(
299299
domain=DOMAIN,
300-
title="Germany, BW",
300+
title="Germany, Baden-Württemberg",
301301
data={"country": "DE", "province": "BW"},
302302
)
303303
entry.add_to_hass(hass)
@@ -316,7 +316,7 @@ async def test_reconfigure_incorrect_language(hass: HomeAssistant) -> None:
316316
assert result["type"] is FlowResultType.ABORT
317317
assert result["reason"] == "reconfigure_successful"
318318
entry = hass.config_entries.async_get_entry(entry.entry_id)
319-
assert entry.title == "Germany, NW"
319+
assert entry.title == "Germany, Nordrhein-Westfalen"
320320
assert entry.data == {"country": "DE", "province": "NW"}
321321

322322

@@ -325,13 +325,13 @@ async def test_reconfigure_entry_exists(hass: HomeAssistant) -> None:
325325
"""Test reconfigure flow stops if other entry already exist."""
326326
entry = MockConfigEntry(
327327
domain=DOMAIN,
328-
title="Germany, BW",
328+
title="Germany, Baden-Württemberg",
329329
data={"country": "DE", "province": "BW"},
330330
)
331331
entry.add_to_hass(hass)
332332
entry2 = MockConfigEntry(
333333
domain=DOMAIN,
334-
title="Germany, NW",
334+
title="Germany, Nordrhein-Westfalen",
335335
data={"country": "DE", "province": "NW"},
336336
)
337337
entry2.add_to_hass(hass)
@@ -350,7 +350,7 @@ async def test_reconfigure_entry_exists(hass: HomeAssistant) -> None:
350350
assert result["type"] is FlowResultType.ABORT
351351
assert result["reason"] == "already_configured"
352352
entry = hass.config_entries.async_get_entry(entry.entry_id)
353-
assert entry.title == "Germany, BW"
353+
assert entry.title == "Germany, Baden-Württemberg"
354354
assert entry.data == {"country": "DE", "province": "BW"}
355355

356356

@@ -425,6 +425,47 @@ async def test_form_with_options(
425425
assert state.state == STATE_OFF
426426

427427

428+
async def test_form_with_subdivision_aliases(hass: HomeAssistant) -> None:
429+
"""Test the flow with a country using subdivision aliases (friendly names)."""
430+
await hass.config.async_set_time_zone("Europe/Paris")
431+
432+
result = await hass.config_entries.flow.async_init(
433+
DOMAIN, context={"source": config_entries.SOURCE_USER}
434+
)
435+
assert result["type"] is FlowResultType.FORM
436+
437+
result = await hass.config_entries.flow.async_configure(
438+
result["flow_id"],
439+
{
440+
CONF_COUNTRY: "FR",
441+
},
442+
)
443+
await hass.async_block_till_done()
444+
445+
assert result["type"] is FlowResultType.FORM
446+
# Find friendly name exposed to the user
447+
options = {
448+
option["value"]: option["label"]
449+
for option in result["data_schema"].schema["province"].config["options"]
450+
}
451+
assert options["RE"] == "La Réunion"
452+
453+
result = await hass.config_entries.flow.async_configure(
454+
result["flow_id"],
455+
{
456+
CONF_PROVINCE: "RE",
457+
},
458+
)
459+
await hass.async_block_till_done(wait_background_tasks=True)
460+
461+
assert result["type"] is FlowResultType.CREATE_ENTRY
462+
assert result["title"] == "France, La Réunion"
463+
assert result["data"] == {
464+
CONF_COUNTRY: "FR",
465+
CONF_PROVINCE: "RE",
466+
}
467+
468+
428469
@pytest.mark.usefixtures("mock_setup_entry")
429470
async def test_options_abort_no_categories(hass: HomeAssistant) -> None:
430471
"""Test the options flow abort if no categories to select."""

0 commit comments

Comments
 (0)