*** Wartungsfenster jeden ersten Mittwoch vormittag im Monat ***

Skip to content
Snippets Groups Projects
Commit be5cf343 authored by Moser, Maximilian's avatar Moser, Maximilian
Browse files

Add configuration for Invenio-Curations

* also start kicking out infrastructure for the "trusted-publisher" role
  which hasn't been used in a good while now
* instead, if curation isn't enabled, it's up to the admins to publish
  records
parent 97415220
Branches
Tags
1 merge request!57Implement new curation workflow
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
from datetime import datetime from datetime import datetime
from invenio_app_rdm.config import NOTIFICATIONS_BUILDERS
from invenio_curations.config import CURATIONS_NOTIFICATIONS_BUILDERS
from invenio_curations.services.facets import status as facets_status
from invenio_curations.services.facets import type as facets_type
from invenio_i18n import gettext as _ from invenio_i18n import gettext as _
from invenio_oauthclient.views.client import auto_redirect_login from invenio_oauthclient.views.client import auto_redirect_login
from invenio_rdm_records.config import RDM_RECORDS_REVIEWS from invenio_rdm_records.config import RDM_RECORDS_REVIEWS
...@@ -50,6 +54,9 @@ CONFIG_TUW_DISABLE_ERROR_MAILS = False ...@@ -50,6 +54,9 @@ CONFIG_TUW_DISABLE_ERROR_MAILS = False
CONFIG_TUW_MINIFY_ENABLED = False CONFIG_TUW_MINIFY_ENABLED = False
"""Enable or disable the Flask-Minify extension.""" """Enable or disable the Flask-Minify extension."""
CONFIG_TUW_CURATIONS_ENABLED = True
"""Enable the `Invenio-Curations` integration."""
# Invenio-Mail # Invenio-Mail
# ============ # ============
...@@ -212,6 +219,21 @@ This config item is checked in the `records_service.review.create()` method. ...@@ -212,6 +219,21 @@ This config item is checked in the `records_service.review.create()` method.
REQUESTS_PERMISSION_POLICY = TUWRequestsPermissionPolicy REQUESTS_PERMISSION_POLICY = TUWRequestsPermissionPolicy
REQUESTS_FACETS = {
"type": {
"facet": facets_type,
"ui": {
"field": "type",
},
},
"status": {
"facet": facets_status,
"ui": {
"field": "status",
},
},
}
# Invenio-Communities # Invenio-Communities
# ================ # ================
...@@ -253,6 +275,21 @@ APP_RDM_DISPLAY_DECIMAL_FILE_SIZES = False ...@@ -253,6 +275,21 @@ APP_RDM_DISPLAY_DECIMAL_FILE_SIZES = False
MAX_CONTENT_LENGTH = 100 * (1024**2) MAX_CONTENT_LENGTH = 100 * (1024**2)
# Invenio-Curations
# =================
NOTIFICATIONS_BUILDERS = {
**NOTIFICATIONS_BUILDERS,
**CURATIONS_NOTIFICATIONS_BUILDERS,
}
CURATIONS_MODERATION_ROLE = "reviewer"
CURATIONS_ALLOW_PUBLISHING_EDITS = True
CURATIONS_PERMISSIONS_VIA_GRANTS = False
# Misc. Configuration # Misc. Configuration
# =================== # ===================
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
"""Invenio module containing some customizations and configuration for TU Wien.""" """Invenio module containing some customizations and configuration for TU Wien."""
from flask import current_app
from flask.config import Config from flask.config import Config
from flask_minify import Minify from flask_minify import Minify
from flask_security.signals import user_registered from flask_security.signals import user_registered
...@@ -90,3 +91,8 @@ class InvenioConfigTUW(object): ...@@ -90,3 +91,8 @@ class InvenioConfigTUW(object):
if minify_enabled and "flask-minify" not in app.extensions: if minify_enabled and "flask-minify" not in app.extensions:
minify = Minify(app, static=False, go=False) minify = Minify(app, static=False, go=False)
app.extensions["flask-minify"] = minify app.extensions["flask-minify"] = minify
@property
def curations_enabled(self):
"""Shorthand for ``current_app.config.get["CONFIG_TUW_CURATIONS_ENABLED"]``."""
return current_app.config["CONFIG_TUW_CURATIONS_ENABLED"]
...@@ -11,12 +11,16 @@ from flask import current_app ...@@ -11,12 +11,16 @@ from flask import current_app
from flask_login import current_user from flask_login import current_user
from flask_principal import RoleNeed, UserNeed from flask_principal import RoleNeed, UserNeed
from invenio_access.permissions import any_user from invenio_access.permissions import any_user
from invenio_curations.services.generators import (
CurationModerators,
IfCurationRequestExists,
)
from invenio_rdm_records.services.generators import ConditionalGenerator from invenio_rdm_records.services.generators import ConditionalGenerator
from invenio_records_permissions.generators import Generator from invenio_records_permissions.generators import Generator, IfConfig
class IfPublished(ConditionalGenerator): class IfPublished(ConditionalGenerator):
"""Allows record owners with the "trusted-publisher" role.""" """Conditional generator that checks if the record has been published before."""
def _condition(self, record=None, **kwargs): def _condition(self, record=None, **kwargs):
"""Check if the record has been published.""" """Check if the record has been published."""
...@@ -101,14 +105,14 @@ def TrustedRecordOwners(exclude=False): ...@@ -101,14 +105,14 @@ def TrustedRecordOwners(exclude=False):
return RecordOwnersWithRole("trusted-user", exclude=exclude) return RecordOwnersWithRole("trusted-user", exclude=exclude)
def TrustedPublisherRecordOwners(exclude=False): def IfCurationsEnabled(then_, else_):
"""Allows record owners with the "trusted-publisher" role.""" """Check if the curations module is enabled."""
return RecordOwnersWithRole("trusted-publisher", exclude=exclude) return IfConfig("CONFIG_TUW_CURATIONS_ENABLED", then_=then_, else_=else_)
def TrustedPublisherForNewButTrustedUserForEdits(exclude=False): def CurationModeratorsIfRequestExists(else_=None):
"""Require "trusted-user" for edits, but "trusted-publisher" for new records.""" """If curations are enabled and a request exists, allow moderators."""
return IfPublished( return IfCurationsEnabled(
then_=[TrustedRecordOwners(exclude=exclude)], [IfCurationRequestExists(then_=[CurationModerators()], else_=[])],
else_=[TrustedPublisherRecordOwners(exclude=exclude)], else_ or [],
) )
...@@ -12,6 +12,11 @@ from typing import Iterable, List ...@@ -12,6 +12,11 @@ from typing import Iterable, List
from invenio_administration.generators import Administration from invenio_administration.generators import Administration
from invenio_communities.generators import CommunityCurators from invenio_communities.generators import CommunityCurators
from invenio_communities.permissions import CommunityPermissionPolicy from invenio_communities.permissions import CommunityPermissionPolicy
from invenio_curations.services.generators import (
IfCurationRequestAccepted,
IfRequestTypes,
)
from invenio_rdm_records.requests.community_submission import CommunitySubmission
from invenio_rdm_records.services.generators import ( from invenio_rdm_records.services.generators import (
AccessGrant, AccessGrant,
CommunityInclusionReviewers, CommunityInclusionReviewers,
...@@ -37,11 +42,13 @@ from invenio_records_permissions.generators import ( ...@@ -37,11 +42,13 @@ from invenio_records_permissions.generators import (
IfConfig, IfConfig,
SystemProcess, SystemProcess,
) )
from invenio_requests.services.generators import Creator, Receiver, Status
from invenio_users_resources.services.permissions import UserManager from invenio_users_resources.services.permissions import UserManager
from .generators import CurationModeratorsIfRequestExists as CurationReviewers
from .generators import ( from .generators import (
DisableIfReadOnly, DisableIfReadOnly,
TrustedPublisherForNewButTrustedUserForEdits, IfCurationsEnabled,
TrustedRecordOwners, TrustedRecordOwners,
TrustedUsers, TrustedUsers,
) )
...@@ -115,9 +122,9 @@ class TUWRecordPermissionPolicy(RDMRecordPermissionPolicy): ...@@ -115,9 +122,9 @@ class TUWRecordPermissionPolicy(RDMRecordPermissionPolicy):
# #
# fmt: off # fmt: off
can_manage = d([TrustedRecordOwners(), RecordCommunitiesAction("curate"), SystemProcess() ] + shared_access["manage"] ) # noqa can_manage = d([TrustedRecordOwners(), RecordCommunitiesAction("curate"), SystemProcess() ] + shared_access["manage"] ) # noqa
can_access_draft = d(can_manage + [RecordOwners(), SubmissionReviewer() ] + shared_access["preview"]) # noqa can_access_draft = d(can_manage + [RecordOwners(), SubmissionReviewer(), CurationReviewers() ] + shared_access["preview"]) # noqa
can_curate = d(can_manage + [ ] + shared_access["edit"] ) # noqa can_curate = d(can_manage + [ ] + shared_access["edit"] ) # noqa
can_review = d(can_curate + [SubmissionReviewer() ] ) # noqa can_review = d(can_curate + [SubmissionReviewer(), CurationReviewers() ] ) # noqa
can_preview = d(can_access_draft + [UserManager ] ) # noqa can_preview = d(can_access_draft + [UserManager ] ) # noqa
can_view = d(can_access_draft + [RecordCommunitiesAction("view"), CommunityInclusionReviewers()] + shared_access["view"] ) # noqa can_view = d(can_access_draft + [RecordCommunitiesAction("view"), CommunityInclusionReviewers()] + shared_access["view"] ) # noqa
can_authenticated = d( [AuthenticatedUser(), SystemProcess() ] ) # noqa can_authenticated = d( [AuthenticatedUser(), SystemProcess() ] ) # noqa
...@@ -158,12 +165,15 @@ class TUWRecordPermissionPolicy(RDMRecordPermissionPolicy): ...@@ -158,12 +165,15 @@ class TUWRecordPermissionPolicy(RDMRecordPermissionPolicy):
# actions # actions
# > can_edit: RecordOwners is needed to not break the 'edit' button on the dashboard (UX) # > can_edit: RecordOwners is needed to not break the 'edit' button on the dashboard (UX)
# > can_publish: TODO only "data stewards" should be able to publish # > can_publish: if curations are enabled, similar as vanilla InvenioRDM (+ reviewers)
# otherwise, just the system and admins
# NOTE: metadata edits for published records don't require another review;
# this is handled by the curation component
can_edit = [IfDeleted(then_=[Disable()], else_=can_curate + [RecordOwners(), DisableIfReadOnly()])] # noqa can_edit = [IfDeleted(then_=[Disable()], else_=can_curate + [RecordOwners(), DisableIfReadOnly()])] # noqa
can_delete_draft = can_curate + [DisableIfReadOnly()] # noqa can_delete_draft = can_curate + [DisableIfReadOnly()] # noqa
can_new_version = can_curate + [DisableIfReadOnly()] # noqa can_new_version = can_curate + [DisableIfReadOnly()] # noqa
can_lift_embargo = can_manage + [DisableIfReadOnly()] # noqa can_lift_embargo = can_manage + [DisableIfReadOnly()] # noqa
can_publish = [TrustedPublisherForNewButTrustedUserForEdits(), SystemProcess(), DisableIfReadOnly()] # noqa can_publish = [IfCurationsEnabled(then_=can_review, else_=[SystemProcess()]), DisableIfReadOnly()] # noqa
# record communities # record communities
# can_add_community: add a record to a community # can_add_community: add a record to a community
...@@ -229,6 +239,12 @@ class TUWRequestsPermissionPolicy(RDMRequestsPermissionPolicy): ...@@ -229,6 +239,12 @@ class TUWRequestsPermissionPolicy(RDMRequestsPermissionPolicy):
# #
# current state: invenio-rdm-records v11.0.0 # current state: invenio-rdm-records v11.0.0
can_read = RDMRequestsPermissionPolicy.can_read + [
Status(["review", "critiqued", "resubmitted"], [Creator(), Receiver()]),
]
can_create_comment = can_read + [DisableIfReadOnly()]
# fmt: off # fmt: off
can_create = RDMRequestsPermissionPolicy.can_create + [DisableIfReadOnly()] # noqa can_create = RDMRequestsPermissionPolicy.can_create + [DisableIfReadOnly()] # noqa
can_update = RDMRequestsPermissionPolicy.can_update + [DisableIfReadOnly()] # noqa can_update = RDMRequestsPermissionPolicy.can_update + [DisableIfReadOnly()] # noqa
...@@ -236,14 +252,31 @@ class TUWRequestsPermissionPolicy(RDMRequestsPermissionPolicy): ...@@ -236,14 +252,31 @@ class TUWRequestsPermissionPolicy(RDMRequestsPermissionPolicy):
can_action_submit = RDMRequestsPermissionPolicy.can_action_submit + [DisableIfReadOnly()] # noqa can_action_submit = RDMRequestsPermissionPolicy.can_action_submit + [DisableIfReadOnly()] # noqa
can_action_cancel = RDMRequestsPermissionPolicy.can_action_cancel + [DisableIfReadOnly()] # noqa can_action_cancel = RDMRequestsPermissionPolicy.can_action_cancel + [DisableIfReadOnly()] # noqa
can_action_expire = RDMRequestsPermissionPolicy.can_action_expire + [DisableIfReadOnly()] # noqa can_action_expire = RDMRequestsPermissionPolicy.can_action_expire + [DisableIfReadOnly()] # noqa
can_action_accept = RDMRequestsPermissionPolicy.can_action_accept + [DisableIfReadOnly()] # noqa
can_action_decline = RDMRequestsPermissionPolicy.can_action_decline + [DisableIfReadOnly()] # noqa can_action_decline = RDMRequestsPermissionPolicy.can_action_decline + [DisableIfReadOnly()] # noqa
can_create_comment = RDMRequestsPermissionPolicy.can_create_comment + [DisableIfReadOnly()] # noqa
can_update_comment = RDMRequestsPermissionPolicy.can_update_comment + [DisableIfReadOnly()] # noqa can_update_comment = RDMRequestsPermissionPolicy.can_update_comment + [DisableIfReadOnly()] # noqa
can_delete_comment = RDMRequestsPermissionPolicy.can_delete_comment + [DisableIfReadOnly()] # noqa can_delete_comment = RDMRequestsPermissionPolicy.can_delete_comment + [DisableIfReadOnly()] # noqa
can_manage_access_options = RDMRequestsPermissionPolicy.can_manage_access_options + [DisableIfReadOnly()] # noqa can_manage_access_options = RDMRequestsPermissionPolicy.can_manage_access_options + [DisableIfReadOnly()] # noqa
# fmt: on # fmt: on
# "rdm-curation" requests have precedence over "community-submission" requests
can_action_accept = [
IfRequestTypes(
request_types=[CommunitySubmission],
then_=[
IfCurationRequestAccepted(
then_=RDMRequestsPermissionPolicy.can_action_accept, else_=[]
)
],
else_=RDMRequestsPermissionPolicy.can_action_accept,
),
DisableIfReadOnly(),
]
# custom actions for curation request actions
can_action_review = can_action_accept
can_action_critique = can_action_accept
can_action_resubmit = can_action_submit
class TUWCommunityPermissionPolicy(CommunityPermissionPolicy): class TUWCommunityPermissionPolicy(CommunityPermissionPolicy):
"""Communities permission policy of TU Wien.""" """Communities permission policy of TU Wien."""
......
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 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.
"""Local proxies for Invenio-Config-TUW objects."""
from flask import current_app
from werkzeug.local import LocalProxy
current_config_tuw = LocalProxy(lambda: current_app.extensions["invenio-config-tuw"])
...@@ -9,11 +9,15 @@ ...@@ -9,11 +9,15 @@
"""Overrides for core services.""" """Overrides for core services."""
from invenio_curations.services.components import (
CurationComponent as BaseCurationComponent,
)
from invenio_drafts_resources.services.records.components import ServiceComponent from invenio_drafts_resources.services.records.components import ServiceComponent
from invenio_pidstore.models import PIDStatus from invenio_pidstore.models import PIDStatus
from invenio_rdm_records.services.components import DefaultRecordsComponents from invenio_rdm_records.services.components import DefaultRecordsComponents
from invenio_records_resources.services.uow import TaskOp 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_email
...@@ -46,8 +50,32 @@ class PublicationNotificationComponent(ServiceComponent): ...@@ -46,8 +50,32 @@ class PublicationNotificationComponent(ServiceComponent):
) )
class CurationComponent(BaseCurationComponent):
"""Curation component that only activates if curations are enabled."""
def publish(self, identity, draft=None, record=None, **kwargs):
"""Check if record curation request has been accepted."""
if current_config_tuw.curations_enabled:
return super().publish(identity, draft=draft, record=record, **kwargs)
def delete_draft(self, identity, draft=None, record=None, force=False):
"""Delete a draft."""
if current_config_tuw.curations_enabled:
return super().delete_draft(
identity, draft=draft, record=record, force=force
)
def update_draft(self, identity, data=None, record=None, errors=None):
"""Update draft handler."""
if current_config_tuw.curations_enabled:
return super().update_draft(
identity, data=data, record=record, errors=errors
)
TUWRecordsComponents = [ TUWRecordsComponents = [
*DefaultRecordsComponents, *DefaultRecordsComponents,
ParentAccessSettingsComponent, ParentAccessSettingsComponent,
PublicationNotificationComponent, PublicationNotificationComponent,
CurationComponent,
] ]
...@@ -220,6 +220,10 @@ def affiliations(db): ...@@ -220,6 +220,10 @@ def affiliations(db):
@pytest.fixture() @pytest.fixture()
def example_record(app, db, files_loc, users, resource_types): def example_record(app, db, files_loc, users, resource_types):
"""Example record.""" """Example record."""
# avoid curations for the tests
curations_enabled = app.config["CONFIG_TUW_CURATIONS_ENABLED"]
app.config["CONFIG_TUW_CURATIONS_ENABLED"] = False
data = { data = {
"access": { "access": {
"record": "public", "record": "public",
...@@ -255,4 +259,5 @@ def example_record(app, db, files_loc, users, resource_types): ...@@ -255,4 +259,5 @@ def example_record(app, db, files_loc, users, resource_types):
record.commit() record.commit()
db.session.commit() db.session.commit()
app.config["CONFIG_TUW_CURATIONS_ENABLED"] = curations_enabled
return record return record
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment