From b3696037801169f05f649e322b9fd43f70a41390 Mon Sep 17 00:00:00 2001 From: Kai Schlamp Date: Sat, 23 Mar 2024 14:55:04 +0000 Subject: [PATCH 01/40] Move accounts and token_authentications apps to shared folder --- .../__init__.py | 0 .../accounts}/__init__.py | 0 .../accounts/admin.py | 0 {radis => adit_radis_shared}/accounts/apps.py | 2 +- .../accounts/backends.py | 0 .../accounts/factories.py | 0 .../accounts/forms.py | 0 .../accounts/middlewares.py | 2 +- .../accounts/migrations/0001_initial.py | 0 .../accounts/migrations/0002_alter_user_id.py | 0 ...3_rename_misc_settings_user_preferences.py | 0 .../migrations/0004_alter_user_preferences.py | 0 .../accounts/migrations/0005_institute.py | 0 .../migrations/0006_user_active_group.py | 0 .../0007_convert_institutes_to_groups.py | 0 .../migrations/0008_remove_institute_users.py | 0 .../migrations/0009_delete_institute.py | 0 .../accounts/migrations}/__init__.py | 0 .../accounts/models.py | 0 .../accounts/_active_group_selector.html | 0 .../templates/accounts/accounts_layout.html | 0 .../accounts/templates/accounts/profile.html | 0 .../accounts/tests/integration/conftest.py | 0 .../accounts/tests/integration/test_login.py | 0 .../accounts/tests/test_migrations.py | 0 {radis => adit_radis_shared}/accounts/urls.py | 0 .../accounts/views.py | 0 .../common}/__init__.py | 0 .../common/utils}/__init__.py | 0 .../common/utils}/testing.py | 0 adit_radis_shared/conftest.py | 62 +++++++++++++++++++ .../token_authentication}/__init__.py | 0 .../token_authentication/admin.py | 0 .../token_authentication/apps.py | 2 +- .../token_authentication/auth.py | 2 +- .../token_authentication/factories.py | 2 +- .../token_authentication/forms.py | 2 +- .../migrations/0001_initial.py | 0 ...token_fraction_alter_token_token_string.py | 0 .../migrations/0003_alter_token_client.py | 0 ..._rename_token_string_token_token_hashed.py | 0 ...n_options_remove_token_expires_and_more.py | 0 .../0006_rename_expiry_time_token_expires.py | 0 .../migrations/0007_alter_token_last_used.py | 0 .../0008_alter_token_token_hashed.py | 0 ...token_owner_alter_token_client_and_more.py | 0 ...10_alter_token_unique_together_and_more.py | 0 ...kensettings_alter_token_client_and_more.py | 0 ...e_token_unique_client_per_user_and_more.py | 0 .../0013_alter_token_description.py | 0 .../migrations/__init__.py | 0 .../token_authentication/models.py | 2 +- .../token_authentication.js | 0 .../_token_authentication_help.html | 0 .../token_authentication_layout.html | 0 .../token_authentication/token_dashboard.html | 0 .../token_authentication/tests/__init__.py | 0 .../token_authentication/tests/conftest.py | 2 +- .../tests/integration/conftest.py | 0 .../integration/test_token_authentication.py | 0 .../token_authentication/urls.py | 0 .../token_authentication/utils/__init__.py | 0 .../token_authentication/utils/crypto.py | 0 .../token_authentication/views.py | 0 pyproject.toml | 2 +- radis/collections/factories.py | 2 +- .../templatetags/collections_extras.py | 2 +- radis/conftest.py | 60 ------------------ .../core/management/commands/create_admin.py | 2 +- radis/core/management/commands/populate_db.py | 10 +-- radis/core/tasks.py | 2 +- radis/core/types.py | 2 +- radis/core/utils/auth_utils.py | 2 +- radis/core/utils/mail.py | 2 +- radis/notes/templatetags/notes_extras.py | 2 +- radis/rag/forms.py | 2 +- radis/settings/base.py | 12 ++-- radis/urls.py | 4 +- 78 files changed, 93 insertions(+), 91 deletions(-) rename {radis/accounts => adit_radis_shared}/__init__.py (100%) rename {radis/accounts/migrations => adit_radis_shared/accounts}/__init__.py (100%) rename {radis => adit_radis_shared}/accounts/admin.py (100%) rename {radis => adit_radis_shared}/accounts/apps.py (63%) rename {radis => adit_radis_shared}/accounts/backends.py (100%) rename {radis => adit_radis_shared}/accounts/factories.py (100%) rename {radis => adit_radis_shared}/accounts/forms.py (100%) rename {radis => adit_radis_shared}/accounts/middlewares.py (92%) rename {radis => adit_radis_shared}/accounts/migrations/0001_initial.py (100%) rename {radis => adit_radis_shared}/accounts/migrations/0002_alter_user_id.py (100%) rename {radis => adit_radis_shared}/accounts/migrations/0003_rename_misc_settings_user_preferences.py (100%) rename {radis => adit_radis_shared}/accounts/migrations/0004_alter_user_preferences.py (100%) rename {radis => adit_radis_shared}/accounts/migrations/0005_institute.py (100%) rename {radis => adit_radis_shared}/accounts/migrations/0006_user_active_group.py (100%) rename {radis => adit_radis_shared}/accounts/migrations/0007_convert_institutes_to_groups.py (100%) rename {radis => adit_radis_shared}/accounts/migrations/0008_remove_institute_users.py (100%) rename {radis => adit_radis_shared}/accounts/migrations/0009_delete_institute.py (100%) rename {radis/token_authentication => adit_radis_shared/accounts/migrations}/__init__.py (100%) rename {radis => adit_radis_shared}/accounts/models.py (100%) rename {radis => adit_radis_shared}/accounts/templates/accounts/_active_group_selector.html (100%) rename {radis => adit_radis_shared}/accounts/templates/accounts/accounts_layout.html (100%) rename {radis => adit_radis_shared}/accounts/templates/accounts/profile.html (100%) rename {radis => adit_radis_shared}/accounts/tests/integration/conftest.py (100%) rename {radis => adit_radis_shared}/accounts/tests/integration/test_login.py (100%) rename {radis => adit_radis_shared}/accounts/tests/test_migrations.py (100%) rename {radis => adit_radis_shared}/accounts/urls.py (100%) rename {radis => adit_radis_shared}/accounts/views.py (100%) rename {radis/token_authentication/migrations => adit_radis_shared/common}/__init__.py (100%) rename {radis/token_authentication/tests => adit_radis_shared/common/utils}/__init__.py (100%) rename {radis => adit_radis_shared/common/utils}/testing.py (100%) create mode 100644 adit_radis_shared/conftest.py rename {radis/token_authentication/utils => adit_radis_shared/token_authentication}/__init__.py (100%) rename {radis => adit_radis_shared}/token_authentication/admin.py (100%) rename {radis => adit_radis_shared}/token_authentication/apps.py (60%) rename {radis => adit_radis_shared}/token_authentication/auth.py (98%) rename {radis => adit_radis_shared}/token_authentication/factories.py (91%) rename {radis => adit_radis_shared}/token_authentication/forms.py (97%) rename {radis => adit_radis_shared}/token_authentication/migrations/0001_initial.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0002_token_fraction_alter_token_token_string.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0003_alter_token_client.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0004_rename_token_string_token_token_hashed.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0005_alter_token_options_remove_token_expires_and_more.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0006_rename_expiry_time_token_expires.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0007_alter_token_last_used.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0008_alter_token_token_hashed.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0009_rename_author_token_owner_alter_token_client_and_more.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0010_alter_token_unique_together_and_more.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0011_delete_tokensettings_alter_token_client_and_more.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0012_remove_token_unique_client_per_user_and_more.py (100%) rename {radis => adit_radis_shared}/token_authentication/migrations/0013_alter_token_description.py (100%) create mode 100644 adit_radis_shared/token_authentication/migrations/__init__.py rename {radis => adit_radis_shared}/token_authentication/models.py (97%) rename {radis => adit_radis_shared}/token_authentication/static/token_authentication/token_authentication.js (100%) rename {radis => adit_radis_shared}/token_authentication/templates/token_authentication/_token_authentication_help.html (100%) rename {radis => adit_radis_shared}/token_authentication/templates/token_authentication/token_authentication_layout.html (100%) rename {radis => adit_radis_shared}/token_authentication/templates/token_authentication/token_dashboard.html (100%) create mode 100644 adit_radis_shared/token_authentication/tests/__init__.py rename {radis => adit_radis_shared}/token_authentication/tests/conftest.py (86%) rename {radis => adit_radis_shared}/token_authentication/tests/integration/conftest.py (100%) rename {radis => adit_radis_shared}/token_authentication/tests/integration/test_token_authentication.py (100%) rename {radis => adit_radis_shared}/token_authentication/urls.py (100%) create mode 100644 adit_radis_shared/token_authentication/utils/__init__.py rename {radis => adit_radis_shared}/token_authentication/utils/crypto.py (100%) rename {radis => adit_radis_shared}/token_authentication/views.py (100%) diff --git a/radis/accounts/__init__.py b/adit_radis_shared/__init__.py similarity index 100% rename from radis/accounts/__init__.py rename to adit_radis_shared/__init__.py diff --git a/radis/accounts/migrations/__init__.py b/adit_radis_shared/accounts/__init__.py similarity index 100% rename from radis/accounts/migrations/__init__.py rename to adit_radis_shared/accounts/__init__.py diff --git a/radis/accounts/admin.py b/adit_radis_shared/accounts/admin.py similarity index 100% rename from radis/accounts/admin.py rename to adit_radis_shared/accounts/admin.py diff --git a/radis/accounts/apps.py b/adit_radis_shared/accounts/apps.py similarity index 63% rename from radis/accounts/apps.py rename to adit_radis_shared/accounts/apps.py index 86c208a0..ff41e242 100644 --- a/radis/accounts/apps.py +++ b/adit_radis_shared/accounts/apps.py @@ -2,4 +2,4 @@ class AccountsConfig(AppConfig): - name = "radis.accounts" + name = "adit_radis_shared.accounts" diff --git a/radis/accounts/backends.py b/adit_radis_shared/accounts/backends.py similarity index 100% rename from radis/accounts/backends.py rename to adit_radis_shared/accounts/backends.py diff --git a/radis/accounts/factories.py b/adit_radis_shared/accounts/factories.py similarity index 100% rename from radis/accounts/factories.py rename to adit_radis_shared/accounts/factories.py diff --git a/radis/accounts/forms.py b/adit_radis_shared/accounts/forms.py similarity index 100% rename from radis/accounts/forms.py rename to adit_radis_shared/accounts/forms.py diff --git a/radis/accounts/middlewares.py b/adit_radis_shared/accounts/middlewares.py similarity index 92% rename from radis/accounts/middlewares.py rename to adit_radis_shared/accounts/middlewares.py index d196387e..9c851ff6 100644 --- a/radis/accounts/middlewares.py +++ b/adit_radis_shared/accounts/middlewares.py @@ -2,7 +2,7 @@ from django.http import HttpRequest -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User class ActiveGroupMiddleware: diff --git a/radis/accounts/migrations/0001_initial.py b/adit_radis_shared/accounts/migrations/0001_initial.py similarity index 100% rename from radis/accounts/migrations/0001_initial.py rename to adit_radis_shared/accounts/migrations/0001_initial.py diff --git a/radis/accounts/migrations/0002_alter_user_id.py b/adit_radis_shared/accounts/migrations/0002_alter_user_id.py similarity index 100% rename from radis/accounts/migrations/0002_alter_user_id.py rename to adit_radis_shared/accounts/migrations/0002_alter_user_id.py diff --git a/radis/accounts/migrations/0003_rename_misc_settings_user_preferences.py b/adit_radis_shared/accounts/migrations/0003_rename_misc_settings_user_preferences.py similarity index 100% rename from radis/accounts/migrations/0003_rename_misc_settings_user_preferences.py rename to adit_radis_shared/accounts/migrations/0003_rename_misc_settings_user_preferences.py diff --git a/radis/accounts/migrations/0004_alter_user_preferences.py b/adit_radis_shared/accounts/migrations/0004_alter_user_preferences.py similarity index 100% rename from radis/accounts/migrations/0004_alter_user_preferences.py rename to adit_radis_shared/accounts/migrations/0004_alter_user_preferences.py diff --git a/radis/accounts/migrations/0005_institute.py b/adit_radis_shared/accounts/migrations/0005_institute.py similarity index 100% rename from radis/accounts/migrations/0005_institute.py rename to adit_radis_shared/accounts/migrations/0005_institute.py diff --git a/radis/accounts/migrations/0006_user_active_group.py b/adit_radis_shared/accounts/migrations/0006_user_active_group.py similarity index 100% rename from radis/accounts/migrations/0006_user_active_group.py rename to adit_radis_shared/accounts/migrations/0006_user_active_group.py diff --git a/radis/accounts/migrations/0007_convert_institutes_to_groups.py b/adit_radis_shared/accounts/migrations/0007_convert_institutes_to_groups.py similarity index 100% rename from radis/accounts/migrations/0007_convert_institutes_to_groups.py rename to adit_radis_shared/accounts/migrations/0007_convert_institutes_to_groups.py diff --git a/radis/accounts/migrations/0008_remove_institute_users.py b/adit_radis_shared/accounts/migrations/0008_remove_institute_users.py similarity index 100% rename from radis/accounts/migrations/0008_remove_institute_users.py rename to adit_radis_shared/accounts/migrations/0008_remove_institute_users.py diff --git a/radis/accounts/migrations/0009_delete_institute.py b/adit_radis_shared/accounts/migrations/0009_delete_institute.py similarity index 100% rename from radis/accounts/migrations/0009_delete_institute.py rename to adit_radis_shared/accounts/migrations/0009_delete_institute.py diff --git a/radis/token_authentication/__init__.py b/adit_radis_shared/accounts/migrations/__init__.py similarity index 100% rename from radis/token_authentication/__init__.py rename to adit_radis_shared/accounts/migrations/__init__.py diff --git a/radis/accounts/models.py b/adit_radis_shared/accounts/models.py similarity index 100% rename from radis/accounts/models.py rename to adit_radis_shared/accounts/models.py diff --git a/radis/accounts/templates/accounts/_active_group_selector.html b/adit_radis_shared/accounts/templates/accounts/_active_group_selector.html similarity index 100% rename from radis/accounts/templates/accounts/_active_group_selector.html rename to adit_radis_shared/accounts/templates/accounts/_active_group_selector.html diff --git a/radis/accounts/templates/accounts/accounts_layout.html b/adit_radis_shared/accounts/templates/accounts/accounts_layout.html similarity index 100% rename from radis/accounts/templates/accounts/accounts_layout.html rename to adit_radis_shared/accounts/templates/accounts/accounts_layout.html diff --git a/radis/accounts/templates/accounts/profile.html b/adit_radis_shared/accounts/templates/accounts/profile.html similarity index 100% rename from radis/accounts/templates/accounts/profile.html rename to adit_radis_shared/accounts/templates/accounts/profile.html diff --git a/radis/accounts/tests/integration/conftest.py b/adit_radis_shared/accounts/tests/integration/conftest.py similarity index 100% rename from radis/accounts/tests/integration/conftest.py rename to adit_radis_shared/accounts/tests/integration/conftest.py diff --git a/radis/accounts/tests/integration/test_login.py b/adit_radis_shared/accounts/tests/integration/test_login.py similarity index 100% rename from radis/accounts/tests/integration/test_login.py rename to adit_radis_shared/accounts/tests/integration/test_login.py diff --git a/radis/accounts/tests/test_migrations.py b/adit_radis_shared/accounts/tests/test_migrations.py similarity index 100% rename from radis/accounts/tests/test_migrations.py rename to adit_radis_shared/accounts/tests/test_migrations.py diff --git a/radis/accounts/urls.py b/adit_radis_shared/accounts/urls.py similarity index 100% rename from radis/accounts/urls.py rename to adit_radis_shared/accounts/urls.py diff --git a/radis/accounts/views.py b/adit_radis_shared/accounts/views.py similarity index 100% rename from radis/accounts/views.py rename to adit_radis_shared/accounts/views.py diff --git a/radis/token_authentication/migrations/__init__.py b/adit_radis_shared/common/__init__.py similarity index 100% rename from radis/token_authentication/migrations/__init__.py rename to adit_radis_shared/common/__init__.py diff --git a/radis/token_authentication/tests/__init__.py b/adit_radis_shared/common/utils/__init__.py similarity index 100% rename from radis/token_authentication/tests/__init__.py rename to adit_radis_shared/common/utils/__init__.py diff --git a/radis/testing.py b/adit_radis_shared/common/utils/testing.py similarity index 100% rename from radis/testing.py rename to adit_radis_shared/common/utils/testing.py diff --git a/adit_radis_shared/conftest.py b/adit_radis_shared/conftest.py new file mode 100644 index 00000000..5fc8795d --- /dev/null +++ b/adit_radis_shared/conftest.py @@ -0,0 +1,62 @@ +import time +from typing import Callable + +import pytest +from playwright.sync_api import Locator, Page, Response + +from adit_radis_shared.accounts.factories import UserFactory +from adit_radis_shared.common.utils.testing import ChannelsLiveServer + + +@pytest.fixture +def channels_live_server(request): + server = ChannelsLiveServer() + request.addfinalizer(server.stop) + return server + + +@pytest.fixture +def poll(): + def _poll( + locator: Locator, + func: Callable[[Locator], Response | None] = lambda loc: loc.page.reload(), + interval: int = 1_500, + timeout: int = 15_000, + ): + start_time = time.time() + while True: + try: + locator.wait_for(timeout=interval) + return locator + except Exception as err: + elapsed_time = (time.time() - start_time) * 1000 + if elapsed_time > timeout: + raise err + + func(locator) + + return _poll + + +@pytest.fixture +def login_user(page: Page): + def _login_user(server_url: str, username: str, password: str): + page.goto(server_url + "/accounts/login") + page.get_by_label("Username").fill(username) + page.get_by_label("Password").fill(password) + page.get_by_text("Log in").click() + + return _login_user + + +@pytest.fixture +def create_and_login_user(page: Page, login_user): + def _create_and_login_user(server_url: str): + password = "mysecret" + user = UserFactory(password=password) + + login_user(server_url, user.username, password) + + return user + + return _create_and_login_user diff --git a/radis/token_authentication/utils/__init__.py b/adit_radis_shared/token_authentication/__init__.py similarity index 100% rename from radis/token_authentication/utils/__init__.py rename to adit_radis_shared/token_authentication/__init__.py diff --git a/radis/token_authentication/admin.py b/adit_radis_shared/token_authentication/admin.py similarity index 100% rename from radis/token_authentication/admin.py rename to adit_radis_shared/token_authentication/admin.py diff --git a/radis/token_authentication/apps.py b/adit_radis_shared/token_authentication/apps.py similarity index 60% rename from radis/token_authentication/apps.py rename to adit_radis_shared/token_authentication/apps.py index 023a8527..22b745b5 100644 --- a/radis/token_authentication/apps.py +++ b/adit_radis_shared/token_authentication/apps.py @@ -2,4 +2,4 @@ class TokenAuthenticationConfig(AppConfig): - name = "radis.token_authentication" + name = "adit_radis_shared.token_authentication" diff --git a/radis/token_authentication/auth.py b/adit_radis_shared/token_authentication/auth.py similarity index 98% rename from radis/token_authentication/auth.py rename to adit_radis_shared/token_authentication/auth.py index 2004973d..866ccc43 100644 --- a/radis/token_authentication/auth.py +++ b/adit_radis_shared/token_authentication/auth.py @@ -5,7 +5,7 @@ from rest_framework.exceptions import AuthenticationFailed from rest_framework.request import Request -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User from .models import Token from .utils.crypto import hash_token, verify_token diff --git a/radis/token_authentication/factories.py b/adit_radis_shared/token_authentication/factories.py similarity index 91% rename from radis/token_authentication/factories.py rename to adit_radis_shared/token_authentication/factories.py index b5ecc5ad..1078fc0d 100644 --- a/radis/token_authentication/factories.py +++ b/adit_radis_shared/token_authentication/factories.py @@ -3,7 +3,7 @@ import factory from django.utils import timezone -from radis.accounts.factories import UserFactory +from adit_radis_shared.accounts.factories import UserFactory from radis.core.factories import BaseDjangoModelFactory from .models import Token diff --git a/radis/token_authentication/forms.py b/adit_radis_shared/token_authentication/forms.py similarity index 97% rename from radis/token_authentication/forms.py rename to adit_radis_shared/token_authentication/forms.py index 78486a3a..18504b17 100644 --- a/radis/token_authentication/forms.py +++ b/adit_radis_shared/token_authentication/forms.py @@ -2,7 +2,7 @@ from crispy_forms.layout import Div, Field, Layout, Submit from django import forms -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User from .models import Token diff --git a/radis/token_authentication/migrations/0001_initial.py b/adit_radis_shared/token_authentication/migrations/0001_initial.py similarity index 100% rename from radis/token_authentication/migrations/0001_initial.py rename to adit_radis_shared/token_authentication/migrations/0001_initial.py diff --git a/radis/token_authentication/migrations/0002_token_fraction_alter_token_token_string.py b/adit_radis_shared/token_authentication/migrations/0002_token_fraction_alter_token_token_string.py similarity index 100% rename from radis/token_authentication/migrations/0002_token_fraction_alter_token_token_string.py rename to adit_radis_shared/token_authentication/migrations/0002_token_fraction_alter_token_token_string.py diff --git a/radis/token_authentication/migrations/0003_alter_token_client.py b/adit_radis_shared/token_authentication/migrations/0003_alter_token_client.py similarity index 100% rename from radis/token_authentication/migrations/0003_alter_token_client.py rename to adit_radis_shared/token_authentication/migrations/0003_alter_token_client.py diff --git a/radis/token_authentication/migrations/0004_rename_token_string_token_token_hashed.py b/adit_radis_shared/token_authentication/migrations/0004_rename_token_string_token_token_hashed.py similarity index 100% rename from radis/token_authentication/migrations/0004_rename_token_string_token_token_hashed.py rename to adit_radis_shared/token_authentication/migrations/0004_rename_token_string_token_token_hashed.py diff --git a/radis/token_authentication/migrations/0005_alter_token_options_remove_token_expires_and_more.py b/adit_radis_shared/token_authentication/migrations/0005_alter_token_options_remove_token_expires_and_more.py similarity index 100% rename from radis/token_authentication/migrations/0005_alter_token_options_remove_token_expires_and_more.py rename to adit_radis_shared/token_authentication/migrations/0005_alter_token_options_remove_token_expires_and_more.py diff --git a/radis/token_authentication/migrations/0006_rename_expiry_time_token_expires.py b/adit_radis_shared/token_authentication/migrations/0006_rename_expiry_time_token_expires.py similarity index 100% rename from radis/token_authentication/migrations/0006_rename_expiry_time_token_expires.py rename to adit_radis_shared/token_authentication/migrations/0006_rename_expiry_time_token_expires.py diff --git a/radis/token_authentication/migrations/0007_alter_token_last_used.py b/adit_radis_shared/token_authentication/migrations/0007_alter_token_last_used.py similarity index 100% rename from radis/token_authentication/migrations/0007_alter_token_last_used.py rename to adit_radis_shared/token_authentication/migrations/0007_alter_token_last_used.py diff --git a/radis/token_authentication/migrations/0008_alter_token_token_hashed.py b/adit_radis_shared/token_authentication/migrations/0008_alter_token_token_hashed.py similarity index 100% rename from radis/token_authentication/migrations/0008_alter_token_token_hashed.py rename to adit_radis_shared/token_authentication/migrations/0008_alter_token_token_hashed.py diff --git a/radis/token_authentication/migrations/0009_rename_author_token_owner_alter_token_client_and_more.py b/adit_radis_shared/token_authentication/migrations/0009_rename_author_token_owner_alter_token_client_and_more.py similarity index 100% rename from radis/token_authentication/migrations/0009_rename_author_token_owner_alter_token_client_and_more.py rename to adit_radis_shared/token_authentication/migrations/0009_rename_author_token_owner_alter_token_client_and_more.py diff --git a/radis/token_authentication/migrations/0010_alter_token_unique_together_and_more.py b/adit_radis_shared/token_authentication/migrations/0010_alter_token_unique_together_and_more.py similarity index 100% rename from radis/token_authentication/migrations/0010_alter_token_unique_together_and_more.py rename to adit_radis_shared/token_authentication/migrations/0010_alter_token_unique_together_and_more.py diff --git a/radis/token_authentication/migrations/0011_delete_tokensettings_alter_token_client_and_more.py b/adit_radis_shared/token_authentication/migrations/0011_delete_tokensettings_alter_token_client_and_more.py similarity index 100% rename from radis/token_authentication/migrations/0011_delete_tokensettings_alter_token_client_and_more.py rename to adit_radis_shared/token_authentication/migrations/0011_delete_tokensettings_alter_token_client_and_more.py diff --git a/radis/token_authentication/migrations/0012_remove_token_unique_client_per_user_and_more.py b/adit_radis_shared/token_authentication/migrations/0012_remove_token_unique_client_per_user_and_more.py similarity index 100% rename from radis/token_authentication/migrations/0012_remove_token_unique_client_per_user_and_more.py rename to adit_radis_shared/token_authentication/migrations/0012_remove_token_unique_client_per_user_and_more.py diff --git a/radis/token_authentication/migrations/0013_alter_token_description.py b/adit_radis_shared/token_authentication/migrations/0013_alter_token_description.py similarity index 100% rename from radis/token_authentication/migrations/0013_alter_token_description.py rename to adit_radis_shared/token_authentication/migrations/0013_alter_token_description.py diff --git a/adit_radis_shared/token_authentication/migrations/__init__.py b/adit_radis_shared/token_authentication/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/radis/token_authentication/models.py b/adit_radis_shared/token_authentication/models.py similarity index 97% rename from radis/token_authentication/models.py rename to adit_radis_shared/token_authentication/models.py index 3901bed1..d9ad93dd 100644 --- a/radis/token_authentication/models.py +++ b/adit_radis_shared/token_authentication/models.py @@ -6,7 +6,7 @@ from django.contrib.auth.models import AbstractBaseUser, AnonymousUser from django.db import models -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User from .utils.crypto import hash_token diff --git a/radis/token_authentication/static/token_authentication/token_authentication.js b/adit_radis_shared/token_authentication/static/token_authentication/token_authentication.js similarity index 100% rename from radis/token_authentication/static/token_authentication/token_authentication.js rename to adit_radis_shared/token_authentication/static/token_authentication/token_authentication.js diff --git a/radis/token_authentication/templates/token_authentication/_token_authentication_help.html b/adit_radis_shared/token_authentication/templates/token_authentication/_token_authentication_help.html similarity index 100% rename from radis/token_authentication/templates/token_authentication/_token_authentication_help.html rename to adit_radis_shared/token_authentication/templates/token_authentication/_token_authentication_help.html diff --git a/radis/token_authentication/templates/token_authentication/token_authentication_layout.html b/adit_radis_shared/token_authentication/templates/token_authentication/token_authentication_layout.html similarity index 100% rename from radis/token_authentication/templates/token_authentication/token_authentication_layout.html rename to adit_radis_shared/token_authentication/templates/token_authentication/token_authentication_layout.html diff --git a/radis/token_authentication/templates/token_authentication/token_dashboard.html b/adit_radis_shared/token_authentication/templates/token_authentication/token_dashboard.html similarity index 100% rename from radis/token_authentication/templates/token_authentication/token_dashboard.html rename to adit_radis_shared/token_authentication/templates/token_authentication/token_dashboard.html diff --git a/adit_radis_shared/token_authentication/tests/__init__.py b/adit_radis_shared/token_authentication/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/radis/token_authentication/tests/conftest.py b/adit_radis_shared/token_authentication/tests/conftest.py similarity index 86% rename from radis/token_authentication/tests/conftest.py rename to adit_radis_shared/token_authentication/tests/conftest.py index 0a28c2eb..aa9988ba 100644 --- a/radis/token_authentication/tests/conftest.py +++ b/adit_radis_shared/token_authentication/tests/conftest.py @@ -1,6 +1,6 @@ import pytest -from radis.accounts.factories import GroupFactory +from adit_radis_shared.accounts.factories import GroupFactory from radis.core.utils.auth_utils import add_permission diff --git a/radis/token_authentication/tests/integration/conftest.py b/adit_radis_shared/token_authentication/tests/integration/conftest.py similarity index 100% rename from radis/token_authentication/tests/integration/conftest.py rename to adit_radis_shared/token_authentication/tests/integration/conftest.py diff --git a/radis/token_authentication/tests/integration/test_token_authentication.py b/adit_radis_shared/token_authentication/tests/integration/test_token_authentication.py similarity index 100% rename from radis/token_authentication/tests/integration/test_token_authentication.py rename to adit_radis_shared/token_authentication/tests/integration/test_token_authentication.py diff --git a/radis/token_authentication/urls.py b/adit_radis_shared/token_authentication/urls.py similarity index 100% rename from radis/token_authentication/urls.py rename to adit_radis_shared/token_authentication/urls.py diff --git a/adit_radis_shared/token_authentication/utils/__init__.py b/adit_radis_shared/token_authentication/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/radis/token_authentication/utils/crypto.py b/adit_radis_shared/token_authentication/utils/crypto.py similarity index 100% rename from radis/token_authentication/utils/crypto.py rename to adit_radis_shared/token_authentication/utils/crypto.py diff --git a/radis/token_authentication/views.py b/adit_radis_shared/token_authentication/views.py similarity index 100% rename from radis/token_authentication/views.py rename to adit_radis_shared/token_authentication/views.py diff --git a/pyproject.toml b/pyproject.toml index ea565209..a1711d9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ reportUnnecessaryTypeIgnoreComment = true [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "radis.settings.development" python_files = ["tests.py", "test_*.py", "*_tests.py"] -testpaths = ["radis"] +testpaths = ["radis", "adit_radis_shared"] log_cli = 0 log_cli_level = "INFO" log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" diff --git a/radis/collections/factories.py b/radis/collections/factories.py index 99b93f11..c2c03d7d 100644 --- a/radis/collections/factories.py +++ b/radis/collections/factories.py @@ -3,7 +3,7 @@ import factory from faker import Faker -from radis.accounts.factories import UserFactory +from adit_radis_shared.accounts.factories import UserFactory from .models import Collection diff --git a/radis/collections/templatetags/collections_extras.py b/radis/collections/templatetags/collections_extras.py index 379a1130..4bd58dd6 100644 --- a/radis/collections/templatetags/collections_extras.py +++ b/radis/collections/templatetags/collections_extras.py @@ -3,7 +3,7 @@ from django.db.models import QuerySet from django.template import Library -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User from radis.reports.models import Report from ..models import Collection diff --git a/radis/conftest.py b/radis/conftest.py index 0a095a83..09326556 100644 --- a/radis/conftest.py +++ b/radis/conftest.py @@ -1,15 +1,9 @@ -import time from multiprocessing import Process -from typing import Callable import nest_asyncio import pytest from django.core.management import call_command from faker import Faker -from playwright.sync_api import Locator, Page, Response - -from radis.accounts.factories import UserFactory -from radis.testing import ChannelsLiveServer fake = Faker() @@ -24,36 +18,6 @@ def pytest_configure(): nest_asyncio.apply() -@pytest.fixture -def poll(): - def _poll( - locator: Locator, - func: Callable[[Locator], Response | None] = lambda loc: loc.page.reload(), - interval: int = 1_500, - timeout: int = 15_000, - ): - start_time = time.time() - while True: - try: - locator.wait_for(timeout=interval) - return locator - except Exception as err: - elapsed_time = (time.time() - start_time) * 1000 - if elapsed_time > timeout: - raise err - - func(locator) - - return _poll - - -@pytest.fixture -def channels_live_server(request): - server = ChannelsLiveServer() - request.addfinalizer(server.stop) - return server - - @pytest.fixture def radis_celery_worker(): def start_worker(): @@ -63,27 +27,3 @@ def start_worker(): p.start() yield p.terminate() - - -@pytest.fixture -def login_user(page: Page): - def _login_user(server_url: str, username: str, password: str): - page.goto(server_url + "/accounts/login") - page.get_by_label("Username").fill(username) - page.get_by_label("Password").fill(password) - page.get_by_text("Log in").click() - - return _login_user - - -@pytest.fixture -def create_and_login_user(page: Page, login_user): - def _create_and_login_user(server_url: str): - password = "mysecret" - user = UserFactory(password=password) - - login_user(server_url, user.username, password) - - return user - - return _create_and_login_user diff --git a/radis/core/management/commands/create_admin.py b/radis/core/management/commands/create_admin.py index 95455908..0b103a29 100644 --- a/radis/core/management/commands/create_admin.py +++ b/radis/core/management/commands/create_admin.py @@ -4,7 +4,7 @@ from django.contrib.auth.models import UserManager from django.core.management.base import BaseCommand -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User class Command(BaseCommand): diff --git a/radis/core/management/commands/populate_db.py b/radis/core/management/commands/populate_db.py index 1820f883..fa82ac0c 100644 --- a/radis/core/management/commands/populate_db.py +++ b/radis/core/management/commands/populate_db.py @@ -9,13 +9,13 @@ from django.db import transaction from faker import Faker -from radis.accounts.factories import AdminUserFactory, GroupFactory, UserFactory -from radis.accounts.models import User +from adit_radis_shared.accounts.factories import AdminUserFactory, GroupFactory, UserFactory +from adit_radis_shared.accounts.models import User +from adit_radis_shared.token_authentication.factories import TokenFactory +from adit_radis_shared.token_authentication.models import FRACTION_LENGTH +from adit_radis_shared.token_authentication.utils.crypto import hash_token from radis.reports.factories import ReportFactory from radis.reports.models import Report -from radis.token_authentication.factories import TokenFactory -from radis.token_authentication.models import FRACTION_LENGTH -from radis.token_authentication.utils.crypto import hash_token from radis.vespa.utils.document_utils import create_documents USER_COUNT = 20 diff --git a/radis/core/tasks.py b/radis/core/tasks.py index b619df34..79113b58 100644 --- a/radis/core/tasks.py +++ b/radis/core/tasks.py @@ -9,7 +9,7 @@ from django.core.management import call_command from django.utils import timezone -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User from radis.core.models import AnalysisJob, AnalysisTask logger = logging.getLogger(__name__) diff --git a/radis/core/types.py b/radis/core/types.py index 8d0df031..ccc71724 100644 --- a/radis/core/types.py +++ b/radis/core/types.py @@ -2,7 +2,7 @@ from django_htmx.middleware import HtmxDetails from rest_framework.request import Request -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User class HtmxHttpRequest(HttpRequest): diff --git a/radis/core/utils/auth_utils.py b/radis/core/utils/auth_utils.py index e0f5b7a6..1b60c81f 100644 --- a/radis/core/utils/auth_utils.py +++ b/radis/core/utils/auth_utils.py @@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType from django.db import models -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User def is_logged_in_user(user: AbstractBaseUser | AnonymousUser) -> TypeGuard[User]: diff --git a/radis/core/utils/mail.py b/radis/core/utils/mail.py index baa0b74e..551137d6 100644 --- a/radis/core/utils/mail.py +++ b/radis/core/utils/mail.py @@ -2,7 +2,7 @@ from django.core.mail import mail_admins, send_mail from django.utils.html import strip_tags -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User def send_mail_to_admins( diff --git a/radis/notes/templatetags/notes_extras.py b/radis/notes/templatetags/notes_extras.py index 1248686f..714b2a84 100644 --- a/radis/notes/templatetags/notes_extras.py +++ b/radis/notes/templatetags/notes_extras.py @@ -2,7 +2,7 @@ from django.template import Library -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User from radis.reports.models import Report from ..models import Note diff --git a/radis/rag/forms.py b/radis/rag/forms.py index 6bf689c5..aa3ef2e2 100644 --- a/radis/rag/forms.py +++ b/radis/rag/forms.py @@ -4,7 +4,7 @@ from crispy_forms.layout import HTML, Column, Div, Layout, Row, Submit from django import forms -from radis.accounts.models import User +from adit_radis_shared.accounts.models import User from radis.reports.models import Modality from radis.search.forms import AGE_STEP, MAX_AGE, MIN_AGE from radis.search.layouts import RangeSlider diff --git a/radis/settings/base.py b/radis/settings/base.py index 0e4dc635..e0a2aafd 100644 --- a/radis/settings/base.py +++ b/radis/settings/base.py @@ -68,8 +68,8 @@ "rest_framework", "adrf", "radis.core.apps.CoreConfig", - "radis.accounts.apps.AccountsConfig", - "radis.token_authentication.apps.TokenAuthenticationConfig", + "adit_radis_shared.accounts.apps.AccountsConfig", + "adit_radis_shared.token_authentication.apps.TokenAuthenticationConfig", "radis.reports.apps.ReportsConfig", "radis.search.apps.SearchConfig", "radis.rag.apps.RagConfig", @@ -92,7 +92,7 @@ "django_htmx.middleware.HtmxMiddleware", "radis.core.middlewares.MaintenanceMiddleware", "radis.core.middlewares.TimezoneMiddleware", - "radis.accounts.middlewares.ActiveGroupMiddleware", + "adit_radis_shared.accounts.middlewares.ActiveGroupMiddleware", ] ROOT_URLCONF = "radis.urls" @@ -136,7 +136,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # A custom authentication backend that supports a single currently active group. -AUTHENTICATION_BACKENDS = ["radis.accounts.backends.ActiveGroupModelBackend"] +AUTHENTICATION_BACKENDS = ["adit_radis_shared.accounts.backends.ActiveGroupModelBackend"] # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators @@ -231,7 +231,7 @@ "rest_framework.permissions.IsAuthenticated", ], "DEFAULT_AUTHENTICATION_CLASSES": [ - "radis.token_authentication.auth.RestTokenAuthentication", + "adit_radis_shared.token_authentication.auth.RestTokenAuthentication", ], } @@ -292,7 +292,7 @@ ] # Settings for django-registration-redux -REGISTRATION_FORM = "radis.accounts.forms.RegistrationForm" +REGISTRATION_FORM = "adit_radis_shared.accounts.forms.RegistrationForm" ACCOUNT_ACTIVATION_DAYS = 14 REGISTRATION_OPEN = True diff --git a/radis/urls.py b/radis/urls.py index 0dde68c9..5179f04d 100644 --- a/radis/urls.py +++ b/radis/urls.py @@ -20,9 +20,9 @@ urlpatterns = [ path("admin-the-great/", admin.site.urls), path("api-auth/", include("rest_framework.urls")), - path("accounts/", include("radis.accounts.urls")), + path("accounts/", include("adit_radis_shared.accounts.urls")), path("", include("radis.core.urls")), - path("token-authentication/", include("radis.token_authentication.urls")), + path("token-authentication/", include("adit_radis_shared.token_authentication.urls")), path("sandbox/", include("radis.sandbox.urls")), path("reports/", include("radis.reports.urls")), path("api/reports/", include("radis.reports.api.urls")), From ca551b8f8661819bfc137ddb44ca56cb98bec279 Mon Sep 17 00:00:00 2001 From: Kai Schlamp Date: Sat, 23 Mar 2024 15:48:17 +0000 Subject: [PATCH 02/40] Make common a real Django app and put external templates and vendor statics in there --- adit_radis_shared/common/apps.py | 5 +++++ .../common}/static/vendor/alpine-morph.js | 0 {radis => adit_radis_shared/common}/static/vendor/alpine.js | 0 .../common}/static/vendor/bootstrap-icons.svg | 0 .../common}/static/vendor/bootstrap.bundle.js | 0 .../common}/static/vendor/bootstrap.bundle.js.map | 0 .../common}/static/vendor/bootstrap.css | 0 .../common}/static/vendor/bootstrap.css.map | 0 .../common}/static/vendor/htmx-alpine-morph.js | 0 {radis => adit_radis_shared/common}/static/vendor/htmx-ws.js | 0 {radis => adit_radis_shared/common}/static/vendor/htmx.js | 0 {radis => adit_radis_shared/common}/static/vendor/popper.js | 0 .../common}/static/vendor/popper.js.map | 0 .../common}/templates/registration/README.md | 0 .../common}/templates/registration/activate.html | 0 .../common}/templates/registration/activation_complete.html | 0 .../registration/activation_complete_admin_pending.html | 0 .../common}/templates/registration/activation_email.html | 0 .../common}/templates/registration/activation_email.txt | 0 .../templates/registration/activation_email_subject.txt | 0 .../common}/templates/registration/admin_approve.html | 0 .../templates/registration/admin_approve_complete.html | 0 .../templates/registration/admin_approve_complete_email.html | 0 .../templates/registration/admin_approve_complete_email.txt | 0 .../registration/admin_approve_complete_email_subject.txt | 0 .../common}/templates/registration/admin_approve_email.html | 0 .../common}/templates/registration/admin_approve_email.txt | 0 .../templates/registration/admin_approve_email_subject.txt | 0 .../common}/templates/registration/login.html | 0 .../common}/templates/registration/logout.html | 0 .../common}/templates/registration/password_change_done.html | 0 .../common}/templates/registration/password_change_form.html | 0 .../templates/registration/password_reset_complete.html | 0 .../templates/registration/password_reset_confirm.html | 0 .../common}/templates/registration/password_reset_done.html | 0 .../common}/templates/registration/password_reset_email.html | 0 .../common}/templates/registration/password_reset_form.html | 0 .../common}/templates/registration/registration_base.html | 0 .../common}/templates/registration/registration_closed.html | 0 .../templates/registration/registration_complete.html | 0 .../common}/templates/registration/registration_form.html | 0 .../templates/registration/resend_activation_complete.html | 0 .../templates/registration/resend_activation_form.html | 0 radis/settings/base.py | 3 ++- 44 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 adit_radis_shared/common/apps.py rename {radis => adit_radis_shared/common}/static/vendor/alpine-morph.js (100%) rename {radis => adit_radis_shared/common}/static/vendor/alpine.js (100%) rename {radis => adit_radis_shared/common}/static/vendor/bootstrap-icons.svg (100%) rename {radis => adit_radis_shared/common}/static/vendor/bootstrap.bundle.js (100%) rename {radis => adit_radis_shared/common}/static/vendor/bootstrap.bundle.js.map (100%) rename {radis => adit_radis_shared/common}/static/vendor/bootstrap.css (100%) rename {radis => adit_radis_shared/common}/static/vendor/bootstrap.css.map (100%) rename {radis => adit_radis_shared/common}/static/vendor/htmx-alpine-morph.js (100%) rename {radis => adit_radis_shared/common}/static/vendor/htmx-ws.js (100%) rename {radis => adit_radis_shared/common}/static/vendor/htmx.js (100%) rename {radis => adit_radis_shared/common}/static/vendor/popper.js (100%) rename {radis => adit_radis_shared/common}/static/vendor/popper.js.map (100%) rename {radis => adit_radis_shared/common}/templates/registration/README.md (100%) rename {radis => adit_radis_shared/common}/templates/registration/activate.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/activation_complete.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/activation_complete_admin_pending.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/activation_email.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/activation_email.txt (100%) rename {radis => adit_radis_shared/common}/templates/registration/activation_email_subject.txt (100%) rename {radis => adit_radis_shared/common}/templates/registration/admin_approve.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/admin_approve_complete.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/admin_approve_complete_email.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/admin_approve_complete_email.txt (100%) rename {radis => adit_radis_shared/common}/templates/registration/admin_approve_complete_email_subject.txt (100%) rename {radis => adit_radis_shared/common}/templates/registration/admin_approve_email.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/admin_approve_email.txt (100%) rename {radis => adit_radis_shared/common}/templates/registration/admin_approve_email_subject.txt (100%) rename {radis => adit_radis_shared/common}/templates/registration/login.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/logout.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/password_change_done.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/password_change_form.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/password_reset_complete.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/password_reset_confirm.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/password_reset_done.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/password_reset_email.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/password_reset_form.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/registration_base.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/registration_closed.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/registration_complete.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/registration_form.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/resend_activation_complete.html (100%) rename {radis => adit_radis_shared/common}/templates/registration/resend_activation_form.html (100%) diff --git a/adit_radis_shared/common/apps.py b/adit_radis_shared/common/apps.py new file mode 100644 index 00000000..6524780a --- /dev/null +++ b/adit_radis_shared/common/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CommonConfig(AppConfig): + name = "adit_radis_shared.common" diff --git a/radis/static/vendor/alpine-morph.js b/adit_radis_shared/common/static/vendor/alpine-morph.js similarity index 100% rename from radis/static/vendor/alpine-morph.js rename to adit_radis_shared/common/static/vendor/alpine-morph.js diff --git a/radis/static/vendor/alpine.js b/adit_radis_shared/common/static/vendor/alpine.js similarity index 100% rename from radis/static/vendor/alpine.js rename to adit_radis_shared/common/static/vendor/alpine.js diff --git a/radis/static/vendor/bootstrap-icons.svg b/adit_radis_shared/common/static/vendor/bootstrap-icons.svg similarity index 100% rename from radis/static/vendor/bootstrap-icons.svg rename to adit_radis_shared/common/static/vendor/bootstrap-icons.svg diff --git a/radis/static/vendor/bootstrap.bundle.js b/adit_radis_shared/common/static/vendor/bootstrap.bundle.js similarity index 100% rename from radis/static/vendor/bootstrap.bundle.js rename to adit_radis_shared/common/static/vendor/bootstrap.bundle.js diff --git a/radis/static/vendor/bootstrap.bundle.js.map b/adit_radis_shared/common/static/vendor/bootstrap.bundle.js.map similarity index 100% rename from radis/static/vendor/bootstrap.bundle.js.map rename to adit_radis_shared/common/static/vendor/bootstrap.bundle.js.map diff --git a/radis/static/vendor/bootstrap.css b/adit_radis_shared/common/static/vendor/bootstrap.css similarity index 100% rename from radis/static/vendor/bootstrap.css rename to adit_radis_shared/common/static/vendor/bootstrap.css diff --git a/radis/static/vendor/bootstrap.css.map b/adit_radis_shared/common/static/vendor/bootstrap.css.map similarity index 100% rename from radis/static/vendor/bootstrap.css.map rename to adit_radis_shared/common/static/vendor/bootstrap.css.map diff --git a/radis/static/vendor/htmx-alpine-morph.js b/adit_radis_shared/common/static/vendor/htmx-alpine-morph.js similarity index 100% rename from radis/static/vendor/htmx-alpine-morph.js rename to adit_radis_shared/common/static/vendor/htmx-alpine-morph.js diff --git a/radis/static/vendor/htmx-ws.js b/adit_radis_shared/common/static/vendor/htmx-ws.js similarity index 100% rename from radis/static/vendor/htmx-ws.js rename to adit_radis_shared/common/static/vendor/htmx-ws.js diff --git a/radis/static/vendor/htmx.js b/adit_radis_shared/common/static/vendor/htmx.js similarity index 100% rename from radis/static/vendor/htmx.js rename to adit_radis_shared/common/static/vendor/htmx.js diff --git a/radis/static/vendor/popper.js b/adit_radis_shared/common/static/vendor/popper.js similarity index 100% rename from radis/static/vendor/popper.js rename to adit_radis_shared/common/static/vendor/popper.js diff --git a/radis/static/vendor/popper.js.map b/adit_radis_shared/common/static/vendor/popper.js.map similarity index 100% rename from radis/static/vendor/popper.js.map rename to adit_radis_shared/common/static/vendor/popper.js.map diff --git a/radis/templates/registration/README.md b/adit_radis_shared/common/templates/registration/README.md similarity index 100% rename from radis/templates/registration/README.md rename to adit_radis_shared/common/templates/registration/README.md diff --git a/radis/templates/registration/activate.html b/adit_radis_shared/common/templates/registration/activate.html similarity index 100% rename from radis/templates/registration/activate.html rename to adit_radis_shared/common/templates/registration/activate.html diff --git a/radis/templates/registration/activation_complete.html b/adit_radis_shared/common/templates/registration/activation_complete.html similarity index 100% rename from radis/templates/registration/activation_complete.html rename to adit_radis_shared/common/templates/registration/activation_complete.html diff --git a/radis/templates/registration/activation_complete_admin_pending.html b/adit_radis_shared/common/templates/registration/activation_complete_admin_pending.html similarity index 100% rename from radis/templates/registration/activation_complete_admin_pending.html rename to adit_radis_shared/common/templates/registration/activation_complete_admin_pending.html diff --git a/radis/templates/registration/activation_email.html b/adit_radis_shared/common/templates/registration/activation_email.html similarity index 100% rename from radis/templates/registration/activation_email.html rename to adit_radis_shared/common/templates/registration/activation_email.html diff --git a/radis/templates/registration/activation_email.txt b/adit_radis_shared/common/templates/registration/activation_email.txt similarity index 100% rename from radis/templates/registration/activation_email.txt rename to adit_radis_shared/common/templates/registration/activation_email.txt diff --git a/radis/templates/registration/activation_email_subject.txt b/adit_radis_shared/common/templates/registration/activation_email_subject.txt similarity index 100% rename from radis/templates/registration/activation_email_subject.txt rename to adit_radis_shared/common/templates/registration/activation_email_subject.txt diff --git a/radis/templates/registration/admin_approve.html b/adit_radis_shared/common/templates/registration/admin_approve.html similarity index 100% rename from radis/templates/registration/admin_approve.html rename to adit_radis_shared/common/templates/registration/admin_approve.html diff --git a/radis/templates/registration/admin_approve_complete.html b/adit_radis_shared/common/templates/registration/admin_approve_complete.html similarity index 100% rename from radis/templates/registration/admin_approve_complete.html rename to adit_radis_shared/common/templates/registration/admin_approve_complete.html diff --git a/radis/templates/registration/admin_approve_complete_email.html b/adit_radis_shared/common/templates/registration/admin_approve_complete_email.html similarity index 100% rename from radis/templates/registration/admin_approve_complete_email.html rename to adit_radis_shared/common/templates/registration/admin_approve_complete_email.html diff --git a/radis/templates/registration/admin_approve_complete_email.txt b/adit_radis_shared/common/templates/registration/admin_approve_complete_email.txt similarity index 100% rename from radis/templates/registration/admin_approve_complete_email.txt rename to adit_radis_shared/common/templates/registration/admin_approve_complete_email.txt diff --git a/radis/templates/registration/admin_approve_complete_email_subject.txt b/adit_radis_shared/common/templates/registration/admin_approve_complete_email_subject.txt similarity index 100% rename from radis/templates/registration/admin_approve_complete_email_subject.txt rename to adit_radis_shared/common/templates/registration/admin_approve_complete_email_subject.txt diff --git a/radis/templates/registration/admin_approve_email.html b/adit_radis_shared/common/templates/registration/admin_approve_email.html similarity index 100% rename from radis/templates/registration/admin_approve_email.html rename to adit_radis_shared/common/templates/registration/admin_approve_email.html diff --git a/radis/templates/registration/admin_approve_email.txt b/adit_radis_shared/common/templates/registration/admin_approve_email.txt similarity index 100% rename from radis/templates/registration/admin_approve_email.txt rename to adit_radis_shared/common/templates/registration/admin_approve_email.txt diff --git a/radis/templates/registration/admin_approve_email_subject.txt b/adit_radis_shared/common/templates/registration/admin_approve_email_subject.txt similarity index 100% rename from radis/templates/registration/admin_approve_email_subject.txt rename to adit_radis_shared/common/templates/registration/admin_approve_email_subject.txt diff --git a/radis/templates/registration/login.html b/adit_radis_shared/common/templates/registration/login.html similarity index 100% rename from radis/templates/registration/login.html rename to adit_radis_shared/common/templates/registration/login.html diff --git a/radis/templates/registration/logout.html b/adit_radis_shared/common/templates/registration/logout.html similarity index 100% rename from radis/templates/registration/logout.html rename to adit_radis_shared/common/templates/registration/logout.html diff --git a/radis/templates/registration/password_change_done.html b/adit_radis_shared/common/templates/registration/password_change_done.html similarity index 100% rename from radis/templates/registration/password_change_done.html rename to adit_radis_shared/common/templates/registration/password_change_done.html diff --git a/radis/templates/registration/password_change_form.html b/adit_radis_shared/common/templates/registration/password_change_form.html similarity index 100% rename from radis/templates/registration/password_change_form.html rename to adit_radis_shared/common/templates/registration/password_change_form.html diff --git a/radis/templates/registration/password_reset_complete.html b/adit_radis_shared/common/templates/registration/password_reset_complete.html similarity index 100% rename from radis/templates/registration/password_reset_complete.html rename to adit_radis_shared/common/templates/registration/password_reset_complete.html diff --git a/radis/templates/registration/password_reset_confirm.html b/adit_radis_shared/common/templates/registration/password_reset_confirm.html similarity index 100% rename from radis/templates/registration/password_reset_confirm.html rename to adit_radis_shared/common/templates/registration/password_reset_confirm.html diff --git a/radis/templates/registration/password_reset_done.html b/adit_radis_shared/common/templates/registration/password_reset_done.html similarity index 100% rename from radis/templates/registration/password_reset_done.html rename to adit_radis_shared/common/templates/registration/password_reset_done.html diff --git a/radis/templates/registration/password_reset_email.html b/adit_radis_shared/common/templates/registration/password_reset_email.html similarity index 100% rename from radis/templates/registration/password_reset_email.html rename to adit_radis_shared/common/templates/registration/password_reset_email.html diff --git a/radis/templates/registration/password_reset_form.html b/adit_radis_shared/common/templates/registration/password_reset_form.html similarity index 100% rename from radis/templates/registration/password_reset_form.html rename to adit_radis_shared/common/templates/registration/password_reset_form.html diff --git a/radis/templates/registration/registration_base.html b/adit_radis_shared/common/templates/registration/registration_base.html similarity index 100% rename from radis/templates/registration/registration_base.html rename to adit_radis_shared/common/templates/registration/registration_base.html diff --git a/radis/templates/registration/registration_closed.html b/adit_radis_shared/common/templates/registration/registration_closed.html similarity index 100% rename from radis/templates/registration/registration_closed.html rename to adit_radis_shared/common/templates/registration/registration_closed.html diff --git a/radis/templates/registration/registration_complete.html b/adit_radis_shared/common/templates/registration/registration_complete.html similarity index 100% rename from radis/templates/registration/registration_complete.html rename to adit_radis_shared/common/templates/registration/registration_complete.html diff --git a/radis/templates/registration/registration_form.html b/adit_radis_shared/common/templates/registration/registration_form.html similarity index 100% rename from radis/templates/registration/registration_form.html rename to adit_radis_shared/common/templates/registration/registration_form.html diff --git a/radis/templates/registration/resend_activation_complete.html b/adit_radis_shared/common/templates/registration/resend_activation_complete.html similarity index 100% rename from radis/templates/registration/resend_activation_complete.html rename to adit_radis_shared/common/templates/registration/resend_activation_complete.html diff --git a/radis/templates/registration/resend_activation_form.html b/adit_radis_shared/common/templates/registration/resend_activation_form.html similarity index 100% rename from radis/templates/registration/resend_activation_form.html rename to adit_radis_shared/common/templates/registration/resend_activation_form.html diff --git a/radis/settings/base.py b/radis/settings/base.py index e0a2aafd..c86070a6 100644 --- a/radis/settings/base.py +++ b/radis/settings/base.py @@ -46,6 +46,7 @@ INSTALLED_APPS = [ "daphne", "whitenoise.runserver_nostatic", + "adit_radis_shared.common.apps.CommonConfig", "registration", "django.contrib.admin", "django.contrib.auth", @@ -100,7 +101,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [BASE_DIR / "radis" / "templates"], + "DIRS": [], "APP_DIRS": True, "OPTIONS": { "context_processors": [ From bd55511884c8b302a0884927c3a0771fb5198928 Mon Sep 17 00:00:00 2001 From: Kai Schlamp Date: Sat, 23 Mar 2024 17:02:40 +0000 Subject: [PATCH 03/40] Moved commands to shared common app --- .../common/management}/__init__.py | 0 .../common/management/base/__init__.py | 0 .../common/management/base/celery_beat.py | 47 ++++++++++++++ .../common/management/base/celery_worker.py | 63 +++++++++++++++++++ .../management/base/hard_reset_migrations.py | 24 +++++++ .../common/management/base/send_test_mail.py | 24 +++++++ .../common}/management/base/server_command.py | 0 .../common/management/commands/__init__.py | 0 .../management/commands/create_admin.py | 0 .../commands/example_async_server.py | 2 +- .../management/commands/example_server.py | 2 +- .../management/commands/generate_cert.py | 3 +- .../common}/utils/debounce.py | 0 radis/core/management/commands/celery_beat.py | 46 +------------- .../core/management/commands/celery_worker.py | 62 +----------------- .../commands/hard_reset_migrations.py | 24 ++----- .../management/commands/send_test_mail.py | 24 ++----- 17 files changed, 178 insertions(+), 143 deletions(-) rename {radis/core/management/base => adit_radis_shared/common/management}/__init__.py (100%) create mode 100644 adit_radis_shared/common/management/base/__init__.py create mode 100644 adit_radis_shared/common/management/base/celery_beat.py create mode 100644 adit_radis_shared/common/management/base/celery_worker.py create mode 100644 adit_radis_shared/common/management/base/hard_reset_migrations.py create mode 100644 adit_radis_shared/common/management/base/send_test_mail.py rename {radis/core => adit_radis_shared/common}/management/base/server_command.py (100%) create mode 100644 adit_radis_shared/common/management/commands/__init__.py rename {radis/core => adit_radis_shared/common}/management/commands/create_admin.py (100%) rename {radis/core => adit_radis_shared/common}/management/commands/example_async_server.py (84%) rename {radis/core => adit_radis_shared/common}/management/commands/example_server.py (84%) rename {radis/core => adit_radis_shared/common}/management/commands/generate_cert.py (99%) rename {radis/core => adit_radis_shared/common}/utils/debounce.py (100%) diff --git a/radis/core/management/base/__init__.py b/adit_radis_shared/common/management/__init__.py similarity index 100% rename from radis/core/management/base/__init__.py rename to adit_radis_shared/common/management/__init__.py diff --git a/adit_radis_shared/common/management/base/__init__.py b/adit_radis_shared/common/management/base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/adit_radis_shared/common/management/base/celery_beat.py b/adit_radis_shared/common/management/base/celery_beat.py new file mode 100644 index 00000000..3a83ab6b --- /dev/null +++ b/adit_radis_shared/common/management/base/celery_beat.py @@ -0,0 +1,47 @@ +import logging +import shlex +import subprocess +from pathlib import Path +from typing import Literal + +from .server_command import ServerCommand + +logger = logging.getLogger(__name__) + + +class CeleryBeatCommand(ServerCommand): + project: Literal["adit", "radis"] + help = "Starts Celery beat scheduler" + server_name = "Celery beat scheduler" + + def __init__(self, *args, **kwargs): + self.beat_process = None + super().__init__(*args, **kwargs) + + def add_arguments(self, parser): + super().add_arguments(parser) + + # https://docs.celeryproject.org/en/stable/reference/cli.html + parser.add_argument( + "-l", + "--loglevel", + default="INFO", + help="Logging level.", + ) + + def run_server(self, **options): + folder_path = Path(f"/var/www/{self.project}/celery/") + folder_path.mkdir(parents=True, exist_ok=True) + schedule_path = folder_path / "celerybeat-schedule" + loglevel = options["loglevel"] + + # --pidfile= disables pidfile creation as we can control the process with subprocess + cmd = f"celery -A {self.project} beat -l {loglevel} -s {str(schedule_path)} --pidfile=" + + self.beat_process = subprocess.Popen(shlex.split(cmd)) + self.beat_process.wait() + + def on_shutdown(self): + assert self.beat_process + self.beat_process.terminate() + self.beat_process.wait() diff --git a/adit_radis_shared/common/management/base/celery_worker.py b/adit_radis_shared/common/management/base/celery_worker.py new file mode 100644 index 00000000..1ef4eb32 --- /dev/null +++ b/adit_radis_shared/common/management/base/celery_worker.py @@ -0,0 +1,63 @@ +import logging +import shlex +import socket +import subprocess +from typing import Literal + +from .server_command import ServerCommand + +logger = logging.getLogger(__name__) + + +class CeleryWorkerCommand(ServerCommand): + project: Literal["adit", "radis"] + help = "Starts a Celery worker" + server_name = "Celery worker" + worker_process: subprocess.Popen | None + + def __init__(self, *args, **kwargs): + self.worker_process = None + super().__init__(*args, **kwargs) + + def add_arguments(self, parser): + super().add_arguments(parser) + + # https://docs.celeryproject.org/en/stable/reference/cli.html + parser.add_argument( + "-Q", + "--queue", + required=True, + help="The celery queue.", + ) + parser.add_argument( + "-l", + "--loglevel", + default="INFO", + help="Logging level.", + ) + parser.add_argument( + "-c", + "--concurrency", + type=int, + default=0, + help="Number of child processes processing the queue (defaults to number of CPUs).", + ) + + def run_server(self, **options): + queue = options["queue"] + loglevel = options["loglevel"] + hostname = f"worker_{queue}_{socket.gethostname()}" + + cmd = f"celery -A {self.project} worker -Q {queue} -l {loglevel} -n {hostname}" + + concurrency = options["concurrency"] + if concurrency >= 1: + cmd += f" -c {concurrency}" + + self.worker_process = subprocess.Popen(shlex.split(cmd)) + self.worker_process.wait() + + def on_shutdown(self): + assert self.worker_process + self.worker_process.terminate() + self.worker_process.wait() diff --git a/adit_radis_shared/common/management/base/hard_reset_migrations.py b/adit_radis_shared/common/management/base/hard_reset_migrations.py new file mode 100644 index 00000000..998f24c0 --- /dev/null +++ b/adit_radis_shared/common/management/base/hard_reset_migrations.py @@ -0,0 +1,24 @@ +from typing import Literal + +from django.conf import settings +from django.core.management import call_command +from django.core.management.base import BaseCommand + + +class HardResetMigrationsCommand(BaseCommand): + project: Literal["adit", "radis"] + help = "Reset all migration files (dangerous!!!)." + + def handle(self, *args, **options): + migration_paths = settings.BASE_DIR.glob(f"./{self.project}/*/migrations/**/*.py") + migration_paths = [i for i in migration_paths if i.name != "__init__.py"] + for migration_path in migration_paths: + migration_path.unlink() + + pyc_paths = settings.BASE_DIR.glob("*/migrations/**/*.pyc") + for pyc_path in pyc_paths: + pyc_path.unlink() + + call_command("reset_db", "--noinput") # needs django_extensions installed + call_command("makemigrations") + call_command("migrate") diff --git a/adit_radis_shared/common/management/base/send_test_mail.py b/adit_radis_shared/common/management/base/send_test_mail.py new file mode 100644 index 00000000..32db947e --- /dev/null +++ b/adit_radis_shared/common/management/base/send_test_mail.py @@ -0,0 +1,24 @@ +from typing import Literal + +from django.conf import settings +from django.core.mail import send_mail +from django.core.management.base import BaseCommand + + +class SendTestMailCommand(BaseCommand): + project_name: Literal["ADIT", "RADIS"] + help = "Send a test mail using the provided Email settings." + + def add_arguments(self, parser): + parser.add_argument("to_address", nargs="?", type=str, default=settings.ADMINS[0][1]) + + def handle(self, *args, **options): + to_address = options["to_address"] + + send_mail( + f"[{self.project_name}] Test Mail", + f"This is a test mail sent by {self.project_name}.", + settings.SERVER_EMAIL, + [to_address], + fail_silently=False, + ) diff --git a/radis/core/management/base/server_command.py b/adit_radis_shared/common/management/base/server_command.py similarity index 100% rename from radis/core/management/base/server_command.py rename to adit_radis_shared/common/management/base/server_command.py diff --git a/adit_radis_shared/common/management/commands/__init__.py b/adit_radis_shared/common/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/radis/core/management/commands/create_admin.py b/adit_radis_shared/common/management/commands/create_admin.py similarity index 100% rename from radis/core/management/commands/create_admin.py rename to adit_radis_shared/common/management/commands/create_admin.py diff --git a/radis/core/management/commands/example_async_server.py b/adit_radis_shared/common/management/commands/example_async_server.py similarity index 84% rename from radis/core/management/commands/example_async_server.py rename to adit_radis_shared/common/management/commands/example_async_server.py index 41dfa89a..0e4feee2 100644 --- a/radis/core/management/commands/example_async_server.py +++ b/adit_radis_shared/common/management/commands/example_async_server.py @@ -1,6 +1,6 @@ import asyncio -from ..base.server_command import AsyncServerCommand +from adit_radis_shared.common.management.base.server_command import AsyncServerCommand class Command(AsyncServerCommand): diff --git a/radis/core/management/commands/example_server.py b/adit_radis_shared/common/management/commands/example_server.py similarity index 84% rename from radis/core/management/commands/example_server.py rename to adit_radis_shared/common/management/commands/example_server.py index bfbf1d9b..a770a7ce 100644 --- a/radis/core/management/commands/example_server.py +++ b/adit_radis_shared/common/management/commands/example_server.py @@ -1,6 +1,6 @@ import time -from ..base.server_command import ServerCommand +from adit_radis_shared.common.management.base.server_command import ServerCommand class Command(ServerCommand): diff --git a/radis/core/management/commands/generate_cert.py b/adit_radis_shared/common/management/commands/generate_cert.py similarity index 99% rename from radis/core/management/commands/generate_cert.py rename to adit_radis_shared/common/management/commands/generate_cert.py index ad6aa0ec..f34ce895 100644 --- a/radis/core/management/commands/generate_cert.py +++ b/adit_radis_shared/common/management/commands/generate_cert.py @@ -2,6 +2,7 @@ from datetime import datetime, timedelta from pathlib import Path +import environ from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization @@ -9,7 +10,7 @@ from cryptography.x509.oid import NameOID from django.core.management.base import BaseCommand -from radis.settings.base import env +env = environ.Env() def generate_selfsigned_cert( diff --git a/radis/core/utils/debounce.py b/adit_radis_shared/common/utils/debounce.py similarity index 100% rename from radis/core/utils/debounce.py rename to adit_radis_shared/common/utils/debounce.py diff --git a/radis/core/management/commands/celery_beat.py b/radis/core/management/commands/celery_beat.py index 155cf5ea..821f6043 100644 --- a/radis/core/management/commands/celery_beat.py +++ b/radis/core/management/commands/celery_beat.py @@ -1,45 +1,5 @@ -import logging -import shlex -import subprocess -from pathlib import Path +from adit_radis_shared.common.management.base.celery_beat import CeleryBeatCommand -from ..base.server_command import ServerCommand -logger = logging.getLogger(__name__) - - -class Command(ServerCommand): - help = "Starts Celery beat scheduler" - server_name = "Celery beat scheduler" - - def __init__(self, *args, **kwargs): - self.beat_process = None - super().__init__(*args, **kwargs) - - def add_arguments(self, parser): - super().add_arguments(parser) - - # https://docs.celeryproject.org/en/stable/reference/cli.html - parser.add_argument( - "-l", - "--loglevel", - default="INFO", - help="Logging level.", - ) - - def run_server(self, **options): - folder_path = Path("/var/www/radis/celery/") - folder_path.mkdir(parents=True, exist_ok=True) - schedule_path = folder_path / "celerybeat-schedule" - loglevel = options["loglevel"] - - # --pidfile= disables pidfile creation as we can control the process with subprocess - cmd = f"celery -A radis beat -l {loglevel} -s {str(schedule_path)} --pidfile=" - - self.beat_process = subprocess.Popen(shlex.split(cmd)) - self.beat_process.wait() - - def on_shutdown(self): - assert self.beat_process - self.beat_process.terminate() - self.beat_process.wait() +class Command(CeleryBeatCommand): + project = "radis" diff --git a/radis/core/management/commands/celery_worker.py b/radis/core/management/commands/celery_worker.py index df03ef21..632b0cdb 100644 --- a/radis/core/management/commands/celery_worker.py +++ b/radis/core/management/commands/celery_worker.py @@ -1,61 +1,5 @@ -import logging -import shlex -import socket -import subprocess +from adit_radis_shared.common.management.base.celery_worker import CeleryWorkerCommand -from ..base.server_command import ServerCommand -logger = logging.getLogger(__name__) - - -class Command(ServerCommand): - help = "Starts a Celery worker" - server_name = "Celery worker" - worker_process: subprocess.Popen | None - - def __init__(self, *args, **kwargs): - self.worker_process = None - super().__init__(*args, **kwargs) - - def add_arguments(self, parser): - super().add_arguments(parser) - - # https://docs.celeryproject.org/en/stable/reference/cli.html - parser.add_argument( - "-Q", - "--queue", - required=True, - help="The celery queue.", - ) - parser.add_argument( - "-l", - "--loglevel", - default="INFO", - help="Logging level.", - ) - parser.add_argument( - "-c", - "--concurrency", - type=int, - default=0, - help="Number of child processes processing the queue (defaults to number of CPUs).", - ) - - def run_server(self, **options): - queue = options["queue"] - loglevel = options["loglevel"] - hostname = f"worker_{queue}_{socket.gethostname()}" - - cmd = f"celery -A radis worker -Q {queue} -l {loglevel} -n {hostname}" - - concurrency = options["concurrency"] - if concurrency >= 1: - cmd += f" -c {concurrency}" - - self.worker_process = subprocess.Popen(shlex.split(cmd)) - self.worker_process.wait() - - def on_shutdown(self): - assert self.worker_process - self.worker_process.terminate() - self.worker_process.wait() +class Command(CeleryWorkerCommand): + project = "radis" diff --git a/radis/core/management/commands/hard_reset_migrations.py b/radis/core/management/commands/hard_reset_migrations.py index d211978a..261f80ea 100644 --- a/radis/core/management/commands/hard_reset_migrations.py +++ b/radis/core/management/commands/hard_reset_migrations.py @@ -1,21 +1,7 @@ -from django.conf import settings -from django.core.management import call_command -from django.core.management.base import BaseCommand +from adit_radis_shared.common.management.base.hard_reset_migrations import ( + HardResetMigrationsCommand, +) -class Command(BaseCommand): - help = "Reset all migration files (dangerous!!!)." - - def handle(self, *args, **options): - migration_paths = settings.BASE_DIR.glob("./radis/*/migrations/**/*.py") - migration_paths = [i for i in migration_paths if i.name != "__init__.py"] - for migration_path in migration_paths: - migration_path.unlink() - - pyc_paths = settings.BASE_DIR.glob("*/migrations/**/*.pyc") - for pyc_path in pyc_paths: - pyc_path.unlink() - - call_command("reset_db", "--noinput") # needs django_extensions installed - call_command("makemigrations") - call_command("migrate") +class Command(HardResetMigrationsCommand): + project = "radis" diff --git a/radis/core/management/commands/send_test_mail.py b/radis/core/management/commands/send_test_mail.py index d6d853a3..82cdb45e 100644 --- a/radis/core/management/commands/send_test_mail.py +++ b/radis/core/management/commands/send_test_mail.py @@ -1,21 +1,7 @@ -from django.conf import settings -from django.core.mail import send_mail -from django.core.management.base import BaseCommand +from adit_radis_shared.common.management.base.send_test_mail import ( + SendTestMailCommand, +) -class Command(BaseCommand): - help = "Send a test mail using the provided Email settings." - - def add_arguments(self, parser): - parser.add_argument("to_address", nargs="?", type=str, default=settings.ADMINS[0][1]) - - def handle(self, *args, **options): - to_address = options["to_address"] - - send_mail( - "[RADIS] Test Mail", - "This is a test mail sent by RADIS.", - settings.SERVER_EMAIL, - [to_address], - fail_silently=False, - ) +class Command(SendTestMailCommand): + project_name = "RADIS" From 2c97de5f7667cc22b475dc33c150cb1e0cc7ec9b Mon Sep 17 00:00:00 2001 From: Kai Schlamp Date: Sat, 23 Mar 2024 18:26:14 +0000 Subject: [PATCH 04/40] Move more stuff to shared folder --- adit_radis_shared/accounts/views.py | 2 +- .../common}/decorators.py | 0 .../common}/forms.py | 0 .../common}/mixins.py | 0 adit_radis_shared/common/models.py | 13 ++ .../core => adit_radis_shared/common}/site.py | 2 +- .../common/static/common/common.css | 91 ++++++++ .../common/static/common/common.js | 215 +++++++++++++++++ .../common/static/common}/theme.js | 0 .../common}/types.py | 0 .../common}/utils/auth_utils.py | 0 adit_radis_shared/common/views.py | 69 ++++++ .../token_authentication/tests/conftest.py | 2 +- .../integration/test_token_authentication.py | 2 +- .../token_authentication/urls.py | 2 +- .../token_authentication/views.py | 2 +- jsconfig.json | 2 +- radis/collections/apps.py | 2 +- radis/collections/filters.py | 2 +- radis/collections/models.py | 2 +- radis/collections/views.py | 4 +- radis/core/filters.py | 3 +- radis/core/models.py | 12 - radis/core/static/core/core.css | 102 -------- radis/core/static/core/core.js | 218 ------------------ radis/core/templates/core/core_layout.html | 6 +- radis/core/views.py | 63 +---- radis/notes/apps.py | 2 +- radis/notes/filters.py | 2 +- radis/notes/models.py | 2 +- radis/notes/views.py | 4 +- radis/rag/apps.py | 2 +- radis/rag/filters.py | 2 +- radis/rag/mixins.py | 2 +- radis/rag/models.py | 3 +- radis/rag/urls.py | 2 +- radis/rag/views.py | 4 +- radis/reports/models.py | 2 +- radis/sandbox/apps.py | 2 +- radis/search/apps.py | 2 +- radis/search/models.py | 2 +- radis/search/views.py | 4 +- radis/settings/base.py | 6 +- 43 files changed, 433 insertions(+), 426 deletions(-) rename {radis/core => adit_radis_shared/common}/decorators.py (100%) rename {radis/core => adit_radis_shared/common}/forms.py (100%) rename {radis/core => adit_radis_shared/common}/mixins.py (100%) create mode 100644 adit_radis_shared/common/models.py rename {radis/core => adit_radis_shared/common}/site.py (96%) create mode 100644 adit_radis_shared/common/static/common/common.css create mode 100644 adit_radis_shared/common/static/common/common.js rename {radis/core/static/core => adit_radis_shared/common/static/common}/theme.js (100%) rename {radis/core => adit_radis_shared/common}/types.py (100%) rename {radis/core => adit_radis_shared/common}/utils/auth_utils.py (100%) create mode 100644 adit_radis_shared/common/views.py diff --git a/adit_radis_shared/accounts/views.py b/adit_radis_shared/accounts/views.py index 3720fda2..0cb8e376 100644 --- a/adit_radis_shared/accounts/views.py +++ b/adit_radis_shared/accounts/views.py @@ -5,8 +5,8 @@ from django.views import View from django.views.generic import TemplateView +from adit_radis_shared.common.types import AuthenticatedHttpRequest from radis.core.http import trigger_toast -from radis.core.types import AuthenticatedHttpRequest class UserProfileView(LoginRequiredMixin, AccessMixin, TemplateView): diff --git a/radis/core/decorators.py b/adit_radis_shared/common/decorators.py similarity index 100% rename from radis/core/decorators.py rename to adit_radis_shared/common/decorators.py diff --git a/radis/core/forms.py b/adit_radis_shared/common/forms.py similarity index 100% rename from radis/core/forms.py rename to adit_radis_shared/common/forms.py diff --git a/radis/core/mixins.py b/adit_radis_shared/common/mixins.py similarity index 100% rename from radis/core/mixins.py rename to adit_radis_shared/common/mixins.py diff --git a/adit_radis_shared/common/models.py b/adit_radis_shared/common/models.py new file mode 100644 index 00000000..ea7bfddc --- /dev/null +++ b/adit_radis_shared/common/models.py @@ -0,0 +1,13 @@ +from django.db import models + + +class AppSettings(models.Model): + id: int + locked = models.BooleanField(default=False) + + class Meta: + abstract = True + + @classmethod + def get(cls): + return cls.objects.first() diff --git a/radis/core/site.py b/adit_radis_shared/common/site.py similarity index 96% rename from radis/core/site.py rename to adit_radis_shared/common/site.py index edcd1561..11478b41 100644 --- a/radis/core/site.py +++ b/adit_radis_shared/common/site.py @@ -29,7 +29,7 @@ def base_context_processor(request: HttpRequest) -> dict[str, Any]: theme_color = preferences.get("theme_color", theme_color) return { - "version": settings.RADIS_VERSION, + "version": settings.PROJECT_VERSION, "base_url": settings.BASE_URL, "support_email": settings.SUPPORT_EMAIL, "main_menu_items": main_menu_items, diff --git a/adit_radis_shared/common/static/common/common.css b/adit_radis_shared/common/static/common/common.css new file mode 100644 index 00000000..f1abf081 --- /dev/null +++ b/adit_radis_shared/common/static/common/common.css @@ -0,0 +1,91 @@ +/*** +* Bootstrap +***/ +[data-bs-theme="dark"] { + .list-group-item { + --bs-list-group-action-hover-bg: #3d4e52; + } +} + +/* No wrap of long Bootstrap tooltips. */ +.tooltip-inner { + max-width: none; +} + +/*** +* Alpine.js +***/ +[x-cloak] { + display: none !important; +} + +/*** +* Toasts panel +***/ +/* Floating panel for toast messages */ +.toasts-panel { + position: fixed; + top: 5em; + right: 1em; +} + +.toast.success svg rect { + fill: green; +} + +.toast.warning svg rect { + fill: yellow; +} + +.toast.error svg rect { + fill: red; +} + +/*** +* django_tables2 +***/ +/* Nice sort indicators in the django-table2 tables for light and dark theme */ +table.table thead th.asc.orderable { + background: url('data:image/svg+xml;utf8,') + no-repeat; + background-position: right 0.5rem center; +} + +table.table thead th.desc.orderable { + background: url('data:image/svg+xml;utf8,') + no-repeat; + background-position: right 0.5rem center; +} + +[data-bs-theme="dark"] table.table thead th.asc.orderable { + background: url('data:image/svg+xml;utf8,') + no-repeat; + background-position: right 0.5rem center; +} + +[data-bs-theme="dark"] table.table thead th.desc.orderable { + background: url('data:image/svg+xml;utf8,') + no-repeat; + background-position: right 0.5rem center; +} + +table.table thead th.orderable { + padding-right: 2rem; + white-space: nowrap; +} + +/*** +* Misc +***/ +/* Place caption of tables on the top. */ +caption { + caption-side: top; +} + +/* When maintenance mode is active. */ +.maintenance-hint { + background-color: orange; + padding: 4px; + font-size: 1.5rem; + text-align: center; +} diff --git a/adit_radis_shared/common/static/common/common.js b/adit_radis_shared/common/static/common/common.js new file mode 100644 index 00000000..af6908a8 --- /dev/null +++ b/adit_radis_shared/common/static/common/common.js @@ -0,0 +1,215 @@ +"use strict"; + +/** + * Execute a function when the DOM is ready. + * @param {EventListener} fn + * @returns {void} + */ +function ready(fn) { + if (document.readyState !== "loading") { + fn(new Event("DOMContentLoaded")); + return; + } + document.addEventListener("DOMContentLoaded", fn); +} + +/** + * Initialize Bootstrap stuff. + */ +ready(function () { + // Enable Bootstrap tooltips + const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]'); + for (const tooltip of tooltips) { + new bootstrap.Tooltip(tooltip); + } + + // Manage the Bootstrap modal when using HTMX + // Based on https://blog.benoitblanchon.fr/django-htmx-modal-form/ + htmx.on("htmx:beforeSwap", (e) => { + // Check if the modal should be static + let staticModal = false; + if (e.detail.target.id == "htmx-dialog" && e.detail.xhr.response) { + const doc = new DOMParser().parseFromString( + e.detail.xhr.response, + "text/html" + ); + if ( + doc.querySelector(".modal-content").hasAttribute("data-dialog-static") + ) { + staticModal = true; + } + } + + const modal = bootstrap.Modal.getOrCreateInstance("#htmx-modal", { + backdrop: staticModal ? "static" : true, + }); + + // An empty response targeting the #dialog does hide the modal + if (e.detail.target.id == "htmx-dialog" && !e.detail.xhr.response) { + modal.hide(); + e.detail.shouldSwap = false; + } + }); + htmx.on("htmx:afterSwap", (e) => { + const modal = bootstrap.Modal.getInstance("#htmx-modal"); + const modalEl = document.getElementById("htmx-modal"); + modalEl.addEventListener("shown.bs.modal", (event) => { + /** @type {HTMLInputElement} */ + const inputEl = modalEl.querySelector("input:not([type=hidden])"); + if (inputEl) { + inputEl.focus(); + } + + if (!inputEl) { + /** @type {HTMLTextAreaElement} */ + const textareaEl = modalEl.querySelector("textarea:not([type=hidden])"); + if (textareaEl) { + textareaEl.focus(); + setTimeout(function () { + textareaEl.selectionStart = textareaEl.selectionEnd = 10000; + }, 0); + } + } + }); + + if (e.detail.target.id == "htmx-dialog") { + modal.show(); + } + }); + htmx.on("#htmx-modal", "hidden.bs.modal", () => { + // Reset the dialog after it was closed + document.getElementById("htmx-dialog").innerHTML = ""; + + // Explicitly dispose it that next time it can be recreated + // static or non static dynamically + bootstrap.Modal.getInstance("#htmx-modal").dispose(); + }); + + // Allow to trigger toasts from HTMX responses using HX-Trigger + // (e.g. by using django_htmx.http.trigger_client_event) + document.body.addEventListener("toast", (/** @type {CustomEvent} */ e) => { + const { level, title, text } = e.detail; + showToast(level, title, text); + }); +}); + +/** + * Get the app config from the DOM that was added by the Django context processor: + * adit_radis_shared.common.site.base_context_processor (public key) + * @returns {object} config + */ +function getConfig() { + const configNode = document.getElementById("public"); + if (!configNode || !configNode.textContent) { + throw new Error("Missing app config."); + } + return JSON.parse(configNode.textContent); +} + +/** + * Update user preferences on the server (sends an AJAX request to the server). + * @param {string} route + * @param {object} data + * @returns {void} + */ +function updatePreferences(route, data) { + const formData = new FormData(); + for (const key in data) { + formData.append(key, data[key]); + } + + let url; + if (route) { + url = `/${route}/update-preferences/`; + } else { + url = "/update-preferences/"; + } + + const config = getConfig(); + const request = new Request(url, { + method: "POST", + headers: { "X-CSRFToken": config.csrf_token }, + mode: "same-origin", // Do not send CSRF token to another domain. + body: formData, + }); + + fetch(request).then(function () { + const config = getConfig(); + if (config.debug) { + console.log("Saved properties to session", data); + } + }); +} + +/** + * Show a new toast (put on top of the toasts stack). + * @param {("success"|"warning"|"error")} level + * @param {string} title + * @param {string} text + * @returns {void} + */ +function showToast(level, title, text) { + window.dispatchEvent( + new CustomEvent("core:add-toast", { + detail: { + level: level, + title: title, + text: text, + }, + }) + ); +} + +/** + * Alpine data model for the toasts panel. + * @param {HTMLElement} panelEl + * @returns {object} Alpine data model + */ +function toastsPanel(panelEl) { + /** + * Helper function to capitalize a string + * @param {string} str + * @returns {string} + */ + function capitalize(str) { + if (typeof str !== "string") return ""; + return str.charAt(0).toUpperCase() + str.slice(1); + } + + return { + options: { + duration: 30000, // 30 seconds + }, + /** + * An array of toasts created by the client. + * @type{Array} + * @property {("success"|"warning"|"error")} level + * @property {string} title + * @property {string} text + */ + toasts: [], + /** + * Called for every new toast to initialize it. + * @param {HTMLElement} toastEl + * @returns {void} + */ + initToast: function (toastEl) { + new bootstrap.Toast(toastEl, { + delay: this.options.duration, + }).show(); + }, + /** + * Add a toast to the toasts list. Call the showToast function above to + * add toasts to the list from another function or script. + * @param {object} toast + * @param {("success"|"warning"|"error")} toast.level + * @param {string} toast.title + * @param {string} toast.text + * @returns {void} + */ + addToast: function (toast) { + toast.title = capitalize(toast.title); + this.toasts.push(toast); + }, + }; +} diff --git a/radis/core/static/core/theme.js b/adit_radis_shared/common/static/common/theme.js similarity index 100% rename from radis/core/static/core/theme.js rename to adit_radis_shared/common/static/common/theme.js diff --git a/radis/core/types.py b/adit_radis_shared/common/types.py similarity index 100% rename from radis/core/types.py rename to adit_radis_shared/common/types.py diff --git a/radis/core/utils/auth_utils.py b/adit_radis_shared/common/utils/auth_utils.py similarity index 100% rename from radis/core/utils/auth_utils.py rename to adit_radis_shared/common/utils/auth_utils.py diff --git a/adit_radis_shared/common/views.py b/adit_radis_shared/common/views.py new file mode 100644 index 00000000..6b99662b --- /dev/null +++ b/adit_radis_shared/common/views.py @@ -0,0 +1,69 @@ +from typing import Any + +from django.contrib.auth.mixins import ( + LoginRequiredMixin, + UserPassesTestMixin, +) +from django.core.exceptions import SuspiciousOperation +from django.http import HttpResponse +from django.urls import re_path +from django.views.generic import View +from django.views.generic.base import TemplateView +from revproxy.views import ProxyView + +from .types import AuthenticatedHttpRequest, HtmxHttpRequest + + +class HtmxTemplateView(TemplateView): + def get(self, request: HtmxHttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: + if not request.htmx: + raise SuspiciousOperation + return super().get(request, *args, **kwargs) + + +class BaseUpdatePreferencesView(LoginRequiredMixin, View): + """Allows the client to update the user preferences. + + We use this to retain some form state between browser refreshes. + The implementations of this view is called by some AJAX requests when specific + form fields are changed. + """ + + allowed_keys: list[str] + + def post(self, request: AuthenticatedHttpRequest) -> HttpResponse: + for key in request.POST.keys(): + if key not in self.allowed_keys: + raise SuspiciousOperation(f'Invalid preference "{key}" to update.') + + preferences = request.user.preferences + + for key, value in request.POST.items(): + if value == "true": + value = True + elif value == "false": + value = False + + preferences[key] = value + + request.user.save() + + return HttpResponse() + + +class AdminProxyView(LoginRequiredMixin, UserPassesTestMixin, ProxyView): + """A reverse proxy view to hide other services behind that only an admin can access. + + By using a reverse proxy we can use the Django authentication + to check for an logged in admin user. + Code from https://stackoverflow.com/a/61997024/166229 + """ + + request: AuthenticatedHttpRequest + + def test_func(self): + return self.request.user.is_staff + + @classmethod + def as_url(cls): + return re_path(rf"^{cls.url_prefix}/(?P.*)$", cls.as_view()) # type: ignore diff --git a/adit_radis_shared/token_authentication/tests/conftest.py b/adit_radis_shared/token_authentication/tests/conftest.py index aa9988ba..ec4cd495 100644 --- a/adit_radis_shared/token_authentication/tests/conftest.py +++ b/adit_radis_shared/token_authentication/tests/conftest.py @@ -1,7 +1,7 @@ import pytest from adit_radis_shared.accounts.factories import GroupFactory -from radis.core.utils.auth_utils import add_permission +from adit_radis_shared.common.utils.auth_utils import add_permission @pytest.fixture diff --git a/adit_radis_shared/token_authentication/tests/integration/test_token_authentication.py b/adit_radis_shared/token_authentication/tests/integration/test_token_authentication.py index af17ec59..bd94fcd2 100644 --- a/adit_radis_shared/token_authentication/tests/integration/test_token_authentication.py +++ b/adit_radis_shared/token_authentication/tests/integration/test_token_authentication.py @@ -2,7 +2,7 @@ import requests from playwright.sync_api import Page, expect -from radis.core.utils.auth_utils import add_user_to_group +from adit_radis_shared.common.utils.auth_utils import add_user_to_group @pytest.mark.integration diff --git a/adit_radis_shared/token_authentication/urls.py b/adit_radis_shared/token_authentication/urls.py index 27a8e20a..864939ac 100644 --- a/adit_radis_shared/token_authentication/urls.py +++ b/adit_radis_shared/token_authentication/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from radis.core.views import HtmxTemplateView +from adit_radis_shared.common.views import HtmxTemplateView from .views import DeleteTokenView, TestView, TokenDashboardView diff --git a/adit_radis_shared/token_authentication/views.py b/adit_radis_shared/token_authentication/views.py index d02e1855..efe2efb4 100644 --- a/adit_radis_shared/token_authentication/views.py +++ b/adit_radis_shared/token_authentication/views.py @@ -8,7 +8,7 @@ from rest_framework.response import Response from rest_framework.views import APIView -from radis.core.types import AuthenticatedHttpRequest +from adit_radis_shared.common.types import AuthenticatedHttpRequest from .forms import GenerateTokenForm from .models import Token diff --git a/jsconfig.json b/jsconfig.json index 1704fdf2..079349d2 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -3,5 +3,5 @@ "checkJs": true, "target": "ES6" }, - "include": ["./globals.d.ts", "./radis/*/static/**/*.js"] + "include": ["./globals.d.ts", "./**/static/**/*.js"] } diff --git a/radis/collections/apps.py b/radis/collections/apps.py index 07500b3d..bc1fe029 100644 --- a/radis/collections/apps.py +++ b/radis/collections/apps.py @@ -13,7 +13,7 @@ def ready(self): def register_app(): - from radis.core.site import register_main_menu_item + from adit_radis_shared.common.site import register_main_menu_item from radis.reports.site import register_report_panel_button register_main_menu_item( diff --git a/radis/collections/filters.py b/radis/collections/filters.py index 9b8bf309..c16183b4 100644 --- a/radis/collections/filters.py +++ b/radis/collections/filters.py @@ -1,6 +1,6 @@ import django_filters -from radis.core.forms import FilterSetFormHelper +from adit_radis_shared.common.forms import FilterSetFormHelper from .models import Collection diff --git a/radis/collections/models.py b/radis/collections/models.py index d671a1a7..0426c29b 100644 --- a/radis/collections/models.py +++ b/radis/collections/models.py @@ -2,7 +2,7 @@ from django.db import models from django.db.models.constraints import UniqueConstraint -from radis.core.models import AppSettings +from adit_radis_shared.common.models import AppSettings from radis.reports.models import Report diff --git a/radis/collections/views.py b/radis/collections/views.py index d51f27d3..3726dae6 100644 --- a/radis/collections/views.py +++ b/radis/collections/views.py @@ -14,8 +14,8 @@ from django_htmx.http import HttpResponseClientRefresh, trigger_client_event from django_tables2 import SingleTableView -from radis.core.mixins import PageSizeSelectMixin -from radis.core.types import AuthenticatedHttpRequest +from adit_radis_shared.common.mixins import PageSizeSelectMixin +from adit_radis_shared.common.types import AuthenticatedHttpRequest from radis.reports.models import Report from .filters import CollectionFilter diff --git a/radis/core/filters.py b/radis/core/filters.py index 15d0c4b5..d6b5f100 100644 --- a/radis/core/filters.py +++ b/radis/core/filters.py @@ -1,7 +1,8 @@ import django_filters from django.http import HttpRequest -from .forms import FilterSetFormHelper +from adit_radis_shared.common.forms import FilterSetFormHelper + from .models import AnalysisJob, AnalysisTask diff --git a/radis/core/models.py b/radis/core/models.py index 1ea862c3..78725b41 100644 --- a/radis/core/models.py +++ b/radis/core/models.py @@ -26,18 +26,6 @@ def get(cls): return cls.objects.first() -class AppSettings(models.Model): - id: int - locked = models.BooleanField(default=False) - - class Meta: - abstract = True - - @classmethod - def get(cls): - return cls.objects.first() - - class AnalysisJob(models.Model): class Status(models.TextChoices): UNVERIFIED = "UV", "Unverified" diff --git a/radis/core/static/core/core.css b/radis/core/static/core/core.css index bb99d70d..699ed320 100644 --- a/radis/core/static/core/core.css +++ b/radis/core/static/core/core.css @@ -1,40 +1,3 @@ -/*** -* Bootstrap -***/ -[data-bs-theme="dark"] { - .list-group-item { - --bs-list-group-action-hover-bg: #3d4e52; - } -} - -/* No wrap of long Bootstrap tooltips. */ -.tooltip-inner { - max-width: none; -} - -/*** -* Misc -***/ - -/* Place caption on tables on the top. */ -caption { - caption-side: top; -} - -/* Shows that you get more info when hovering it. */ -.info-hint { - border-bottom: 1px dotted black; - cursor: help; -} - -/* When maintenance mode is active. */ -.maintenance-hint { - background-color: orange; - padding: 4px; - font-size: 1.5rem; - text-align: center; -} - .search-summary { white-space: normal; } @@ -57,68 +20,3 @@ caption { -webkit-line-clamp: 3; -webkit-box-orient: vertical; } - -/*** -* Alpine.js -***/ - -[x-cloak] { - display: none !important; -} - -/*** -* Toasts panel -***/ - -/* Floating panel for toast messages */ -.toasts-panel { - position: fixed; - top: 5em; - right: 1em; -} - -.toast.success svg rect { - fill: green; -} - -.toast.warning svg rect { - fill: yellow; -} - -.toast.error svg rect { - fill: red; -} - -/*** -* django_tables2 -***/ - -/* Nice sort indicators in the django-table2 tables for light and dark theme */ -table.table thead th.asc.orderable { - background: url('data:image/svg+xml;utf8,') - no-repeat; - background-position: right 0.5rem center; -} - -table.table thead th.desc.orderable { - background: url('data:image/svg+xml;utf8,') - no-repeat; - background-position: right 0.5rem center; -} - -[data-bs-theme="dark"] table.table thead th.asc.orderable { - background: url('data:image/svg+xml;utf8,') - no-repeat; - background-position: right 0.5rem center; -} - -[data-bs-theme="dark"] table.table thead th.desc.orderable { - background: url('data:image/svg+xml;utf8,') - no-repeat; - background-position: right 0.5rem center; -} - -table.table thead th.orderable { - padding-right: 2rem; - white-space: nowrap; -} diff --git a/radis/core/static/core/core.js b/radis/core/static/core/core.js index 6b2ee50f..e69de29b 100644 --- a/radis/core/static/core/core.js +++ b/radis/core/static/core/core.js @@ -1,218 +0,0 @@ -"use strict"; - -/** - * Execute a function when the DOM is ready. - * @param {Function} fn - * @returns {void} - */ -function ready(fn) { - if (document.readyState !== "loading") { - fn(); - return; - } - // @ts-ignore - document.addEventListener("DOMContentLoaded", fn); -} - -/** - * Initialize Bootstrap stuff. - */ -ready(function () { - // Enable Bootstrap tooltips - const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]'); - for (const tooltip of tooltips) { - new bootstrap.Tooltip(tooltip); - } - - // Manage the Bootstrap modal when using HTMX - // Based on https://blog.benoitblanchon.fr/django-htmx-modal-form/ - htmx.on("htmx:beforeSwap", (e) => { - // Check if the modal should be static - let staticModal = false; - if (e.detail.target.id == "htmx-dialog" && e.detail.xhr.response) { - const doc = new DOMParser().parseFromString( - e.detail.xhr.response, - "text/html" - ); - if ( - doc.querySelector(".modal-content").hasAttribute("data-dialog-static") - ) { - staticModal = true; - } - } - - const modal = bootstrap.Modal.getOrCreateInstance("#htmx-modal", { - backdrop: staticModal ? "static" : true, - }); - - // An empty response targeting the #dialog does hide the modal - if (e.detail.target.id == "htmx-dialog" && !e.detail.xhr.response) { - modal.hide(); - e.detail.shouldSwap = false; - } - }); - htmx.on("htmx:afterSwap", (e) => { - const modal = bootstrap.Modal.getInstance("#htmx-modal"); - const modalEl = document.getElementById("htmx-modal"); - modalEl.addEventListener("shown.bs.modal", (event) => { - const inputEl = modalEl.querySelector("input:not([type=hidden])"); - if (inputEl) { - // @ts-ignore - inputEl.focus(); - } - - if (!inputEl) { - const textareaEl = modalEl.querySelector("textarea:not([type=hidden])"); - if (textareaEl) { - // @ts-ignore - textareaEl.focus(); - setTimeout(function () { - // @ts-ignore - textareaEl.selectionStart = textareaEl.selectionEnd = 10000; - }, 0); - } - } - }); - - if (e.detail.target.id == "htmx-dialog") { - modal.show(); - } - }); - htmx.on("#htmx-modal", "hidden.bs.modal", () => { - // Reset the dialog after it was closed - document.getElementById("htmx-dialog").innerHTML = ""; - - // Explicitly dispose it that next time it can be recreated - // static or non static dynamically - bootstrap.Modal.getInstance("#htmx-modal").dispose(); - }); - - // Allow to trigger toasts from HTMX responses using HX-Trigger - // (e.g. by using django_htmx.http.trigger_client_event) - document.body.addEventListener("toast", (e) => { - // @ts-ignore - const { level, title, text } = e.detail; - showToast(level, title, text); - }); -}); - -/** - * Get the app config from the DOM that was added by the Django context processor: - * radis.core.site.base_context_processor (public key) - * @returns {object} config - */ -function getConfig() { - const configNode = document.getElementById("public"); - if (!configNode || !configNode.textContent) { - throw new Error("Missing app config."); - } - return JSON.parse(configNode.textContent); -} - -/** - * Update user preferences on the server (sends an AJAX request to the server). - * @param {string} route - * @param {object} data - * @returns {void} - */ -function updatePreferences(route, data) { - const formData = new FormData(); - for (const key in data) { - formData.append(key, data[key]); - } - - let url; - if (route) { - url = `/${route}/update-preferences/`; - } else { - url = "/update-preferences/"; - } - - const config = getConfig(); - const request = new Request(url, { - method: "POST", - headers: { "X-CSRFToken": config.csrf_token }, - mode: "same-origin", // Do not send CSRF token to another domain. - body: formData, - }); - - fetch(request).then(function () { - const config = getConfig(); - if (config.debug) { - console.log("Saved properties to session", data); - } - }); -} - -/** - * Show a new toast (put on top of the toasts stack). - * @param {("success"|"warning"|"error")} level - * @param {string} title - * @param {string} text - * @returns {void} - */ -function showToast(level, title, text) { - window.dispatchEvent( - new CustomEvent("core:add-toast", { - detail: { - level: level, - title: title, - text: text, - }, - }) - ); -} - -/** - * Alpine data model for the toasts panel. - * @param {HTMLElement} panelEl - * @returns {object} Alpine data model - */ -function toastsPanel(panelEl) { - /** - * Helper function to capitalize a string - * @param {string} str - * @returns {string} - */ - function capitalize(str) { - if (typeof str !== "string") return ""; - return str.charAt(0).toUpperCase() + str.slice(1); - } - - return { - options: { - duration: 30000, // 30 seconds - }, - /** - * An array of toasts created by the client. - * @type{Array} - * @property {("success"|"warning"|"error")} level - * @property {string} title - * @property {string} text - */ - toasts: [], - /** - * Called for every new toast to initialize it. - * @param {HTMLElement} toastEl - * @returns {void} - */ - initToast: function (toastEl) { - new bootstrap.Toast(toastEl, { - delay: this.options.duration, - }).show(); - }, - /** - * Add a toast to the toasts list. Call the showToast function above to - * add toasts to the list from another function or script. - * @param {object} toast - * @param {("success"|"warning"|"error")} toast.level - * @param {string} toast.title - * @param {string} toast.text - * @returns {void} - */ - addToast: function (toast) { - toast.title = capitalize(toast.title); - this.toasts.push(toast); - }, - }; -} diff --git a/radis/core/templates/core/core_layout.html b/radis/core/templates/core/core_layout.html index fa1ec136..79fa857e 100644 --- a/radis/core/templates/core/core_layout.html +++ b/radis/core/templates/core/core_layout.html @@ -20,7 +20,7 @@ {% block meta %} {% endblock meta %} {# We put the theme JavaScript stuff on top to avoid flickering #} - + {# Favicon #} + {% block css %} {% endblock css %} @@ -66,6 +69,7 @@ {# Our Javascript stuff #} {{ public|json_script:"public" }} + {% block script %} {% endblock script %} diff --git a/radis/core/views.py b/radis/core/views.py index f7c6cfe8..cefccfcd 100644 --- a/radis/core/views.py +++ b/radis/core/views.py @@ -22,16 +22,16 @@ from django_filters.filterset import FilterSet from django_filters.views import FilterView from django_tables2 import SingleTableMixin, Table -from revproxy.views import ProxyView +from adit_radis_shared.common.forms import BroadcastForm +from adit_radis_shared.common.mixins import PageSizeSelectMixin, RelatedFilterMixin +from adit_radis_shared.common.types import AuthenticatedHttpRequest +from adit_radis_shared.common.views import AdminProxyView, BaseUpdatePreferencesView from radis.celery import app as celery_app from radis.core.utils.model_utils import reset_tasks -from .forms import BroadcastForm -from .mixins import PageSizeSelectMixin, RelatedFilterMixin from .models import AnalysisJob, AnalysisTask, CoreSettings from .tasks import broadcast_mail -from .types import AuthenticatedHttpRequest, HtmxHttpRequest THEME = "theme" @@ -41,13 +41,6 @@ def admin_section(request: HttpRequest) -> HttpResponse: return render(request, "core/admin_section.html") -class HtmxTemplateView(TemplateView): - def get(self, request: HtmxHttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: - if not request.htmx: - raise SuspiciousOperation - return super().get(request, *args, **kwargs) - - class BroadcastView(LoginRequiredMixin, UserPassesTestMixin, FormView): template_name = "core/broadcast.html" form_class = BroadcastForm @@ -83,36 +76,6 @@ def get_context_data(self, **kwargs) -> dict[str, Any]: return context -class BaseUpdatePreferencesView(LoginRequiredMixin, View): - """Allows the client to update the user preferences. - - We use this to retain some form state between browser refreshes. - The implementations of this view is called by some AJAX requests when specific - form fields are changed. - """ - - allowed_keys: list[str] - - def post(self, request: AuthenticatedHttpRequest) -> HttpResponse: - for key in request.POST.keys(): - if key not in self.allowed_keys: - raise SuspiciousOperation(f'Invalid preference "{key}" to update.') - - preferences = request.user.preferences - - for key, value in request.POST.items(): - if value == "true": - value = True - elif value == "false": - value = False - - preferences[key] = value - - request.user.save() - - return HttpResponse() - - class UpdatePreferencesView(BaseUpdatePreferencesView): allowed_keys = [THEME] @@ -446,24 +409,6 @@ def post(self, request: AuthenticatedHttpRequest, *args, **kwargs) -> HttpRespon return redirect(task) -class AdminProxyView(LoginRequiredMixin, UserPassesTestMixin, ProxyView): - """A reverse proxy view to hide other services behind that only an admin can access. - - By using a reverse proxy we can use the Django authentication - to check for an logged in admin user. - Code from https://stackoverflow.com/a/61997024/166229 - """ - - request: AuthenticatedHttpRequest - - def test_func(self): - return self.request.user.is_staff - - @classmethod - def as_url(cls): - return re_path(rf"^{cls.url_prefix}/(?P.*)$", cls.as_view()) # type: ignore - - class FlowerProxyView(AdminProxyView): upstream = f"http://{settings.FLOWER_HOST}:{settings.FLOWER_PORT}" # type: ignore url_prefix = "flower" diff --git a/radis/notes/apps.py b/radis/notes/apps.py index 594cf444..9a5a85c9 100644 --- a/radis/notes/apps.py +++ b/radis/notes/apps.py @@ -13,7 +13,7 @@ def ready(self): def register_app(): - from radis.core.site import register_main_menu_item + from adit_radis_shared.common.site import register_main_menu_item from radis.reports.site import register_report_panel_button register_main_menu_item( diff --git a/radis/notes/filters.py b/radis/notes/filters.py index 63204c4d..9d646f01 100644 --- a/radis/notes/filters.py +++ b/radis/notes/filters.py @@ -1,6 +1,6 @@ import django_filters -from radis.core.forms import FilterSetFormHelper +from adit_radis_shared.common.forms import FilterSetFormHelper from .models import Note diff --git a/radis/notes/models.py b/radis/notes/models.py index f91816c5..c5673d10 100644 --- a/radis/notes/models.py +++ b/radis/notes/models.py @@ -2,7 +2,7 @@ from django.db import models from django.db.models.constraints import UniqueConstraint -from radis.core.models import AppSettings +from adit_radis_shared.common.models import AppSettings from radis.reports.models import Report diff --git a/radis/notes/views.py b/radis/notes/views.py index 7043b7fb..207604da 100644 --- a/radis/notes/views.py +++ b/radis/notes/views.py @@ -8,8 +8,8 @@ from django_filters.views import FilterView from django_htmx.http import HttpResponseClientRefresh, trigger_client_event -from radis.core.mixins import HtmxOnlyMixin, PageSizeSelectMixin -from radis.core.types import AuthenticatedHttpRequest +from adit_radis_shared.common.mixins import HtmxOnlyMixin, PageSizeSelectMixin +from adit_radis_shared.common.types import AuthenticatedHttpRequest from radis.notes.filters import NoteFilter from radis.notes.forms import NoteEditForm from radis.notes.models import Note diff --git a/radis/rag/apps.py b/radis/rag/apps.py index 2d1560b7..d4f88a76 100644 --- a/radis/rag/apps.py +++ b/radis/rag/apps.py @@ -15,7 +15,7 @@ def ready(self): def register_app(): - from radis.core.site import register_main_menu_item + from adit_radis_shared.common.site import register_main_menu_item register_main_menu_item( url_name="rag_job_create", diff --git a/radis/rag/filters.py b/radis/rag/filters.py index c28a07a4..b0bdc765 100644 --- a/radis/rag/filters.py +++ b/radis/rag/filters.py @@ -1,8 +1,8 @@ import django_filters from django.http import HttpRequest +from adit_radis_shared.common.forms import FilterSetFormHelper from radis.core.filters import AnalysisJobFilter, AnalysisTaskFilter -from radis.core.forms import FilterSetFormHelper from .models import RagJob, RagTask diff --git a/radis/rag/mixins.py b/radis/rag/mixins.py index 668e88d7..633e8e13 100644 --- a/radis/rag/mixins.py +++ b/radis/rag/mixins.py @@ -1,4 +1,4 @@ -from radis.core.mixins import LockedMixin +from adit_radis_shared.common.mixins import LockedMixin from .apps import SECTION_NAME from .models import RagAppSettings diff --git a/radis/rag/models.py b/radis/rag/models.py index 57685cb2..33e701b8 100644 --- a/radis/rag/models.py +++ b/radis/rag/models.py @@ -7,7 +7,8 @@ from django.urls import reverse from django.utils.functional import lazy -from radis.core.models import AnalysisJob, AnalysisTask, AppSettings +from adit_radis_shared.common.models import AppSettings +from radis.core.models import AnalysisJob, AnalysisTask from radis.reports.models import Report from .site import retrieval_providers diff --git a/radis/rag/urls.py b/radis/rag/urls.py index e3a4ab8e..b7509c09 100644 --- a/radis/rag/urls.py +++ b/radis/rag/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from radis.core.views import HtmxTemplateView +from adit_radis_shared.common.views import HtmxTemplateView from .views import ( RagJobCancelView, diff --git a/radis/rag/views.py b/radis/rag/views.py index 61a5c573..1323c78a 100644 --- a/radis/rag/views.py +++ b/radis/rag/views.py @@ -10,8 +10,8 @@ from django.views.generic import DetailView from formtools.wizard.views import SessionWizardView -from radis.core.mixins import PageSizeSelectMixin, RelatedFilterMixin -from radis.core.types import AuthenticatedHttpRequest +from adit_radis_shared.common.mixins import PageSizeSelectMixin, RelatedFilterMixin +from adit_radis_shared.common.types import AuthenticatedHttpRequest from radis.core.views import ( AnalysisJobCancelView, AnalysisJobDeleteView, diff --git a/radis/reports/models.py b/radis/reports/models.py index 7fb5a7fd..92c757d3 100644 --- a/radis/reports/models.py +++ b/radis/reports/models.py @@ -4,7 +4,7 @@ from django.contrib.postgres.fields import ArrayField from django.db import models -from radis.core.models import AppSettings +from adit_radis_shared.common.models import AppSettings from radis.core.utils.date_utils import calculate_age from radis.core.validators import ( no_backslash_char_validator, diff --git a/radis/sandbox/apps.py b/radis/sandbox/apps.py index 4e8c1f8d..d9fa2d48 100644 --- a/radis/sandbox/apps.py +++ b/radis/sandbox/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -from radis.core.site import register_main_menu_item +from adit_radis_shared.common.site import register_main_menu_item SECTION_NAME = "Sandbox" diff --git a/radis/search/apps.py b/radis/search/apps.py index 5cffa968..6425f18f 100644 --- a/radis/search/apps.py +++ b/radis/search/apps.py @@ -13,7 +13,7 @@ def ready(self): def register_app(): - from radis.core.site import register_main_menu_item + from adit_radis_shared.common.site import register_main_menu_item register_main_menu_item( url_name="search", diff --git a/radis/search/models.py b/radis/search/models.py index 81dcb8d9..0f3b029f 100644 --- a/radis/search/models.py +++ b/radis/search/models.py @@ -1,6 +1,6 @@ import logging -from radis.core.models import AppSettings +from adit_radis_shared.common.models import AppSettings logger = logging.getLogger(__name__) diff --git a/radis/search/views.py b/radis/search/views.py index 4da07da0..3e326805 100644 --- a/radis/search/views.py +++ b/radis/search/views.py @@ -7,8 +7,8 @@ from django.shortcuts import render from django.views import View -from radis.core.mixins import HtmxOnlyMixin -from radis.core.types import AuthenticatedApiRequest, AuthenticatedHttpRequest +from adit_radis_shared.common.mixins import HtmxOnlyMixin +from adit_radis_shared.common.types import AuthenticatedApiRequest, AuthenticatedHttpRequest from radis.search.forms import SearchForm from .site import Search, SearchFilters, search_providers diff --git a/radis/settings/base.py b/radis/settings/base.py index c86070a6..81e81565 100644 --- a/radis/settings/base.py +++ b/radis/settings/base.py @@ -25,9 +25,9 @@ # the file is not present. if (BASE_DIR / "pyproject.toml").exists(): pyproject = toml.load(BASE_DIR / "pyproject.toml") - RADIS_VERSION = pyproject["tool"]["poetry"]["version"] + PROJECT_VERSION = pyproject["tool"]["poetry"]["version"] else: - RADIS_VERSION = "???" + PROJECT_VERSION = "???" READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False) # type: ignore if READ_DOT_ENV_FILE: @@ -109,7 +109,7 @@ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", - "radis.core.site.base_context_processor", + "adit_radis_shared.common.site.base_context_processor", "radis.reports.site.base_context_processor", ], }, From dd9fbb69d4c67686ad46a190ec12b8a109275c54 Mon Sep 17 00:00:00 2001 From: Kai Schlamp Date: Sat, 23 Mar 2024 23:59:45 +0000 Subject: [PATCH 05/40] Move even more stuff to shared folder (inclusive templates) --- adit_radis_shared/accounts/factories.py | 13 +-- adit_radis_shared/accounts/views.py | 2 +- .../common}/factories.py | 0 adit_radis_shared/common/forms.py | 2 +- adit_radis_shared/common/middlewares.py | 63 +++++++++++ adit_radis_shared/common/mixins.py | 2 +- adit_radis_shared/common/models.py | 16 +++ adit_radis_shared/common/site.py | 6 +- .../common/static/common/common.js | 22 +--- .../common/static/common/theme.js | 7 +- .../templates/common}/_alert_icons.html | 2 +- .../templates/common}/_bootstrap_icon.html | 0 .../templates/common}/_filter_set_field.html | 0 .../templates/common}/_messages_panel.html | 4 +- .../common/templates/common}/_navbar.html | 6 +- .../templates/common}/_navbar_actions.html | 6 +- .../templates/common}/_navbar_links.html | 0 .../common}/_navbar_theme_switcher.html | 2 +- .../common}/_navbar_user_manager.html | 2 +- .../common/templates/common}/_pagination.html | 0 .../templates/common}/_theme_icons.html | 2 +- .../templates/common}/_toasts_panel.html | 4 +- .../templates/common/common_layout.html | 76 +++++++++++++ .../templates/common}/section_locked.html | 2 +- .../common/templatetags/__init__.py | 0 .../common/templatetags/common_extras.py | 62 +++++++++++ .../common/utils/htmx_triggers.py | 0 .../token_authentication/factories.py | 2 +- .../token_authentication_layout.html | 1 + .../token_authentication/token_dashboard.html | 2 +- globals.d.ts | 1 + pyproject.toml | 2 +- .../collections/_collection_detail.html | 2 +- .../collections/_collection_select.html | 2 +- .../_collection_select_button.html | 2 +- .../collections/collection_detail.html | 2 +- .../collections/collection_list.html | 2 +- .../collections/collections_layout.html | 1 + radis/core/middlewares.py | 60 +---------- radis/core/models.py | 14 +-- radis/core/tables.py | 2 +- .../core/_job_detail_control_panel.html | 2 +- .../core/_job_table_control_panel.html | 2 +- .../core/_task_detail_control_panel.html | 2 +- radis/core/templates/core/admin_section.html | 2 +- radis/core/templates/core/core_layout.html | 100 ++++-------------- radis/core/templatetags/core_extras.py | 55 ---------- radis/core/views.py | 5 +- radis/notes/templates/notes/_note_edit.html | 2 +- .../templates/notes/_note_edit_button.html | 2 +- radis/notes/templates/notes/note_list.html | 2 +- radis/notes/templates/notes/notes_layout.html | 1 + radis/rag/forms.py | 2 +- radis/rag/templates/rag/_rag_help.html | 2 +- radis/rag/templates/rag/rag_job_detail.html | 2 +- radis/rag/templates/rag/rag_job_list.html | 2 +- .../templates/rag/rag_job_questions_form.html | 2 +- .../templates/rag/rag_job_search_form.html | 2 +- radis/rag/templates/rag/rag_layout.html | 1 + radis/rag/templates/rag/rag_result_list.html | 4 +- radis/rag/templates/rag/rag_task_detail.html | 3 +- radis/reports/templates/reports/_links.html | 3 +- .../reports/_report_buttons_panel.html | 2 +- .../templates/reports/reports_layout.html | 1 + .../templates/search/_search_results.html | 2 +- .../search/form_elements/query_input.html | 2 +- radis/search/templates/search/search.html | 2 +- .../templates/search/search_layout.html | 1 + radis/settings/base.py | 4 +- 69 files changed, 320 insertions(+), 288 deletions(-) rename {radis/core => adit_radis_shared/common}/factories.py (100%) create mode 100644 adit_radis_shared/common/middlewares.py rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_alert_icons.html (91%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_bootstrap_icon.html (100%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_filter_set_field.html (100%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_messages_panel.html (88%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_navbar.html (76%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_navbar_actions.html (74%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_navbar_links.html (100%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_navbar_theme_switcher.html (97%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_navbar_user_manager.html (96%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_pagination.html (100%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_theme_icons.html (88%) rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/_toasts_panel.html (89%) create mode 100644 adit_radis_shared/common/templates/common/common_layout.html rename {radis/core/templates/core => adit_radis_shared/common/templates/common}/section_locked.html (64%) create mode 100644 adit_radis_shared/common/templatetags/__init__.py create mode 100644 adit_radis_shared/common/templatetags/common_extras.py rename radis/core/http.py => adit_radis_shared/common/utils/htmx_triggers.py (100%) diff --git a/adit_radis_shared/accounts/factories.py b/adit_radis_shared/accounts/factories.py index e18adaef..9227999f 100644 --- a/adit_radis_shared/accounts/factories.py +++ b/adit_radis_shared/accounts/factories.py @@ -1,18 +1,11 @@ -from typing import Generic, TypeVar, cast +from typing import cast import factory from django.contrib.auth.models import Group -from .models import User - -T = TypeVar("T") +from adit_radis_shared.common.factories import BaseDjangoModelFactory - -# We can't use BaseDjangoModelFactory of radis.core.factories because of circular imports -class BaseDjangoModelFactory(Generic[T], factory.django.DjangoModelFactory): - @classmethod - def create(cls, *args, **kwargs) -> T: - return super().create(*args, **kwargs) +from .models import User class UserFactory(BaseDjangoModelFactory[User]): diff --git a/adit_radis_shared/accounts/views.py b/adit_radis_shared/accounts/views.py index 0cb8e376..038bfd4f 100644 --- a/adit_radis_shared/accounts/views.py +++ b/adit_radis_shared/accounts/views.py @@ -6,7 +6,7 @@ from django.views.generic import TemplateView from adit_radis_shared.common.types import AuthenticatedHttpRequest -from radis.core.http import trigger_toast +from adit_radis_shared.common.utils.htmx_triggers import trigger_toast class UserProfileView(LoginRequiredMixin, AccessMixin, TemplateView): diff --git a/radis/core/factories.py b/adit_radis_shared/common/factories.py similarity index 100% rename from radis/core/factories.py rename to adit_radis_shared/common/factories.py diff --git a/adit_radis_shared/common/forms.py b/adit_radis_shared/common/forms.py index 86a7fca3..699fc394 100644 --- a/adit_radis_shared/common/forms.py +++ b/adit_radis_shared/common/forms.py @@ -83,7 +83,7 @@ def build_filter_set_layout(self): filter_field.button_label, css_class="btn-secondary btn-sm", ), - template="core/_filter_set_field.html", + template="common/_filter_set_field.html", ), ) diff --git a/adit_radis_shared/common/middlewares.py b/adit_radis_shared/common/middlewares.py new file mode 100644 index 00000000..552722a3 --- /dev/null +++ b/adit_radis_shared/common/middlewares.py @@ -0,0 +1,63 @@ +import re + +import pytz +from django.conf import settings +from django.template.response import TemplateResponse +from django.urls import reverse +from django.utils import timezone + +from .models import ProjectSettings + + +def is_html_response(response): + return response.has_header("Content-Type") and response["Content-Type"].startswith("text/html") + + +class BaseMaintenanceMiddleware: + """Render a maintenance template if in maintenance mode. + + Adapted from http://blog.ankitjaiswal.tech/put-your-django-site-on-maintenanceoffline-mode/ + """ + + project_settings: type[ProjectSettings] + template_name: str + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + login_request = request.path == reverse("auth_login") + logout_request = request.path == reverse("auth_logout") + if login_request or logout_request: + return self.get_response(request) + + settings = self.project_settings.get() + assert settings + in_maintenance = settings.maintenance_mode + if in_maintenance and not request.user.is_staff: + response = TemplateResponse(request, self.template_name) + return response.render() + + response = self.get_response(request) + if is_html_response(response) and in_maintenance and request.user.is_staff: + response.content = re.sub( + r"", + r"\g<0>
Site is in maintenance mode!
", + response.content.decode("utf-8"), + ) + return response + + +class TimezoneMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + tzname = request.session.get("django_timezone") + if not tzname: + tzname = settings.USER_TIME_ZONE + if tzname: + timezone.activate(pytz.timezone(tzname)) + else: + timezone.deactivate() + return self.get_response(request) diff --git a/adit_radis_shared/common/mixins.py b/adit_radis_shared/common/mixins.py index fa023ef2..8f94cd00 100644 --- a/adit_radis_shared/common/mixins.py +++ b/adit_radis_shared/common/mixins.py @@ -43,7 +43,7 @@ def dispatch( raise SuspiciousOperation() return TemplateView.as_view( - template_name="core/section_locked.html", + template_name="common/section_locked.html", extra_context={"section_name": self.section_name}, )(request) diff --git a/adit_radis_shared/common/models.py b/adit_radis_shared/common/models.py index ea7bfddc..919c9e7a 100644 --- a/adit_radis_shared/common/models.py +++ b/adit_radis_shared/common/models.py @@ -1,6 +1,22 @@ from django.db import models +class ProjectSettings(models.Model): + id: int + maintenance_mode = models.BooleanField(default=False) + announcement = models.TextField(blank=True) + + class Meta: + abstract = True + + def __str__(self): + return f"{self.__class__.__name__} [ID {self.id}]" + + @classmethod + def get(cls): + return cls.objects.first() + + class AppSettings(models.Model): id: int locked = models.BooleanField(default=False) diff --git a/adit_radis_shared/common/site.py b/adit_radis_shared/common/site.py index 11478b41..b0b8359d 100644 --- a/adit_radis_shared/common/site.py +++ b/adit_radis_shared/common/site.py @@ -4,6 +4,8 @@ from django.http import HttpRequest from django.middleware.csrf import get_token +THEME_PREFERENCE_KEY = "theme" + class MainMenuItem(NamedTuple): url_name: str @@ -35,10 +37,10 @@ def base_context_processor(request: HttpRequest) -> dict[str, Any]: "main_menu_items": main_menu_items, "theme": theme, "theme_color": theme_color, - # Variables in public are also available on the client via JavaScript, - # see base_generic.html + # This data will be available on the client, see common_layout.html "public": { "debug": settings.DEBUG, "csrf_token": get_token(request), + "theme_preference_key": THEME_PREFERENCE_KEY, }, } diff --git a/adit_radis_shared/common/static/common/common.js b/adit_radis_shared/common/static/common/common.js index af6908a8..8a8721a3 100644 --- a/adit_radis_shared/common/static/common/common.js +++ b/adit_radis_shared/common/static/common/common.js @@ -93,19 +93,6 @@ ready(function () { }); }); -/** - * Get the app config from the DOM that was added by the Django context processor: - * adit_radis_shared.common.site.base_context_processor (public key) - * @returns {object} config - */ -function getConfig() { - const configNode = document.getElementById("public"); - if (!configNode || !configNode.textContent) { - throw new Error("Missing app config."); - } - return JSON.parse(configNode.textContent); -} - /** * Update user preferences on the server (sends an AJAX request to the server). * @param {string} route @@ -125,17 +112,15 @@ function updatePreferences(route, data) { url = "/update-preferences/"; } - const config = getConfig(); const request = new Request(url, { method: "POST", - headers: { "X-CSRFToken": config.csrf_token }, + headers: { "X-CSRFToken": window.public.csrf_token }, mode: "same-origin", // Do not send CSRF token to another domain. body: formData, }); fetch(request).then(function () { - const config = getConfig(); - if (config.debug) { + if (window.public.debug) { console.log("Saved properties to session", data); } }); @@ -150,7 +135,8 @@ function updatePreferences(route, data) { */ function showToast(level, title, text) { window.dispatchEvent( - new CustomEvent("core:add-toast", { + // listened by common/_toasts_panel.html + new CustomEvent("common:add-toast", { detail: { level: level, title: title, diff --git a/adit_radis_shared/common/static/common/theme.js b/adit_radis_shared/common/static/common/theme.js index 84acb1c9..fb4b02e6 100644 --- a/adit_radis_shared/common/static/common/theme.js +++ b/adit_radis_shared/common/static/common/theme.js @@ -1,15 +1,14 @@ // Adapted from https://getbootstrap.com/docs/5.3/customize/color-modes/#javascript +// We are using pure Javascript here (no Alpine.js component) as we load this script +// before everything else to avoid any flickering. (() => { "use strict"; - // Keep in sync with cores/views.py - const THEME = "theme"; - const getStoredTheme = () => document.documentElement.getAttribute("data-theme-preference"); const setStoredTheme = (theme) => { document.documentElement.setAttribute("data-theme-preference", theme); - updatePreferences(null, { [THEME]: theme }); + updatePreferences(null, { [window.public.theme_preference_key]: theme }); }; const getPreferredTheme = () => { diff --git a/radis/core/templates/core/_alert_icons.html b/adit_radis_shared/common/templates/common/_alert_icons.html similarity index 91% rename from radis/core/templates/core/_alert_icons.html rename to adit_radis_shared/common/templates/common/_alert_icons.html index 9831a104..27ead40a 100644 --- a/radis/core/templates/core/_alert_icons.html +++ b/adit_radis_shared/common/templates/common/_alert_icons.html @@ -1,4 +1,4 @@ -{% load bootstrap_icon from core_extras %} +{% load bootstrap_icon from common_extras %} {% bootstrap_icon "info-circle" 24 %} diff --git a/radis/core/templates/core/_bootstrap_icon.html b/adit_radis_shared/common/templates/common/_bootstrap_icon.html similarity index 100% rename from radis/core/templates/core/_bootstrap_icon.html rename to adit_radis_shared/common/templates/common/_bootstrap_icon.html diff --git a/radis/core/templates/core/_filter_set_field.html b/adit_radis_shared/common/templates/common/_filter_set_field.html similarity index 100% rename from radis/core/templates/core/_filter_set_field.html rename to adit_radis_shared/common/templates/common/_filter_set_field.html diff --git a/radis/core/templates/core/_messages_panel.html b/adit_radis_shared/common/templates/common/_messages_panel.html similarity index 88% rename from radis/core/templates/core/_messages_panel.html rename to adit_radis_shared/common/templates/common/_messages_panel.html index 01f45c48..bd64f66b 100644 --- a/radis/core/templates/core/_messages_panel.html +++ b/adit_radis_shared/common/templates/common/_messages_panel.html @@ -1,7 +1,7 @@ {# Messages are placed by the server using the Django messages framework #} -{% load alert_class message_symbol from core_extras %} +{% load alert_class message_symbol from common_extras %}
- {% include "core/_alert_icons.html" %} + {% include "common/_alert_icons.html" %} {% for message in messages %}