From 571706e62eb1df1f4aefc834578149dfba38dd6e Mon Sep 17 00:00:00 2001 From: Anna Shamray Date: Fri, 8 Mar 2024 18:02:06 +0100 Subject: [PATCH] :sparkles: [https://github.com/maykinmedia/django-setup-configuration/issues/1] add configuration steps --- requirements/base.in | 1 + requirements/base.txt | 12 ++++++ requirements/ci.txt | 6 +++ requirements/dev.txt | 2 + src/objects/conf/base.py | 37 ++++++++++++++++++ src/objects/config/__init__.py | 0 src/objects/config/demo.py | 63 +++++++++++++++++++++++++++++++ src/objects/config/objecttypes.py | 58 ++++++++++++++++++++++++++++ src/objects/config/site.py | 37 ++++++++++++++++++ src/objects/urls.py | 1 + src/objects/utils/__init__.py | 29 ++++++++++++++ 11 files changed, 246 insertions(+) create mode 100644 src/objects/config/__init__.py create mode 100644 src/objects/config/demo.py create mode 100644 src/objects/config/objecttypes.py create mode 100644 src/objects/config/site.py diff --git a/requirements/base.in b/requirements/base.in index d2a65397..79196f25 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -3,6 +3,7 @@ open-api-framework # Core python libraries glom # data represenation based on spec jsonschema +furl # Django libraries git+https://github.com/maykinmedia/django-setup-configuration.git@feature/1-config-command diff --git a/requirements/base.txt b/requirements/base.txt index 43de3dc3..3f530d14 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -178,6 +178,8 @@ faker==8.1.0 # via zgw-consumers flower==2.0.1 # via open-api-framework +furl==2.1.3 + # via -r requirements/base.in gemma-zds-client==1.0.1 # via # commonground-api-common @@ -225,6 +227,8 @@ notifications-api-common==0.2.2 # commonground-api-common open-api-framework==0.2.0 # via -r requirements/base.in +orderedmultidict==1.0.1 + # via furl oyaml==1.0 # via commonground-api-common packaging==23.2 @@ -290,8 +294,16 @@ sentry-sdk==1.39.2 # via open-api-framework six==1.16.0 # via +<<<<<<< HEAD # bleach # isodate +======= + # django-markup + # furl + # isodate + # jsonschema + # orderedmultidict +>>>>>>> 6096ba9 (:sparkles: [https://github.com/maykinmedia/django-setup-configuration/issues/1] add configuration steps) # python-dateutil # qrcode # requests-mock diff --git a/requirements/ci.txt b/requirements/ci.txt index 50664399..cba1590c 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -281,6 +281,8 @@ flower==2.0.1 # open-api-framework freezegun==1.1.0 # via -r requirements/test-tools.in +furl==2.1.3 + # via -r requirements/base.txt gemma-zds-client==1.0.1 # via # -r requirements/base.txt @@ -356,6 +358,10 @@ notifications-api-common==0.2.2 # commonground-api-common open-api-framework==0.2.0 # via -r requirements/base.txt +orderedmultidict==1.0.1 + # via + # -r requirements/base.txt + # furl oyaml==1.0 # via # -r requirements/base.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index 1e56b77d..bed0e33b 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -315,6 +315,8 @@ flower==2.0.1 # open-api-framework freezegun==1.1.0 # via -r requirements/ci.txt +furl==2.1.3 + # via -r requirements/ci.txt gemma-zds-client==1.0.1 # via # -r requirements/ci.txt diff --git a/src/objects/conf/base.py b/src/objects/conf/base.py index f8dd0aa8..7df32842 100644 --- a/src/objects/conf/base.py +++ b/src/objects/conf/base.py @@ -119,6 +119,7 @@ # Project applications. "objects.accounts", "objects.api", + "objects.config", "objects.core", "objects.token", "objects.utils", @@ -482,3 +483,39 @@ CELERY_TASK_SOFT_TIME_LIMIT = config( "CELERY_TASK_SOFT_TIME_LIMIT", default=5 * 60 ) # soft + +# +# Django setup configuration +# +SETUP_CONFIGURATION_STEPS = [ + "objects.config.site.SiteConfigurationStep", + "objects.config.objecttypes.ObjecttypesStep", + "objects.config.demo.DemoUserStep", +] + + +# +# Objecttypes settings +# + +# setup_configuration command +# sites config +SITES_CONFIG_ENABLE = config("SITES_CONFIG_ENABLE", default=True) +OBJECTS_DOMAIN = config("OBJECTS_DOMAIN", "") +OBJECTS_ORGANIZATION = config("OBJECTS_ORGANIZATION", "") +# objecttypes config +OBJECTS_OBJECTTYPES_CONFIG_ENABLE = config( + "OBJECTS_OBJECTTYPES_CONFIG_ENABLE", default=True +) +OBJECTTYPES_API_ROOT = config("OBJECTTYPES_API_ROOT", "") +if OBJECTTYPES_API_ROOT and not OBJECTTYPES_API_ROOT.endswith("/"): + OBJECTTYPES_API_ROOT = f"{OBJECTTYPES_API_ROOT.strip()}/" +OBJECTTYPES_API_OAS = config( + "OBJECTTYPES_API_OAS", default=f"{OBJECTTYPES_API_ROOT}schema/openapi.yaml" +) +OBJECTS_OBJECTTYPES_TOKEN = config("OBJECTS_OBJECTTYPES_TOKEN", "") +# Demo User Configuration +DEMO_CONFIG_ENABLE = config("DEMO_CONFIG_ENABLE", default=DEBUG) +DEMO_TOKEN = config("DEMO_TOKEN", "") +DEMO_PERSON = config("DEMO_PERSON", "") +DEMO_EMAIL = config("DEMO_EMAIL", "") diff --git a/src/objects/config/__init__.py b/src/objects/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/objects/config/demo.py b/src/objects/config/demo.py new file mode 100644 index 00000000..3cb673a5 --- /dev/null +++ b/src/objects/config/demo.py @@ -0,0 +1,63 @@ +from django.conf import settings +from django.urls import reverse + +import requests +from django_setup_configuration.configuration import BaseConfigurationStep +from django_setup_configuration.exceptions import SelfTestFailed + +from objects.token.models import TokenAuth +from objects.utils import build_absolute_url + + +class DemoUserStep(BaseConfigurationStep): + """ + Create demo user to request Objects API + + **NOTE** For now demo user has all permissions. + """ + + verbose_name = "Demo User Configuration" + required_settings = [ + "DEMO_TOKEN", + "DEMO_PERSON", + "DEMO_EMAIL", + ] + enable_setting = "DEMO_CONFIG_ENABLE" + + def is_configured(self) -> bool: + return TokenAuth.objects.filter(token=settings.DEMO_TOKEN).exists() + + def configure(self): + token_auth, created = TokenAuth.objects.get_or_create( + token=settings.DEMO_TOKEN, + defaults={ + "contact_person": settings.DEMO_PERSON, + "email": settings.DEMO_EMAIL, + "is_superuser": True, + }, + ) + if ( + token_auth.contact_person != settings.DEMO_PERSON + or token_auth.email != settings.DEMO_EMAIL + ): + token_auth.contact_person = settings.DEMO_PERSON + token_auth.email = settings.DEMO_EMAIL + token_auth.save(update_fields=["contact_person", "email"]) + + def test_configuration(self): + endpoint = reverse("v2:object-list") + full_url = build_absolute_url(endpoint, request=None) + + try: + response = requests.get( + full_url, + headers={ + "HTTP_AUTHORIZATION": f"Token {settings.OBJECTS_OBJECTTYPES_TOKEN}", + "Accept": "application/json", + }, + ) + response.raise_for_status() + except requests.RequestException as exc: + raise SelfTestFailed( + "Could not list objects for the configured token" + ) from exc diff --git a/src/objects/config/objecttypes.py b/src/objects/config/objecttypes.py new file mode 100644 index 00000000..480ff67e --- /dev/null +++ b/src/objects/config/objecttypes.py @@ -0,0 +1,58 @@ +from django.conf import settings + +import requests +from django_setup_configuration.configuration import BaseConfigurationStep +from django_setup_configuration.exceptions import SelfTestFailed +from zds_client.client import ClientError +from zgw_consumers.constants import APITypes, AuthTypes +from zgw_consumers.models import Service + + +class ObjecttypesStep(BaseConfigurationStep): + """ + Configure credentials for Objects API to request Objecttypes API + + Normal mode doesn't change the token after its initial creation. + If the token is changed, run this command with 'overwrite' flag + """ + + verbose_name = "Objecttypes Configuration" + required_settings = [ + "OBJECTTYPES_API_ROOT", + "OBJECTS_OBJECTTYPES_TOKEN", + ] + enable_setting = "OBJECTS_OBJECTTYPES_CONFIG_ENABLE" + + def is_configured(self) -> bool: + return Service.objects.filter(api_root=settings.OBJECTTYPES_API_ROOT).exists() + + def configure(self) -> None: + service, created = Service.objects.update_or_create( + api_root=settings.OBJECTTYPES_API_ROOT, + defaults={ + "label": "Objecttypes API", + "api_type": APITypes.orc, + "oas": settings.OBJECTTYPES_API_OAS, + "auth_type": AuthTypes.api_key, + "header_key": "Authorization", + "header_value": f"Token {settings.OBJECTS_OBJECTTYPES_TOKEN}", + }, + ) + if not created: + service.oas = settings.OBJECTTYPES_API_OAS + service.header_value = f"Token {settings.OBJECTS_OBJECTTYPES_TOKEN}" + service.save(update_fields=["oas", "header_value"]) + + def test_configuration(self) -> None: + """ + This check depends on the configuration in Objecttypes + """ + client = Service.objects.get( + api_root=settings.OBJECTTYPES_API_ROOT + ).build_client() + try: + client.list("objecttype") + except (requests.RequestException, ClientError) as exc: + raise SelfTestFailed( + "Could not Could not retrieve list of objecttypes from Objecttypes API." + ) from exc diff --git a/src/objects/config/site.py b/src/objects/config/site.py new file mode 100644 index 00000000..af20fb0e --- /dev/null +++ b/src/objects/config/site.py @@ -0,0 +1,37 @@ +from django.conf import settings +from django.contrib.sites.models import Site +from django.urls import reverse + +import requests +from django_setup_configuration.configuration import BaseConfigurationStep +from django_setup_configuration.exceptions import SelfTestFailed + +from objects.utils import build_absolute_url + + +class SiteConfigurationStep(BaseConfigurationStep): + """ + Configure the application site/domain. + """ + + verbose_name = "Site Configuration" + required_settings = ["OBJECTS_DOMAIN", "OBJECTS_ORGANIZATION"] + enable_setting = "SITES_CONFIG_ENABLE" + + def is_configured(self) -> bool: + site = Site.objects.get_current() + return site.domain == settings.OBJECTS_DOMAIN + + def configure(self): + site = Site.objects.get_current() + site.domain = settings.OBJECTS_DOMAIN + site.name = f"Objects {settings.OBJECTS_ORGANIZATION}".strip() + site.save() + + def test_configuration(self): + full_url = build_absolute_url(reverse("home")) + try: + response = requests.get(full_url) + response.raise_for_status() + except requests.RequestException as exc: + raise SelfTestFailed(f"Could not access home page at '{full_url}'") from exc diff --git a/src/objects/urls.py b/src/objects/urls.py index 93bd2f34..23c5f9a2 100644 --- a/src/objects/urls.py +++ b/src/objects/urls.py @@ -48,6 +48,7 @@ template_name="index.html", extra_context={"version": api_settings.DEFAULT_VERSION}, ), + name="home", ), path("ref/", include("vng_api_common.urls")), path("ref/", include("notifications_api_common.urls")), diff --git a/src/objects/utils/__init__.py b/src/objects/utils/__init__.py index e69de29b..ab878fd7 100644 --- a/src/objects/utils/__init__.py +++ b/src/objects/utils/__init__.py @@ -0,0 +1,29 @@ +from django.conf import settings +from django.http import HttpRequest + +from furl import furl + + +def get_domain() -> str: + """ + Obtain the domain/netloc according to settings or configuration. + """ + from django.contrib.sites.models import Site + + if settings.OBJECTS_DOMAIN: + return settings.OBJECTS_DOMAIN + + return Site.objects.get_current().domain + + +def build_absolute_url(path: str, request: HttpRequest | None = None) -> str: + if request is not None: + return request.build_absolute_uri(path) + + domain = get_domain() + _furl = furl( + scheme="https" if settings.IS_HTTPS else "http", + netloc=domain, + path=path, + ) + return _furl.url