diff --git a/docs/installation/config_cli.rst b/docs/installation/config_cli.rst index 0f733163..f2ebef1d 100644 --- a/docs/installation/config_cli.rst +++ b/docs/installation/config_cli.rst @@ -35,16 +35,29 @@ Objects API Sites configuration ------------------- -.. - _TODO: +Create or update a (single) YAML configuration file with your settings: + +.. code-block:: yaml + ... + sites_config_enable: true + sites: + items: + - domain: example.com + name: Example site + + - domain: test.example.com + name: Test site + ... +.. note:: The ``domain`` field will be used to lookup existing ``Site``'s. Objecttypes configuration ------------------------- -Create a (single) YAML configuration file with your settings: +Create or update a (single) YAML configuration file with your settings: .. code-block:: yaml + ... objecttypes_config_enable: true objecttypes: items: @@ -55,6 +68,7 @@ Create a (single) YAML configuration file with your settings: - uuid: b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2 name: Object Type 2 service_identifier: service-2 + ... .. note:: The ``uuid`` field will be used to lookup existing ``ObjectType``'s. @@ -65,12 +79,6 @@ Demo user configuration _TODO: -Sites configuration -------------------- - -.. - _TODO: - Objects configuration --------------------- @@ -79,13 +87,6 @@ Objects configuration _TODO: -Demo user configuration ------------------------ - -.. - _TODO: - - Execution ========= diff --git a/src/objects/conf/base.py b/src/objects/conf/base.py index 87df16bf..201c4f70 100644 --- a/src/objects/conf/base.py +++ b/src/objects/conf/base.py @@ -85,5 +85,6 @@ # SETUP_CONFIGURATION_STEPS = ( "zgw_consumers.contrib.setup_configuration.steps.ServiceConfigurationStep", + "objects.setup_configuration.steps.sites.SitesConfigurationStep", "objects.setup_configuration.steps.objecttypes.ObjectTypesConfigurationStep", ) diff --git a/src/objects/setup_configuration/models/sites.py b/src/objects/setup_configuration/models/sites.py new file mode 100644 index 00000000..3ecdeee6 --- /dev/null +++ b/src/objects/setup_configuration/models/sites.py @@ -0,0 +1,17 @@ +from django.contrib.sites.models import Site +from django_setup_configuration.models import ConfigurationModel +from pydantic import Field + + +class SiteConfigurationModel(ConfigurationModel): + class Meta: + django_model_refs = { + Site: ( + "domain", + "name", + ) + } + + +class SitesConfigurationModel(ConfigurationModel): + items: list[SiteConfigurationModel] = Field() diff --git a/src/objects/setup_configuration/steps/sites.py b/src/objects/setup_configuration/steps/sites.py new file mode 100644 index 00000000..5ed62f29 --- /dev/null +++ b/src/objects/setup_configuration/steps/sites.py @@ -0,0 +1,40 @@ +from django.contrib.sites.models import Site + +from django.core.exceptions import ValidationError +from django.db import IntegrityError +from django_setup_configuration.configuration import BaseConfigurationStep +from django_setup_configuration.exceptions import ConfigurationRunFailed + +from objects.setup_configuration.models.sites import SitesConfigurationModel + + +class SitesConfigurationStep(BaseConfigurationStep): + config_model = SitesConfigurationModel + verbose_name = "Sites configuration" + + namespace = "sites" + enable_setting = "sites_config_enable" + + def execute(self, model: SitesConfigurationModel) -> None: + for item in model.items: + site_kwargs = dict(domain=item.domain, name=item.name) + site_instance = Site(**site_kwargs) + + try: + site_instance.full_clean(exclude=("id",), validate_unique=False) + except ValidationError as exception: + exception_message = ( + f"Validation error(s) occured for site {item.domain}." + ) + raise ConfigurationRunFailed(exception_message) from exception + + try: + Site.objects.update_or_create( + domain=item.domain, + defaults=dict(name=item.name) + ) + except IntegrityError as exception: + exception_message = ( + f"Failed configuring site {item.domain}." + ) + raise ConfigurationRunFailed(exception_message) from exception diff --git a/src/objects/setup_configuration/tests/__init__.py b/src/objects/setup_configuration/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/objects/setup_configuration/tests/files/sites_empty_database.yaml b/src/objects/setup_configuration/tests/files/sites_empty_database.yaml new file mode 100644 index 00000000..808bdf57 --- /dev/null +++ b/src/objects/setup_configuration/tests/files/sites_empty_database.yaml @@ -0,0 +1,8 @@ +sites_config_enable: true +sites: + items: + - domain: example.com + name: Example site + + - domain: alternative.example.com + name: Alternative example site diff --git a/src/objects/setup_configuration/tests/files/sites_existing_sites.yaml b/src/objects/setup_configuration/tests/files/sites_existing_sites.yaml new file mode 100644 index 00000000..d2f00fbb --- /dev/null +++ b/src/objects/setup_configuration/tests/files/sites_existing_sites.yaml @@ -0,0 +1,8 @@ +sites_config_enable: true +sites: + items: + - domain: example.com + name: Example site (revised) + + - domain: test.example.com + name: Test site diff --git a/src/objects/setup_configuration/tests/files/sites_idempotent_step.yaml b/src/objects/setup_configuration/tests/files/sites_idempotent_step.yaml new file mode 100644 index 00000000..808bdf57 --- /dev/null +++ b/src/objects/setup_configuration/tests/files/sites_idempotent_step.yaml @@ -0,0 +1,8 @@ +sites_config_enable: true +sites: + items: + - domain: example.com + name: Example site + + - domain: alternative.example.com + name: Alternative example site diff --git a/src/objects/setup_configuration/tests/files/sites_invalid_domain.yaml b/src/objects/setup_configuration/tests/files/sites_invalid_domain.yaml new file mode 100644 index 00000000..e8995718 --- /dev/null +++ b/src/objects/setup_configuration/tests/files/sites_invalid_domain.yaml @@ -0,0 +1,8 @@ +sites_config_enable: true +sites: + items: + - domain: example.com + name: Example site + + - domain: foobar whitespace.com + name: Invalid site diff --git a/src/objects/setup_configuration/tests/test_site_config.py b/src/objects/setup_configuration/tests/test_site_config.py new file mode 100644 index 00000000..0c07189b --- /dev/null +++ b/src/objects/setup_configuration/tests/test_site_config.py @@ -0,0 +1,101 @@ +from pathlib import Path + +from django.contrib.sites.models import Site +from django.db.models import QuerySet +from django.test import TestCase + +from django_setup_configuration.exceptions import ConfigurationRunFailed +from django_setup_configuration.test_utils import execute_single_step + +from objects.setup_configuration.steps.sites import SitesConfigurationStep + +TEST_FILES = (Path(__file__).parent / "files").resolve() + + +class SitesConfigurationStepTests(TestCase): + def test_empty_database(self): + test_file_path = str(TEST_FILES / "sites_empty_database.yaml") + + execute_single_step(SitesConfigurationStep, yaml_source=test_file_path) + + sites: QuerySet[Site] = Site.objects.all() + + self.assertEqual(sites.count(), 2) + + example_site: Site = sites.get(name="Example site") + self.assertEqual(example_site.domain, "example.com") + + alternative_site: Site = sites.get(name="Alternative example site") + self.assertEqual(alternative_site.domain, "alternative.example.com") + + def test_existing_sites(self): + test_file_path = str(TEST_FILES / "sites_existing_sites.yaml") + + example_site, _ = Site.objects.get_or_create( + domain="example.com", + defaults=dict(name="Example site") + ) + + alternative_site = Site.objects.create( + domain="alternative.example.com", + name="Alternative example site", + ) + + execute_single_step(SitesConfigurationStep, yaml_source=test_file_path) + + sites: QuerySet[Site] = Site.objects.order_by("name") + + self.assertEqual(sites.count(), 3) + + example_site: Site = sites.get(name="Example site (revised)") + self.assertEqual(example_site.domain, "example.com") + + alternative_site: Site = sites.get(name="Alternative example site") + self.assertEqual(alternative_site.domain, "alternative.example.com") + + test_site: Site = sites.get(name="Test site") + self.assertEqual(test_site.domain, "test.example.com") + + def test_invalid_domain(self): + test_file_path = str(TEST_FILES / "sites_invalid_domain.yaml") + + with self.assertRaises(ConfigurationRunFailed): + execute_single_step(SitesConfigurationStep, yaml_source=test_file_path) + + sites: QuerySet[Site] = Site.objects.all() + + # the default test site created during test runs + self.assertEqual(sites.count(), 1) + + site: Site = sites.get() + + self.assertEqual(site.domain, "example.com") + + def test_idempotent_step(self): + test_file_path = str(TEST_FILES / "sites_idempotent_step.yaml") + + execute_single_step(SitesConfigurationStep, yaml_source=test_file_path) + + sites: QuerySet[Site] = Site.objects.all() + + self.assertEqual(sites.count(), 2) + + example_site: Site = sites.get(name="Example site") + self.assertEqual(example_site.domain, "example.com") + + alternative_site: Site = sites.get(name="Alternative example site") + self.assertEqual(alternative_site.domain, "alternative.example.com") + + execute_single_step(SitesConfigurationStep, yaml_source=test_file_path) + + self.assertEqual(Site.objects.count(), 2) + + example_site.refresh_from_db() + + self.assertEqual(example_site.name, "Example site") + self.assertEqual(example_site.domain, "example.com") + + alternative_site.refresh_from_db() + + self.assertEqual(alternative_site.name, "Alternative example site") + self.assertEqual(alternative_site.domain, "alternative.example.com")