From 189315d01ff598ce85c70a146e38744d80c25829 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Thu, 9 Mar 2023 11:39:30 +0100 Subject: [PATCH 01/10] notifications: add user preferences filter schemas: add notifications schema --- invenio_users_resources/notifications.py | 27 +++++++++++++++++++++ invenio_users_resources/services/schemas.py | 6 +++++ 2 files changed, 33 insertions(+) create mode 100644 invenio_users_resources/notifications.py diff --git a/invenio_users_resources/notifications.py b/invenio_users_resources/notifications.py new file mode 100644 index 0000000..7e048a2 --- /dev/null +++ b/invenio_users_resources/notifications.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2023 Graz University of Technology. +# +# Invenio-Users-Resources is free software; you can redistribute it and/or +# modify it under the terms of the MIT License; see LICENSE file for more +# details. + +"""User specific resources for notifications.""" + + +from invenio_records_resources.notifications import RecipientFilter + + +class UserPreferencesRecipientFilter(RecipientFilter): + """Recipient filter for notifications being enabled at all.""" + + def run(self, recipients, **kwargs): + """Filter recipients.""" + return [ + r + for r in recipients + if r.get("user", {}) + .get("preferences", {}) + .get("notifications", {}) + .get("enabled", False) + ] diff --git a/invenio_users_resources/services/schemas.py b/invenio_users_resources/services/schemas.py index 9ad7a5a..315d775 100644 --- a/invenio_users_resources/services/schemas.py +++ b/invenio_users_resources/services/schemas.py @@ -91,3 +91,9 @@ class UserGhostSchema(Schema): dump_only=True, ) is_ghost = fields.Boolean(dump_only=True) + + +class NotificationPreferences(Schema): + """Schema for notification preferences.""" + + enabled: fields.Bool() From a0de8de931f487b0db68b508c811f89515c8a18f Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Thu, 13 Apr 2023 11:38:56 +0200 Subject: [PATCH 02/10] notifications: add user recipient and user email backend --- invenio_users_resources/notifications.py | 41 ++++++++++++++++++-- tests/test_notifications.py | 49 ++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 tests/test_notifications.py diff --git a/invenio_users_resources/notifications.py b/invenio_users_resources/notifications.py index 7e048a2..d14054c 100644 --- a/invenio_users_resources/notifications.py +++ b/invenio_users_resources/notifications.py @@ -9,19 +9,52 @@ """User specific resources for notifications.""" -from invenio_records_resources.notifications import RecipientFilter +from invenio_accounts.models import User +from invenio_notifications.models import Recipient +from invenio_notifications.services.filters import RecipientFilter class UserPreferencesRecipientFilter(RecipientFilter): """Recipient filter for notifications being enabled at all.""" - def run(self, recipients, **kwargs): + @classmethod + def __call___(cls, notification, recipients): """Filter recipients.""" - return [ + recipients = [ r for r in recipients - if r.get("user", {}) + if r.get("data", {}) .get("preferences", {}) .get("notifications", {}) .get("enabled", False) ] + return recipients + + +class UserRecipient: + def __init__(self, key): + """Ctor.""" + self.key = key + + def __call__(self, notification, recipients): + """Update required recipient information and add backend id.""" + user = notification[self.key] + if isinstance(user, User): + if user.preferences["notifications"]["enabled"]: + recipients[user.id] = Recipient(data=user.dump()) + else: + recipients[user.get("id")] = Recipient(data=user) + return recipients + + +class UserEmailBackend: + def __call__(self, notification, recipient): + """Update required recipient information and add backend id.""" + return "email" + # user = recipient.data + # rec.backends.append( + # { + # "backend": "email", + # "to": f"{user.profile.full_name} <{user.email}>", + # } + # ) diff --git a/tests/test_notifications.py b/tests/test_notifications.py new file mode 100644 index 0000000..5a06789 --- /dev/null +++ b/tests/test_notifications.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2023 Graz University of Technology. +# +# Invenio-Users-Resources is free software; you can redistribute it and/or +# modify it under the terms of the MIT License; see LICENSE file for more +# details. + +"""Notification related tests.""" + +from invenio_users_resources.notifications import ( + UserPreferencesRecipientFilter, +) +from invenio_users_resources.records.api import UserAggregate + + +# def test_user_recipient_filter(user_pub): +# """Test user recipient filter for notifications.""" +# preferences_filter = UserPreferencesRecipientFilter + +# u = UserAggregate.from_user(user_pub.user).dumps() + +# user_notifications_enabled = u.copy() +# user_notifications_enabled["preferences"]["notifications"] = { +# "enabled": True +# } +# user_notifications_disabled = u.copy() +# user_notifications_disabled["preferences"]["notifications"] = { +# "enabled": False +# } + +# recipient_enabled = { +# "user": user_notifications_enabled, +# "backends": [], +# } +# recipient_disabled = { +# "user": user_notifications_disabled, +# "backends": [], +# } + +# filtered_users = preferences_filter.run( +# [ +# recipient_disabled, +# recipient_enabled, +# ] +# ) + +# assert 1 == len(filtered_users) +# assert recipient_enabled == filtered_users[0]["user"] From 374964d90927b901711adfd061fe443b36a58a30 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Wed, 19 Apr 2023 13:53:10 +0200 Subject: [PATCH 03/10] resolvers: add UserResultItemResolver notifications: update UserPreferencesFilter to work with dict tests: add test for UserPreferencesFilter --- invenio_users_resources/entity_resolvers.py | 15 +++++ invenio_users_resources/notifications.py | 42 +++++++------ tests/test_notifications.py | 66 ++++++++++----------- 3 files changed, 68 insertions(+), 55 deletions(-) diff --git a/invenio_users_resources/entity_resolvers.py b/invenio_users_resources/entity_resolvers.py index b2f22e5..d0449ca 100644 --- a/invenio_users_resources/entity_resolvers.py +++ b/invenio_users_resources/entity_resolvers.py @@ -16,6 +16,7 @@ from invenio_records_resources.references.entity_resolvers import ( EntityProxy, EntityResolver, + ResultItemResolver, ) from .proxies import current_users_service @@ -83,3 +84,17 @@ def matches_entity(self, entity): def _get_entity_proxy(self, ref_dict): """Return a UserProxy for the given reference dict.""" return UserProxy(self, ref_dict) + + +class UserResultItemResolver(ResultItemResolver): + """Resolver for user result items.""" + + type_id = "user" + + def __init__(self): + """Ctor.""" + super().__init__( + UsersServiceConfig.result_item_cls, + UsersServiceConfig.service_id, + type_key=self.type_id, + ) diff --git a/invenio_users_resources/notifications.py b/invenio_users_resources/notifications.py index d14054c..d3b24b9 100644 --- a/invenio_users_resources/notifications.py +++ b/invenio_users_resources/notifications.py @@ -9,48 +9,52 @@ """User specific resources for notifications.""" -from invenio_accounts.models import User from invenio_notifications.models import Recipient +from invenio_notifications.services.builders import RecipientBackendGenerator from invenio_notifications.services.filters import RecipientFilter +from invenio_records.dictutils import dict_lookup class UserPreferencesRecipientFilter(RecipientFilter): """Recipient filter for notifications being enabled at all.""" - @classmethod - def __call___(cls, notification, recipients): + def __call__(self, notification, recipients): """Filter recipients.""" - recipients = [ - r - for r in recipients - if r.get("data", {}) - .get("preferences", {}) - .get("notifications", {}) - .get("enabled", False) - ] + for key in list(recipients.keys()): + r = recipients[key] + if not ( + r.data.get("preferences", {}) + .get("notifications", {}) + .get("enabled", False) + ): + del recipients[key] + return recipients class UserRecipient: + """User recipient generator for a notification.""" + def __init__(self, key): """Ctor.""" self.key = key def __call__(self, notification, recipients): """Update required recipient information and add backend id.""" - user = notification[self.key] - if isinstance(user, User): - if user.preferences["notifications"]["enabled"]: - recipients[user.id] = Recipient(data=user.dump()) - else: - recipients[user.get("id")] = Recipient(data=user) + user = dict_lookup(notification.context, self.key) + if user.get("preferences", {}).get("notifications", {}).get("enabled", True): + recipients[user["id"]] = Recipient(data=user) return recipients -class UserEmailBackend: - def __call__(self, notification, recipient): +class UserEmailBackend(RecipientBackendGenerator): + """User related email backend generator for a notification.""" + + def __call__(self, notification, recipient, backends): """Update required recipient information and add backend id.""" + backends.append("email") return "email" + # NOTE: Not sure about the backend payload yet. Is it needed? # user = recipient.data # rec.backends.append( # { diff --git a/tests/test_notifications.py b/tests/test_notifications.py index 5a06789..21a11a4 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -8,42 +8,36 @@ """Notification related tests.""" -from invenio_users_resources.notifications import ( - UserPreferencesRecipientFilter, -) +from copy import deepcopy + +from invenio_notifications.models import Notification, Recipient + +from invenio_users_resources.notifications import UserPreferencesRecipientFilter from invenio_users_resources.records.api import UserAggregate -# def test_user_recipient_filter(user_pub): -# """Test user recipient filter for notifications.""" -# preferences_filter = UserPreferencesRecipientFilter - -# u = UserAggregate.from_user(user_pub.user).dumps() - -# user_notifications_enabled = u.copy() -# user_notifications_enabled["preferences"]["notifications"] = { -# "enabled": True -# } -# user_notifications_disabled = u.copy() -# user_notifications_disabled["preferences"]["notifications"] = { -# "enabled": False -# } - -# recipient_enabled = { -# "user": user_notifications_enabled, -# "backends": [], -# } -# recipient_disabled = { -# "user": user_notifications_disabled, -# "backends": [], -# } - -# filtered_users = preferences_filter.run( -# [ -# recipient_disabled, -# recipient_enabled, -# ] -# ) - -# assert 1 == len(filtered_users) -# assert recipient_enabled == filtered_users[0]["user"] +def test_user_recipient_filter(user_pub): + """Test user recipient filter for notifications.""" + preferences_filter = UserPreferencesRecipientFilter() + + u = UserAggregate.from_user(user_pub.user).dumps() + + user_notifications_enabled = deepcopy(u) + user_notifications_enabled["preferences"]["notifications"] = {"enabled": True} + user_notifications_disabled = deepcopy(u) + user_notifications_disabled["preferences"]["notifications"] = {"enabled": False} + + n = Notification(type="", context={}) + recipient_enabled = Recipient(data=user_notifications_enabled) + recipient_disabled = Recipient(data=user_notifications_disabled) + + filtered_recipients = preferences_filter( + notification=n, + recipients={ + user_notifications_disabled["id"]: recipient_disabled, + user_notifications_enabled["id"]: recipient_enabled, + }, + ) + + assert 1 == len(filtered_recipients) + assert recipient_enabled == filtered_recipients[user_notifications_enabled["id"]] From 2dcc0c410746e0f28a7ab57718d61d27c0258f75 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Thu, 27 Apr 2023 12:06:06 +0200 Subject: [PATCH 04/10] setup: add invenio-notifications as dependency resolvers: refactor import --- invenio_users_resources/entity_resolvers.py | 7 +++--- invenio_users_resources/notifications.py | 24 +++++++++------------ setup.cfg | 1 + 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/invenio_users_resources/entity_resolvers.py b/invenio_users_resources/entity_resolvers.py index d0449ca..7c77d10 100644 --- a/invenio_users_resources/entity_resolvers.py +++ b/invenio_users_resources/entity_resolvers.py @@ -16,7 +16,7 @@ from invenio_records_resources.references.entity_resolvers import ( EntityProxy, EntityResolver, - ResultItemResolver, + ServiceResultResolver, ) from .proxies import current_users_service @@ -86,7 +86,7 @@ def _get_entity_proxy(self, ref_dict): return UserProxy(self, ref_dict) -class UserResultItemResolver(ResultItemResolver): +class UserResultItemResolver(ServiceResultResolver): """Resolver for user result items.""" type_id = "user" @@ -94,7 +94,6 @@ class UserResultItemResolver(ResultItemResolver): def __init__(self): """Ctor.""" super().__init__( - UsersServiceConfig.result_item_cls, - UsersServiceConfig.service_id, + service_id=UsersServiceConfig.service_id, type_key=self.type_id, ) diff --git a/invenio_users_resources/notifications.py b/invenio_users_resources/notifications.py index d3b24b9..ab0dceb 100644 --- a/invenio_users_resources/notifications.py +++ b/invenio_users_resources/notifications.py @@ -9,8 +9,12 @@ """User specific resources for notifications.""" +from invenio_notifications.backends.email import EmailNotificationBackend from invenio_notifications.models import Recipient -from invenio_notifications.services.builders import RecipientBackendGenerator +from invenio_notifications.services.builders import ( + RecipientBackendGenerator, + RecipientGenerator, +) from invenio_notifications.services.filters import RecipientFilter from invenio_records.dictutils import dict_lookup @@ -32,7 +36,7 @@ def __call__(self, notification, recipients): return recipients -class UserRecipient: +class UserRecipient(RecipientGenerator): """User recipient generator for a notification.""" def __init__(self, key): @@ -42,8 +46,7 @@ def __init__(self, key): def __call__(self, notification, recipients): """Update required recipient information and add backend id.""" user = dict_lookup(notification.context, self.key) - if user.get("preferences", {}).get("notifications", {}).get("enabled", True): - recipients[user["id"]] = Recipient(data=user) + recipients[user["id"]] = Recipient(data=user) return recipients @@ -52,13 +55,6 @@ class UserEmailBackend(RecipientBackendGenerator): def __call__(self, notification, recipient, backends): """Update required recipient information and add backend id.""" - backends.append("email") - return "email" - # NOTE: Not sure about the backend payload yet. Is it needed? - # user = recipient.data - # rec.backends.append( - # { - # "backend": "email", - # "to": f"{user.profile.full_name} <{user.email}>", - # } - # ) + backend_id = EmailNotificationBackend.id + backends.append(backend_id) + return backend_id diff --git a/setup.cfg b/setup.cfg index aa6604f..280decb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,6 +29,7 @@ zip_safe = False install_requires = invenio-accounts>=2.2.0,<3.0.0 invenio-i18n>=2.0.0 + invenio-notifications>=0.1.0,<1.0.0 invenio-oauthclient>=2.2.0,<3.0.0 invenio-records-resources>=4.0.0,<5.0.0 From f61d1b6635c5f30fc7759881455fd4c6771ae6ec Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Fri, 28 Apr 2023 11:24:52 +0200 Subject: [PATCH 05/10] resolvers: remove UserResultItemResolver This class did not provide any additional implementation details and could be easily reconstructed. --- invenio_users_resources/entity_resolvers.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/invenio_users_resources/entity_resolvers.py b/invenio_users_resources/entity_resolvers.py index 7c77d10..b2f22e5 100644 --- a/invenio_users_resources/entity_resolvers.py +++ b/invenio_users_resources/entity_resolvers.py @@ -16,7 +16,6 @@ from invenio_records_resources.references.entity_resolvers import ( EntityProxy, EntityResolver, - ServiceResultResolver, ) from .proxies import current_users_service @@ -84,16 +83,3 @@ def matches_entity(self, entity): def _get_entity_proxy(self, ref_dict): """Return a UserProxy for the given reference dict.""" return UserProxy(self, ref_dict) - - -class UserResultItemResolver(ServiceResultResolver): - """Resolver for user result items.""" - - type_id = "user" - - def __init__(self): - """Ctor.""" - super().__init__( - service_id=UsersServiceConfig.service_id, - type_key=self.type_id, - ) From 2bcebecde19d48ca6fff8fbac51c11ac09472840 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Thu, 4 May 2023 08:36:45 +0200 Subject: [PATCH 06/10] notifications: move UserEmailBackend to invenio-notifications --- invenio_users_resources/notifications.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/invenio_users_resources/notifications.py b/invenio_users_resources/notifications.py index ab0dceb..ddbca42 100644 --- a/invenio_users_resources/notifications.py +++ b/invenio_users_resources/notifications.py @@ -9,12 +9,8 @@ """User specific resources for notifications.""" -from invenio_notifications.backends.email import EmailNotificationBackend from invenio_notifications.models import Recipient -from invenio_notifications.services.builders import ( - RecipientBackendGenerator, - RecipientGenerator, -) +from invenio_notifications.services.builders import RecipientGenerator from invenio_notifications.services.filters import RecipientFilter from invenio_records.dictutils import dict_lookup @@ -48,13 +44,3 @@ def __call__(self, notification, recipients): user = dict_lookup(notification.context, self.key) recipients[user["id"]] = Recipient(data=user) return recipients - - -class UserEmailBackend(RecipientBackendGenerator): - """User related email backend generator for a notification.""" - - def __call__(self, notification, recipient, backends): - """Update required recipient information and add backend id.""" - backend_id = EmailNotificationBackend.id - backends.append(backend_id) - return backend_id From 05da4be80a5ae50f88e08a424d3b49acc8b97954 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Thu, 4 May 2023 08:39:39 +0200 Subject: [PATCH 07/10] mappings: add notifications to mappings records: add default value for notifications when parsing user data --- invenio_users_resources/records/api.py | 6 ++++++ .../records/mappings/os-v1/users/user-v1.0.0.json | 7 +++++++ .../records/mappings/os-v2/users/user-v1.0.0.json | 7 +++++++ .../records/mappings/v7/users/user-v1.0.0.json | 9 ++++++++- invenio_users_resources/services/schemas.py | 2 +- 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/invenio_users_resources/records/api.py b/invenio_users_resources/records/api.py index 320449d..5947b86 100644 --- a/invenio_users_resources/records/api.py +++ b/invenio_users_resources/records/api.py @@ -40,6 +40,12 @@ def parse_user_data(user): default_locale = current_app.config.get("BABEL_DEFAULT_LOCALE", "en") data["preferences"].setdefault("locale", default_locale) data["preferences"].setdefault("timezone", "Europe/Zurich") + data["preferences"].setdefault( + "notifications", + { + "enabled": True, + }, + ) return data diff --git a/invenio_users_resources/records/mappings/os-v1/users/user-v1.0.0.json b/invenio_users_resources/records/mappings/os-v1/users/user-v1.0.0.json index 183e575..92480e7 100644 --- a/invenio_users_resources/records/mappings/os-v1/users/user-v1.0.0.json +++ b/invenio_users_resources/records/mappings/os-v1/users/user-v1.0.0.json @@ -85,6 +85,13 @@ "timezone": { "type": "keyword", "index":"false" + }, + "notifications": { + "properties": { + "enabled": { + "type": "boolean" + } + } } }, "dynamic": true diff --git a/invenio_users_resources/records/mappings/os-v2/users/user-v1.0.0.json b/invenio_users_resources/records/mappings/os-v2/users/user-v1.0.0.json index 183e575..92480e7 100644 --- a/invenio_users_resources/records/mappings/os-v2/users/user-v1.0.0.json +++ b/invenio_users_resources/records/mappings/os-v2/users/user-v1.0.0.json @@ -85,6 +85,13 @@ "timezone": { "type": "keyword", "index":"false" + }, + "notifications": { + "properties": { + "enabled": { + "type": "boolean" + } + } } }, "dynamic": true diff --git a/invenio_users_resources/records/mappings/v7/users/user-v1.0.0.json b/invenio_users_resources/records/mappings/v7/users/user-v1.0.0.json index 86f8f89..880ec16 100644 --- a/invenio_users_resources/records/mappings/v7/users/user-v1.0.0.json +++ b/invenio_users_resources/records/mappings/v7/users/user-v1.0.0.json @@ -85,10 +85,17 @@ "timezone": { "type": "keyword", "index": "false" + }, + "notifications": { + "properties": { + "enabled": { + "type": "boolean" + } + } } }, "dynamic": true } } } -} \ No newline at end of file +} diff --git a/invenio_users_resources/services/schemas.py b/invenio_users_resources/services/schemas.py index 315d775..6e7ef27 100644 --- a/invenio_users_resources/services/schemas.py +++ b/invenio_users_resources/services/schemas.py @@ -96,4 +96,4 @@ class UserGhostSchema(Schema): class NotificationPreferences(Schema): """Schema for notification preferences.""" - enabled: fields.Bool() + enabled = fields.Bool() From d21fd1dd4626c4425e6da1c5c97d1e9d59ba89df Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Thu, 4 May 2023 08:41:19 +0200 Subject: [PATCH 08/10] tests: add and adapt notification test cases --- tests/conftest.py | 56 ++++++++++++++++++++++++++++ tests/test_notifications.py | 74 ++++++++++++++++++++++++++++--------- 2 files changed, 113 insertions(+), 17 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index df81da7..eca4183 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ # Copyright (C) 2022 TU Wien. # Copyright (C) 2022 European Union. # Copyright (C) 2022 CERN. +# Copyright (C) 2023 Graz University of Technology. # # Invenio-Users-Resources is free software; you can redistribute it and/or # modify it under the terms of the MIT License; see LICENSE file for more @@ -19,16 +20,27 @@ from invenio_access.permissions import any_user as any_user_need from invenio_accounts.proxies import current_datastore from invenio_app.factory import create_api +from marshmallow import fields from invenio_users_resources.proxies import ( current_groups_service, current_users_service, ) from invenio_users_resources.records import GroupAggregate, UserAggregate +from invenio_users_resources.services.schemas import ( + NotificationPreferences, + UserPreferencesSchema, +) pytest_plugins = ("celery.contrib.pytest",) +class UserPreferencesNotificationsSchema(UserPreferencesSchema): + """Schema extending preferences with notification preferences.""" + + notifications = fields.Nested(NotificationPreferences) + + # # Application # @@ -43,6 +55,8 @@ def app_config(app_config): ] = "invenio_jsonschemas.proxies.current_refresolver_store" # Variable not used. We set it to silent warnings app_config["JSONSCHEMAS_HOST"] = "not-used" + # setting preferences schema to test notifications + app_config["ACCOUNTS_USER_PREFERENCES_SCHEMA"] = UserPreferencesNotificationsSchema return app_config @@ -146,6 +160,36 @@ def users_data(): }, "active": False, }, + { + "username": "notification_enabled", + "email": "notification-enabled@inveniosoftware.org", + "profile": { + "full_name": "Mr. Worldwide", + "affiliations": "World", + }, + "preferences": { + "visibility": "restricted", + "email_visibility": "public", + "notifications": { + "enabled": True, + }, + }, + }, + { + "username": "notification_disabled", + "email": "notification-disabled@inveniosoftware.org", + "profile": { + "full_name": "Loner", + "affiliations": "Home", + }, + "preferences": { + "visibility": "restricted", + "email_visibility": "public", + "notifications": { + "enabled": False, + }, + }, + }, ] @@ -226,3 +270,15 @@ def user_inactive(users): def user_unconfirmed(users): """Unconfirmed user.""" return users["unconfirmed"] + + +@pytest.fixture(scope="module") +def user_notification_enabled(users): + """User with notfications enabled.""" + return users["notification_enabled"] + + +@pytest.fixture(scope="module") +def user_notification_disabled(users): + """User with notfications disabled.""" + return users["notification_disabled"] diff --git a/tests/test_notifications.py b/tests/test_notifications.py index 21a11a4..6876bb1 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -8,35 +8,75 @@ """Notification related tests.""" -from copy import deepcopy - from invenio_notifications.models import Notification, Recipient -from invenio_users_resources.notifications import UserPreferencesRecipientFilter +from invenio_users_resources.notifications import ( + UserPreferencesRecipientFilter, + UserRecipient, +) from invenio_users_resources.records.api import UserAggregate -def test_user_recipient_filter(user_pub): - """Test user recipient filter for notifications.""" - preferences_filter = UserPreferencesRecipientFilter() +def test_user_recipient_generator(user_notification_disabled, user_notification_enabled): + generator_disabled = UserRecipient(key="disabled") + generator_enabled = UserRecipient(key="enabled") + + user_notifications_disabled = UserAggregate.from_user(user_notification_disabled.user).dumps() + user_notifications_enabled = UserAggregate.from_user( + user_notification_enabled.user + ).dumps() + + n = Notification( + type="", + context={ + "disabled": user_notifications_disabled, + "enabled": user_notifications_enabled, + }, + ) + + recipients = {} - u = UserAggregate.from_user(user_pub.user).dumps() + generator_disabled(n, recipients=recipients) + assert 1 == len(recipients) + assert user_notifications_disabled["id"] in recipients.keys() - user_notifications_enabled = deepcopy(u) - user_notifications_enabled["preferences"]["notifications"] = {"enabled": True} - user_notifications_disabled = deepcopy(u) - user_notifications_disabled["preferences"]["notifications"] = {"enabled": False} + generator_enabled(n, recipients=recipients) + assert 2 == len(recipients) + assert [ + user_notifications_disabled["id"], + user_notifications_enabled["id"], + ] == list(recipients) + + # checking that user does not get added twice + generator_disabled(n, recipients=recipients) + assert 2 == len(recipients) + assert [ + user_notifications_disabled["id"], + user_notifications_enabled["id"], + ] == list(recipients) + + +def test_user_recipient_filter(user_notification_disabled, user_notification_enabled): + """Test user recipient filter for notifications.""" + + user_notifications_disabled = UserAggregate.from_user(user_notification_disabled.user).dumps() + user_notifications_enabled = UserAggregate.from_user( + user_notification_enabled.user + ).dumps() n = Notification(type="", context={}) - recipient_enabled = Recipient(data=user_notifications_enabled) - recipient_disabled = Recipient(data=user_notifications_disabled) + recipient_enabled = Recipient(data=user_notifications_disabled) + recipient_disabled = Recipient(data=user_notifications_enabled) + + recipients = { + user_notifications_disabled["id"]: recipient_disabled, + user_notifications_enabled["id"]: recipient_enabled, + } + preferences_filter = UserPreferencesRecipientFilter() filtered_recipients = preferences_filter( notification=n, - recipients={ - user_notifications_disabled["id"]: recipient_disabled, - user_notifications_enabled["id"]: recipient_enabled, - }, + recipients=recipients, ) assert 1 == len(filtered_recipients) From 8a53158fb73ae365b6070ef31456b8f1ba7ea293 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Fri, 5 May 2023 10:30:07 +0200 Subject: [PATCH 09/10] tests: fix notification tests --- tests/test_notifications.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/test_notifications.py b/tests/test_notifications.py index 6876bb1..736585e 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -17,11 +17,15 @@ from invenio_users_resources.records.api import UserAggregate -def test_user_recipient_generator(user_notification_disabled, user_notification_enabled): +def test_user_recipient_generator( + user_notification_disabled, user_notification_enabled +): generator_disabled = UserRecipient(key="disabled") generator_enabled = UserRecipient(key="enabled") - user_notifications_disabled = UserAggregate.from_user(user_notification_disabled.user).dumps() + user_notifications_disabled = UserAggregate.from_user( + user_notification_disabled.user + ).dumps() user_notifications_enabled = UserAggregate.from_user( user_notification_enabled.user ).dumps() @@ -59,14 +63,16 @@ def test_user_recipient_generator(user_notification_disabled, user_notification_ def test_user_recipient_filter(user_notification_disabled, user_notification_enabled): """Test user recipient filter for notifications.""" - user_notifications_disabled = UserAggregate.from_user(user_notification_disabled.user).dumps() + user_notifications_disabled = UserAggregate.from_user( + user_notification_disabled.user + ).dumps() user_notifications_enabled = UserAggregate.from_user( user_notification_enabled.user ).dumps() n = Notification(type="", context={}) - recipient_enabled = Recipient(data=user_notifications_disabled) - recipient_disabled = Recipient(data=user_notifications_enabled) + recipient_enabled = Recipient(data=user_notifications_enabled) + recipient_disabled = Recipient(data=user_notifications_disabled) recipients = { user_notifications_disabled["id"]: recipient_disabled, From f79874958fa370ae21b59e2bc9dc5971b436f2a1 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Fri, 5 May 2023 12:46:42 +0200 Subject: [PATCH 10/10] notifications: split classes into respective files --- .../notifications/__init__.py | 9 ++++++ .../filters.py} | 20 +------------ .../notifications/generators.py | 28 +++++++++++++++++++ tests/test_notifications.py | 6 ++-- 4 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 invenio_users_resources/notifications/__init__.py rename invenio_users_resources/{notifications.py => notifications/filters.py} (55%) create mode 100644 invenio_users_resources/notifications/generators.py diff --git a/invenio_users_resources/notifications/__init__.py b/invenio_users_resources/notifications/__init__.py new file mode 100644 index 0000000..c856e17 --- /dev/null +++ b/invenio_users_resources/notifications/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2023 Graz University of Technology. +# +# Invenio-Users-Resources is free software; you can redistribute it and/or +# modify it under the terms of the MIT License; see LICENSE file for more +# details. + +"""Notification related files for user purposes.""" diff --git a/invenio_users_resources/notifications.py b/invenio_users_resources/notifications/filters.py similarity index 55% rename from invenio_users_resources/notifications.py rename to invenio_users_resources/notifications/filters.py index ddbca42..2ca16f3 100644 --- a/invenio_users_resources/notifications.py +++ b/invenio_users_resources/notifications/filters.py @@ -6,13 +6,9 @@ # modify it under the terms of the MIT License; see LICENSE file for more # details. -"""User specific resources for notifications.""" +"""User specific filters for notifications.""" - -from invenio_notifications.models import Recipient -from invenio_notifications.services.builders import RecipientGenerator from invenio_notifications.services.filters import RecipientFilter -from invenio_records.dictutils import dict_lookup class UserPreferencesRecipientFilter(RecipientFilter): @@ -30,17 +26,3 @@ def __call__(self, notification, recipients): del recipients[key] return recipients - - -class UserRecipient(RecipientGenerator): - """User recipient generator for a notification.""" - - def __init__(self, key): - """Ctor.""" - self.key = key - - def __call__(self, notification, recipients): - """Update required recipient information and add backend id.""" - user = dict_lookup(notification.context, self.key) - recipients[user["id"]] = Recipient(data=user) - return recipients diff --git a/invenio_users_resources/notifications/generators.py b/invenio_users_resources/notifications/generators.py new file mode 100644 index 0000000..c995f44 --- /dev/null +++ b/invenio_users_resources/notifications/generators.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2023 Graz University of Technology. +# +# Invenio-Users-Resources is free software; you can redistribute it and/or +# modify it under the terms of the MIT License; see LICENSE file for more +# details. + +"""User specific generators for notifications.""" + + +from invenio_notifications.models import Recipient +from invenio_notifications.services.generators import RecipientGenerator +from invenio_records.dictutils import dict_lookup + + +class UserRecipient(RecipientGenerator): + """User recipient generator for a notification.""" + + def __init__(self, key): + """Ctor.""" + self.key = key + + def __call__(self, notification, recipients): + """Update required recipient information and add backend id.""" + user = dict_lookup(notification.context, self.key) + recipients[user["id"]] = Recipient(data=user) + return recipients diff --git a/tests/test_notifications.py b/tests/test_notifications.py index 736585e..5691fc5 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -10,10 +10,8 @@ from invenio_notifications.models import Notification, Recipient -from invenio_users_resources.notifications import ( - UserPreferencesRecipientFilter, - UserRecipient, -) +from invenio_users_resources.notifications.filters import UserPreferencesRecipientFilter +from invenio_users_resources.notifications.generators import UserRecipient from invenio_users_resources.records.api import UserAggregate