Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Krist/be 443 configure notifications #99

Merged
merged 39 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a22ce04
Merge remote-tracking branch 'refs/remotes/origin/krist/translations-…
Oct 31, 2024
46c9347
intermediary commit
Oct 31, 2024
bb12cad
Not using CommunityRoleAggregate
Nov 11, 2024
ca3e228
Merge remote-tracking branch 'refs/remotes/origin/main' into krist/be…
Nov 11, 2024
0379fef
test completion
Nov 11, 2024
f4c446a
intermediary commit
Nov 12, 2024
92ab7c8
version bump
Nov 12, 2024
bdd26f1
format
Nov 12, 2024
88673c7
format
Nov 13, 2024
f50cefa
translations
Nov 13, 2024
8559714
Merge branch 'refs/heads/krist/be-492-expand-on-request-instance-seem…
Dec 13, 2024
1c48d26
intermediary commit
Dec 19, 2024
7de480b
type checking v1
Dec 31, 2024
c1ede9c
tests refactor
Dec 31, 2024
8253117
format
Jan 6, 2025
041c09c
removed comments
Jan 6, 2025
6f13e9a
type check fixes andtype checks for generators
Jan 6, 2025
56ee372
moved test code to pytest-oarepo
Jan 13, 2025
c51fc8d
various minor corrections
Jan 14, 2025
2776188
Merge remote-tracking branch 'origin/main' into krist/refactor
Jan 14, 2025
8175030
record owner in record community tests rewritten
Jan 14, 2025
9f648de
comments
Jan 14, 2025
c56f423
intermediary commit
Jan 17, 2025
1a981e3
another test refactor
Jan 21, 2025
a1e734e
run-tests revert
Jan 21, 2025
7eabbe6
link2testclient dependant on host fixture
Jan 21, 2025
9e4dae5
type checking in dependancies check
Jan 22, 2025
f69ca69
format
Jan 22, 2025
0c0afc0
using RecipientGenerator for community_role
Jan 23, 2025
0dd5fe3
Merge remote-tracking branch 'refs/remotes/origin/main' into krist/be…
Jan 28, 2025
2efdd15
Merge branch 'refs/heads/krist/refactor' into krist/be-443-configure-…
Jan 28, 2025
4f34ab1
merge with refactors
Jan 28, 2025
3e8f872
format
Jan 28, 2025
934e06f
disable python warnings
Jan 28, 2025
01cad34
deleted local install
Jan 28, 2025
2f2a542
query string parameter to search only published records
Jan 28, 2025
ebe079c
review changes
Jan 29, 2025
6693f58
notification generator update
mesemus Jan 31, 2025
08187ed
Adding index refresh
mesemus Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions oarepo_communities/cf/workflows.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import marshmallow as ma
from flask import current_app
from invenio_records_resources.services.custom_fields import KeywordCF

if TYPE_CHECKING:
from typing import Any


def validate_workflow(value):
def validate_workflow(value: str) -> bool:
return value in current_app.config["WORKFLOWS"]


class WorkflowSchemaField(ma.fields.Str):
def __init__(self, **kwargs):
def __init__(self, **kwargs: Any) -> None:
super().__init__(validate=[validate_workflow], **kwargs)


class WorkflowCF(KeywordCF):
def __init__(self, name, **kwargs):
def __init__(self, name: str, **kwargs: Any) -> None:
super().__init__(name, field_cls=WorkflowSchemaField, **kwargs)


# hack to get lazy choices serialized to JSON
class LazyChoices(list):
def __init__(self, func):
def __init__(self, func: callable):
self._func = func

def __iter__(self):
Expand Down
7 changes: 6 additions & 1 deletion oarepo_communities/config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from invenio_communities.config import COMMUNITIES_ROUTES as INVENIO_COMMUNITIES_ROUTES
from oarepo_runtime.i18n import lazy_gettext as _

from .cf.workflows import WorkflowCF, lazy_workflow_options
from .notifications.generators import CommunityRoleEmailRecipient
from .requests.migration import (
ConfirmCommunityMigrationRequestType,
InitiateCommunityMigrationRequestType,
)
from .requests.remove_secondary import RemoveSecondaryCommunityRequestType
from .requests.submission_secondary import SecondaryCommunitySubmissionRequestType
from .resolvers.ui import CommunityRoleUIResolver
from invenio_communities.config import COMMUNITIES_ROUTES as INVENIO_COMMUNITIES_ROUTES

REQUESTS_REGISTERED_TYPES = [
InitiateCommunityMigrationRequestType(),
Expand Down Expand Up @@ -67,3 +68,7 @@
DISPLAY_USER_COMMUNITIES = True

DISPLAY_NEW_COMMUNITIES = True

NOTIFICATION_RECIPIENTS_RESOLVERS = {
"community_role": {"email": CommunityRoleEmailRecipient}
}
22 changes: 14 additions & 8 deletions oarepo_communities/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,51 @@
class CommunityAlreadyIncludedException(Exception):
"""The record is already in the community."""

description = "The record is already included in this community."
description = _("The record is already included in this community.")


class CommunityNotIncludedException(Exception):
"""The record is already in the community."""

description = "The record is not included in this community."
description = _("The record is not included in this community.")


class PrimaryCommunityException(Exception):
"""The record is already in the community."""

description = "Primary community can't be removed, can only be migrated to another."
description = _(
"Primary community can't be removed, can only be migrated to another."
)


class MissingDefaultCommunityError(ValidationError):
""""""

description = _("Default community is not present in the input.")


class MissingCommunitiesError(ValidationError):
""""""

description = _("Communities are not present in the input.")


class CommunityAlreadyExists(Exception):
"""The record is already in the community."""

description = "The record is already included in this community."
description = _("The record is already included in this community.")


class RecordCommunityMissing(Exception):
"""Record does not belong to the community."""

def __init__(self, record_id, community_id):
def __init__(self, record_id: str, community_id: str):
"""Initialise error."""
self.record_id = record_id
self.community_id = community_id

@property
def description(self):
def description(self) -> str:
"""Exception description."""
return "The record {record_id} in not included in the community {community_id}.".format(
record_id=self.record_id, community_id=self.community_id
Expand All @@ -53,11 +59,11 @@ def description(self):
class OpenRequestAlreadyExists(Exception):
"""An open request already exists."""

def __init__(self, request_id):
def __init__(self, request_id: str):
"""Initialize exception."""
self.request_id = request_id

@property
def description(self):
def description(self) -> str:
"""Exception's description."""
return _("There is already an open inclusion request for this community.")
47 changes: 36 additions & 11 deletions oarepo_communities/ext.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from __future__ import annotations

from functools import cached_property
from typing import TYPE_CHECKING

from deepmerge import conservative_merger
from flask_principal import identity_loaded

import oarepo_communities.cli # noqa - imported to register CLI commands
Expand All @@ -9,19 +13,24 @@
from .services.community_inclusion.service import CommunityInclusionService
from .services.community_records.config import CommunityRecordsServiceConfig
from .services.community_records.service import CommunityRecordsService
from .services.community_role.config import CommunityRoleServiceConfig
from .services.community_role.service import CommunityRoleService
from .utils import get_urlprefix_service_id_mapping, load_community_user_needs
from .workflow import community_default_workflow

if TYPE_CHECKING:
from flask import Flask


class OARepoCommunities(object):
"""OARepo extension of Invenio-Vocabularies."""

def __init__(self, app=None):
def __init__(self, app: Flask = None) -> None:
"""Extension initialization."""
if app:
self.init_app(app)

def init_app(self, app):
def init_app(self, app: Flask) -> None:
"""Flask application initialization."""
self.app = app
self.init_services(app)
Expand All @@ -30,7 +39,7 @@ def init_app(self, app):
self.init_config(app)
app.extensions["oarepo-communities"] = self

def init_config(self, app):
def init_config(self, app: Flask) -> None:
"""Initialize configuration."""

from . import config, ext_config
Expand Down Expand Up @@ -68,51 +77,63 @@ def init_config(self, app):
**app.config.get("COMMUNITIES_ROUTES", {}),
}

app_registered_event_types = app.config.setdefault(
"NOTIFICATION_RECIPIENTS_RESOLVERS", {}
)
app.config["NOTIFICATION_RECIPIENTS_RESOLVERS"] = conservative_merger.merge(
app_registered_event_types, config.NOTIFICATION_RECIPIENTS_RESOLVERS
)

@cached_property
def urlprefix_serviceid_mapping(self):
def urlprefix_serviceid_mapping(self) -> dict[str, str]:
return get_urlprefix_service_id_mapping()

def get_community_default_workflow(self, **kwargs):
def get_community_default_workflow(self, **kwargs) -> str | None:
return community_default_workflow(**kwargs)

def init_services(self, app):
def init_services(self, app: Flask) -> None:
"""Initialize communities service."""
# Services
self.community_records_service = CommunityRecordsService(
config=CommunityRecordsServiceConfig.build(app),
)

self.community_inclusion_service = CommunityInclusionService()
self.community_role_service = CommunityRoleService(
config=CommunityRoleServiceConfig()
)

def init_resources(self, app):
def init_resources(self, app: Flask) -> None:
"""Initialize communities resources."""
# Resources
self.community_records_resource = CommunityRecordsResource(
config=CommunityRecordsResourceConfig.build(app),
service=self.community_records_service,
)

def init_hooks(self, app):
def init_hooks(self, app: Flask) -> None:
"""Initialize hooks."""

@identity_loaded.connect_via(app)
def on_identity_loaded(_, identity):
load_community_user_needs(identity)

def get_default_community_from_record(self, record, **kwargs):
"""
def get_default_community_from_record(self, record: Record, **kwargs: Any):
record = record.parent if hasattr(record, "parent") else record
try:
return record.communities.default.id
except AttributeError:
return None
"""


def api_finalize_app(app):
def api_finalize_app(app: Flask) -> None:
"""Finalize app."""
finalize_app(app)


def finalize_app(app):
def finalize_app(app: Flask) -> None:
"""Finalize app."""

# Register services - cannot be done in extension because
Expand All @@ -126,6 +147,10 @@ def finalize_app(app):
ext.community_records_service,
service_id=ext.community_records_service.config.service_id,
)
rr_ext.registry.register(
ext.community_role_service,
service_id=ext.community_role_service.config.service_id,
)
# indexers
# idx_ext.registry.register(ext.community_records_service.indexer, indexer_id="communities")

Expand Down
21 changes: 21 additions & 0 deletions oarepo_communities/i18n/translations/cs/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,24 @@ msgstr "Odebrat sekundární komunitu"
#: /home/ron/prace/oarepo-communities/oarepo_communities/requests/submission_secondary.py:29
msgid "Secondary community submission"
msgstr "Přijmout do sekundární komunity"

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:8
#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:40
msgid "The record is already included in this community."
msgstr "Záznam už je v této komunitě."

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:14
msgid "The record is not included in this community."
msgstr "Záznam není v této komunitě."

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:20
msgid "Primary community can't be removed, can only be migrated to another."
msgstr "Záznam nemůže být odebrán z hlavní komunity, je možné ho pouze přesunout do jiné."

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:28
msgid "Default community is not present in the input."
msgstr "Na vstupu chybí výchozí komunita."

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:34
msgid "Communities are not present in the input."
msgstr "Na vstupu chybí komunity."
21 changes: 21 additions & 0 deletions oarepo_communities/i18n/translations/en/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,24 @@ msgstr ""
#: /home/ron/prace/oarepo-communities/oarepo_communities/requests/submission_secondary.py:29
msgid "Secondary community submission"
msgstr ""

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:8
#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:40
msgid "The record is already included in this community."
msgstr ""

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:14
msgid "The record is not included in this community."
msgstr ""

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:20
msgid "Primary community can't be removed, can only be migrated to another."
msgstr ""

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:28
msgid "Default community is not present in the input."
msgstr ""

#: /home/ron/prace/oarepo-communities/oarepo_communities/errors.py:34
msgid "Communities are not present in the input."
msgstr ""
4 changes: 3 additions & 1 deletion oarepo_communities/i18n/webpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"entry": {},
"dependencies": {},
"devDependencies": {},
"aliases": {"@translations/oarepo_communities": "translations/oarepo_communities/i18next.js"},
"aliases": {
"@translations/oarepo_communities": "translations/oarepo_communities/i18next.js"
},
}
},
)
10 changes: 9 additions & 1 deletion oarepo_communities/identity_to_entity_references.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
def community_role_mappings(identity):
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from flask_principal import Identity


def community_role_mappings(identity: Identity) -> list[dict[str, str]]:
community_roles = [
(n.value, n.role) for n in identity.provides if n.method == "community"
]
Expand Down
Empty file.
31 changes: 31 additions & 0 deletions oarepo_communities/notifications/generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from invenio_access.models import User
from invenio_communities.members.records.models import MemberModel
from invenio_notifications.models import Recipient
from oarepo_requests.notifications.generators import SpecificEntityRecipient

if TYPE_CHECKING:
from typing import Any


class CommunityRoleEmailRecipient(SpecificEntityRecipient):
"""Community role recipient generator for a notification."""

def _get_recipients(self, entity: Any) -> dict[Recipient]:
community_id = entity.community_id
role = entity.role

return {
user.email: Recipient(data={"email": user.email})
for user in (
User.query.join(MemberModel)
.filter(
MemberModel.role == role,
MemberModel.community_id == str(community_id),
)
.all()
)
}
2 changes: 1 addition & 1 deletion oarepo_communities/records/systemfields/communities.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@

class OARepoCommunitiesFieldContext(MappingSystemFieldMixin, CommunitiesFieldContext):
@property
def mapping(self):
def mapping(self) -> dict:
return COMMUNITIES_MAPPING
Loading