diff --git a/api/admin/controller/library_settings.py b/api/admin/controller/library_settings.py index 7fd020bef..be2e7d639 100644 --- a/api/admin/controller/library_settings.py +++ b/api/admin/controller/library_settings.py @@ -18,6 +18,7 @@ from api.admin.problem_details import * from api.circulation_manager import CirculationManager from api.config import Configuration +from api.ekirjasto_consortium import EkirjastoConsortiumMonitor from api.lanes import create_default_lanes from core.configuration.library import LibrarySettings from core.model import ( @@ -31,6 +32,7 @@ from core.model.announcements import SETTING_NAME as ANNOUNCEMENT_SETTING_NAME from core.model.announcements import Announcement from core.model.library import LibraryLogo +from core.scripts import RunMonitorScript from core.util.problem_detail import ProblemDetail, ProblemError @@ -159,6 +161,11 @@ def process_post(self) -> Response: # Trigger a site configuration change site_configuration_has_changed(self._db) + # Finland, update municipality list if consortium is defined + consortium = library.settings.kirkanta_consortium_slug + if consortium and consortium != "disabled": + RunMonitorScript(EkirjastoConsortiumMonitor, library=library).run() + if is_new: # Now that the configuration settings are in place, create # a default set of lanes. diff --git a/api/ekirjasto_consortium.py b/api/ekirjasto_consortium.py index 2793ea830..ee2fb9ba6 100644 --- a/api/ekirjasto_consortium.py +++ b/api/ekirjasto_consortium.py @@ -10,6 +10,7 @@ from core.metadata_layer import TimestampData from core.model.library import Library from core.monitor import Monitor +from core.util.cache import memoize class KirkantaConsortium(BaseModel): @@ -95,7 +96,11 @@ class EkirjastoConsortiumMonitor(Monitor): _KIRKANTA_JUNK_CITIES: list[str] = ["Äävekaupunki"] # Manually managed list of Kirkanta city names don't match with Koodistopalvelu - KIRKANTA_CITY_NAME_ALIASES: dict[str, str] = {"Pedersöre": "Pedersören kunta"} + _KIRKANTA_CITY_NAME_ALIASES: dict[str, str] = {"Pedersöre": "Pedersören kunta"} + + def __init__(self, _db, collection=None, library: Library | None = None): + super().__init__(_db, collection) + self.library = library def run_once(self, progress): kirkanta_consortiums = self._fetch_kirkanta_consortiums() @@ -114,8 +119,7 @@ def run_once(self, progress): f"will be assigned to the default library." ) - libraries = self._db.query(Library).all() - + libraries = [self.library] if self.library else self._db.query(Library).all() for library in libraries: self._synchronize_library( library, kirkanta_consortiums, kirkanta_cities, koodisto_concept_codes @@ -127,26 +131,26 @@ def run_once(self, progress): f"The following Kirkanta city names were " f"not found in Koodistopalvelu: {missing_cities}. " f"Please add the missing city names manually to " - f"KIRKANTA_CITY_NAME_ALIASES." + f"_KIRKANTA_CITY_NAME_ALIASES." ) return TimestampData( achievements=f"Kirkanta synchronization done!", ) def _fetch_kirkanta_cities(self) -> list[KirkantaCity]: - return self._get( + return EkirjastoConsortiumMonitor._get( KirkantaCities, f"{self._KIRKANTA_API_URL}/city", {"limit": 9999} ).items def _fetch_kirkanta_consortiums(self) -> list[KirkantaConsortium]: - return self._get( + return EkirjastoConsortiumMonitor._get( KirkantaConsortiums, f"{self._KIRKANTA_API_URL}/consortium", {"status": "ACTIVE", "limit": 9999}, ).items def _fetch_koodisto_concept_codes(self, page: int = 1) -> list[KoodistoConceptCode]: - response: KoodistoConceptCodes = self._get( + response: KoodistoConceptCodes = EkirjastoConsortiumMonitor._get( KoodistoConceptCodes, f"{self._KOODISTO_API_URL}/classifications/{self._KOODISTO_CLASSIFICATION_ID}/conceptcodes", {"pageSize": self._KOODISTO_MAX_ALLOWED_PAGESIZE, "page": page}, @@ -252,7 +256,7 @@ def _verify_all_kirkanta_cities_found_in_koodisto( if missing_names: logging.warning( "The following Kirkanta city names are not found in Koodistopalvelu: %s. " - "Please add the missing city name manually to KIRKANTA_CITY_NAME_ALIASES.", + "Please add the missing city name manually to _KIRKANTA_CITY_NAME_ALIASES.", str(missing_names), ) return missing_names @@ -270,7 +274,7 @@ def _to_koodisto_code( if koodisto_city_name == kirkanta_city.name: return code - alias: str | None = self.KIRKANTA_CITY_NAME_ALIASES.get( + alias: str | None = self._KIRKANTA_CITY_NAME_ALIASES.get( kirkanta_city.name, ) if alias and koodisto_city_name == alias: @@ -280,7 +284,9 @@ def _to_koodisto_code( R = TypeVar("R", bound=BaseModel) - def _get(self, response_type: type[R], url: str, params=None) -> R: + @memoize(ttls=3600) + @staticmethod + def _get(response_type: type[R], url: str, params=None) -> R: """Perform HTTP GET request and parse the response into a pydantic model of type R""" try: response = requests.get(url, params) diff --git a/core/configuration/library.py b/core/configuration/library.py index 454a992dc..a72d14fd6 100644 --- a/core/configuration/library.py +++ b/core/configuration/library.py @@ -135,9 +135,9 @@ class LibrarySettings(BaseSettings): kirkanta_consortium_slug: str | None = FormField( None, form=LibraryConfFormItem( - label="Synchronize consortium data form Kirkanta", - description="If selected, the list of municipalities is updated automatically from Kirkanta. " - "Has no effect if configured for the default library.", + label="Consortium", + description="If selected, the municipalities are kept automatically " + "in sync with Kirkanta. Has no effect if set for the default library.", category="Kirkanta Synchronization", type=ConfigurationFormItemType.SELECT, # The keys here should match the Kirkanta consortium slug @@ -193,10 +193,9 @@ class LibrarySettings(BaseSettings): form=LibraryConfFormItem( label="The municipalities belonging to this consortium", type=ConfigurationFormItemType.LIST, - format="municipality-code", description="Each value should be a valid " '' - "municipality code.", + "municipality code. This list is populated automatically if the consortium is selected.", category="Kirkanta Synchronization", level=Level.ALL_ACCESS, ), @@ -736,8 +735,22 @@ def validate_municipalities( cls, value: list[str] | None, field: ModelField ) -> list[str] | None: """Verify that municipality IDs are valid.""" - # TODO: implement validation - return value + if not value: + return value + + for code in value: + if not code.isdigit(): + raise SettingsValidationError( + problem_detail=UNKNOWN_LANGUAGE.detailed( + f'"{cls.get_form_field_label(field.name)}": "{code}" is not a valid language code.' + ) + ) + + return cls._remove_duplicates(value) + + @classmethod + def _remove_duplicates(cls, values: list[str]): + return list(set(values)) @validator( "large_collection_languages",