From ed855225261f202dc511051c2c5afa3c89905f3e Mon Sep 17 00:00:00 2001 From: Maximilian Moser <maximilian.moser@tuwien.ac.at> Date: Thu, 6 Feb 2025 18:40:02 +0100 Subject: [PATCH 1/4] Use notifications mechanisms for publication notification * this brings it more in line with the request notifications * previously we were restricted to emails, now it will be whatever notification backends we use * also remove the mail templates - they now live in code until we enhance the notification backend to be more flexible w.r.t. the rendered templates --- CHANGES.rst | 5 ++ invenio_config_tuw/services.py | 4 +- invenio_config_tuw/tasks.py | 77 +++++++++++-------- .../templates/mails/record_published.html | 30 -------- .../templates/mails/record_published.txt | 23 ------ tests/test_misc.py | 29 +++++-- 6 files changed, 77 insertions(+), 91 deletions(-) delete mode 100644 invenio_config_tuw/users/templates/mails/record_published.html delete mode 100644 invenio_config_tuw/users/templates/mails/record_published.txt diff --git a/CHANGES.rst b/CHANGES.rst index d4ee794..a4b894e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,11 @@ Changes ======= +Version <next> + +- Rename task `send_publication_notification_email` and make it use notifications + + Version 2025.1.3 (released 2025-02-06) - Update translation infrastructure diff --git a/invenio_config_tuw/services.py b/invenio_config_tuw/services.py index 7335225..82fc2ec 100644 --- a/invenio_config_tuw/services.py +++ b/invenio_config_tuw/services.py @@ -18,7 +18,7 @@ from invenio_rdm_records.services.components import DefaultRecordsComponents from invenio_records_resources.services.uow import TaskOp from .proxies import current_config_tuw -from .tasks import send_publication_notification_email +from .tasks import send_publication_notification class ParentAccessSettingsComponent(ServiceComponent): @@ -46,7 +46,7 @@ class PublicationNotificationComponent(ServiceComponent): if not has_been_published: self.uow.register( - TaskOp(send_publication_notification_email, record.pid.pid_value) + TaskOp(send_publication_notification, record.pid.pid_value) ) diff --git a/invenio_config_tuw/tasks.py b/invenio_config_tuw/tasks.py index a08c1d9..1103149 100644 --- a/invenio_config_tuw/tasks.py +++ b/invenio_config_tuw/tasks.py @@ -10,21 +10,24 @@ from typing import Optional from celery import shared_task -from flask import current_app, render_template +from flask import current_app, url_for from invenio_access.permissions import system_identity from invenio_accounts.proxies import current_datastore -from invenio_mail.tasks import send_email +from invenio_notifications.tasks import broadcast_notification from invenio_rdm_records.proxies import current_rdm_records_service as records_service +from .notifications import UserNotificationBuilder + @shared_task(ignore_result=True) -def send_publication_notification_email(recid: str, user_id: Optional[str] = None): +def send_publication_notification(recid: str, user_id: Optional[str] = None): """Send the record uploader an email about the publication of their record.""" - record = records_service.read(identity=system_identity, id_=recid) + record = records_service.read(identity=system_identity, id_=recid)._obj + record_title = record["metadata"]["title"] if user_id is not None: user = current_datastore.get_user(user_id) else: - owner = record._obj.parent.access.owner + owner = record.parent.access.owner if owner is not None and owner.owner_type == "user": user = owner.resolve() else: @@ -33,31 +36,45 @@ def send_publication_notification_email(recid: str, user_id: Optional[str] = Non ) return - html_message = render_template( - [ - "invenio_theme_tuw/mails/record_published.html", - "mails/record_published.html", - ], - uploader=user, - record=record, - app=current_app, - ) - message = render_template( - [ - "invenio_theme_tuw/mails/record_published.txt", - "mails/record_published.txt", - ], - uploader=user, - record=record, - app=current_app, + # build the message + datacite_test_mode = current_app.config["DATACITE_TEST_MODE"] + if "identifier" in record.get("pids", {}).get("doi", {}): + doi = record["pids"]["doi"]["identifier"] + + if datacite_test_mode: + doi_base_url = "https://handle.test.datacite.org" + doi_type = "DOI-like handle" + else: + doi_base_url = "https://doi.org" + doi_type = "DOI" + + doi_url = f"{doi_base_url}/{doi}" + link_line = f"It is now available under the following {doi_type}: {doi_url}" + link_line_html = f'It is now available under the following {doi_type}: <a href="{doi_url}">{doi_url}</a>' + + else: + landing_page_url = url_for( + "invenio_app_rdm_records.record_detail", + pid_value=record.pid.pid_value, + _external=True, + ) + link_line = f"It is now available under the following URL: {landing_page_url}" + link_line_html = f'It is now available under the following URL: <a href="{landing_page_url}">{landing_page_url}</a>' + + publish_line = f'Your record "{record_title}" just got published!' + edits_line = "Metadata edits for this record will *not* require another review." + edits_line_html = ( + "Metadata edits for this record will <em>not</em> require another review." ) - record_title = record["metadata"]["title"] - send_email( - { - "subject": f'Your record "{record_title}" was published', - "html": html_message, - "body": message, - "recipients": [user.email], - } + message = "\n".join([publish_line, link_line, "", edits_line]) + html_message = "<br />".join([publish_line, link_line_html, "", edits_line_html]) + + # send the notification + notification = UserNotificationBuilder().build( + receiver={"user": user.id}, + subject=f'Your record "{record_title}" was published', + message=message, + html_message=html_message, ) + broadcast_notification(notification.dumps()) diff --git a/invenio_config_tuw/users/templates/mails/record_published.html b/invenio_config_tuw/users/templates/mails/record_published.html deleted file mode 100644 index f2914dc..0000000 --- a/invenio_config_tuw/users/templates/mails/record_published.html +++ /dev/null @@ -1,30 +0,0 @@ -{#- - 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. --#} -{%- if record.pids and record.pids.doi %} - {%- set doi = record.pids.doi.identifier %} - {%- set doi_base_url = "https://doi.org/" if not app.config["DATACITE_TEST_MODE"] else "https://handle.test.datacite.org/" %} - {%- set doi_ref = "DOI" if not app.config["DATACITE_TEST_MODE"] else "DOI-like handle" %} - {%- set avail_message = 'It is now available under the following ' + doi_ref + ': <a href="' + doi_base_url + doi + '">' + doi_base_url + doi + '</a>' %} -{%- else %} - {%- set avail_message = 'It is now available under the following URL: <a href="' + record.links.self_html + '">' + record.links.self_html + '</a>' %} -{%- endif %} -<p> - Dear {{ uploader.user_profile.full_name }}, -</p> -<p> - Your record "{{ record.metadata.title }}" just got published! - <br /> - {{ avail_message|safe }} -</p> -<p> - Metadata edits for this record will <em>not</em> require another review. -</p> -<p> - Cheers, - <br /> - The team at the Center for Research Data Management -</p> diff --git a/invenio_config_tuw/users/templates/mails/record_published.txt b/invenio_config_tuw/users/templates/mails/record_published.txt deleted file mode 100644 index 211355a..0000000 --- a/invenio_config_tuw/users/templates/mails/record_published.txt +++ /dev/null @@ -1,23 +0,0 @@ -{#- - 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. --#} -{%- if record.pids and record.pids.doi %} - {%- set doi = record.pids.doi.identifier %} - {%- set doi_base_url = "https://doi.org/" if not app.config["DATACITE_TEST_MODE"] else "https://handle.test.datacite.org/" %} - {%- set doi_ref = "DOI" if not app.config["DATACITE_TEST_MODE"] else "DOI-like handle" %} - {%- set avail_message = "It is now available under the following " + doi_ref + ": " + doi_base_url + doi %} -{%- else %} - {%- set avail_message = "It is now available under the following URL: " + record.links.self_html %} -{%- endif %} -Dear {{ uploader.user_profile.full_name }}, - -Your record "{{ record.metadata.title }}" just got published! -{{ avail_message|safe }} - -Metadata edits for this record will *not* require another review. - -Cheers, -The team at the Center for Research Data Management diff --git a/tests/test_misc.py b/tests/test_misc.py index 8d86bf8..3e56618 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -14,17 +14,34 @@ from invenio_db import db import invenio_config_tuw from invenio_config_tuw.startup import register_smtp_error_handler -from invenio_config_tuw.tasks import send_publication_notification_email +from invenio_config_tuw.tasks import send_publication_notification from invenio_config_tuw.users.utils import current_user_as_creator def test_send_publication_notification_email(example_record, mocker): """Test (not really) sending an email about the publication of a record.""" - mocker.patch("invenio_config_tuw.tasks.send_email") - - send_publication_notification_email(example_record.pid.pid_value) - - invenio_config_tuw.tasks.send_email.assert_called_once() + record = example_record + with mocker.patch("invenio_config_tuw.tasks.broadcast_notification"): + send_publication_notification(record.pid.pid_value) + + # the `expected_args` will need to be updated whenever we change the messages + recid = record.pid.pid_value + title = record.metadata["title"] + expected_args = { + "type": "user-notification", + "context": { + "receiver": {"user": record.parent.access.owned_by.resolve().id}, + "subject": f'Your record "{title}" was published', + "message": f'Your record "{title}" just got published!\nIt is now available under the following URL: https://localhost/records/{recid}\n\nMetadata edits for this record will *not* require another review.', + "html_message": f'Your record "{title}" just got published!<br />It is now available under the following URL: <a href="https://localhost/records/{recid}">https://localhost/records/{recid}</a><br /><br />Metadata edits for this record will <em>not</em> require another review.', + "plain_message": None, + "md_message": None, + }, + } + + invenio_config_tuw.tasks.broadcast_notification.assert_called_once_with( + expected_args + ) def test_record_metadata_current_user_as_creator(client_with_login): -- GitLab From ad21f638338757ac93b319a41c7b7d78db5a4688 Mon Sep 17 00:00:00 2001 From: Maximilian Moser <maximilian.moser@tuwien.ac.at> Date: Fri, 7 Feb 2025 14:39:51 +0100 Subject: [PATCH 2/4] Add possibility to have prefixed config items override unprefixed ones * this can be very useful for having a single project that is used for both "local" and "containerized" execution, where some configuration items should be slightly different for either (e.g. `SEARCH_HOSTS`) * currently we have this functionality implemented in our `invenio.cfg` but there it is a bit awkward due to the fact that environment variables have not been translated into `app.config` yet --- invenio_config_tuw/config.py | 14 ++++++++++++++ invenio_config_tuw/startup.py | 20 ++++++++++++++++++++ pyproject.toml | 2 ++ 3 files changed, 36 insertions(+) diff --git a/invenio_config_tuw/config.py b/invenio_config_tuw/config.py index bcf2067..a72b647 100644 --- a/invenio_config_tuw/config.py +++ b/invenio_config_tuw/config.py @@ -100,6 +100,20 @@ Functions can be either supplied via reference, or via import string. A value of ``None`` disables this feature. """ +CONFIG_TUW_CONFIG_OVERRIDE_PREFIX = None +"""Prefix to check for when overriding configuration items. + +If this value is set to any string, then configuration items whose keys start +with this prefix will be used to override the values for configuration items with +the same name (but without the prefix). + +Example: +If the value is set to "CONTAINERIZED_", then the value of "CONTAINERIZED_SEARCH_HOSTS" +will override the value of "SEARCH_HOSTS". + +A value of `None` (the default) will disable this feature. +""" + # Invenio-Mail # ============ diff --git a/invenio_config_tuw/startup.py b/invenio_config_tuw/startup.py index 09e7b8b..d656ba5 100644 --- a/invenio_config_tuw/startup.py +++ b/invenio_config_tuw/startup.py @@ -146,6 +146,26 @@ def override_flask_config(app): app.add_template_global(app.config, "config") +def override_prefixed_config(app): + """Override config items with their prefixed siblings' values. + + The prefix is determined via the config item "CONFIG_TUW_CONFIG_OVERRIDE_PREFIX". + Configuration items with this prefix will override the values for + + If the prefix is set to `None` (the default), then this feature will be disabled. + """ + prefix = app.config.get("CONFIG_TUW_CONFIG_OVERRIDE_PREFIX", None) + if prefix is None: + return + + prefix_len = len(prefix) + pairs = [(k, v) for k, v in app.config.items() if k.startswith(prefix)] + + for key, value in pairs: + key = key[prefix_len:] + app.config[key] = value + + def patch_flask_create_url_adapter(app): """Patch Flask's {host,subdomain}_matching with 3.1 behavior. diff --git a/pyproject.toml b/pyproject.toml index 0248b02..426f619 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,7 @@ invenio_config_tuw_search_drafts = "invenio_config_tuw.startup:override_search_d invenio_config_tuw_curation_settings = "invenio_config_tuw.startup:register_menu_entries" invenio_config_tuw_curation_request = "invenio_config_tuw.startup:customize_curation_request_type" invenio_config_tuw_flask_config = "invenio_config_tuw.startup:override_flask_config" +invenio_config_tuw_override_prefixed_config = "invenio_config_tuw.startup:override_prefixed_config" invenio_config_tuw_patch_flask = "invenio_config_tuw.startup:patch_flask_create_url_adapter" [project.entry-points."invenio_base.api_finalize_app"] @@ -89,6 +90,7 @@ invenio_config_tuw_mail_handler = "invenio_config_tuw.startup:register_smtp_erro invenio_config_tuw_search_drafts = "invenio_config_tuw.startup:override_search_drafts_options" invenio_config_tuw_curation_request = "invenio_config_tuw.startup:customize_curation_request_type" invenio_config_tuw_flask_config = "invenio_config_tuw.startup:override_flask_config" +invenio_config_tuw_override_prefixed_config = "invenio_config_tuw.startup:override_prefixed_config" invenio_config_tuw_patch_flask = "invenio_config_tuw.startup:patch_flask_create_url_adapter" [project.entry-points."invenio_i18n.translations"] -- GitLab From 5ce7d546631b62b378af2a84af76e9f1acbcd90f Mon Sep 17 00:00:00 2001 From: Maximilian Moser <maximilian.moser@tuwien.ac.at> Date: Fri, 7 Feb 2025 13:56:41 +0100 Subject: [PATCH 3/4] Add secondary email to notification preferences * this enables users to set an alternative email address to which notifications will be sent, in case they don't like to use their institutional email address --- invenio_config_tuw/config.py | 3 + invenio_config_tuw/notifications/backends.py | 7 ++ invenio_config_tuw/users/preferences.py | 15 ++- invenio_config_tuw/users/schemas.py | 12 +- .../templates/notifications_settings.html | 111 ++++++++++++++++++ invenio_config_tuw/users/views.py | 30 ++++- 6 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 invenio_config_tuw/users/templates/notifications_settings.html diff --git a/invenio_config_tuw/config.py b/invenio_config_tuw/config.py index a72b647..899edbc 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 fd31231..c4ad17c 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 14ac06c..00612b6 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 1085798..3134866 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 0000000..51793b3 --- /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 5b0c350..a2f3a52 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, + ) -- GitLab From a7e057c7b32f7cc6afcf345b500ef64329c1eaf5 Mon Sep 17 00:00:00 2001 From: Maximilian Moser <maximilian.moser@tuwien.ac.at> Date: Sat, 8 Feb 2025 11:58:14 +0100 Subject: [PATCH 4/4] Bump version to v2025.1.4 --- CHANGES.rst | 4 +++- invenio_config_tuw/__init__.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a4b894e..60d22ec 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,9 +9,11 @@ Changes ======= -Version <next> +Version 2025.1.4 (released 2025-02-08) - Rename task `send_publication_notification_email` and make it use notifications +- Enable overriding config items with values from prefixed variants +- Add possibility to set a secondary email address for user notifications Version 2025.1.3 (released 2025-02-06) diff --git a/invenio_config_tuw/__init__.py b/invenio_config_tuw/__init__.py index 5d70276..970b424 100644 --- a/invenio_config_tuw/__init__.py +++ b/invenio_config_tuw/__init__.py @@ -9,6 +9,6 @@ from .ext import InvenioConfigTUW -__version__ = "2025.1.3" +__version__ = "2025.1.4" __all__ = ("__version__", "InvenioConfigTUW") -- GitLab