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.
Files changed (34) hide show
  1. invenio_app_rdm/__init__.py +1 -1
  2. invenio_app_rdm/administration/moderation/__init__.py +11 -0
  3. invenio_app_rdm/administration/moderation/requests.py +175 -0
  4. invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/requests.html +14 -0
  5. invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/requests_details.html +89 -0
  6. invenio_app_rdm/administration/user_moderation/user_moderation.py +4 -4
  7. invenio_app_rdm/config.py +47 -0
  8. invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/deposit.html +3 -0
  9. invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/manage_menu.html +3 -3
  10. invenio_app_rdm/records_ui/utils.py +22 -1
  11. invenio_app_rdm/records_ui/views/deposits.py +59 -7
  12. invenio_app_rdm/records_ui/views/records.py +43 -1
  13. invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/guest-access-request/index.html +5 -5
  14. invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/record-deletion/index.html +93 -0
  15. invenio_app_rdm/requests_ui/views/requests.py +17 -2
  16. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/RequestsSearchResultItem.js +81 -0
  17. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/RequestsSearchbarLayout.js +48 -0
  18. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/index.js +22 -0
  19. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion/DeletionModal.js +356 -0
  20. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion/DeletionRadioGroup.js +44 -0
  21. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion.js +81 -0
  22. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/RDMDepositForm.js +22 -2
  23. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/index.js +2 -1
  24. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ManageButton.js +72 -20
  25. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordCommunitySubmission/RecordCommunitySubmissionModal.js +1 -1
  26. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordManagement.js +10 -3
  27. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/index.js +1 -0
  28. invenio_app_rdm/theme/webpack.py +2 -1
  29. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/METADATA +20 -5
  30. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/RECORD +34 -23
  31. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/entry_points.txt +2 -0
  32. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/WHEEL +0 -0
  33. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/licenses/LICENSE +0 -0
  34. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/top_level.txt +0 -0
@@ -17,6 +17,6 @@
17
17
  #
18
18
  # See PEP 0440 for details - https://www.python.org/dev/peps/pep-0440
19
19
 
20
- __version__ = "14.0.0b0.dev4"
20
+ __version__ = "14.0.0b1.dev2"
21
21
 
22
22
  __all__ = ("__version__",)
@@ -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
+ }
@@ -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-2024 CERN.
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 = _("Moderation")
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 = 1
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, fields=["id", "title"], type=vocabulary_type
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
- # if editing draft of a published record
526
- if record.get("status") == "published":
527
- _record = service.read(g.identity, id_=record["id"]).to_dict()
528
- published_record = ui_serializer.dump_obj(_record)
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