From 659aa43e998b370108972567a248eed350d134e1 Mon Sep 17 00:00:00 2001 From: Michael Collins <15347726+michaeljcollinsuk@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:54:28 +0100 Subject: [PATCH] Refactor (#172) * Move things * Restructure tests * Refactor templates to project root * Remove interfaces directory * Move users from core to to app * Move middleware to core * Register auth, core as apps * Rename base directory and references from controlpanel to ap * Replace dashboard references with analytical-platform-ui * Fixes so the container builds and runs * Fix tests * RIP Data Platform * Bump libpq-dev version * Retry libpq-dev version --- .devcontainer/devcontainer.json | 4 +-- .flake8 | 2 ++ .github/workflows/release.yml | 8 ++--- Dockerfile | 8 ++--- Makefile | 4 +-- README.md | 10 +++---- {controlpanel => ap}/__init__.py | 0 {controlpanel => ap}/asgi.py | 4 +-- {controlpanel/core => ap/auth}/__init__.py | 0 ap/auth/apps.py | 6 ++++ {controlpanel/core => ap}/auth/oidc.py | 2 +- ap/auth/urls.py | 10 +++++++ {controlpanel/core => ap}/auth/utils.py | 0 ap/auth/views/__init__.py | 1 + .../web/auth => ap/auth/views}/mixins.py | 2 +- .../web/auth => ap/auth/views}/views.py | 4 +-- .../core/common => ap/core}/__init__.py | 0 ap/core/apps.py | 5 ++++ .../web => ap/core}/context_processors.py | 6 ---- ap/core/middleware/__init__.py | 1 + .../core}/middleware/never_cache.py | 1 + .../core/common => ap/core}/utils.py | 0 ap/settings/__init__.py | 1 + {controlpanel => ap}/settings/common.py | 29 ++++++++++--------- {controlpanel => ap}/settings/test.py | 4 +-- {controlpanel => ap}/urls.py | 5 +++- .../core/migrations => ap/users}/__init__.py | 0 {controlpanel/core => ap/users}/apps.py | 4 +-- .../users}/migrations/0001_initial.py | 23 +++++---------- .../tasks => ap/users/migrations}/__init__.py | 0 .../core/models/user.py => ap/users/models.py | 4 +-- ap/views.py | 7 +++++ {controlpanel => ap}/wsgi.py | 4 +-- .../web/static => assets/scss}/app.scss | 0 contrib/docker-compose-postgres.yml | 8 ++--- contrib/docker-compose-test.yml | 8 ++--- controlpanel/core/auth/__init__.py | 5 ---- .../migrations/0002_alter_user_options.py | 16 ---------- controlpanel/core/models/__init__.py | 1 - controlpanel/domains/apps.py | 0 controlpanel/interfaces/__init__.py | 0 controlpanel/interfaces/api/__init__.py | 0 controlpanel/interfaces/cli/__init__.py | 0 controlpanel/interfaces/urls.py | 5 ---- controlpanel/interfaces/web/__init__.py | 0 controlpanel/interfaces/web/apps.py | 5 ---- controlpanel/interfaces/web/auth/__init__.py | 6 ---- .../interfaces/web/data_products/__init__.py | 1 - .../interfaces/web/data_products/views.py | 5 ---- .../web/templates/data-products.html | 8 ----- controlpanel/interfaces/web/urls.py | 13 --------- controlpanel/interfaces/web/views.py | 19 ------------ controlpanel/middleware/__init__.py | 1 - controlpanel/settings/__init__.py | 1 - manage.py | 2 +- package-lock.json | 4 +-- package.json | 6 ++-- pyproject.toml | 9 ++++++ pytest.ini | 2 +- scripts/container/entrypoint.sh | 2 +- .../web/templates => templates}/base.html | 2 +- .../web/templates => templates}/home.html | 0 .../includes/header.html | 2 +- .../includes/moj_header.svg | 0 .../includes/nav_item.html | 0 .../includes/navigation.html | 0 .../templates => templates}/login-fail.html | 0 .../data_products => tests/auth}/__init__.py | 0 tests/{core => }/auth/test_oidc.py | 2 +- tests/conftest.py | 6 ++-- tests/core/auth/__init__.py | 0 .../web => core}/test_context_processors.py | 5 ++-- 72 files changed, 125 insertions(+), 178 deletions(-) rename {controlpanel => ap}/__init__.py (100%) rename {controlpanel => ap}/asgi.py (72%) rename {controlpanel/core => ap/auth}/__init__.py (100%) create mode 100644 ap/auth/apps.py rename {controlpanel/core => ap}/auth/oidc.py (98%) create mode 100644 ap/auth/urls.py rename {controlpanel/core => ap}/auth/utils.py (100%) create mode 100644 ap/auth/views/__init__.py rename {controlpanel/interfaces/web/auth => ap/auth/views}/mixins.py (90%) rename {controlpanel/interfaces/web/auth => ap/auth/views}/views.py (95%) rename {controlpanel/core/common => ap/core}/__init__.py (100%) create mode 100644 ap/core/apps.py rename {controlpanel/interfaces/web => ap/core}/context_processors.py (77%) create mode 100644 ap/core/middleware/__init__.py rename {controlpanel => ap/core}/middleware/never_cache.py (84%) rename {controlpanel/core/common => ap/core}/utils.py (100%) create mode 100644 ap/settings/__init__.py rename {controlpanel => ap}/settings/common.py (93%) rename {controlpanel => ap}/settings/test.py (87%) rename {controlpanel => ap}/urls.py (51%) rename {controlpanel/core/migrations => ap/users}/__init__.py (100%) rename {controlpanel/core => ap/users}/apps.py (61%) rename {controlpanel/core => ap/users}/migrations/0001_initial.py (85%) rename {controlpanel/core/tasks => ap/users/migrations}/__init__.py (100%) rename controlpanel/core/models/user.py => ap/users/models.py (87%) create mode 100644 ap/views.py rename {controlpanel => ap}/wsgi.py (72%) rename {controlpanel/interfaces/web/static => assets/scss}/app.scss (100%) delete mode 100644 controlpanel/core/auth/__init__.py delete mode 100644 controlpanel/core/migrations/0002_alter_user_options.py delete mode 100644 controlpanel/core/models/__init__.py delete mode 100644 controlpanel/domains/apps.py delete mode 100644 controlpanel/interfaces/__init__.py delete mode 100644 controlpanel/interfaces/api/__init__.py delete mode 100644 controlpanel/interfaces/cli/__init__.py delete mode 100644 controlpanel/interfaces/urls.py delete mode 100644 controlpanel/interfaces/web/__init__.py delete mode 100644 controlpanel/interfaces/web/apps.py delete mode 100644 controlpanel/interfaces/web/auth/__init__.py delete mode 100644 controlpanel/interfaces/web/data_products/__init__.py delete mode 100644 controlpanel/interfaces/web/data_products/views.py delete mode 100644 controlpanel/interfaces/web/templates/data-products.html delete mode 100644 controlpanel/interfaces/web/urls.py delete mode 100644 controlpanel/interfaces/web/views.py delete mode 100644 controlpanel/middleware/__init__.py delete mode 100644 controlpanel/settings/__init__.py rename {controlpanel/interfaces/web/templates => templates}/base.html (87%) rename {controlpanel/interfaces/web/templates => templates}/home.html (100%) rename {controlpanel/interfaces/web/templates => templates}/includes/header.html (93%) rename {controlpanel/interfaces/web/templates => templates}/includes/moj_header.svg (100%) rename {controlpanel/interfaces/web/templates => templates}/includes/nav_item.html (100%) rename {controlpanel/interfaces/web/templates => templates}/includes/navigation.html (100%) rename {controlpanel/interfaces/web/templates => templates}/login-fail.html (100%) rename {controlpanel/domains/data_products => tests/auth}/__init__.py (100%) rename tests/{core => }/auth/test_oidc.py (92%) delete mode 100644 tests/core/auth/__init__.py rename tests/{interfaces/web => core}/test_context_processors.py (83%) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4cd7fe93..4ece7ad0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - "name": "analytical-platform-dashboard", + "name": "analytical-platform-ui", "image": "ghcr.io/ministryofjustice/devcontainer-base:latest", "features": { "ghcr.io/devcontainers/features/node:1": { @@ -14,7 +14,7 @@ }, "postCreateCommand": "bash scripts/devcontainer/post-create.sh", "postStartCommand": "bash scripts/devcontainer/post-start.sh", - "runArgs": ["--name=analytical-platform-dashboard-devcontainer"], + "runArgs": ["--name=analytical-platform-ui-devcontainer"], "customizations": { "vscode": { "extensions": [ diff --git a/.flake8 b/.flake8 index ae1f8a56..c8155880 100644 --- a/.flake8 +++ b/.flake8 @@ -3,3 +3,5 @@ max-line-length = 100 extend-ignore = E203, E704 exclude = venv +per-file-ignores = + ap/*/migrations/*:E501,W292 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 79a78012..a1107157 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,17 +38,17 @@ jobs: with: context: . push: true - tags: ghcr.io/${{ github.repository_owner }}/analytical-platform-dashboard:${{ github.ref_name }} + tags: ghcr.io/${{ github.repository_owner }}/analytical-platform-ui:${{ github.ref_name }} - name: Sign id: sign run: | - cosign sign --yes ghcr.io/${{ github.repository_owner }}/analytical-platform-dashboard@${{ steps.push.outputs.digest }} + cosign sign --yes ghcr.io/${{ github.repository_owner }}/analytical-platform-ui@${{ steps.push.outputs.digest }} - name: Verify id: verify run: | cosign verify \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ - --certificate-identity=https://github.com/${{ github.repository_owner }}/analytical-platform-dashboard/.github/workflows/release.yml@refs/tags/${{ github.ref_name }} \ - ghcr.io/${{ github.repository_owner }}/analytical-platform-dashboard@${{ steps.push.outputs.digest }} + --certificate-identity=https://github.com/${{ github.repository_owner }}/analytical-platform-ui/.github/workflows/release.yml@refs/tags/${{ github.ref_name }} \ + ghcr.io/${{ github.repository_owner }}/analytical-platform-ui@${{ steps.push.outputs.digest }} diff --git a/Dockerfile b/Dockerfile index de50b83f..79e251cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM public.ecr.aws/docker/library/node:20.11.1 AS build-node WORKDIR / COPY package.json package-lock.json ./ -COPY controlpanel/interfaces/web/static/app.scss ./controlpanel/interfaces/web/static/app.scss +COPY assets/scss/app.scss ./assets/scss/app.scss RUN npm install \ && npm run css @@ -13,9 +13,9 @@ RUN apk add --no-cache --virtual .build-deps \ libffi-dev=3.4.4-r3 \ gcc=13.2.1_git20231014-r0 \ musl-dev=1.2.4_git20230717-r4 \ - && apk add --no-cache libpq-dev=16.2-r0 + && apk add --no-cache libpq-dev=16.3-r0 -WORKDIR /controlpanel +WORKDIR /ap RUN mkdir --parents static/assets/fonts \ && mkdir --parents static/assets/images \ @@ -27,7 +27,7 @@ COPY --from=build-node node_modules/govuk-frontend/dist/govuk/assets/images/. st COPY --from=build-node node_modules/govuk-frontend/dist/govuk/all.bundle.js static/assets/js/govuk.js COPY scripts/container/entrypoint.sh /usr/local/bin/entrypoint.sh COPY requirements.txt manage.py ./ -COPY controlpanel controlpanel +COPY ap ap COPY tests tests RUN pip install --no-cache-dir --requirement requirements.txt \ diff --git a/Makefile b/Makefile index 076818d4..6df3815c 100644 --- a/Makefile +++ b/Makefile @@ -29,9 +29,9 @@ serve-sso: aws-sso exec --profile analytical-platform-development:AdministratorAccess -- python manage.py runserver container: - docker build -t controlpanel . + docker build -t ap . test: container @echo @echo "> Running Python Tests (In Docker)..." - IMAGE_TAG=controlpanel docker compose --file=contrib/docker-compose-test.yml run --rm interfaces + IMAGE_TAG=ap docker compose --file=contrib/docker-compose-test.yml run --rm interfaces diff --git a/README.md b/README.md index b4a89f26..17170df7 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ -# Analytical Platform Dashboard +# Analytical Platform UI -[![repo standards badge](https://img.shields.io/endpoint?labelColor=231f20&color=005ea5&style=for-the-badge&label=MoJ%20Compliant&url=https%3A%2F%2Foperations-engineering-reports.cloud-platform.service.justice.gov.uk%2Fapi%2Fv1%2Fcompliant_public_repositories%2Fendpoint%2Fanalytical-platform-dashboard&logo=data:image/png;base64,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)](https://operations-engineering-reports.cloud-platform.service.justice.gov.uk/public-report/analytical-platform-dashboard) +[![repo standards badge](https://img.shields.io/endpoint?labelColor=231f20&color=005ea5&style=for-the-badge&label=MoJ%20Compliant&url=https%3A%2F%2Foperations-engineering-reports.cloud-platform.service.justice.gov.uk%2Fapi%2Fv1%2Fcompliant_public_repositories%2Fendpoint%2Fanalytical-platform-ui&logo=data:image/png;base64,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)](https://operations-engineering-reports.cloud-platform.service.justice.gov.uk/public-report/analytical-platform-ui) ## Running Locally The dashboard is run in a DevContainer via Docker. The DevContainer VSCode extension is recommended, as is Docker Desktop. -For more information on Dev Containers, see the [Data Platform docs.](https://technical-documentation.data-platform.service.justice.gov.uk/documentation/platform/infrastructure/developing.html#developing-the-data-platform) +For more information on Dev Containers, see the [Analytical Platform docs.](https://technical-documentation.data-platform.service.justice.gov.uk/documentation/platform/infrastructure/developing.html#developing-the-data-platform) ### Building the DevContainer -To build the dev container, ensure docker desktop is running, then open the AP Dashboard project in VSCode. Open the command pallet by hitting command+shift+p and search for ```Dev Containers: Reopen in container``` and hit enter. This will build the dev container. +To build the dev container, ensure docker desktop is running, then open the AP UI project in VSCode. Open the command pallet by hitting command+shift+p and search for ```Dev Containers: Reopen in container``` and hit enter. This will build the dev container. If you are using a workspace with multiple applications, search for ```Dev Containers: Open folder in Container…``` instead, then select the AP UI folder. Once the dev container has finished building, it should install all the required Python and npm dependencies, as well as run the migrations. ### Environment Variables -There is an example environment file stored on 1Password named ```Analytical Platform Dashboard Env```. Paste the contents into a new file called ```.env``` in the root of the project. +There is an example environment file stored on 1Password named ```Analytical Platform UI Env```. Paste the contents into a new file called ```.env``` in the root of the project. ### Running Development Server To run the server, you will need to use aws-sso cli. To find the correct profile, run ```aws-sso list``` in the terminal. This will provide you with a link to sign in via SSO. Once signed in, a list of profiles will be displayed. You are looking for the profile name linked to the ```analytical-platform-development``` AccountAlias. diff --git a/controlpanel/__init__.py b/ap/__init__.py similarity index 100% rename from controlpanel/__init__.py rename to ap/__init__.py diff --git a/controlpanel/asgi.py b/ap/asgi.py similarity index 72% rename from controlpanel/asgi.py rename to ap/asgi.py index a5e6f332..8390482a 100644 --- a/controlpanel/asgi.py +++ b/ap/asgi.py @@ -1,5 +1,5 @@ """ -ASGI config for controlpanel project. +ASGI config for ap project. It exposes the ASGI callable as a module-level variable named ``application``. @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "controlpanel.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ap.settings") application = get_asgi_application() diff --git a/controlpanel/core/__init__.py b/ap/auth/__init__.py similarity index 100% rename from controlpanel/core/__init__.py rename to ap/auth/__init__.py diff --git a/ap/auth/apps.py b/ap/auth/apps.py new file mode 100644 index 00000000..8bc018f7 --- /dev/null +++ b/ap/auth/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthConfig(AppConfig): + name = "ap.auth" + label = "oidc_auth" diff --git a/controlpanel/core/auth/oidc.py b/ap/auth/oidc.py similarity index 98% rename from controlpanel/core/auth/oidc.py rename to ap/auth/oidc.py index 05dd2580..04446f48 100644 --- a/controlpanel/core/auth/oidc.py +++ b/ap/auth/oidc.py @@ -4,7 +4,7 @@ import structlog from authlib.integrations.django_client import OAuth -from controlpanel.core.models.user import User +from ap.users.models import User logger = structlog.get_logger(__name__) diff --git a/ap/auth/urls.py b/ap/auth/urls.py new file mode 100644 index 00000000..efb9e827 --- /dev/null +++ b/ap/auth/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("authenticate/", views.OIDCAuthenticationView.as_view(), name="authenticate"), + path("login/", views.OIDCLoginView.as_view(), name="login"), + path("logout/", views.LogoutView.as_view(), name="logout"), + path("login-fail/", views.LoginFail.as_view(), name="login-fail"), +] diff --git a/controlpanel/core/auth/utils.py b/ap/auth/utils.py similarity index 100% rename from controlpanel/core/auth/utils.py rename to ap/auth/utils.py diff --git a/ap/auth/views/__init__.py b/ap/auth/views/__init__.py new file mode 100644 index 00000000..3cf94d3f --- /dev/null +++ b/ap/auth/views/__init__.py @@ -0,0 +1 @@ +from .views import LoginFail, LogoutView, OIDCAuthenticationView, OIDCLoginView # noqa diff --git a/controlpanel/interfaces/web/auth/mixins.py b/ap/auth/views/mixins.py similarity index 90% rename from controlpanel/interfaces/web/auth/mixins.py rename to ap/auth/views/mixins.py index 3320f0e1..6b6784e5 100644 --- a/controlpanel/interfaces/web/auth/mixins.py +++ b/ap/auth/views/mixins.py @@ -2,7 +2,7 @@ from django.shortcuts import redirect from django.urls import reverse -from controlpanel.core.auth import OIDCSessionValidator +from ap.auth.oidc import OIDCSessionValidator class OIDCLoginRequiredMixin(LoginRequiredMixin): diff --git a/controlpanel/interfaces/web/auth/views.py b/ap/auth/views/views.py similarity index 95% rename from controlpanel/interfaces/web/auth/views.py rename to ap/auth/views/views.py index da6f9afc..23b0fb3f 100644 --- a/controlpanel/interfaces/web/auth/views.py +++ b/ap/auth/views/views.py @@ -10,8 +10,8 @@ from authlib.common.security import generate_token from authlib.integrations.django_client import OAuthError -from controlpanel.core.auth import OIDCSubAuthenticationBackend, oauth -from controlpanel.core.auth.utils import pkce_transform +from ap.auth.oidc import OIDCSubAuthenticationBackend, oauth +from ap.auth.utils import pkce_transform class OIDCLoginView(View): diff --git a/controlpanel/core/common/__init__.py b/ap/core/__init__.py similarity index 100% rename from controlpanel/core/common/__init__.py rename to ap/core/__init__.py diff --git a/ap/core/apps.py b/ap/core/apps.py new file mode 100644 index 00000000..b07d7ba8 --- /dev/null +++ b/ap/core/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + name = "ap.core" diff --git a/controlpanel/interfaces/web/context_processors.py b/ap/core/context_processors.py similarity index 77% rename from controlpanel/interfaces/web/context_processors.py rename to ap/core/context_processors.py index 9f839eec..03e345d1 100644 --- a/controlpanel/interfaces/web/context_processors.py +++ b/ap/core/context_processors.py @@ -2,15 +2,9 @@ def nav_items(request): - data_products_url = reverse("data-products") return { "nav_items": [ {"name": "Home", "url": "/", "active": request.get_full_path() == "/"}, - { - "name": "Data Products", - "url": data_products_url, - "active": request.get_full_path() == data_products_url, - }, ] } diff --git a/ap/core/middleware/__init__.py b/ap/core/middleware/__init__.py new file mode 100644 index 00000000..9d7acff3 --- /dev/null +++ b/ap/core/middleware/__init__.py @@ -0,0 +1 @@ +from .never_cache import DisableClientSideCachingMiddleware # noqa diff --git a/controlpanel/middleware/never_cache.py b/ap/core/middleware/never_cache.py similarity index 84% rename from controlpanel/middleware/never_cache.py rename to ap/core/middleware/never_cache.py index 7da79be8..eb74c4d4 100644 --- a/controlpanel/middleware/never_cache.py +++ b/ap/core/middleware/never_cache.py @@ -1,6 +1,7 @@ from django.utils.cache import add_never_cache_headers +# TODO this was copied from Control Panel do we still want/need it? class DisableClientSideCachingMiddleware: def __init__(self, get_response): self.get_response = get_response diff --git a/controlpanel/core/common/utils.py b/ap/core/utils.py similarity index 100% rename from controlpanel/core/common/utils.py rename to ap/core/utils.py diff --git a/ap/settings/__init__.py b/ap/settings/__init__.py new file mode 100644 index 00000000..b8d00c27 --- /dev/null +++ b/ap/settings/__init__.py @@ -0,0 +1 @@ +from ap.settings.common import * # noqa diff --git a/controlpanel/settings/common.py b/ap/settings/common.py similarity index 93% rename from controlpanel/settings/common.py rename to ap/settings/common.py index 7b479add..8f0620bd 100644 --- a/controlpanel/settings/common.py +++ b/ap/settings/common.py @@ -1,5 +1,5 @@ """ -Django settings for controlpanel project. +Django settings for ap project. Generated by 'django-admin startproject' using Django 4.2.7. @@ -9,6 +9,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.2/ref/settings/ """ + import os from os.path import abspath, dirname, join from pathlib import Path @@ -17,10 +18,10 @@ import structlog # Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent +BASE_DIR = Path(__file__).resolve().parent.parent.parent # Name of the project -PROJECT_NAME = "controlpanel" +PROJECT_NAME = "ap" # Absolute path of project Django directory DJANGO_ROOT = dirname(dirname(abspath(__file__))) @@ -61,14 +62,14 @@ "govuk_frontend_django", # Provide structured log service "django_structlog", - # The core logic of Control panel - "controlpanel.core", - # The frontend part of Control panel - "controlpanel.interfaces.web", + # First party project defined apps + "ap.auth", + "ap.core", + "ap.users", ] MIDDLEWARE = [ - "controlpanel.middleware.DisableClientSideCachingMiddleware", + "ap.core.middleware.DisableClientSideCachingMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -83,12 +84,12 @@ # The list of authentication backend used for checking user's access to app AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"] -ROOT_URLCONF = "controlpanel.urls" +ROOT_URLCONF = "ap.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": [BASE_DIR / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -96,8 +97,8 @@ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", - "controlpanel.interfaces.web.context_processors.nav_items", - "controlpanel.interfaces.web.context_processors.header_context", + "ap.core.context_processors.nav_items", + "ap.core.context_processors.header_context", ], }, }, @@ -206,7 +207,7 @@ SESSION_COOKIE_SECURE = True # Custom user model class -AUTH_USER_MODEL = "core.User" +AUTH_USER_MODEL = "users.User" # -- OIDC Settings AZURE_CLIENT_ID = os.environ.get("AZURE_CLIENT_ID") @@ -262,7 +263,7 @@ "handlers": ["console"], "level": LOG_LEVEL, }, - "controlpanel": { + "ap": { "handlers": ["console"], "level": LOG_LEVEL, }, diff --git a/controlpanel/settings/test.py b/ap/settings/test.py similarity index 87% rename from controlpanel/settings/test.py rename to ap/settings/test.py index f73118dc..2da90dee 100644 --- a/controlpanel/settings/test.py +++ b/ap/settings/test.py @@ -1,12 +1,12 @@ # First-party/Local -from controlpanel.settings.common import * # noqa: F403 +from ap.settings.common import * # noqa: F403 ENV = "test" LOG_LEVEL = "WARNING" LOGGING["loggers"]["django_structlog"]["level"] = LOG_LEVEL # noqa: F405 -LOGGING["loggers"]["controlpanel"]["level"] = LOG_LEVEL # noqa: F405 +LOGGING["loggers"]["ap"]["level"] = LOG_LEVEL # noqa: F405 AUTHENTICATION_BACKENDS = [ "rules.permissions.ObjectPermissionBackend", diff --git a/controlpanel/urls.py b/ap/urls.py similarity index 51% rename from controlpanel/urls.py rename to ap/urls.py index 8d0fbf4b..bcc146b2 100644 --- a/controlpanel/urls.py +++ b/ap/urls.py @@ -1,7 +1,10 @@ from django.contrib import admin from django.urls import include, path +from . import views + urlpatterns = [ - path("", include("controlpanel.interfaces.urls")), + path("", views.IndexView.as_view(), name="index"), + path("auth/", include("ap.auth.urls")), path("admin/", admin.site.urls), ] diff --git a/controlpanel/core/migrations/__init__.py b/ap/users/__init__.py similarity index 100% rename from controlpanel/core/migrations/__init__.py rename to ap/users/__init__.py diff --git a/controlpanel/core/apps.py b/ap/users/apps.py similarity index 61% rename from controlpanel/core/apps.py rename to ap/users/apps.py index 4444e2a3..5d859d16 100644 --- a/controlpanel/core/apps.py +++ b/ap/users/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class CliConfig(AppConfig): +class UsersConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" - name = "controlpanel.core" + name = "ap.users" diff --git a/controlpanel/core/migrations/0001_initial.py b/ap/users/migrations/0001_initial.py similarity index 85% rename from controlpanel/core/migrations/0001_initial.py rename to ap/users/migrations/0001_initial.py index 23abb648..dae5f937 100644 --- a/controlpanel/core/migrations/0001_initial.py +++ b/ap/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2023-11-15 01:24 +# Generated by Django 4.2.11 on 2024-06-25 14:08 import django.contrib.auth.models import django.contrib.auth.validators @@ -26,8 +26,7 @@ class Migration(migrations.Migration): "is_superuser", models.BooleanField( default=False, - help_text="Designates that this user has all permissions without " - "explicitly assigning them.", + help_text="Designates that this user has all permissions without explicitly assigning them.", verbose_name="superuser status", ), ), @@ -35,8 +34,7 @@ class Migration(migrations.Migration): "username", models.CharField( error_messages={"unique": "A user with that username already exists."}, - help_text="Required. 150 characters or fewer. Letters, " - "digits and @/./+/-/_ only.", + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], @@ -67,8 +65,7 @@ class Migration(migrations.Migration): "is_active", models.BooleanField( default=True, - help_text="Designates whether this user should be treated as active. " - "Unselect this instead of deleting accounts.", + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", verbose_name="active", ), ), @@ -78,18 +75,14 @@ class Migration(migrations.Migration): default=django.utils.timezone.now, verbose_name="date joined" ), ), - ( - "user_id", - models.CharField(max_length=128, primary_key=True, serialize=False), - ), + ("user_id", models.CharField(max_length=128, primary_key=True, serialize=False)), ("name", models.CharField(blank=True, max_length=256)), ("nickname", models.CharField(blank=True, max_length=256)), ( "groups", models.ManyToManyField( blank=True, - help_text="The groups this user belongs to. A user will get all " - "permissions granted to each of their groups.", + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", related_name="user_set", related_query_name="user", to="auth.group", @@ -109,8 +102,8 @@ class Migration(migrations.Migration): ), ], options={ - "db_table": "control_panel_user", - "ordering": ("nickname",), + "db_table": "ap_user", + "ordering": ("name",), }, managers=[ ("objects", django.contrib.auth.models.UserManager()), diff --git a/controlpanel/core/tasks/__init__.py b/ap/users/migrations/__init__.py similarity index 100% rename from controlpanel/core/tasks/__init__.py rename to ap/users/migrations/__init__.py diff --git a/controlpanel/core/models/user.py b/ap/users/models.py similarity index 87% rename from controlpanel/core/models/user.py rename to ap/users/models.py index ac8b1eb5..ed6f9dea 100644 --- a/controlpanel/core/models/user.py +++ b/ap/users/models.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import AbstractUser from django.db import models -from controlpanel.core.common.utils import sanitize_dns_label +from ap.core.utils import sanitize_dns_label class User(AbstractUser): @@ -12,7 +12,7 @@ class User(AbstractUser): REQUIRED_FIELDS = ["email", "user_id"] class Meta: - db_table = "control_panel_user" + db_table = "ap_user" ordering = ("name",) def __repr__(self): diff --git a/ap/views.py b/ap/views.py new file mode 100644 index 00000000..467073e3 --- /dev/null +++ b/ap/views.py @@ -0,0 +1,7 @@ +from django.views.generic import TemplateView + +from ap.auth.views.mixins import OIDCLoginRequiredMixin + + +class IndexView(OIDCLoginRequiredMixin, TemplateView): + template_name = "home.html" diff --git a/controlpanel/wsgi.py b/ap/wsgi.py similarity index 72% rename from controlpanel/wsgi.py rename to ap/wsgi.py index a2aabccf..e35562d2 100644 --- a/controlpanel/wsgi.py +++ b/ap/wsgi.py @@ -1,5 +1,5 @@ """ -WSGI config for controlpanel project. +WSGI config for ap project. It exposes the WSGI callable as a module-level variable named ``application``. @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "controlpanel.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ap.settings") application = get_wsgi_application() diff --git a/controlpanel/interfaces/web/static/app.scss b/assets/scss/app.scss similarity index 100% rename from controlpanel/interfaces/web/static/app.scss rename to assets/scss/app.scss diff --git a/contrib/docker-compose-postgres.yml b/contrib/docker-compose-postgres.yml index aae06302..a1bf5b30 100644 --- a/contrib/docker-compose-postgres.yml +++ b/contrib/docker-compose-postgres.yml @@ -6,13 +6,13 @@ services: image: public.ecr.aws/docker/library/postgres:15.4 restart: always environment: - POSTGRES_USER: controlpanel - POSTGRES_PASSWORD: controlpanel - POSTGRES_DB: controlpanel + POSTGRES_USER: ap + POSTGRES_PASSWORD: ap + POSTGRES_DB: ap ports: - "5432:5432" healthcheck: - test: ["CMD-SHELL", "pg_isready -U controlpanel"] + test: ["CMD-SHELL", "pg_isready -U ap"] interval: 5s timeout: 5s retries: 5 diff --git a/contrib/docker-compose-test.yml b/contrib/docker-compose-test.yml index 191ee497..53a9ab8b 100644 --- a/contrib/docker-compose-test.yml +++ b/contrib/docker-compose-test.yml @@ -15,12 +15,12 @@ services: environment: ALLOWED_HOSTS: "localhost 127.0.0.1 0.0.0.0" DB_HOST: "db" - DB_NAME: controlpanel - DB_PASSWORD: controlpanel + DB_NAME: ap + DB_PASSWORD: ap DB_PORT: 5432 - DB_USER: controlpanel + DB_USER: ap DEBUG: "True" - DJANGO_SETTINGS_MODULE: "controlpanel.settings.test" + DJANGO_SETTINGS_MODULE: "ap.settings.test" ENV: "dev" PYTHONUNBUFFERED: "1" SECRET_KEY: "1234567890" diff --git a/controlpanel/core/auth/__init__.py b/controlpanel/core/auth/__init__.py deleted file mode 100644 index 2dccca88..00000000 --- a/controlpanel/core/auth/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from controlpanel.core.auth.oidc import ( # noqa - OIDCSessionValidator, - OIDCSubAuthenticationBackend, - oauth, -) diff --git a/controlpanel/core/migrations/0002_alter_user_options.py b/controlpanel/core/migrations/0002_alter_user_options.py deleted file mode 100644 index 567cd402..00000000 --- a/controlpanel/core/migrations/0002_alter_user_options.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 4.2.11 on 2024-04-10 13:16 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("core", "0001_initial"), - ] - - operations = [ - migrations.AlterModelOptions( - name="user", - options={"ordering": ("name",)}, - ), - ] diff --git a/controlpanel/core/models/__init__.py b/controlpanel/core/models/__init__.py deleted file mode 100644 index b0dcafc1..00000000 --- a/controlpanel/core/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from controlpanel.core.models.user import User # noqa diff --git a/controlpanel/domains/apps.py b/controlpanel/domains/apps.py deleted file mode 100644 index e69de29b..00000000 diff --git a/controlpanel/interfaces/__init__.py b/controlpanel/interfaces/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/controlpanel/interfaces/api/__init__.py b/controlpanel/interfaces/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/controlpanel/interfaces/cli/__init__.py b/controlpanel/interfaces/cli/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/controlpanel/interfaces/urls.py b/controlpanel/interfaces/urls.py deleted file mode 100644 index 86f9c1d4..00000000 --- a/controlpanel/interfaces/urls.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.urls import include, path - -urlpatterns = [ - path("", include("controlpanel.interfaces.web.urls")), -] diff --git a/controlpanel/interfaces/web/__init__.py b/controlpanel/interfaces/web/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/controlpanel/interfaces/web/apps.py b/controlpanel/interfaces/web/apps.py deleted file mode 100644 index f3e00f85..00000000 --- a/controlpanel/interfaces/web/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class WebInterfaceConfig(AppConfig): - name = "controlpanel.interfaces.web" diff --git a/controlpanel/interfaces/web/auth/__init__.py b/controlpanel/interfaces/web/auth/__init__.py deleted file mode 100644 index ff30c3b6..00000000 --- a/controlpanel/interfaces/web/auth/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from controlpanel.interfaces.web.auth.views import ( # noqa - LoginFail, - LogoutView, - OIDCAuthenticationView, - OIDCLoginView, -) diff --git a/controlpanel/interfaces/web/data_products/__init__.py b/controlpanel/interfaces/web/data_products/__init__.py deleted file mode 100644 index 83ee1dc7..00000000 --- a/controlpanel/interfaces/web/data_products/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from controlpanel.interfaces.web.data_products.views import DataProductsView # noqa diff --git a/controlpanel/interfaces/web/data_products/views.py b/controlpanel/interfaces/web/data_products/views.py deleted file mode 100644 index 89751188..00000000 --- a/controlpanel/interfaces/web/data_products/views.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.views.generic import TemplateView - - -class DataProductsView(TemplateView): - template_name = "data-products.html" diff --git a/controlpanel/interfaces/web/templates/data-products.html b/controlpanel/interfaces/web/templates/data-products.html deleted file mode 100644 index ceaa74dd..00000000 --- a/controlpanel/interfaces/web/templates/data-products.html +++ /dev/null @@ -1,8 +0,0 @@ - - -{% extends "base.html" %} - -{% block content %} -