diff --git a/backend/news/55.internal b/backend/news/55.internal new file mode 100644 index 0000000..bc4c39a --- /dev/null +++ b/backend/news/55.internal @@ -0,0 +1 @@ +Allow use of environment variables for create-site script [@ericof] diff --git a/backend/scripts/create_site.py b/backend/scripts/create_site.py index cf96ca3..101294e 100644 --- a/backend/scripts/create_site.py +++ b/backend/scripts/create_site.py @@ -1,9 +1,10 @@ from AccessControl.SecurityManagement import newSecurityManager from pathlib import Path +from kitconcept.intranet.utils.scripts import asbool +from kitconcept.intranet.utils.scripts import parse_answers from plone.distribution.api import site as site_api from Testing.makerequest import makerequest -import json import logging import os import transaction @@ -23,21 +24,6 @@ SCRIPT_DIR = Path().cwd() / "scripts" -truthy = frozenset(("t", "true", "y", "yes", "on", "1")) - - -def asbool(s): - """Return the boolean value ``True`` if the case-lowered value of string - input ``s`` is a :term:`truthy string`. If ``s`` is already one of the - boolean values ``True`` or ``False``, return it.""" - if s is None: - return False - if isinstance(s, bool): - return s - s = str(s).strip() - return s.lower() in truthy - - app = makerequest(globals()["app"]) request = app.REQUEST @@ -47,53 +33,60 @@ def asbool(s): newSecurityManager(None, admin) -def get_answers_file() -> Path: - filename = f"{ANSWERS}.json" +def get_answers_file(filename: str) -> Path: return SCRIPT_DIR / filename -def parse_answers(answers_file: Path, site_id: str = "") -> dict: - answers = json.loads(answers_file.read_text()) - if "distribution" not in answers: - # This is a bug in plone.distribution and should be fixed there - answers["distribution"] = DISTRIBUTION - if site_id: - answers["site_id"] = site_id - return answers - - # VARS DISTRIBUTION = os.getenv("DISTRIBUTION", "kitconcept-intranet") -SITE_ID = os.getenv("SITE_ID") # if set, this overrides the value in ANSWERS -ANSWERS = os.getenv("ANSWERS", "default") +ANSWERS_FILE = os.getenv("ANSWERS", "default.json") DELETE_EXISTING = asbool(os.getenv("DELETE_EXISTING")) +# ANSWERS OVERRIDE +ANSWERS = { + "site_id": os.getenv("SITE_ID"), + "title": os.getenv("SITE_TITLE"), + "description": os.getenv("SITE_DESCRIPTION"), + "default_language": os.getenv("SITE_DEFAULT_LANGUAGE"), + "portal_timezone": os.getenv("SITE_PORTAL_TIMEZONE"), + "workflow": os.getenv("SITE_WORKFLOW"), + "setup_content": os.getenv("SITE_SETUP_CONTENT", "true"), +} + + +def main(): + # Load site creation parameters + answers_file = get_answers_file(ANSWERS_FILE) + answers = parse_answers(answers_file, ANSWERS) + if "distribution" not in answers: + answers["distribution"] = DISTRIBUTION + site_id = answers["site_id"] -# Load site creation parameters -answers_file = get_answers_file() -answers = parse_answers(answers_file, SITE_ID) -site_id = answers["site_id"] - - -logger.info(f"Creating a new Plone site @ {site_id}") -logger.info(f" - Using the {DISTRIBUTION} distribution and answers from {answers_file}") - - -if site_id in app.objectIds() and DELETE_EXISTING: - app.manage_delObjects([site_id]) - transaction.commit() - app._p_jar.sync() - logger.info(f" - Deleted existing site with id {site_id}") -else: + logger.info(f"Creating a new Plone site @ {site_id}") logger.info( - f" - Stopping site creation, as there is already a site with id {site_id} " - "at the instance. Set DELETE_EXISTING=1 to delete the existing site before " - "creating a new one." + f" - Using the {DISTRIBUTION} distribution and answers from {answers_file}" ) -if site_id not in app.objectIds(): - site = site_api._create_site( - context=app, distribution_name=DISTRIBUTION, answers=answers - ) - transaction.commit() - app._p_jar.sync() - logger.info(" - Site created!") + if site_id in app.objectIds(): + if DELETE_EXISTING: + app.manage_delObjects([site_id]) + transaction.commit() + app._p_jar.sync() + logger.info(f" - Deleted existing site with id {site_id}") + else: + logger.info( + " - Stopping site creation, as there is already a site with id " + f"{site_id} at the instance. Set DELETE_EXISTING=1 to delete " + "the existing site before creating a new one." + ) + + if site_id not in app.objectIds(): + site = site_api._create_site( + context=app, distribution_name=DISTRIBUTION, answers=answers + ) + transaction.commit() + app._p_jar.sync() + logger.info(" - Site created!") + + +if __name__ == "__main__": + main() diff --git a/backend/src/kitconcept/intranet/behaviors/theming.py b/backend/src/kitconcept/intranet/behaviors/theming.py index 0ffff20..47c5af8 100644 --- a/backend/src/kitconcept/intranet/behaviors/theming.py +++ b/backend/src/kitconcept/intranet/behaviors/theming.py @@ -38,23 +38,19 @@ "blocks_layout": {}, } -BLOCKS_SCHEMA = json.dumps( - { - "type": "object", - "properties": { - "blocks": {"type": "object"}, - "blocks_layout": {"type": "object"}, - }, - } -) - -FONT_VOCABULARY = SimpleVocabulary( - [ - SimpleTerm(value="default", title=_("Default FZJ font")), - SimpleTerm(value="impact-arialNarrow", title=_("Impact / Arial Narrow")), - SimpleTerm(value="georgia-lucidaSans", title=_("Georgia / Lucida Sans")), - ] -) +BLOCKS_SCHEMA = json.dumps({ + "type": "object", + "properties": { + "blocks": {"type": "object"}, + "blocks_layout": {"type": "object"}, + }, +}) + +FONT_VOCABULARY = SimpleVocabulary([ + SimpleTerm(value="default", title=_("Default FZJ font")), + SimpleTerm(value="impact-arialNarrow", title=_("Impact / Arial Narrow")), + SimpleTerm(value="georgia-lucidaSans", title=_("Georgia / Lucida Sans")), +]) @provider(IFormFieldProvider) diff --git a/backend/src/kitconcept/intranet/utils/__init__.py b/backend/src/kitconcept/intranet/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/kitconcept/intranet/utils/scripts.py b/backend/src/kitconcept/intranet/utils/scripts.py new file mode 100644 index 0000000..8994886 --- /dev/null +++ b/backend/src/kitconcept/intranet/utils/scripts.py @@ -0,0 +1,31 @@ +from pathlib import Path + +import json + + +truthy = frozenset(("t", "true", "y", "yes", "on", "1")) + + +def asbool(s): + """Return the boolean value ``True`` if the case-lowered value of string + input ``s`` is a :term:`truthy string`. If ``s`` is already one of the + boolean values ``True`` or ``False``, return it.""" + if s is None: + return False + if isinstance(s, bool): + return s + s = str(s).strip() + return s.lower() in truthy + + +def parse_answers(answers_file: Path, answers_env: dict) -> dict: + answers = json.loads(answers_file.read_text()) + for key in answers: + env_value = answers_env.get(key, "") + if key == "setup_content" and env_value.strip(): + env_value = asbool(env_value) + elif not env_value: + continue + # Override answers_file value + answers[key] = env_value + return answers diff --git a/backend/tests/utils/default.json b/backend/tests/utils/default.json new file mode 100644 index 0000000..78b51af --- /dev/null +++ b/backend/tests/utils/default.json @@ -0,0 +1,9 @@ +{ + "site_id": "Plone", + "title": "Plone Intranet by kitconcept", + "description": "A Plone Intranet distribution provided by kitconcept GmbH", + "default_language": "en", + "portal_timezone": "Europe/Berlin", + "workflow": "public", + "setup_content": true +} diff --git a/backend/tests/utils/test_scripts.py b/backend/tests/utils/test_scripts.py new file mode 100644 index 0000000..34cbaf1 --- /dev/null +++ b/backend/tests/utils/test_scripts.py @@ -0,0 +1,38 @@ +from kitconcept.intranet.utils.scripts import parse_answers +from pathlib import Path + +import pytest + + +@pytest.fixture +def answers_file(): + path = Path(__file__).parent / "default.json" + return path + + +@pytest.mark.parametrize( + "key,value,expected", + ( + ("site_id", "", "Plone"), + ("site_id", "Site", "Site"), + ("title", "Foo Bar", "Foo Bar"), + ("description", "A new site", "A new site"), + ( + "description", + "", + "A Plone Intranet distribution provided by kitconcept GmbH", + ), + ("default_language", "", "en"), + ("default_language", "de", "de"), + ("portal_timezone", "", "Europe/Berlin"), + ("portal_timezone", "UTC", "UTC"), + ("workflow", "", "public"), + ("workflow", "private", "private"), + ("setup_content", "", True), + ("setup_content", "f", False), + ), +) +def test_parse_answers(answers_file, key: str, value: str, expected: str | bool): + answers = {key: value} + result = parse_answers(answers_file, answers) + assert result[key] == expected