diff --git a/invenio_config_tuw/config.py b/invenio_config_tuw/config.py index a72b64770f35b586b037b5c0294e04b8ece72e32..899edbc509aed499923dac86468b27d0d886993a 100644 --- a/invenio_config_tuw/config.py +++ b/invenio_config_tuw/config.py @@ -45,6 +45,7 @@ from .users import ( tuw_registration_form, ) from .users.utils import check_user_email_for_tuwien, current_user_as_creator +from .users.views import notification_settings # Invenio-Config-TUW # ================== @@ -386,3 +387,5 @@ NOTIFICATIONS_BACKENDS = { **NOTIFICATIONS_BACKENDS, TUWEmailNotificationBackend.id: TUWEmailNotificationBackend(), } + +NOTIFICATIONS_SETTINGS_VIEW_FUNCTION = notification_settings diff --git a/invenio_config_tuw/notifications/backends.py b/invenio_config_tuw/notifications/backends.py index fd31231677dec245961f49360749e5a9a05582bd..c4ad17ce4b4faad5d3244e703bedd5d688b784d9 100644 --- a/invenio_config_tuw/notifications/backends.py +++ b/invenio_config_tuw/notifications/backends.py @@ -32,6 +32,12 @@ class TUWEmailNotificationBackend(EmailNotificationBackend): if site_id: subject = f"[{site_id}] {subject}" + secondary_email = ( + recipient.data.get("preferences", {}) + .get("notifications", {}) + .get("secondary_email", None) + ) + resp = send_email( { "subject": subject, @@ -40,6 +46,7 @@ class TUWEmailNotificationBackend(EmailNotificationBackend): "recipients": [ recipient.data.get("email") or recipient.data.get("email_hidden") ], + "cc": [secondary_email] if secondary_email else [], "sender": current_app.config["MAIL_DEFAULT_SENDER"], "reply_to": current_app.config["MAIL_DEFAULT_REPLY_TO"], "extra_headers": { diff --git a/invenio_config_tuw/users/preferences.py b/invenio_config_tuw/users/preferences.py index 14ac06c18b8f9c6fa4ec104ffd20a9515d311825..00612b67c72077ece820b560584ec5468523caf6 100644 --- a/invenio_config_tuw/users/preferences.py +++ b/invenio_config_tuw/users/preferences.py @@ -8,7 +8,8 @@ """Form for curation-related user settings, inspired by the notifications settings.""" from flask_wtf import FlaskForm -from wtforms import BooleanField +from invenio_users_resources.forms import NotificationsForm +from wtforms import BooleanField, EmailField class CurationPreferencesProxy: @@ -62,3 +63,15 @@ class CurationPreferencesForm(FlaskForm): """Populate the object.""" user = self.proxy_cls(user) return super().populate_obj(user) + + +class TUWNotificationsForm(NotificationsForm): + """Form for editing user notification preferences.""" + + secondary_email = EmailField( + label="Secondary email", + description=( + "Secondary email address for notifications. " + "If set, this email address will be added in CC." + ), + ) diff --git a/invenio_config_tuw/users/schemas.py b/invenio_config_tuw/users/schemas.py index 10857988edc470c257725ded490454b80026bb09..3134866f4e9731a34ff3c885b69d6663451a8e08 100644 --- a/invenio_config_tuw/users/schemas.py +++ b/invenio_config_tuw/users/schemas.py @@ -11,7 +11,10 @@ from invenio_app_rdm.users.schemas import NotificationsUserSchema as UserSchema from invenio_app_rdm.users.schemas import ( UserPreferencesNotificationsSchema as UserPreferencesSchema, ) -from invenio_users_resources.services.schemas import UserProfileSchema +from invenio_users_resources.services.schemas import ( + NotificationPreferences, + UserProfileSchema, +) from marshmallow import fields @@ -25,10 +28,17 @@ class TUWUserProfileSchema(UserProfileSchema): # preferences +class TUWNotificationPreferencesSchema(NotificationPreferences): + """Schema for notification preferences.""" + + secondary_email = fields.Email() + + class TUWUserPreferencesSchema(UserPreferencesSchema): """User preferences schema with TU Wien extensions.""" curation_consent = fields.Boolean(default=False) + notifications = fields.Nested(TUWNotificationPreferencesSchema) # complete user schema diff --git a/invenio_config_tuw/users/templates/notifications_settings.html b/invenio_config_tuw/users/templates/notifications_settings.html new file mode 100644 index 0000000000000000000000000000000000000000..51793b3dd348cff0debc141b1ec64e2d3e3fd88a --- /dev/null +++ b/invenio_config_tuw/users/templates/notifications_settings.html @@ -0,0 +1,111 @@ +{# -*- coding: utf-8 -*- + +This file is part of Invenio. +Copyright (C) 2023 Graz University of Technology. +Copyright (C) 2025 TU Wien. + +Invenio-Config-TUW is free software; you can redistribute it and/or +modify it under the terms of the MIT License; see LICENSE file for more +details. +#} +{#- base: invenio_users_resources v5.2.0 -#} +{#- file: invenio_users_resources/templates/semantic-ui/invenio_users_resources/settings/notifications.html -#} +{#- changes: rewording & mild restructuring, adding input for secondary email; see "change:" comments in the code #} + +{%- extends config.USERPROFILES_SETTINGS_TEMPLATE %} +{% from "invenio_userprofiles/settings/_macros.html" import render_field, form_errors %} + +{%- block settings_content scoped %} + <section aria-label="{{ _('Notifications') }}" class="ui segments"> + <div class="ui segment secondary"> + <i class="bell icon" aria-hidden="true"></i> + <h2 class="ui tiny header inline-block m-0">{{ _("Notifications") }}</h2> + </div> + <div class="ui segment"> + <form {% if not read_only %}method="POST" {% endif %} name="notifications_form" class="ui form"> + + <div class="ui four column grid"> + <div class="two column stackable tablet-mobile row"> + <div class="column"> + {%- set form = notifications_form %} + {%- for field in form %} + {%- if field.widget.input_type == 'hidden' %} + {{ field() }} + {%- endif %} + {%- endfor %} + + {#- change: create vars for other fields -#} + {%- set enabled_field = form.enabled %} + {%- set secondary_email_field = form.secondary_email %} + {%- set matrix_user_id_field = form.matrix_user_id %} + {%- set notif_enabled = current_user.preferences.get("notifications", {}).get("enabled", True) %} + + <div class="field"> + <label for="{{ enabled_field.id }}">{{ enabled_field.label }}</label> + {#- change: add contextual text, and restructure input #} + <p> + <small> + Note that this setting only affects <strong>automatic notifications</strong>. + Contact requests will still be sent to both primary and secondary email address, regardless of this setting. + </small> + </p> + <div class="ui toggle on-off checkbox"> + <input type="checkbox" name="{{ enabled_field.id }}" id="{{ enabled_field.id }}" {{ "checked" if notif_enabled else "" }}> + <label> + <small class="ml-10">{{ enabled_field.description }}</small> + </label> + </div> + + </div> + </div> + + <div class="column"> + {#- change: remove use of url_for() #} + <div class="field"> + <label>{{ _("Primary email") }}</label> + <p> + <small> + {#- change: remove mention of changing email address #} + {% trans %} + We use your primary email address for sending notifications. + {% endtrans %} + </small> + </p> + + <div class="ui basic label large ml-0">{{ current_user.email }}</div> + </div> + + {#- change: add input for secondary email address #} + <div class="field"> + <label for="{{ secondary_email_field.id }}">{{ secondary_email_field.label }}</label> + <p> + <small> + {{ secondary_email_field.description }} + </small> + </p> + + <div class="ui"> + <input + type="email" name="{{ secondary_email_field.id }}" id="{{ secondary_email_field.id }}" + value={{ current_user.preferences.get("notifications", {}).get("secondary_email", "") }}> + </div> + </div> + </div> + {{ form.id }} + <div class="one column row"> + <div class="form-actions"> + <a href="./" class="ui labeled icon button mt-5"> + <i class="close icon" aria-hidden="true"></i> {{ _('Cancel') }} + </a> + <button type="submit" name="submit" value="{{ form._prefix }}" class="ui primary labeled icon button mt-5"> + <i class="check icon" aria-hidden="true"></i> + {{ _('Update notification preferences') }} + </button> + </div> + </div> + </div> + </div> + </form> + </div> + </section> +{% endblock settings_content%} diff --git a/invenio_config_tuw/users/views.py b/invenio_config_tuw/users/views.py index 5b0c35043760a79e8f6137212c697d3a02d4f050..a2f3a52eef63c31663f1d8929548301c313c83e5 100644 --- a/invenio_config_tuw/users/views.py +++ b/invenio_config_tuw/users/views.py @@ -9,9 +9,11 @@ from flask import Blueprint, current_app, flash, render_template, request from flask_login import current_user, login_required +from invenio_app_rdm.theme.views import handle_notifications_form from invenio_db import db +from invenio_i18n import lazy_gettext as _ -from .preferences import CurationPreferencesForm +from .preferences import CurationPreferencesForm, TUWNotificationsForm user_settings_blueprint = Blueprint( "invenio_config_tuw_settings", @@ -44,3 +46,29 @@ def curation_settings_view(): ["invenio_theme_tuw/settings/curation.html", "curation_settings.html"], preferences_curation_form=preferences_curation_form, ) + + +def notification_settings(): + """View for notification settings.""" + preferences_notifications_form = TUWNotificationsForm( + formdata=None, obj=current_user, prefix="preferences-notifications" + ) + + # Pick form + form_name = request.form.get("submit", None) + form = preferences_notifications_form if form_name else None + + # Process form + if form: + form.process(formdata=request.form) + if form.validate_on_submit(): + handle_notifications_form(form) + flash(_("Notification preferences were updated."), category="success") + + return render_template( + [ + "invenio_theme_tuw/notifications_settings.html", + "notifications_settings.html", + ], + notifications_form=preferences_notifications_form, + )