invenio-app-rdm 14.0.0b0.dev4__py2.py3-none-any.whl → 14.0.0b1.dev2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- invenio_app_rdm/__init__.py +1 -1
- invenio_app_rdm/administration/moderation/__init__.py +11 -0
- invenio_app_rdm/administration/moderation/requests.py +175 -0
- invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/requests.html +14 -0
- invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/requests_details.html +89 -0
- invenio_app_rdm/administration/user_moderation/user_moderation.py +4 -4
- invenio_app_rdm/config.py +47 -0
- invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/deposit.html +3 -0
- invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/manage_menu.html +3 -3
- invenio_app_rdm/records_ui/utils.py +22 -1
- invenio_app_rdm/records_ui/views/deposits.py +59 -7
- invenio_app_rdm/records_ui/views/records.py +43 -1
- invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/guest-access-request/index.html +5 -5
- invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/record-deletion/index.html +93 -0
- invenio_app_rdm/requests_ui/views/requests.py +17 -2
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/RequestsSearchResultItem.js +81 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/RequestsSearchbarLayout.js +48 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/index.js +22 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion/DeletionModal.js +356 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion/DeletionRadioGroup.js +44 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion.js +81 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/RDMDepositForm.js +22 -2
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/index.js +2 -1
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ManageButton.js +72 -20
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordCommunitySubmission/RecordCommunitySubmissionModal.js +1 -1
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordManagement.js +10 -3
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/index.js +1 -0
- invenio_app_rdm/theme/webpack.py +2 -1
- {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/METADATA +20 -5
- {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/RECORD +34 -23
- {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/entry_points.txt +2 -0
- {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/WHEEL +0 -0
- {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/licenses/LICENSE +0 -0
- {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/top_level.txt +0 -0
invenio_app_rdm/__init__.py
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2025 CERN.
|
|
4
|
+
#
|
|
5
|
+
# Invenio App RDM is free software; you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the MIT License; see LICENSE file for more details.
|
|
7
|
+
"""Requests moderation administration module."""
|
|
8
|
+
|
|
9
|
+
from .requests import ModerationRequestDetailView, ModerationRequestListView
|
|
10
|
+
|
|
11
|
+
__all__ = ("ModerationRequestListView", "ModerationRequestDetailView")
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2025 CERN.
|
|
4
|
+
#
|
|
5
|
+
# Invenio App RDM is free software; you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the MIT License; see LICENSE file for more details.
|
|
7
|
+
"""Invenio administration view module for requests moderation."""
|
|
8
|
+
|
|
9
|
+
from functools import partial
|
|
10
|
+
|
|
11
|
+
from flask import current_app, g
|
|
12
|
+
from flask_login import current_user
|
|
13
|
+
from invenio_administration.views.base import (
|
|
14
|
+
AdminResourceDetailView,
|
|
15
|
+
AdminResourceListView,
|
|
16
|
+
)
|
|
17
|
+
from invenio_i18n import lazy_gettext as _
|
|
18
|
+
from invenio_i18n.ext import current_i18n
|
|
19
|
+
from invenio_rdm_records.requests import RecordDeletion
|
|
20
|
+
from invenio_requests.proxies import current_requests
|
|
21
|
+
from invenio_search_ui.searchconfig import search_app_config
|
|
22
|
+
from invenio_users_resources.proxies import current_user_resources
|
|
23
|
+
from invenio_vocabularies.proxies import current_service as vocabulary_service
|
|
24
|
+
from marshmallow_utils.fields.babel import gettext_from_dict
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ModerationRequestListView(AdminResourceListView):
|
|
28
|
+
"""Requests moderation admin search view."""
|
|
29
|
+
|
|
30
|
+
api_endpoint = "/requests"
|
|
31
|
+
extension_name = "invenio-requests"
|
|
32
|
+
name = "requests"
|
|
33
|
+
resource_config = "requests_resource"
|
|
34
|
+
request_headers = {"Accept": "application/vnd.inveniordm.v1+json"}
|
|
35
|
+
title = _("Requests")
|
|
36
|
+
menu_label = _("Requests")
|
|
37
|
+
category = _("Moderation")
|
|
38
|
+
pid_path = "id"
|
|
39
|
+
icon = "mail"
|
|
40
|
+
template = "invenio_app_rdm/administration/requests.html"
|
|
41
|
+
order = 1
|
|
42
|
+
|
|
43
|
+
display_search = True
|
|
44
|
+
display_delete = False
|
|
45
|
+
display_create = False
|
|
46
|
+
display_edit = False
|
|
47
|
+
display_read = False
|
|
48
|
+
|
|
49
|
+
search_config_name = "APP_RDM_MODERATION_REQUEST_SEARCH"
|
|
50
|
+
search_facets_config_name = "APP_RDM_MODERATION_REQUEST_FACETS"
|
|
51
|
+
search_sort_config_name = "APP_RDM_MODERATION_REQUEST_SORT_OPTIONS"
|
|
52
|
+
|
|
53
|
+
item_field_list = {
|
|
54
|
+
"type": {"text": _("Type"), "order": 1, "width": 3},
|
|
55
|
+
"created": {"text": _("Created"), "order": 2, "width": 3},
|
|
56
|
+
"last_reply": {"text": _("Last reply"), "order": 3, "width": 3},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def request_type(self):
|
|
61
|
+
"""Request type property."""
|
|
62
|
+
request_type = self.resource.service.request_type_registry.lookup(
|
|
63
|
+
"record-deletion"
|
|
64
|
+
)
|
|
65
|
+
return request_type
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_service_schema(cls):
|
|
69
|
+
"""Get marshmallow schema of the assigned service."""
|
|
70
|
+
request_type = cls.resource.service.request_type_registry.lookup(
|
|
71
|
+
"record-deletion"
|
|
72
|
+
)
|
|
73
|
+
# handle dynamic schema class declaration for requests
|
|
74
|
+
schema_cls = request_type.marshmallow_schema()
|
|
75
|
+
schema = schema_cls()
|
|
76
|
+
return schema
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def disabled():
|
|
80
|
+
"""Disable the view on demand."""
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def init_search_config(self):
|
|
84
|
+
"""Build search view config."""
|
|
85
|
+
return partial(
|
|
86
|
+
search_app_config,
|
|
87
|
+
config_name=self.get_search_app_name(),
|
|
88
|
+
available_facets=current_app.config.get(self.search_facets_config_name),
|
|
89
|
+
sort_options=current_app.config[self.search_sort_config_name],
|
|
90
|
+
endpoint=self.get_api_endpoint(),
|
|
91
|
+
headers=self.get_search_request_headers(),
|
|
92
|
+
initial_filters=[["is_open", "true"]],
|
|
93
|
+
hidden_params=[
|
|
94
|
+
["expand", "1"],
|
|
95
|
+
["type", RecordDeletion.type_id],
|
|
96
|
+
],
|
|
97
|
+
pagination_options=(20, 50),
|
|
98
|
+
default_size=20,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ModerationRequestDetailView(AdminResourceDetailView):
|
|
103
|
+
"""Configuration for moderation request detail view."""
|
|
104
|
+
|
|
105
|
+
url = "/requests/<pid_value>"
|
|
106
|
+
extension_name = "invenio-requests"
|
|
107
|
+
api_endpoint = "/requests"
|
|
108
|
+
name = "request_details"
|
|
109
|
+
resource_config = "requests_resource"
|
|
110
|
+
title = _("Request details")
|
|
111
|
+
display_delete = False
|
|
112
|
+
display_edit = False
|
|
113
|
+
|
|
114
|
+
pid_path = "id"
|
|
115
|
+
template = "invenio_app_rdm/administration/requests_details.html"
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def get_service_schema(cls):
|
|
119
|
+
"""Get marshmallow schema of the assigned service."""
|
|
120
|
+
request_type = cls.resource.service.request_type_registry.lookup(
|
|
121
|
+
"record-deletion"
|
|
122
|
+
)
|
|
123
|
+
# handle dynamic schema class declaration for requests
|
|
124
|
+
schema_cls = request_type.marshmallow_schema()
|
|
125
|
+
schema = schema_cls()
|
|
126
|
+
return schema
|
|
127
|
+
|
|
128
|
+
def get_context(self, pid_value=None):
|
|
129
|
+
"""Create details view context."""
|
|
130
|
+
name = self.name
|
|
131
|
+
schema = self.get_service_schema()
|
|
132
|
+
serialized_schema = self._schema_to_json(schema)
|
|
133
|
+
fields = self.item_field_list
|
|
134
|
+
request = current_requests.requests_service.read(
|
|
135
|
+
id_=pid_value, identity=g.identity, expand=True
|
|
136
|
+
).to_dict()
|
|
137
|
+
avatar = current_user_resources.users_service.links_item_tpl.expand(
|
|
138
|
+
g.identity, current_user
|
|
139
|
+
)["avatar"]
|
|
140
|
+
permissions = []
|
|
141
|
+
if "reason" in request["payload"]:
|
|
142
|
+
reason_title = vocabulary_service.read(
|
|
143
|
+
g.identity,
|
|
144
|
+
("removalreasons", request["payload"]["reason"]),
|
|
145
|
+
).to_dict()
|
|
146
|
+
|
|
147
|
+
request["payload"]["reason_label"] = gettext_from_dict(
|
|
148
|
+
reason_title["title"],
|
|
149
|
+
current_i18n.locale,
|
|
150
|
+
current_app.config.get("BABEL_DEFAULT_LOCALE", "en"),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
"invenio_request": request,
|
|
155
|
+
"user_avatar": avatar,
|
|
156
|
+
"permissions": permissions,
|
|
157
|
+
"request_headers": self.request_headers,
|
|
158
|
+
"name": name,
|
|
159
|
+
"resource_schema": serialized_schema,
|
|
160
|
+
"fields": fields,
|
|
161
|
+
"exclude_fields": self.item_field_exclude_list,
|
|
162
|
+
"ui_config": self.item_field_list,
|
|
163
|
+
"pid": pid_value,
|
|
164
|
+
"api_endpoint": self.get_api_endpoint(),
|
|
165
|
+
"title": self.title,
|
|
166
|
+
"list_endpoint": self.get_list_view_endpoint(),
|
|
167
|
+
"actions": self.serialize_actions(),
|
|
168
|
+
"pid_path": self.pid_path,
|
|
169
|
+
"display_edit": self.display_edit,
|
|
170
|
+
"display_delete": self.display_delete,
|
|
171
|
+
"list_ui_endpoint": self.get_list_view_endpoint(),
|
|
172
|
+
"resource_name": (
|
|
173
|
+
self.resource_name if self.resource_name else self.pid_path
|
|
174
|
+
),
|
|
175
|
+
}
|
invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/requests.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Copyright (C) 2025 CERN.
|
|
3
|
+
|
|
4
|
+
Invenio App RDM is free software; you can redistribute it and/or modify it
|
|
5
|
+
under the terms of the MIT License; see LICENSE file for more details.
|
|
6
|
+
#}
|
|
7
|
+
|
|
8
|
+
{% extends "invenio_administration/search.html" %}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
{% block javascript %}
|
|
12
|
+
{{ super() }}
|
|
13
|
+
{{ webpack['invenio-requests-administration.js'] }}
|
|
14
|
+
{% endblock %}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Copyright (C) 2025 CERN.
|
|
3
|
+
|
|
4
|
+
Invenio App RDM is free software; you can redistribute it and/or modify it
|
|
5
|
+
under the terms of the MIT License; see LICENSE file for more details.
|
|
6
|
+
#}
|
|
7
|
+
|
|
8
|
+
{% extends admin_base_template %}
|
|
9
|
+
|
|
10
|
+
{% from "invenio_requests/macros/request_header.html" import inclusion_request_header %}
|
|
11
|
+
|
|
12
|
+
{% set title = invenio_request.title %}
|
|
13
|
+
|
|
14
|
+
{% block page_title %}{% endblock page_title %}
|
|
15
|
+
|
|
16
|
+
{% block admin_page_content %}
|
|
17
|
+
{% set title = _("Request ") %}
|
|
18
|
+
{{ super() }}
|
|
19
|
+
{%- block request_body %}
|
|
20
|
+
<div class="ui container request-detail-page">
|
|
21
|
+
{%- block request_header %}
|
|
22
|
+
{% set back_button_url = url_for("administration.requests") %}
|
|
23
|
+
{{
|
|
24
|
+
inclusion_request_header(
|
|
25
|
+
back_button_url=back_button_url,
|
|
26
|
+
back_button_text=_("Back to requests"),
|
|
27
|
+
request=invenio_request,
|
|
28
|
+
accepted=request_is_accepted
|
|
29
|
+
)
|
|
30
|
+
}}
|
|
31
|
+
|
|
32
|
+
<div class="twelve wide column mt-10">
|
|
33
|
+
<p><strong>{{ _("Reason:") }}</strong> {{ invenio_request["payload"]["reason_label"] }}</p>
|
|
34
|
+
<p><strong>{{ _("Justification:") }}</strong> {{ invenio_request["payload"]["comment"] }}</p>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="ui divider"></div>
|
|
38
|
+
{%- endblock request_header %}
|
|
39
|
+
|
|
40
|
+
{%- block request_timeline %}
|
|
41
|
+
<div id="request-detail"
|
|
42
|
+
data-record='{{ invenio_request | tojson }}'
|
|
43
|
+
data-default-query-config='{{ dict(size=config["REQUESTS_TIMELINE_PAGE_SIZE"]) | tojson }}'
|
|
44
|
+
data-user-avatar='{{ user_avatar | tojson }}'
|
|
45
|
+
data-permissions='{{ permissions | tojson }}'
|
|
46
|
+
data-config='{{ dict(allowGroupReviewers=config["USERS_RESOURCES_GROUPS_ENABLED"], enableReviewers=config["REQUESTS_REVIEWERS_ENABLED"], maxReviewers=config["REQUESTS_REVIEWERS_MAX_NUMBER"]) | tojson }}'
|
|
47
|
+
>{# react app root #}
|
|
48
|
+
<div class="ui grid">
|
|
49
|
+
<div class="stretched row">
|
|
50
|
+
<div class="thirteen wide column">
|
|
51
|
+
<div class="ui">
|
|
52
|
+
<div class="ui fluid placeholder">
|
|
53
|
+
<div class="image header">
|
|
54
|
+
<div class="line"></div>
|
|
55
|
+
<div class="line"></div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="paragraph">
|
|
58
|
+
{% for i in range(6) %}
|
|
59
|
+
<div class="line"></div>
|
|
60
|
+
{% endfor %}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="three wide column">
|
|
66
|
+
{% for i in range(6) %}
|
|
67
|
+
<div class="ui fluid placeholder">
|
|
68
|
+
<div class="image header">
|
|
69
|
+
<div class="line"></div>
|
|
70
|
+
<div class="line"></div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="ui divider"></div>
|
|
74
|
+
{% endfor %}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
{%- endblock request_timeline %}
|
|
80
|
+
|
|
81
|
+
</div>
|
|
82
|
+
{% endblock request_body %}
|
|
83
|
+
{% endblock admin_page_content %}
|
|
84
|
+
|
|
85
|
+
{% block javascript %}
|
|
86
|
+
{{ super() }}
|
|
87
|
+
{% include config.THEME_JAVASCRIPT_TEMPLATE %}
|
|
88
|
+
{{ webpack['invenio-requests-base.js'] }}
|
|
89
|
+
{% endblock %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2023-
|
|
3
|
+
# Copyright (C) 2023-2025 CERN.
|
|
4
4
|
# Copyright (C) 2024 KTH Royal Institute of Technology.
|
|
5
5
|
#
|
|
6
6
|
# Invenio App RDM is free software; you can redistribute it and/or modify it
|
|
@@ -27,13 +27,13 @@ class UserModerationListView(AdminResourceListView):
|
|
|
27
27
|
name = "moderation"
|
|
28
28
|
resource_config = "requests_resource"
|
|
29
29
|
request_headers = {"Accept": "application/vnd.inveniordm.v1+json"}
|
|
30
|
-
title = _("Moderation")
|
|
31
|
-
menu_label = _("
|
|
30
|
+
title = _("User Moderation")
|
|
31
|
+
menu_label = _("Users")
|
|
32
32
|
category = _("Moderation")
|
|
33
33
|
pid_path = "id"
|
|
34
34
|
icon = "users"
|
|
35
35
|
template = "invenio_app_rdm/administration/user_moderation.html"
|
|
36
|
-
order =
|
|
36
|
+
order = 2
|
|
37
37
|
|
|
38
38
|
display_search = True
|
|
39
39
|
display_delete = False
|
invenio_app_rdm/config.py
CHANGED
|
@@ -60,6 +60,8 @@ from invenio_rdm_records.notifications.builders import (
|
|
|
60
60
|
GuestAccessRequestSubmitNotificationBuilder,
|
|
61
61
|
GuestAccessRequestSubmittedNotificationBuilder,
|
|
62
62
|
GuestAccessRequestTokenCreateNotificationBuilder,
|
|
63
|
+
RecordDeletionAcceptNotificationBuilder,
|
|
64
|
+
RecordDeletionDeclineNotificationBuilder,
|
|
63
65
|
UserAccessRequestAcceptNotificationBuilder,
|
|
64
66
|
UserAccessRequestCancelNotificationBuilder,
|
|
65
67
|
UserAccessRequestDeclineNotificationBuilder,
|
|
@@ -87,6 +89,7 @@ from invenio_requests.notifications.builders import (
|
|
|
87
89
|
CommentRequestEventCreateNotificationBuilder,
|
|
88
90
|
)
|
|
89
91
|
from invenio_requests.resources.requests.config import request_error_handlers
|
|
92
|
+
from invenio_requests.services.requests import facets
|
|
90
93
|
from invenio_stats.aggregations import StatAggregator
|
|
91
94
|
from invenio_stats.contrib.event_builders import build_file_unique_id
|
|
92
95
|
from invenio_stats.processors import (
|
|
@@ -1424,6 +1427,9 @@ NOTIFICATIONS_BUILDERS = {
|
|
|
1424
1427
|
community_notifications.SubComInvitationAccept.type: community_notifications.SubComInvitationAccept,
|
|
1425
1428
|
community_notifications.SubComInvitationDecline.type: community_notifications.SubComInvitationDecline,
|
|
1426
1429
|
community_notifications.SubComInvitationExpire.type: community_notifications.SubComInvitationExpire,
|
|
1430
|
+
# Record deletion
|
|
1431
|
+
RecordDeletionAcceptNotificationBuilder.type: RecordDeletionAcceptNotificationBuilder,
|
|
1432
|
+
RecordDeletionDeclineNotificationBuilder.type: RecordDeletionDeclineNotificationBuilder,
|
|
1427
1433
|
}
|
|
1428
1434
|
"""Notification builders."""
|
|
1429
1435
|
|
|
@@ -1511,3 +1517,44 @@ SITEMAP_SECTIONS = [
|
|
|
1511
1517
|
SitemapSectionOfRDMRecords(),
|
|
1512
1518
|
SitemapSectionOfCommunities(),
|
|
1513
1519
|
]
|
|
1520
|
+
|
|
1521
|
+
|
|
1522
|
+
# Moderation requests search configuration
|
|
1523
|
+
# ========================================
|
|
1524
|
+
APP_RDM_MODERATION_REQUEST_SEARCH = {
|
|
1525
|
+
"facets": ["status", "is_open"],
|
|
1526
|
+
"sort": ["bestmatch", "newest", "oldest", "last_replied"],
|
|
1527
|
+
}
|
|
1528
|
+
"""Moderation requests search configuration."""
|
|
1529
|
+
|
|
1530
|
+
APP_RDM_MODERATION_REQUEST_SORT_OPTIONS = {
|
|
1531
|
+
"bestmatch": dict(
|
|
1532
|
+
title=_("Best match"),
|
|
1533
|
+
fields=["_score"],
|
|
1534
|
+
),
|
|
1535
|
+
"newest": dict(
|
|
1536
|
+
title=_("Newest"),
|
|
1537
|
+
fields=["-created"],
|
|
1538
|
+
),
|
|
1539
|
+
"oldest": dict(
|
|
1540
|
+
title=_("Oldest"),
|
|
1541
|
+
fields=["created"],
|
|
1542
|
+
),
|
|
1543
|
+
"last_replied": dict(
|
|
1544
|
+
title=_("Last replied"),
|
|
1545
|
+
fields=["last_reply.created"],
|
|
1546
|
+
),
|
|
1547
|
+
}
|
|
1548
|
+
"""Definitions of available record sort options."""
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
APP_RDM_MODERATION_REQUEST_FACETS = {
|
|
1552
|
+
"status": {
|
|
1553
|
+
"facet": facets.status,
|
|
1554
|
+
"ui": {
|
|
1555
|
+
"field": "status",
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
"is_open": {"facet": facets.is_open, "ui": {"field": "is_open"}},
|
|
1559
|
+
}
|
|
1560
|
+
"""Available facets defined for this module."""
|
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
value='{{ config.RDM_RECORDS_RESTRICTION_GRACE_PERIOD.days | tojson }}'>
|
|
49
49
|
<input type="hidden" name="deposits-allow-record-restriction"
|
|
50
50
|
value='{{ config.RDM_RECORDS_ALLOW_RESTRICTION_AFTER_GRACE_PERIOD | tojson }}'>
|
|
51
|
+
{% if record_deletion %}
|
|
52
|
+
<input type="hidden" name="deposits-record-deletion" value='{{ record_deletion | tojson }}'>
|
|
53
|
+
{% endif %}
|
|
51
54
|
<input type="hidden" name="config-groups-enabled"
|
|
52
55
|
value='{{ config.USERS_RESOURCES_GROUPS_ENABLED | tojson }}'>
|
|
53
56
|
<input type="hidden" name="records-resources-allow-empty-files"
|
|
@@ -18,6 +18,9 @@ it under the terms of the MIT License; see LICENSE file for more details.
|
|
|
18
18
|
data-is-preview-submission-request="{{ is_preview_submission_request | tojson }}"
|
|
19
19
|
data-current-user-id="{{ current_user.id }}"
|
|
20
20
|
data-groups-enabled='{{ config.USERS_RESOURCES_GROUPS_ENABLED | tojson }}'
|
|
21
|
+
{% if record_deletion %}
|
|
22
|
+
data-record-deletion='{{ record_deletion | tojson }}'
|
|
23
|
+
{% endif %}
|
|
21
24
|
{% if config.RDM_DETAIL_SIDE_BAR_MANAGE_ATTRIBUTES_EXTENSION_TEMPLATE %}
|
|
22
25
|
{% include config.RDM_DETAIL_SIDE_BAR_MANAGE_ATTRIBUTES_EXTENSION_TEMPLATE %}
|
|
23
26
|
{% endif %}
|
|
@@ -28,9 +31,6 @@ it under the terms of the MIT License; see LICENSE file for more details.
|
|
|
28
31
|
<div class="full line"></div>
|
|
29
32
|
<div class="full line"></div>
|
|
30
33
|
<div class="full line"></div>
|
|
31
|
-
<div class="full line"></div>
|
|
32
|
-
<div class="full line"></div>
|
|
33
|
-
<div class="full line"></div>
|
|
34
34
|
</div>
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2020 CERN.
|
|
3
|
+
# Copyright (C) 2020-2025 CERN.
|
|
4
4
|
# Copyright (C) 2020 Northwestern University.
|
|
5
5
|
# Copyright (C) 2021 TU Wien.
|
|
6
6
|
#
|
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
from itertools import chain
|
|
13
13
|
|
|
14
14
|
from flask import current_app
|
|
15
|
+
from invenio_access.permissions import system_identity
|
|
16
|
+
from invenio_rdm_records.requests.record_deletion import RecordDeletion
|
|
15
17
|
from invenio_records.dictutils import dict_set
|
|
16
18
|
from invenio_records.errors import MissingModelError
|
|
17
19
|
from invenio_records_files.api import FileObject
|
|
20
|
+
from invenio_requests.proxies import current_requests_service
|
|
21
|
+
from invenio_search.api import dsl
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
def previewer_record_file_factory(pid, record, filename):
|
|
@@ -88,3 +92,20 @@ def dump_external_resource(
|
|
|
88
92
|
},
|
|
89
93
|
"template": template,
|
|
90
94
|
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_existing_deletion_request(record_id):
|
|
98
|
+
"""Return existing open deletion requests for the record."""
|
|
99
|
+
existing_requests = current_requests_service.search(
|
|
100
|
+
system_identity,
|
|
101
|
+
extra_filter=dsl.Q(
|
|
102
|
+
"bool",
|
|
103
|
+
must=[
|
|
104
|
+
dsl.Q("term", **{"topic.record": record_id}),
|
|
105
|
+
dsl.Q("term", **{"type": RecordDeletion.type_id}),
|
|
106
|
+
dsl.Q("term", **{"is_open": True}),
|
|
107
|
+
],
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
if existing_requests.total > 0:
|
|
111
|
+
return list(existing_requests)[0]
|
|
@@ -26,6 +26,7 @@ from invenio_i18n.ext import current_i18n
|
|
|
26
26
|
from invenio_rdm_records.proxies import current_rdm_records
|
|
27
27
|
from invenio_rdm_records.records.api import get_files_quota
|
|
28
28
|
from invenio_rdm_records.resources.serializers import UIJSONSerializer
|
|
29
|
+
from invenio_rdm_records.services.config import RDMRecordDeletionPolicy
|
|
29
30
|
from invenio_rdm_records.services.schemas import RDMRecordSchema
|
|
30
31
|
from invenio_rdm_records.services.schemas.utils import dump_empty
|
|
31
32
|
from invenio_rdm_records.views import file_transfer_type
|
|
@@ -37,7 +38,7 @@ from invenio_vocabularies.records.models import VocabularyScheme
|
|
|
37
38
|
from marshmallow_utils.fields.babel import gettext_from_dict
|
|
38
39
|
from sqlalchemy.orm import load_only
|
|
39
40
|
|
|
40
|
-
from ..utils import set_default_value
|
|
41
|
+
from ..utils import get_existing_deletion_request, set_default_value
|
|
41
42
|
from .decorators import (
|
|
42
43
|
no_cache_response,
|
|
43
44
|
pass_draft,
|
|
@@ -207,10 +208,13 @@ class VocabulariesOptions:
|
|
|
207
208
|
for hit in subset_resource_types.to_dict()["hits"]["hits"]
|
|
208
209
|
]
|
|
209
210
|
|
|
210
|
-
def _dump_vocabulary_w_basic_fields(self, vocabulary_type):
|
|
211
|
+
def _dump_vocabulary_w_basic_fields(self, vocabulary_type, extra_filter=None):
|
|
211
212
|
"""Dump vocabulary with id and title field."""
|
|
212
213
|
results = vocabulary_service.read_all(
|
|
213
|
-
g.identity,
|
|
214
|
+
g.identity,
|
|
215
|
+
fields=["id", "title"],
|
|
216
|
+
type=vocabulary_type,
|
|
217
|
+
extra_filter=extra_filter,
|
|
214
218
|
)
|
|
215
219
|
return [
|
|
216
220
|
{
|
|
@@ -303,6 +307,13 @@ class VocabulariesOptions:
|
|
|
303
307
|
"scheme": self.identifier_schemes(),
|
|
304
308
|
}
|
|
305
309
|
|
|
310
|
+
def removal_reasons(self):
|
|
311
|
+
"""Dump removal reasons vocabulary."""
|
|
312
|
+
self._vocabularies["removal_reasons"] = self._dump_vocabulary_w_basic_fields(
|
|
313
|
+
"removalreasons", extra_filter=dsl.Q("term", tags="deletion-request")
|
|
314
|
+
)
|
|
315
|
+
return self._vocabularies["removal_reasons"]
|
|
316
|
+
|
|
306
317
|
def dump(self):
|
|
307
318
|
"""Dump into dict."""
|
|
308
319
|
# TODO: Nest vocabularies inside "metadata" key so that frontend dumber
|
|
@@ -314,6 +325,7 @@ class VocabulariesOptions:
|
|
|
314
325
|
self.contributor_roles()
|
|
315
326
|
self.subjects()
|
|
316
327
|
self.identifiers()
|
|
328
|
+
self.removal_reasons()
|
|
317
329
|
# We removed
|
|
318
330
|
# vocabularies["relation_type"] = _dump_relation_types_vocabulary()
|
|
319
331
|
return self._vocabularies
|
|
@@ -522,10 +534,49 @@ def deposit_edit(pid_value, draft=None, draft_files=None, files_locked=True):
|
|
|
522
534
|
record = ui_serializer.dump_obj(draft.to_dict())
|
|
523
535
|
|
|
524
536
|
published_record = None
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
537
|
+
if record["is_published"]:
|
|
538
|
+
published_record_result = service.read(g.identity, id_=record["id"]).to_dict()
|
|
539
|
+
published_record = ui_serializer.dump_obj(published_record_result)
|
|
540
|
+
|
|
541
|
+
rec_del = RDMRecordDeletionPolicy().evaluate(
|
|
542
|
+
g.identity, published_record_result._record
|
|
543
|
+
)
|
|
544
|
+
immediate, request = rec_del["immediate_deletion"], rec_del["request_deletion"]
|
|
545
|
+
rd_enabled = immediate.enabled or request.enabled
|
|
546
|
+
rd_valid_user = (
|
|
547
|
+
rec_del["immediate_deletion"].valid_user
|
|
548
|
+
or rec_del["request_deletion"].valid_user
|
|
549
|
+
)
|
|
550
|
+
rd_allowed = immediate.allowed or request.allowed
|
|
551
|
+
existing_request = get_existing_deletion_request(record.get("id"))
|
|
552
|
+
|
|
553
|
+
if rd_allowed:
|
|
554
|
+
record_deletion = {
|
|
555
|
+
"enabled": rd_enabled,
|
|
556
|
+
"valid_user": rd_valid_user,
|
|
557
|
+
"allowed": rd_allowed,
|
|
558
|
+
"recordDeletion": rec_del,
|
|
559
|
+
"checklist": (
|
|
560
|
+
current_app.config["RDM_IMMEDIATE_RECORD_DELETION_CHECKLIST"]
|
|
561
|
+
if immediate.allowed
|
|
562
|
+
else current_app.config["RDM_REQUEST_RECORD_DELETION_CHECKLIST"]
|
|
563
|
+
),
|
|
564
|
+
"context": {
|
|
565
|
+
"files": draft._record.files.count,
|
|
566
|
+
"internalDoi": draft._record.pids["doi"]["provider"] != "external",
|
|
567
|
+
},
|
|
568
|
+
}
|
|
569
|
+
else:
|
|
570
|
+
record_deletion = {
|
|
571
|
+
"enabled": rd_enabled,
|
|
572
|
+
"valid_user": rd_valid_user,
|
|
573
|
+
"allowed": rd_allowed,
|
|
574
|
+
}
|
|
575
|
+
record_deletion["existing_request"] = (
|
|
576
|
+
existing_request["links"]["self_html"] if existing_request else None
|
|
577
|
+
)
|
|
578
|
+
else:
|
|
579
|
+
record_deletion = {}
|
|
529
580
|
|
|
530
581
|
community_ui = None
|
|
531
582
|
community_theme = None
|
|
@@ -600,6 +651,7 @@ def deposit_edit(pid_value, draft=None, draft_files=None, files_locked=True):
|
|
|
600
651
|
"manage_record_access",
|
|
601
652
|
]
|
|
602
653
|
),
|
|
654
|
+
record_deletion=record_deletion,
|
|
603
655
|
)
|
|
604
656
|
|
|
605
657
|
|
|
@@ -29,6 +29,7 @@ from invenio_rdm_records.records.systemfields.access.access_settings import (
|
|
|
29
29
|
AccessSettings,
|
|
30
30
|
)
|
|
31
31
|
from invenio_rdm_records.resources.serializers import UIJSONSerializer
|
|
32
|
+
from invenio_rdm_records.services.config import RDMRecordDeletionPolicy
|
|
32
33
|
from invenio_stats.proxies import current_stats
|
|
33
34
|
from invenio_users_resources.proxies import current_user_resources
|
|
34
35
|
from marshmallow import ValidationError
|
|
@@ -37,7 +38,7 @@ from invenio_app_rdm.records_ui.previewer.iiif_simple import (
|
|
|
37
38
|
previewable_extensions as image_extensions,
|
|
38
39
|
)
|
|
39
40
|
|
|
40
|
-
from ..utils import get_external_resources
|
|
41
|
+
from ..utils import get_existing_deletion_request, get_external_resources
|
|
41
42
|
from .decorators import (
|
|
42
43
|
add_signposting_content_resources,
|
|
43
44
|
add_signposting_landing_page,
|
|
@@ -156,6 +157,44 @@ def record_detail(
|
|
|
156
157
|
record._record.parent["access"]["settings"] = AccessSettings({}).dump()
|
|
157
158
|
|
|
158
159
|
record_ui = UIJSONSerializer().dump_obj(record.to_dict())
|
|
160
|
+
|
|
161
|
+
rec_del = RDMRecordDeletionPolicy().evaluate(g.identity, record._record)
|
|
162
|
+
|
|
163
|
+
immediate, request = rec_del["immediate_deletion"], rec_del["request_deletion"]
|
|
164
|
+
rd_enabled = immediate.enabled or request.enabled
|
|
165
|
+
rd_valid_user = (
|
|
166
|
+
rec_del["immediate_deletion"].valid_user
|
|
167
|
+
or rec_del["request_deletion"].valid_user
|
|
168
|
+
)
|
|
169
|
+
rd_allowed = immediate.allowed or request.allowed
|
|
170
|
+
existing_request = get_existing_deletion_request(record.id)
|
|
171
|
+
|
|
172
|
+
if rd_allowed:
|
|
173
|
+
record_deletion = {
|
|
174
|
+
"enabled": rd_enabled,
|
|
175
|
+
"valid_user": rd_valid_user,
|
|
176
|
+
"allowed": rd_allowed,
|
|
177
|
+
"recordDeletion": rec_del,
|
|
178
|
+
"checklist": (
|
|
179
|
+
current_app.config["RDM_IMMEDIATE_RECORD_DELETION_CHECKLIST"]
|
|
180
|
+
if immediate.allowed
|
|
181
|
+
else current_app.config["RDM_REQUEST_RECORD_DELETION_CHECKLIST"]
|
|
182
|
+
),
|
|
183
|
+
"context": {
|
|
184
|
+
"files": record._record.files.count,
|
|
185
|
+
"internalDoi": record._record.pids["doi"]["provider"] != "external",
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
else:
|
|
189
|
+
record_deletion = {
|
|
190
|
+
"enabled": rd_enabled,
|
|
191
|
+
"valid_user": rd_valid_user,
|
|
192
|
+
"allowed": rd_allowed,
|
|
193
|
+
}
|
|
194
|
+
record_deletion["existing_request"] = (
|
|
195
|
+
existing_request["links"]["self_html"] if existing_request else None
|
|
196
|
+
)
|
|
197
|
+
|
|
159
198
|
is_draft = record_ui["is_draft"]
|
|
160
199
|
custom_fields = load_custom_fields()
|
|
161
200
|
# keep only landing page configurable custom fields
|
|
@@ -243,6 +282,8 @@ def record_detail(
|
|
|
243
282
|
"view",
|
|
244
283
|
"media_read_files",
|
|
245
284
|
"moderate",
|
|
285
|
+
"request_deletion",
|
|
286
|
+
"immediately_delete",
|
|
246
287
|
]
|
|
247
288
|
),
|
|
248
289
|
custom_fields_ui=custom_fields["ui"],
|
|
@@ -253,6 +294,7 @@ def record_detail(
|
|
|
253
294
|
community_ui=resolved_community_ui,
|
|
254
295
|
external_resources=get_external_resources(record),
|
|
255
296
|
user_avatar=avatar,
|
|
297
|
+
record_deletion=record_deletion,
|
|
256
298
|
)
|
|
257
299
|
|
|
258
300
|
|