diff --git a/docs/customize/index.md b/docs/customize/index.md index 0376aee6..27de5c5e 100644 --- a/docs/customize/index.md +++ b/docs/customize/index.md @@ -34,4 +34,3 @@ In the following sections, we cover customization opportunities that InvenioRDM - [Names](vocabularies/names.md) - [Subjects](vocabularies/subjects.md) - [Users](vocabularies/users.md) -- [Notifications](notifications.md) - customize content, recipients and backends of notifications diff --git a/docs/develop/architecture/notifications.md b/docs/develop/architecture/notifications.md new file mode 100644 index 00000000..ff8db45d --- /dev/null +++ b/docs/develop/architecture/notifications.md @@ -0,0 +1,172 @@ +# Notifications + +_Introduced in InvenioRDM v12_ + +The notification system in InvenioRDM is provided via the [`invenio-notifications`](https://github.com/inveniosoftware/invenio-notifications) module. + + +## Service Level +Notifications are registered at the service level in the unit of work and send off to a celery task - which takes care of further processing. When dispatching this notification in the service, the context shall be as minimal as possible, to reduce passing huge chunks of data. +The implications of sending all of the required immediately would be a hit on performance and higher memory usage. +As the notification operations are registered during methods of the service level, it would increase response time for requests using these methods. +As we can offload the extension of the notification to a worker, the initial request can be processed quicker and save some precious time - leading to faster response times. + +This is an example of creating a notification +```py +n = Notification(type="community-submission", context={"request": request.id}) +``` + +After it is build, it can be dumped and send to a background task for further processing. The serialized notification will look like this +```py +{ + "type": "community-submission", + "context": { + "request": 1 + } +} +``` + + +## Notification Manager +A notification manager is created, which will rely on certain [configuration values](../howtos/notifications.md#configuration-values) and provide logic to send notifications. Its task is also to call respective methods to resolve the notification context, generate recipients, filter recipients and generate the backend ids for further processing. With all information created, it will then dispatch further tasks to relay the notification to the backend, which will take care of sending the actual notification. + + + +
+A recipient entity could look like this + +```py +{ + "data": { + "id": "5", + "created": "2023-08-03T12:37:24.353656+00:00", + "updated": "2023-08-03T12:37:24.971301+00:00", + "links": { + "self": "https://127.0.0.1:5000/api/users/5", + "avatar": "https://127.0.0.1:5000/api/users/5/avatar.svg" + }, + "revision_id": 5, + "active": true, + "confirmed": true, + "is_current_user": false, + "email": "newuser@newuser.org", + "username": null, + "profile": { + "full_name": "New User", + "affiliations": "CERN" + }, + "preferences": { + "visibility": "public", + "email_visibility": "restricted", + "locale": "en", + "timezone": "Europe/Zurich", + "notifications": { + "enabled": true + } + } + } +} +``` +
+ +
+After resolving all entities, a notification could look like this + +```py +{ + "type": "community-invitation.submit", + "context": { + "request": { + "id": "372cd107-7b76-4a45-9c10-c339f4c2a3ac", + "created": "2023-08-03T12:32:06.251565+00:00", + "updated": "2023-08-03T12:32:06.260397+00:00", + "links": { + "actions": { + "accept": "https://127.0.0.1:5000/api/requests/372cd107-7b76-4a45-9c10-c339f4c2a3ac/actions/accept", + "decline": "https://127.0.0.1:5000/api/requests/372cd107-7b76-4a45-9c10-c339f4c2a3ac/actions/decline", + "cancel": "https://127.0.0.1:5000/api/requests/372cd107-7b76-4a45-9c10-c339f4c2a3ac/actions/cancel", + "expire": "https://127.0.0.1:5000/api/requests/372cd107-7b76-4a45-9c10-c339f4c2a3ac/actions/expire" + }, + "self": "https://127.0.0.1:5000/api/requests/372cd107-7b76-4a45-9c10-c339f4c2a3ac", + "self_html": "https://127.0.0.1:5000/requests/372cd107-7b76-4a45-9c10-c339f4c2a3ac", + "comments": "https://127.0.0.1:5000/api/requests/372cd107-7b76-4a45-9c10-c339f4c2a3ac/comments", + "timeline": "https://127.0.0.1:5000/api/requests/372cd107-7b76-4a45-9c10-c339f4c2a3ac/timeline" + }, + "revision_id": 2, + "type": "community-invitation", + "title": "Invitation to join \"My Community\"", + "description": "You will join as \"Reader\".", + "number": "2", + "status": "submitted", + "is_closed": false, + "is_open": true, + "expires_at": "2023-09-02T12:32:06.239340+00:00", + "is_expired": false, + "created_by": { + "id": "232d2ae9-ac03-4359-bc8e-9fa95e66ced0", + "created": "2023-08-03T12:32:05.556911+00:00", + "updated": "2023-08-03T12:32:05.595929+00:00", + "links": { + "featured": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0/featured", + "self": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0", + "self_html": "https://127.0.0.1:5000/communities/public", + "settings_html": "https://127.0.0.1:5000/communities/public/settings", + "logo": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0/logo", + "rename": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0/rename", + "members": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0/members", + "public_members": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0/members/public", + "invitations": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0/invitations", + "requests": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0/requests", + "records": "https://127.0.0.1:5000/api/communities/232d2ae9-ac03-4359-bc8e-9fa95e66ced0/records" + }, + "revision_id": 2, + "slug": "public", + "metadata": { + "title": "My Community" + }, + "access": { + "visibility": "public", + "member_policy": "open", + "record_policy": "open", + "review_policy": "closed" + }, + "custom_fields": {} + }, + "receiver": { + "id": "5", + "created": "2023-08-03T12:32:05.683922+00:00", + "updated": "2023-08-03T12:32:06.366896+00:00", + "links": { + "self": "https://127.0.0.1:5000/api/users/5", + "avatar": "https://127.0.0.1:5000/api/users/5/avatar.svg" + }, + "revision_id": 6, + "active": true, + "confirmed": true, + "is_current_user": false, + "email": "newuser@newuser.org", + "username": null, + "profile": { + "full_name": "New User", + "affiliations": "CERN" + }, + "preferences": { + "visibility": "public", + "email_visibility": "restricted", + "locale": "en", + "timezone": "Europe/Zurich", + "notifications": { + "enabled": true + } + } + }, + "topic": { + "community": "232d2ae9-ac03-4359-bc8e-9fa95e66ced0" + } + }, + "role": "Reader", + "message": null + } +} +``` +
diff --git a/docs/customize/notifications.md b/docs/develop/howtos/notifications.md similarity index 78% rename from docs/customize/notifications.md rename to docs/develop/howtos/notifications.md index 4aa96e74..0ec4c03f 100644 --- a/docs/customize/notifications.md +++ b/docs/develop/howtos/notifications.md @@ -1,11 +1,5 @@ -# Notifications - -Depending on your instance needs regarding notifications, you might want to customize the default values provided by the `invenio-notifications` module. - -Specifically, we are going to have a look at templates and the following variables: - -- [NOTIFICATIONS_BACKENDS](#notifications_backends) -- [NOTIFICATIONS_BUILDERS](#notifications_builders) +# How to modify notifications +This guide will show you how you can configure and modify all things related to notifications. ## Templates @@ -141,10 +135,14 @@ In our case, we are happy with the content in the general template and _only_ wa Only the specified blocks will be overriden. Other blocks stay as they are in the base template. -## NOTIFICATIONS_BACKENDS -This config variable allows to specify the available backends. For a detailed description on backends, checkout the respective [reference section](/reference/notifications/#backends). -For instance, you can provide an implementation for your institution's preferred communication tool and send notifications via this backend. For this, simply extend the `NotificationBackend` class and implement the `send` method. + + +## Build your own +This will show you can build your own notification classes. + +### Notification Backend +Building your own notification backend can be achieved in a few steps. For instance, you can provide an implementation for your institution's preferred communication tool and send notifications via this backend. For this, simply extend the `NotificationBackend` class and implement the `send` method. ```py from invenio_notifications.backends import JinjaTemplateLoaderMixin, NotificationBackend, @@ -161,18 +159,7 @@ class InstitutationalBackend(NotificationBackend, JinjaTemplateLoaderMixin): institutation_communication_tool.send_message(user_id=recipient.data["id"], template["md_body"]) ``` -This backend can now be specified (e.g. in `invenio.cfg`): - -```py -NOTIFICATION_BACKENDS = { - EmailNotificationBackend.id: EmailNotificationBackend, - InstitutationalBackend.id: InstitutationalBackend, -} -``` - -## NOTIFICATIONS_BUILDERS - -This config variable defines which builder class should be used for a specific notification type. For detailed description on builders, filters and generators , checkout the respective [reference section](/reference/notifications/#builders-filters-generators). +### Notification Builder Let us assume that you want to override who will get notified in the event of a community record submission (community curator and owner) and add the previously defined backend (so recipients will get notified via whatever the base class has defined and via the `InstitutationalBackend`). To do this, we will create a custom builder, which will inherit most of the properties from the existing base class. @@ -193,10 +180,56 @@ class CustomSubmissionBuilder(CommunityInclusionSubmittedNotificationBuilder): ] ``` -This builder can now be specified (e.g. in `invenio.cfg`): +## Configuration Values + +Configuration values used in the `invenio-notifications` module can be overriden, in order to adapt instances to specific needs. + + +### NOTIFICATION_BACKENDS + +This config variable allows to specify the available backends. For a detailed description on backends, checkout the respective [reference section](/reference/notifications/#backends). +For instance, you can provide an implementation for your institution's preferred communication tool and send notifications via this backend. + +As an example, take the backend shown in [build your own backend](#notification-backend). Then you only have to specify it in the config variable (e.g. in `invenio.cfg`). + +```py +NOTIFICATION_BACKENDS = { + EmailNotificationBackend.id: EmailNotificationBackend, + InstitutationalBackend.id: InstitutationalBackend, +} +``` + +### NOTIFICATION_BUILDERS + +Specifies [notification builders](#notificationbuilder) to be used for certain types of notifications. When a notification is handled by the manager, it will lookup the type in this variable and build the notification with the provided builder class. + +As an example, take the backend shown in [build your own builder](#notification-builder). Then you only have to specify it in the config variable (e.g. in `invenio.cfg`). ```py NOTIFICATIONS_BUILDERS = { CustomSubmissionBuilder.type: CustomSubmissionBuilder, } ``` + +Since the custom builder uses the same `type` as the builder we want to override, we can use either `type` as key for the entry. The following snippet will have the same effect, but it is more clear what will be overriden. + +```py +NOTIFICATIONS_BUILDERS = { + CommunityInclusionSubmittedNotificationBuilder.type: CustomSubmissionBuilder, +} +``` + + +### NOTIFICATIONS_ENTITY_RESOLVERS + +Specifies entity resolvers (not to be confused with [EntityResolve](#entityresolve)) to be used for resolving the notification context. These are usually `ServiceResultResolver` objects, which provide functionality to dump an object to a reference dictionary and later on use the dump to fetch information as seen on the API/service level (i.e. fully resolved objects with links for easy access). + +```py +NOTIFICATIONS_ENTITY_RESOLVERS = [ + RDMRecordServiceResultResolver(), + ServiceResultResolver(service_id="users", type_key="user"), + ServiceResultResolver(service_id="communities", type_key="community"), + ServiceResultResolver(service_id="requests", type_key="request"), + ServiceResultResolver(service_id="request_events", type_key="request_event"), +] +``` diff --git a/docs/develop/index.md b/docs/develop/index.md index 62eca14c..13f7a714 100644 --- a/docs/develop/index.md +++ b/docs/develop/index.md @@ -53,6 +53,7 @@ Step-by-step guides on how to perform certain tasks: - [Fix a vulnerability](howtos/security-fix.md) - [Test emails locally](howtos/dev_email.md) - [Migrate legacy routes](howtos/route_migration.md) +- [Create and configure notifications](howtos/notifications.md) ## Architecture @@ -66,6 +67,7 @@ choices: - [Requests](architecture/requests.md) - [Communities](architecture/communities.md) - [Records](architecture/records.md) +- [Notifications](architecture/notifications.md) - [Recommended reading](architecture/reading.md) ## Concepts diff --git a/docs/reference/notifications.md b/docs/reference/notifications.md index 990f5684..1a2a6e47 100644 --- a/docs/reference/notifications.md +++ b/docs/reference/notifications.md @@ -2,184 +2,64 @@ _Introduced in InvenioRDM v12_ -The notification system in InvenioRDM is provided via the [`invenio-notifications`](https://github.com/inveniosoftware/invenio-notifications) module. -This module provides customization possibilities for: +## Models -- [Notification Backends](#backends): A notification backend is responsible for the actual sending of the notification to a recipient. -- [Notification Builders](#notification_builders): A notification builder specifies builders and filters in order to construct the notification context, fetch recipients, filter recipients and choose which backends should be used to send the notification. -- [Resolvers](#notifications_entity_resolvers): An entity resolver knows how to fetch an entity given its identity. - -A notification manager is created, which will rely on these configuration values and provide logic to send notifications. Its task is also to call respective methods to resolve the notification context, generate recipients, filter recipients and generate the backend ids for further processing. With all information created, it will then dispatch further tasks to relay the notification to the backend, which will take care of sending the actual notification. - -Notifications are registered at the service level in the unit of work and send off to a celery task - which takes care of further processing. - -## Base Entities - -Let's first have a look at the base entities provided. - -### Models - -#### Notification +### Notification A notification is a simple dataclass, holding information about its type and the context. -```py -@dataclass -class Notification: - - type: str - context: dict -``` +#### Attributes -The context attribute of the notification holds information relevant for further processing and will be expanded throughout the workflow. -When dispatching this notification in the service, the context shall be as minimal as possible, to reduce passing huge chunks of data. -The implications of sending all of the required immediately would be a hit on performance and higher memory usage. -As the notification operations are registered during methods of the service level, it would increase response time for requests using these methods. -As we can offload the extension of the notification to a worker, the initial request can be processed quicker and save some precious time - leading to a more usable UI. +- `type` specifies the notification type and is used for template selection as well as the key for notification builders. +- `context` holds information relevant for further processing and will be expanded throughout the workflow. -An example creation of a notification object could be: +#### Methods +- `dumps(self)`: Dumps the object as dict. -```py -n = Notification(type="community-submission", context='{"request": request.id}') -``` +### Recipient -#### Recipient +Simple dataclass, holding information about a recipient. -A recipient is a simple dataclass, holding information about a recipient. +#### Attributes -```py -@dataclass -class Recipient: - data: dict -``` - -The data attribute holds information required for contact purposes (i.e. for a user, this means information about the name, email and notification preferences). +- `data` attribute holds information required for contact purposes (i.e. for a user, this means information about the name, email and notification preferences). An example creation of a recipient object could be: -```py - -u = current_user_service.read(...) -r = Recipient(data=u.to_dict()) -``` - -### Builders, Filters, Generators - -A few classes are in place, to provide a general interface for processing a notification and generate all necessary information. - -#### ContextGenerator -A context generator is supposed to be doing work on as well as extend/expand the notification context. -As an example, this could mean adding a new key/value pair to the context. +#### Methods +- `dumps(self)`: Dumps the object as dict. -It specifies a callable method expecting the notification, which can be modified in place. -_EntityResolve_ +## Builders, Filters, Generators -This is a discrete implementation of the context generator, using the registered entity resolvers of the `invenio-notifications` module to expand the context. It will take a key to do a dictionary look-up and store the resolved entity at the index of the key. +A few classes are in place, to provide a general interface for processing a notification and generate all necessary information. -```py -class EntityResolve(ContextGenerator): - """Payload generator for a notification using the entity resolvers.""" +### ContextGenerator - def __init__(self, key): - """Constructor.""" - self.key = key +A context generator is doing work on as well as extend/expand the notification context. - def __call__(self, notification): - """Update required recipient information and add backend id.""" - entity_ref = dict_lookup(notification.context, self.key) - entity = EntityResolverRegistry.resolve_entity(entity_ref) - dict_set(notification.context, self.key, entity) - return notification -``` -#### RecipientGenerator +### RecipientGenerator A recipient generator will get the fully expanded notification and the recipients from previously ran recipient generators. The task of this generator is to generate recipients (i.e. users, groups) based on the context and add them to the previous recipients. -The recipients received is a map: - -```py -recipients = { - "3": { - "name": "Admin the admin", - "email": "admin@invenio.ch", - ... - }, - ... -} -``` - -Based on the recipients key, this takes care of duplicate entries (i.e. if user with id 3 is added twice, the old value will be overwritten). - -_UserRecipientGenerator_ - -This is a discrete implementation of the recipient generator. Based on the provided key, it will do a lookup in the notification context to get the user data dump. With this information, a new recipient will be created and added to the recipients. - -```py -class UserRecipient(RecipientGenerator): - """User recipient generator for a notification.""" - - def __init__(self, key): - """Constructor.""" - 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 -``` - -#### RecipientFilter - -A recipient filter will get the fully expanded notification and all created recipients. The task of the filter is to filter recipients in place, which do not fulfill a certain requirement. - -_UserPreferencesRecipientFilter_ - -This is a discrete implementation of the recipient filter. It will remove recipients, which do not have notifications enabled via their preferences. -```py -class UserPreferencesRecipientFilter(RecipientFilter): - """Recipient filter for notifications being enabled at all.""" +### RecipientFilter - def __call__(self, notification, recipients): - """Filter recipients.""" - for key in list(recipients.keys()): - r = recipients[key] - if not ( - r.data.get("preferences", {}) - .get("notifications", {}) - .get("enabled", False) - ): - del recipients[key] +A recipient filter will get the fully expanded notification and all created recipients. The task of the filter is to filter recipients in place, based on specified certain criterias. - return recipients -``` -#### RecipientBackendGenerator +### RecipientBackendGenerator A recipient backend generator will get the fully expanded notification, a single recipient and previously created backend ids. The task of this generator is to return the id of the backend, it wants to send the notification to. Additionally, if the backend depends on information, not yet available in the recipient, it can modify the recipient in place and provide this information. -_UserEmailBackend_ -This is a discrete implementation of the recipient backend generator. When called, it will add the id of the email backend to the backends parameter. Since all information needed for sending mails are already available in the recipient, it does not have to do any additonal work. +### NotificationBuilder -```py -class UserEmailBackend(RecipientBackendGenerator): - """User related email backend generator for a notification.""" +Based on specified attributes, it takes care of building the context, recipients, recipient backends and filter recipients. - def __call__(self, notification, recipient, backends): - """Add backend id to backends.""" - backend_id = EmailNotificationBackend.id - backends.append(backend_id) - return backend_id -``` - -#### NotificationBuilder - -A notification builder has following class attributes: +#### Attributes - `context`: List of [ContextGenerator](#contextgenerator) - `recipients`: List of [RecipientBuilder](#recipientgenerator) @@ -187,116 +67,39 @@ A notification builder has following class attributes: - `recipient_backends`: List of [RecipientBackendGenerator](#recipientbackendgenerator) - `type`: Name of the notification it shall build. -Additionally, class methods take care of creating all needed information for sending a notification. Each method will iterate over their respective attribute and return the cumulative result. - -### Backends - -A notification backend is responsible for the actual sending of the notification to a recipient. To complete this task, its `send` method will receive all information required from the notification system. A backend shall not perform any queries. It may render templates based on its own needs and with the notification content provided. - -Each backend shall have an `id`, to distinguish it from others. Backends are registered in the notification manager. - -#### JinjaTemplateLoaderMixin - -`JinjaTemplateLoaderMixin` is supposed to make handling jinja templates easier. It already takes care of loading templates and rendering the blocks inside of them. This mixin will also take care of factoring in the locale and the backend id when choosing the template (i.e. a more specific template will take precedence over a general template). - -#### EmailNotificationBackend - -This is a discrete implemenation of a notification backend. With the help of a [JinjaTemplateLoaderMixin](#jinjatemplateloadermixin), it will render a notification template based on the notification type provided. - -```py -class EmailBackend(Backend): +#### Methods - id = "email" +Class methods take care of creating all needed information for sending a notification. Each method will iterate over their respective attribute and return the cumulative result. - def send(self, notification: Notification, recipient: Recipient): - """Mail sending implementation.""" - content = self.render_template(notification, recipient) - resp = send_email({ - "subject": content["subject"], - "html_body": content["html_body"], - "plain_body": content["plain_body"], - "recipients": [f"{recipient.name} <{recipient.email}>"], - "sender": current_app.config["MAIL_DEFAULT_SENDER"], - }) - return resp -``` +- `build(cls, **kwargs)`: Build notification based on type and additional context. +- `resolve_context(cls, notification)`: Resolve all references in the notification context. +- `build_recipients(cls, notification)`: Return a dictionary of unique recipients for the notification. +- `filter_recipients(cls, notification, recipients)`: Apply filters to the recipients. +- `build_recipient_backends(cls, notification, recipient)`: Return the backends for recipient. -## Templates +## Backends -The Jinja templates provided shall include all parts of the notification that are subject to special formatting by a backend (e.g. subject, HTML/plaintext/markdown body, etc.) in separate Jinja blocks. -Additional backends should provide their own templates, to be as specific as possible. +### Notification Backend +A notification backend is responsible for the actual sending of the notification to a recipient. -This is an example notification +#### Attributes -```jinja -{# notifications/community-submission.submitted.jinja #} +- `id`: Identifier of the backend. -{%- block subject -%} -New record submission for your community {{ notification.context.get("request").get("receiver").get("metadata").get("title") }} submitted by {{ notification.context.get("request").get("created_by").get("username") }} -{%- endblock subject -%} +#### Methods -{%- block html_body -%} -

The record "{{ notification.context.get("request").get("topic").get("metadata").get("title") }}" was submitted to your community {{ notification.context.get("request").get("receiver").get("metadata").get("title") }} by {{ notification.context.get("request").get("created_by").get("username") }}.

+- `send(self, notification, recipient)`: Send the notification message to a recipient and perform any additional tasks required to do so. -Review the request -{%- endblock html_body -%} +### JinjaTemplateLoaderMixin -{%- block plain_body -%} -The record "{{ notification.context.get("request").get("topic").get("metadata").get("title") }}" was submitted to your community {{ notification.context.get("request").get("receiver").get("metadata").get("title") }} by {{ notification.context.get("request").get("created_by").get("username") }}. - -Review the request: {{ notification.context.get("request").get("links").get("self_html") }} -{%- endblock plain_body -%} - -{# Markdown for Slack/Mattermost/chat #} -{%- block md_body -%} -The record "{{ notification.context.get("request").get("topic").get("metadata").get("title") }}" was submitted to your community {{ notification.context.get("request").get("receiver").get("metadata").get("title") }} by {{ notification.context.get("request").get("created_by").get("username")}}. - -[Review the request]({{ notification.context.get("request").get("links").get("self_html") }}) -{%- endblock md_body -%} -``` - -## Configuration Values - -Configuration values used in the `invenio-notifications` module can be overriden, in order to adapt instances to specific needs. - -### NOTIFICATION_BACKENDS - -Specifies available [notification backends](#backends) for sending notifications. - -```py -NOTIFICATIONS_BACKENDS = { - "email": EmailBackend, - "cern": CERNNotificationsBackend, - "slack": SlackBackend, - } -``` - -### NOTIFICATION_BUILDERS - -Specifies [notification builders](#notificationbuilder) to be used for certain types of notifications. When a notification is handled by the manager, it will lookup the type in this variable and build the notification with the provided builder class. +`JinjaTemplateLoaderMixin` is supposed to make handling jinja templates easier. It already takes care of loading templates and rendering the blocks inside of them. This mixin will also take care of factoring in the locale and the backend id when choosing the template (i.e. a more specific template will take precedence over a general template). -```py -NOTIFICATIONS_BUILDERS = { - "community_submission_create": CommunitySubmissionCreate, - "community_submission_accept": CommunitySubmissionAccept, - "community_submission_reject": CommunitySubmissionReject, - "member_invitation_create": CommunityMemberInvitationCreate, - "member_invitation_accept": CommunityMemberInvitationAccept, - "member_invitation_reject": CommunityMemberInvitationReject, - "request_comment_create": RequestCommentCreate, -} -``` +#### Attributes +- `template_folder` specifies the folder for template lookup. -### NOTIFICATIONS_ENTITY_RESOLVERS +#### Methods +- `render_template(self, notification, recipient)`: Render template for a notification. Fetch the template based on the notification type and return the template blocks. More specific templates take precedence over less specific ones. Rendered template will also take the locale into account. -Specifies entity resolvers (not to be confused with [EntityResolve](#entityresolve)) to be used for resolving the notification context. These are usually `ServiceResultResolver` objects, which provide functionality to dump an object to a reference dictionary and later on use the dump to fetch information as seen on the API/service level (i.e. fully resolved objects with links for easy access). +### EmailNotificationBackend -```py -NOTIFICATIONS_ENTITY_RESOLVERS = [ - RDMRecordServiceResultResolver(), - ServiceResultResolver(service_id="users", type_key="user"), - ServiceResultResolver(service_id="communities", type_key="community"), - ServiceResultResolver(service_id="requests", type_key="request"), - ServiceResultResolver(service_id="request_events", type_key="request_event"), -] -``` +This is a discrete implemenation of a notification backend. With the help of a [JinjaTemplateLoaderMixin](#jinjatemplateloadermixin), it will render a notification template based on the notification type provided. diff --git a/mkdocs.yml b/mkdocs.yml index f31be97b..4ac557bf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -77,7 +77,6 @@ nav: - Storage: "customize/s3.md" - Upload Limits: "customize/upload_limits.md" - Metadata-only records: "customize/metadata_only.md" - - Notifications: "customize/notifications.md" - Develop: - Overview: "develop/index.md" - Getting started: @@ -118,6 +117,8 @@ nav: - Test emails locally: develop/howtos/dev_email.md - Migrate legacy routes: develop/howtos/route_migration.md - Back up search indices: develop/howtos/backup_search_indices.md + - Create and configure notifications: develop/howtos/notifications.md + - Architecture: - Introduction: develop/architecture/index.md - Infrastructure: develop/architecture/infrastructure.md @@ -127,6 +128,7 @@ nav: - Communities: develop/architecture/communities.md - Records: develop/architecture/records.md - Event handling: develop/architecture/event_handling.md + - Notifications: develop/architecture/notifications.md - Recommended reading: develop/architecture/reading.md - Concepts: - Optimistic concurrency control: develop/concepts/concurrency-control.md