diff --git a/ephios/api/templates/api/access_token_list.html b/ephios/api/templates/api/access_token_list.html index 28ee8cf0d..22cf78301 100644 --- a/ephios/api/templates/api/access_token_list.html +++ b/ephios/api/templates/api/access_token_list.html @@ -6,9 +6,9 @@

{% translate "Integrations" %}

- {% blocktranslate trimmed %} - Other applications can integrate with ephios by asking you for access to your data. - You can also create personal API tokens to access ephios from other applications. + {% blocktranslate trimmed with platform=platform_name %} + Other applications can integrate with {{ platform }} by asking you for access to your data. + You can also create personal API tokens to access {{ platform }} from other applications. If you are unsure about the origins of a token or the security of the third party application please revoke access. {% endblocktranslate %} diff --git a/ephios/api/templates/oauth2_provider/application_list.html b/ephios/api/templates/oauth2_provider/application_list.html index 39780926d..a21057090 100644 --- a/ephios/api/templates/oauth2_provider/application_list.html +++ b/ephios/api/templates/oauth2_provider/application_list.html @@ -5,9 +5,9 @@

{% translate "App integrations" %}

- {% blocktranslate trimmed %} - On this page you can configure other apps and programs to work with ephios as an authentication provider - using the OAuth2-standard. Other ways to integrate with ephios include the plugin system and the API. + {% blocktranslate trimmed with platform=platform_name %} + On this page you can configure other apps and programs to work with {{ platform }} as an authentication provider + using the OAuth2-standard. Other ways to integrate with {{ platform }} include the plugin system and the API. More detailed information can be found in the documentation. {% endblocktranslate %}

diff --git a/ephios/core/context.py b/ephios/core/context.py index 844b08808..93be2485c 100644 --- a/ephios/core/context.py +++ b/ephios/core/context.py @@ -4,18 +4,10 @@ from django.utils.translation import get_language from dynamic_preferences.registries import global_preferences_registry +from ephios.core.dynamic import dynamic_settings from ephios.core.models import AbstractParticipation from ephios.core.signals import footer_link, html_head, nav_link, navbar_html - - -def get_brand_logo_static_path(request): - from ephios.core.signals import brand_logo_static_path - - for _, result in brand_logo_static_path.send(None, request=request): - if result: - return result - return "ephios/img/ephios-text-black.png" - +from ephios.core.views.pwa import get_pwa_app_icons NAV_USERPROFILE_KEY = "__userprofile__" @@ -52,13 +44,14 @@ def ephios_base_context(request): "nav": nav, "nav_groups": nav_groups, "nav_userprofile": nav_userprofile, - "brand_logo_static_path": get_brand_logo_static_path(request), "signalled_html_head": _html_head, "signalled_nav_html": _navbar_additional_html, "footer": footer, "LANGUAGE_CODE": get_language(), "ephios_version": settings.EPHIOS_VERSION, - "PWA_APP_ICONS": settings.PWA_APP_ICONS, + "PWA_APP_ICONS": get_pwa_app_icons(), + "PWA_APP_SPLASH_SCREEN": [], "DEBUG": settings.DEBUG, "organization_name": global_preferences_registry.manager()["general__organization_name"], + "platform_name": dynamic_settings.PLATFORM_NAME, } diff --git a/ephios/core/dynamic.py b/ephios/core/dynamic.py new file mode 100644 index 000000000..b0b8f8e6e --- /dev/null +++ b/ephios/core/dynamic.py @@ -0,0 +1,25 @@ +class DynamicSettingsProxy: + """ + Proxy access to django settings but first check if any receiver overwrites it with a dynamic value. + """ + + NONE = object() + + def __init__(self): + from django.conf import settings + + self._django_settings = settings + + def __getattr__(self, name): + from ephios.core.signals import provide_dynamic_settings + + for _, result in provide_dynamic_settings.send(None, name=name): + if result is not None: + if result is DynamicSettingsProxy.NONE: + return None + return result + # default to django settings + return getattr(self._django_settings, f"DEFAULT_{name}") + + +dynamic_settings = DynamicSettingsProxy() diff --git a/ephios/core/forms/users.py b/ephios/core/forms/users.py index c1ad0a704..760e35cd8 100644 --- a/ephios/core/forms/users.py +++ b/ephios/core/forms/users.py @@ -20,6 +20,7 @@ from guardian.shortcuts import assign_perm, get_objects_for_group, remove_perm from ephios.core.consequences import WorkingHoursConsequenceHandler +from ephios.core.dynamic import dynamic_settings from ephios.core.models import QualificationGrant, UserProfile, WorkingHours from ephios.core.models.users import IdentityProvider from ephios.core.services.notifications.backends import enabled_notification_backends @@ -132,7 +133,6 @@ class GroupForm(PermissionFormMixin, ModelForm): required=False, ) is_management_group = PermissionField( - label=_("Can change permissions and manage ephios"), help_text=_( "If checked, users in this group can edit all users, change groups, their permissions and memberships " "as well as define eventtypes and qualifications." @@ -171,6 +171,9 @@ def __init__(self, **kwargs): for field_name, field in extra_fields: self.base_fields[field_name] = field super().__init__(**kwargs) + self.fields["is_management_group"].label = _( + "Can change permissions and manage {platform}" + ).format(platform=dynamic_settings.PLATFORM_NAME) self.helper = FormHelper() self.helper.layout = Layout( Field("name"), @@ -252,7 +255,7 @@ class UserProfileForm(PermissionFormMixin, ModelForm): is_staff = PermissionField( label=_("Administrator"), help_text=_( - "If checked, this user can change technical ephios settings as well as edit all user profiles, " + "If checked, this user can change technical settings as well as edit all user profiles, " "groups, qualifications, events and event types." ), permissions=MANAGEMENT_PERMISSIONS, diff --git a/ephios/core/ical.py b/ephios/core/ical.py index cca742932..a90fc019b 100644 --- a/ephios/core/ical.py +++ b/ephios/core/ical.py @@ -1,4 +1,3 @@ -from django.conf import settings from django.contrib.auth import get_user_model from django.db.models import Prefetch from django.shortcuts import get_object_or_404 @@ -6,6 +5,7 @@ from guardian.shortcuts import get_users_with_perms from icalendar import vCalAddress +from ephios.core.dynamic import dynamic_settings from ephios.core.models import AbstractParticipation, Shift @@ -38,7 +38,7 @@ def item_location(self, item): return item.event.location def item_guid(self, item): - return f"{item.pk}@{settings.GET_SITE_URL()}" + return f"{item.pk}@{dynamic_settings.SITE_URL}" def item_organizer(self, item): user = get_users_with_perms(item.event, only_with_perms_in=["change_event"]).first() diff --git a/ephios/core/migrations/0022_identityprovider.py b/ephios/core/migrations/0022_identityprovider.py index 57e9b9a3b..c9f934aa6 100644 --- a/ephios/core/migrations/0022_identityprovider.py +++ b/ephios/core/migrations/0022_identityprovider.py @@ -4,7 +4,7 @@ def migrate_oidc_provider(apps, schema_editor): - from ephios import settings + from django.conf import settings try: if settings.env.bool("ENABLE_OIDC_CLIENT"): diff --git a/ephios/core/services/files.py b/ephios/core/services/files.py index e1069d7cd..4ef38ccf0 100644 --- a/ephios/core/services/files.py +++ b/ephios/core/services/files.py @@ -11,6 +11,8 @@ from django.urls import reverse from django.views import View +from ephios.core.dynamic import dynamic_settings + class UserContentView(View): """ @@ -22,7 +24,7 @@ class UserContentView(View): def dispatch(self, request, *args, **kwargs): # If media files are served from a different domain and # the user requests media files from the app domain via this view --> 404 - if (loc := urlsplit(settings.GET_USERCONTENT_URL()).netloc) and request.get_host() != loc: + if (loc := urlsplit(dynamic_settings.USERCONTENT_URL).netloc) and request.get_host() != loc: raise PermissionDenied() return super().dispatch(request, *args, **kwargs) @@ -64,7 +66,7 @@ def redirect_to_file_download(field_file): """ Shortcut for redirecting to the ticketed media file download view. """ - if loc := urlsplit(settings.GET_USERCONTENT_URL()).netloc: + if loc := urlsplit(dynamic_settings.USERCONTENT_URL).netloc: ticket = file_ticket(field_file) path = reverse("core:file_ticket", kwargs={"ticket": ticket}) return redirect(urlunsplit(("http" if settings.DEBUG else "https", loc, path, "", ""))) @@ -81,9 +83,9 @@ def __call__(self, request): response = self.get_response(request) if ( # if the usercontent URL does not contain a domain, the request host will be checked against `None` --> no redirect loop - request.get_host() == urlsplit(settings.GET_USERCONTENT_URL()).netloc + request.get_host() == urlsplit(dynamic_settings.USERCONTENT_URL).netloc and request.resolver_match and not getattr(request.resolver_match.func.view_class, "is_usercontent_view", False) ): - return redirect(urljoin(settings.GET_SITE_URL(), request.path)) + return redirect(urljoin(dynamic_settings.SITE_URL, request.path)) return response diff --git a/ephios/core/services/notifications/backends.py b/ephios/core/services/notifications/backends.py index ef2d06887..703d3db18 100644 --- a/ephios/core/services/notifications/backends.py +++ b/ephios/core/services/notifications/backends.py @@ -15,6 +15,7 @@ from ephios.core.models.users import Notification from ephios.core.services.mail.send import send_mail +from ephios.core.templatetags.settings_extras import as_brand_static_path from ephios.extra.i18n import language logger = logging.getLogger(__name__) @@ -38,7 +39,7 @@ def send_all_notifications(): CACHE_LOCK_KEY = "notification_sending_running" if cache.get(CACHE_LOCK_KEY): return - cache.set(CACHE_LOCK_KEY, str(uuid.uuid4()), timeout=1800) + cache.set(CACHE_LOCK_KEY, str(uuid.uuid4()), timeout=1800) # will be cleared if no errors occur backends = set(installed_notification_backends()) for backend in backends: @@ -167,7 +168,7 @@ def send(cls, notification): payload = { "head": str(notification.subject), "body": notification.body, - "icon": "/static/ephios/img/ephios-symbol-red.svg", + "icon": as_brand_static_path("appicon-prod.svg"), } if actions := notification.get_actions(): payload["url"] = actions[0][1] diff --git a/ephios/core/services/notifications/types.py b/ephios/core/services/notifications/types.py index 2cd8271b6..1824f88bc 100644 --- a/ephios/core/services/notifications/types.py +++ b/ephios/core/services/notifications/types.py @@ -1,7 +1,6 @@ from typing import List from urllib.parse import urlparse -from django.conf import settings from django.contrib.auth.tokens import default_token_generator from django.template.loader import render_to_string from django.urls import reverse @@ -13,6 +12,7 @@ from guardian.shortcuts import get_users_with_perms from requests import PreparedRequest +from ephios.core.dynamic import dynamic_settings from ephios.core.models import AbstractParticipation, Event, LocalParticipation, UserProfile from ephios.core.models.users import Consequence, Notification from ephios.core.signals import register_notification_types @@ -98,7 +98,7 @@ def get_actions_with_referrer(cls, notification): """ actions = [] for label, url in cls.get_actions(notification): - if urlparse(url).netloc in settings.GET_SITE_URL(): + if urlparse(url).netloc in dynamic_settings.SITE_URL: req = PreparedRequest() req.prepare_url(url, {NOTIFICATION_READ_PARAM_NAME: notification.pk}) url = req.url @@ -116,13 +116,17 @@ def send(cls, user: UserProfile, **kwargs): @classmethod def get_subject(cls, notification): - return _("ephios account updated") + return _("{platform} account updated").format(platform=dynamic_settings.PLATFORM_NAME) @classmethod def get_body(cls, notification): + org_name = global_preferences_registry.manager().get("general__organization_name") return _( - "You're receiving this email because your account at ephios has been updated." - ).format(url=cls._get_personal_data_url(notification), email=notification.user.email) + "You're receiving this email because your {platform} account at {org_name} has been updated." + ).format( + platform=dynamic_settings.PLATFORM_NAME, + org_name=org_name, + ) @classmethod def get_actions(cls, notification): @@ -149,16 +153,21 @@ def send(cls, user: UserProfile, **kwargs): @classmethod def get_subject(cls, notification): - org_name = global_preferences_registry.manager().get("general__organization_name") - return _("Welcome to {}!").format(org_name) + return _("Welcome to {}!").format(dynamic_settings.PLATFORM_NAME) @classmethod def get_body(cls, notification): + org_name = global_preferences_registry.manager().get("general__organization_name") return _( - "You're receiving this email because a new account has been created for you at ephios.\n" + "You're receiving this email because a new {platform} account has been created for you at {org_name}.\n" "Please go to the following page and choose a password: {url} \n" "Your username is your email address: {email}" - ).format(url=cls._get_reset_url(notification), email=notification.user.email) + ).format( + url=cls._get_reset_url(notification), + email=notification.user.email, + platform=dynamic_settings.PLATFORM_NAME, + org_name=org_name, + ) @classmethod def get_actions(cls, notification): diff --git a/ephios/core/services/password_reset.py b/ephios/core/services/password_reset.py index 8490867c2..f8e0180fc 100644 --- a/ephios/core/services/password_reset.py +++ b/ephios/core/services/password_reset.py @@ -1,10 +1,12 @@ import logging -from django.conf import settings from django.contrib.auth.password_validation import MinimumLengthValidator from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.translation import gettext as _ +from dynamic_preferences.registries import global_preferences_registry + +from ephios.core.dynamic import dynamic_settings logger = logging.getLogger(__name__) @@ -17,13 +19,17 @@ def password_changed(self, password, user): token.revoke() # send notification to user + org_name = global_preferences_registry.manager().get("general__organization_name") text_content = _( - "Your password for {site} has been changed. If you didn't request this change, contact an administrator immediately." - ).format(site=settings.GET_SITE_URL()) + "The password for your {platform} account at {org_name} has been changed. " + "If you didn't request this change, contact an administrator immediately." + ).format(platform=dynamic_settings.PLATFORM_NAME, org_name=org_name) html_content = render_to_string( "core/mails/base.html", { - "subject": _("Your ephios password has been changed"), + "subject": _("Your {platform} password has been changed").format( + platform=dynamic_settings.PLATFORM_NAME + ), "body": text_content, }, ) diff --git a/ephios/core/signals.py b/ephios/core/signals.py index a78ecf903..ffe95a3a3 100644 --- a/ephios/core/signals.py +++ b/ephios/core/signals.py @@ -14,15 +14,6 @@ ``request`` and are expected to return HTML. """ -brand_logo_static_path = PluginSignal() -""" -This signal allows you to overwrite the default brand logo path. -You will get the request as the keyword argument -``request`` and are expected to return a string containing the path to -a static file. -""" - - register_consequence_handlers = PluginSignal() """ This signal is sent out to get all known consequence handlers. Receivers should return a list of @@ -185,6 +176,14 @@ """ +provide_dynamic_settings = PluginSignal() +""" +Use this signal to overwrite the defaults of django settings accessed +using ``ephios.core.signals.DynamicSettingsProxy``. +Receivers receive a ``name`` keyword argument naming the setting. +""" + + @receiver( register_consequence_handlers, dispatch_uid="ephios.core.signals.register_base_consequence_handlers", @@ -220,36 +219,52 @@ def register_core_notification_backends(sender, **kwargs): @receiver(nav_link, dispatch_uid="ephios.core.signals.register_nav_links") def register_nav_links(sender, request, **kwargs): return ( - [ - { - "label": _("Working hours"), - "url": reverse_lazy("core:workinghours_list"), - "active": request.resolver_match - and request.resolver_match.url_name == "workinghours_list", - "group": _("Management"), - }, - { - "label": _("Users"), - "url": reverse_lazy("core:userprofile_list"), - "active": request.resolver_match - and request.resolver_match.url_name == "userprofile_list", - "group": _("Management"), - }, - ] - if request.user.has_perm("core.view_userprofile") - else [] - ) + ( - [ - { - "label": _("Groups"), - "url": reverse_lazy("core:group_list"), - "active": request.resolver_match - and request.resolver_match.url_name == "group_list", - "group": _("Management"), - } - ] - if request.user.has_perm("auth.view_group") - else [] + ( + [ + { + "label": _("Working hours"), + "url": reverse_lazy("core:workinghours_list"), + "active": request.resolver_match + and request.resolver_match.url_name == "workinghours_list", + "group": _("Management"), + }, + { + "label": _("Users"), + "url": reverse_lazy("core:userprofile_list"), + "active": request.resolver_match + and request.resolver_match.url_name == "userprofile_list", + "group": _("Management"), + }, + ] + if request.user.has_perm("core.view_userprofile") + else [] + ) + + ( + [ + { + "label": _("Groups"), + "url": reverse_lazy("core:group_list"), + "active": request.resolver_match + and request.resolver_match.url_name == "group_list", + "group": _("Management"), + } + ] + if request.user.has_perm("auth.view_group") + else [] + ) + + ( + [ + { + "label": _("Settings"), + "url": reverse_lazy("core:settings_instance"), + "active": request.resolver_match + and request.resolver_match.url_name == "settings_instance", + "group": _("Management"), + } + ] + if request.user.is_staff + else [] + ) ) diff --git a/ephios/core/templates/core/group_form.html b/ephios/core/templates/core/group_form.html index d39f6be34..563893957 100644 --- a/ephios/core/templates/core/group_form.html +++ b/ephios/core/templates/core/group_form.html @@ -21,8 +21,8 @@

{% translate "Create new group" %}

{% if oidc_group_claims %} {% endif %} diff --git a/ephios/core/templates/core/mails/base.html b/ephios/core/templates/core/mails/base.html index b96879470..65a4e2260 100644 --- a/ephios/core/templates/core/mails/base.html +++ b/ephios/core/templates/core/mails/base.html @@ -1,4 +1,5 @@ {% load settings_extras %} +{% load settings_extras %} {% load email_extras %} {% load settings_extras %} {% load rich_text %} @@ -123,9 +124,8 @@ - {% brand_logo_static_path as logo_path %} Logo + src="data:image/png;base64,{% base64_static_file "email_logo.png"|as_brand_static_path %}"/> diff --git a/ephios/core/templates/core/notification_list.html b/ephios/core/templates/core/notification_list.html index 9c48d98d4..08d7646fa 100644 --- a/ephios/core/templates/core/notification_list.html +++ b/ephios/core/templates/core/notification_list.html @@ -2,6 +2,10 @@ {% load rich_text %} {% load i18n %} +{% block title %} + {% translate "Notifications" %} +{% endblock %} + {% block content %}

{% translate "Notifications" %} @@ -34,7 +38,8 @@

{% for label, url in notification.get_actions %} - {{ label }} + {{ label }} {% endfor %}
diff --git a/ephios/core/templates/core/userprofile_form.html b/ephios/core/templates/core/userprofile_form.html index 0be702a96..d5ef9f6cd 100644 --- a/ephios/core/templates/core/userprofile_form.html +++ b/ephios/core/templates/core/userprofile_form.html @@ -25,8 +25,8 @@

{% translate "Add new user" %}

{% if oidc_group_claims %}