diff --git a/README.md b/README.md index 19ade51..db4ca04 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,12 @@ ckanext.editable_config.options.blacklist = ckan.site_title ckan.site_descriptio # (optional, default: 0) ckanext.editable_config.charge_timeout = 10 +# Additional validators that are used when config option overrides are +# applied. Use this option if CKAN validators are not strict enough and you +# see the way to break the application by providing valid values for options. +# (optional, default: {}) +ckanext.editable_config.additional_validators = {"ckan.site_title": "less_than_100 do_not_contain_exclamation_mark"} + # Remove "Config" tab from CKAN's Admin UI. # (optional, default: false) ckanext.editable_config.disable_admin_config_tab = True diff --git a/ckanext/editable_config/config.py b/ckanext/editable_config/config.py index 1f0c50a..72070b6 100644 --- a/ckanext/editable_config/config.py +++ b/ckanext/editable_config/config.py @@ -8,6 +8,7 @@ CHARGE_TIMEOUT = "ckanext.editable_config.charge_timeout" DISABLE_CONFIG_TAB = "ckanext.editable_config.disable_admin_config_tab" CONVERT_CORE_OVERRIDES = "ckanext.editable_config.convert_core_overrides" +ADDITIONAL_VALIDATORS = "ckanext.editable_config.additional_validators" def extra_editable() -> list[str]: @@ -32,3 +33,7 @@ def disable_admin_config_tab() -> bool: def convert_core_overrides() -> bool: return tk.config[CONVERT_CORE_OVERRIDES] + + +def additional_validators() -> dict[str, str]: + return tk.config[ADDITIONAL_VALIDATORS] diff --git a/ckanext/editable_config/config_declaration.yaml b/ckanext/editable_config/config_declaration.yaml index 59aa8a2..ce00493 100644 --- a/ckanext/editable_config/config_declaration.yaml +++ b/ckanext/editable_config/config_declaration.yaml @@ -47,6 +47,16 @@ groups: may not be visible immediately - you'll have to wait `charge_timeout` seconds in worst case. + - key: ckanext.editable_config.additional_validators + example: '{"ckan.site_title": "less_than_100 do_not_contain_exclamation_mark"}' + default: {} + validators: convert_to_json_if_string dict_only + description: | + Additional validators that are used when config option overrides are + applied. Use this option if CKAN validators are not strict enough and + you see the way to break the application by providing valid values + for options. + - key: ckanext.editable_config.disable_admin_config_tab type: bool example: true diff --git a/ckanext/editable_config/plugin.py b/ckanext/editable_config/plugin.py index 8387bf5..4bc75e3 100644 --- a/ckanext/editable_config/plugin.py +++ b/ckanext/editable_config/plugin.py @@ -55,6 +55,16 @@ def update_config(self, config_: CKANConfig): cd[key].flags &= ~Flag.editable + self._add_validators(config.additional_validators()) + + def _add_validators(self, validators: dict[str, str]): + for key, names in validators.items(): + if key not in cd: + log.warning("%s is not declared", key) + continue + option = cd[Key.from_string(key)] + option.append_validators(names) + def _update_editable_flag(self, keys: list[str], enable: bool): for key in keys: if key not in cd: diff --git a/ckanext/editable_config/tests/logic/test_action.py b/ckanext/editable_config/tests/logic/test_action.py index 3fa6ced..b51a7a8 100644 --- a/ckanext/editable_config/tests/logic/test_action.py +++ b/ckanext/editable_config/tests/logic/test_action.py @@ -80,6 +80,23 @@ def test_valid(self, faker, ckan_config): call_action("editable_config_change", options={"ckan.site_title": title}) assert ckan_config["ckan.site_title"] == title + @pytest.mark.ckan_config(config.EXTRA_EDITABLE, "ckan.datasets_per_page") + @pytest.mark.ckan_config( + config.ADDITIONAL_VALIDATORS, + {"ckan.datasets_per_page": "is_positive_integer"}, + ) + def test_additional_validators(self): + """Options are validated""" + with pytest.raises(tk.ValidationError): + call_action( + "editable_config_change", + options={"ckan.datasets_per_page": -1}, + ) + call_action( + "editable_config_change", + options={"ckan.datasets_per_page": 1}, + ) + @pytest.mark.usefixtures("with_plugins", "non_clean_db", "with_autoclean") class TestRevert: