invenio-app-rdm 13.0.0b3.dev5__py2.py3-none-any.whl → 13.0.0b3.dev7__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 (28) hide show
  1. invenio_app_rdm/__init__.py +1 -1
  2. invenio_app_rdm/administration/audit_logs/__init__.py +12 -0
  3. invenio_app_rdm/administration/audit_logs/audit_logs.py +77 -0
  4. invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/audit_logs.html +14 -0
  5. invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/details.html +2 -2
  6. invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/detail.html +9 -0
  7. invenio_app_rdm/records_ui/views/__init__.py +2 -0
  8. invenio_app_rdm/records_ui/views/decorators.py +10 -7
  9. invenio_app_rdm/requests_ui/views/requests.py +6 -3
  10. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/auditLogs/ViewAction.js +0 -0
  11. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/auditLogs/index.js +29 -0
  12. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/auditLogs/search/SearchResultItemLayout.js +71 -0
  13. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/auditLogs/search/index.js +9 -0
  14. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/RDMDepositForm.js +1 -1
  15. invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/files_integrity_report/email/files_integrity_report.html +10 -11
  16. invenio_app_rdm/theme/webpack.py +1 -0
  17. {invenio_app_rdm-13.0.0b3.dev5.dist-info → invenio_app_rdm-13.0.0b3.dev7.dist-info}/METADATA +20 -6
  18. {invenio_app_rdm-13.0.0b3.dev5.dist-info → invenio_app_rdm-13.0.0b3.dev7.dist-info}/RECORD +27 -20
  19. {invenio_app_rdm-13.0.0b3.dev5.dist-info → invenio_app_rdm-13.0.0b3.dev7.dist-info}/WHEEL +1 -1
  20. {invenio_app_rdm-13.0.0b3.dev5.dist-info → invenio_app_rdm-13.0.0b3.dev7.dist-info}/entry_points.txt +1 -0
  21. tests/conftest.py +0 -41
  22. tests/mock_module/templates/mock_mail.html +3 -4
  23. tests/test_tasks.py +121 -16
  24. tests/ui/conftest.py +17 -5
  25. tests/ui/test_file_download.py +73 -0
  26. invenio_app_rdm/urls.py +0 -72
  27. {invenio_app_rdm-13.0.0b3.dev5.dist-info → invenio_app_rdm-13.0.0b3.dev7.dist-info}/licenses/LICENSE +0 -0
  28. {invenio_app_rdm-13.0.0b3.dev5.dist-info → invenio_app_rdm-13.0.0b3.dev7.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__ = "13.0.0b3.dev5"
20
+ __version__ = "13.0.0b3.dev7"
21
21
 
22
22
  __all__ = ("__version__",)
@@ -0,0 +1,12 @@
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
+
8
+ """Audit logs administration module."""
9
+
10
+ from .audit_logs import AuditLogListView
11
+
12
+ __all__ = "AuditLogListView"
@@ -0,0 +1,77 @@
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
+
8
+ """Invenio administration view module for audit logs."""
9
+ from flask import current_app
10
+ from invenio_administration.views.base import AdminResourceListView
11
+ from invenio_i18n import lazy_gettext as _
12
+
13
+
14
+ class AuditLogListView(AdminResourceListView):
15
+ """Audit logs admin search view."""
16
+
17
+ api_endpoint = "/audit-logs/"
18
+ extension_name = "invenio-audit-logs"
19
+ name = "audit-logs"
20
+ resource_config = "audit_log_resource"
21
+
22
+ title = "Audit Logs"
23
+ menu_label = "Audit Logs"
24
+ category = "Logs"
25
+ pid_path = "id"
26
+ icon = "file alternate"
27
+ template = "invenio_app_rdm/administration/audit_logs.html"
28
+ order = 1
29
+ search_request_headers = {"Accept": "application/vnd.inveniordm.v1+json"}
30
+
31
+ display_search = True
32
+ display_delete = False
33
+ display_create = False
34
+ display_edit = False
35
+
36
+ item_field_list = {
37
+ "id": {
38
+ "text": _("Log ID"),
39
+ "order": 1,
40
+ "width": 3,
41
+ "width": 3,
42
+ },
43
+ "resource.type": {
44
+ "text": _("Resource"),
45
+ "order": 2,
46
+ "width": 2,
47
+ },
48
+ "resource.id": { # Link to resource in the `resource_type` admin panel
49
+ "text": _("Resource ID"),
50
+ "order": 3,
51
+ "width": 2,
52
+ },
53
+ "action": {
54
+ "text": _("Action"),
55
+ "order": 4,
56
+ "width": 2,
57
+ },
58
+ "user.id": { # Link to user in user admin panel
59
+ "text": _("User"),
60
+ "order": 5,
61
+ "width": 3,
62
+ },
63
+ "created": {
64
+ "text": _("Timestamp"),
65
+ "order": 6,
66
+ "width": 7,
67
+ },
68
+ }
69
+
70
+ search_config_name = "AUDIT_LOGS_SEARCH"
71
+ search_facets_config_name = "AUDIT_LOGS_FACETS"
72
+ search_sort_config_name = "AUDIT_LOGS_SORT_OPTIONS"
73
+
74
+ @staticmethod
75
+ def disabled():
76
+ """Disable the view on demand."""
77
+ return not current_app.config["AUDIT_LOGS_ENABLED"]
@@ -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-audit-logs-administration.js'] }}
14
+ {% endblock %}
@@ -9,7 +9,7 @@
9
9
  #}
10
10
 
11
11
  {%- from "invenio_app_rdm/records/macros/detail.html" import
12
- list_languages, show_dates, show_detail, show_detail_conference %}
12
+ list_languages, show_dates, show_detail, show_detail_conference, show_detail_thesis %}
13
13
 
14
14
  {%- set id_doi = record.pids.get('doi', {}).get('identifier') %}
15
15
 
@@ -26,7 +26,7 @@
26
26
  {{ show_detail(_('Publisher'), metadata.publisher) if metadata.publisher }}
27
27
  {{ show_detail(_('Published in'), record.ui.publishing_information.journal) if record.ui.get('publishing_information', {}).get('journal') }}
28
28
  {{ show_detail(_('Imprint'), record.ui.publishing_information.imprint) if record.ui.get('publishing_information', {}).get('imprint') }}
29
- {{ show_detail(_('Awarding university'), record.ui.publishing_information.thesis) if record.ui.get('publishing_information', {}).get('thesis', {}) }}
29
+ {{ show_detail_thesis(_('Thesis'), record.ui.publishing_information.thesis) if record.ui.get('publishing_information', {}).get('thesis', {}) }}
30
30
  {{ show_detail(_('Conference'), show_detail_conference(record.ui.conference)) if record.ui.conference }}
31
31
  {{ show_detail(_('Languages'), list_languages(record.ui.languages)) if record.ui.languages }}
32
32
  {{ show_detail(_('Formats'), ", ".join(metadata.formats)) if metadata.formats }}
@@ -309,3 +309,12 @@
309
309
  class="fa fa-external-link"></i> {{ _('Conference website') }}</a></dd>
310
310
  {%- endif %}
311
311
  {% endmacro %}
312
+
313
+
314
+ {% macro show_detail_thesis(title, thesis) %}
315
+ <dt class="ui tiny header">
316
+ {{ title }}
317
+ </dt>
318
+ <dd>{{ thesis | safe }}
319
+ </dd>
320
+ {% endmacro %}
@@ -22,6 +22,7 @@ from invenio_records_resources.services.errors import (
22
22
  PermissionDeniedError,
23
23
  RecordPermissionDeniedError,
24
24
  )
25
+ from sqlalchemy.orm.exc import NoResultFound
25
26
 
26
27
  from invenio_app_rdm.views import create_url_rule
27
28
 
@@ -161,6 +162,7 @@ def create_blueprint(app):
161
162
  blueprint.register_error_handler(PIDUnregistered, not_found_error)
162
163
  blueprint.register_error_handler(KeyError, not_found_error)
163
164
  blueprint.register_error_handler(FileKeyNotFoundError, not_found_error)
165
+ blueprint.register_error_handler(NoResultFound, not_found_error)
164
166
  blueprint.register_error_handler(DraftNotCreatedError, draft_not_found_error)
165
167
  blueprint.register_error_handler(
166
168
  PermissionDeniedError, record_permission_denied_error
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
3
  # Copyright (C) 2019-2025 CERN.
4
- # Copyright (C) 2019-2021 Northwestern University.
4
+ # Copyright (C) 2019-2025 Northwestern University.
5
5
  # Copyright (C) 2021 TU Wien.
6
6
  #
7
7
  # Invenio App RDM is free software; you can redistribute it and/or modify it
@@ -13,6 +13,7 @@ from functools import wraps
13
13
 
14
14
  from flask import current_app, g, make_response, redirect, request, session, url_for
15
15
  from flask_login import login_required
16
+ from invenio_base import invenio_url_for
16
17
  from invenio_communities.communities.resources.serializer import (
17
18
  UICommunityJSONSerializer,
18
19
  )
@@ -25,8 +26,6 @@ from invenio_rdm_records.resources.serializers.signposting import (
25
26
  from invenio_records_resources.services.errors import PermissionDeniedError
26
27
  from sqlalchemy.orm.exc import NoResultFound
27
28
 
28
- from invenio_app_rdm.urls import record_url_for
29
-
30
29
 
31
30
  def service():
32
31
  """Get the record service."""
@@ -376,17 +375,21 @@ def _get_header(rel, value, link_type=None):
376
375
 
377
376
 
378
377
  def _get_signposting_collection(pid_value):
379
- ui_url = record_url_for(pid_value=pid_value)
378
+ ui_url = invenio_url_for(
379
+ "invenio_app_rdm_records.record_detail", pid_value=pid_value
380
+ )
380
381
  return _get_header("collection", ui_url, "text/html")
381
382
 
382
383
 
383
384
  def _get_signposting_describes(pid_value):
384
- ui_url = record_url_for(pid_value=pid_value)
385
+ ui_url = invenio_url_for(
386
+ "invenio_app_rdm_records.record_detail", pid_value=pid_value
387
+ )
385
388
  return _get_header("describes", ui_url, "text/html")
386
389
 
387
390
 
388
391
  def _get_signposting_linkset(pid_value):
389
- api_url = record_url_for(_app="api", pid_value=pid_value)
392
+ api_url = invenio_url_for("records.read", pid_value=pid_value)
390
393
  return _get_header("linkset", api_url, "application/linkset+json")
391
394
 
392
395
 
@@ -412,7 +415,7 @@ def add_signposting_landing_page(f):
412
415
  response.headers["Link"] = signposting_headers
413
416
  else:
414
417
  pid_value = kwargs["pid_value"]
415
- signposting_link = record_url_for(_app="api", pid_value=pid_value)
418
+ signposting_link = invenio_url_for("records.read", pid_value=pid_value)
416
419
 
417
420
  response.headers["Link"] = (
418
421
  f'<{signposting_link}> ; rel="linkset" ; type="application/linkset+json"' # fmt: skip
@@ -352,7 +352,8 @@ def community_dashboard_request_view(request, community, community_ui, **kwargs)
352
352
  base_template="invenio_communities/details/base.html",
353
353
  invenio_request=request.to_dict(),
354
354
  record=record_ui,
355
- community=community_ui,
355
+ community=community,
356
+ community_ui=community_ui,
356
357
  checks=checks,
357
358
  permissions=permissions,
358
359
  is_preview=is_draft, # preview only when draft
@@ -376,7 +377,8 @@ def community_dashboard_request_view(request, community, community_ui, **kwargs)
376
377
  theme=community.to_dict().get("theme", {}),
377
378
  base_template="invenio_communities/details/members/base.html",
378
379
  invenio_request=request.to_dict(),
379
- community=community.to_dict(),
380
+ community=community,
381
+ community_ui=community_ui,
380
382
  permissions=permissions,
381
383
  request_is_accepted=request_is_accepted,
382
384
  user_avatar=avatar,
@@ -389,7 +391,8 @@ def community_dashboard_request_view(request, community, community_ui, **kwargs)
389
391
  theme=community.to_dict().get("theme", {}),
390
392
  base_template="invenio_communities/details/base.html",
391
393
  invenio_request=request.to_dict(),
392
- community=community_ui,
394
+ community=community,
395
+ community_ui=community_ui,
393
396
  permissions=permissions,
394
397
  request_is_accepted=request_is_accepted,
395
398
  user_avatar=avatar,
@@ -0,0 +1,29 @@
1
+ // This file is part of InvenioCommunities
2
+ // Copyright (C) 2025 CERN.
3
+ //
4
+ // Invenio 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
+ import { initDefaultSearchComponents } from "@js/invenio_administration";
8
+ import { createSearchAppInit } from "@js/invenio_search_ui";
9
+ import { NotificationController } from "@js/invenio_administration";
10
+ import { SearchResultItemLayout } from "./search";
11
+ import { SearchFacets } from "@js/invenio_administration";
12
+
13
+ const domContainer = document.getElementById("invenio-search-config");
14
+
15
+ const defaultComponents = initDefaultSearchComponents(domContainer);
16
+
17
+ const overridenComponents = {
18
+ ...defaultComponents,
19
+ "InvenioAdministration.SearchResultItem.layout": SearchResultItemLayout,
20
+ "SearchApp.facets": SearchFacets,
21
+ };
22
+
23
+ createSearchAppInit(
24
+ overridenComponents,
25
+ true,
26
+ "invenio-search-config",
27
+ false,
28
+ NotificationController
29
+ );
@@ -0,0 +1,71 @@
1
+ /*
2
+ * This file is part of Invenio.
3
+ * Copyright (C) 2025 CERN.
4
+ *
5
+ * Invenio 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
+ */
8
+
9
+ import PropTypes from "prop-types";
10
+ import React, { Component } from "react";
11
+ import { Item, Table } from "semantic-ui-react";
12
+ import { Image } from "react-invenio-forms";
13
+ import { withState } from "react-searchkit";
14
+ import { i18next } from "@translations/invenio_app_rdm/i18next";
15
+
16
+ class SearchResultItemComponent extends Component {
17
+ componentDidMount() {
18
+ console.error("result", this.props.result);
19
+ }
20
+
21
+ refreshAfterAction = () => {
22
+ const { updateQueryState, currentQueryState } = this.props;
23
+ updateQueryState(currentQueryState);
24
+ };
25
+
26
+ render() {
27
+ const { result } = this.props;
28
+
29
+ const {
30
+ id,
31
+ created,
32
+ action,
33
+ resource: { id: resourceId, type: resourceType },
34
+ user: { id: userId, email: userEmail },
35
+ } = result;
36
+
37
+ return (
38
+ <Table.Row>
39
+ <Table.Cell data-label={i18next.t("Log ID")}>
40
+ <a target="_blank" rel="noreferrer noopener" href={result.links.self}>
41
+ {id}
42
+ </a>
43
+ </Table.Cell>
44
+ <Table.Cell data-label={i18next.t("Resource")}>{resourceType}</Table.Cell>
45
+ <Table.Cell data-label={i18next.t("Resource ID")}>
46
+ <a href={`/administration/${resourceType}s?q=id:${resourceId}`}>
47
+ {resourceId}
48
+ </a>
49
+ </Table.Cell>
50
+ <Table.Cell data-label={i18next.t("Action")}>{action}</Table.Cell>
51
+ <Table.Cell data-label={i18next.t("User")}>
52
+ <Item className="flex" key={userId}>
53
+ <Image src={`/api/users/${userId}/avatar.svg`} avatar loadFallbackFirst />
54
+ <a href={`/administration/users?q=id:${userId}`}>
55
+ {userEmail} ({userId})
56
+ </a>
57
+ </Item>
58
+ </Table.Cell>
59
+ <Table.Cell data-label={i18next.t("Date")}>{created}</Table.Cell>
60
+ </Table.Row>
61
+ );
62
+ }
63
+ }
64
+
65
+ SearchResultItemComponent.propTypes = {
66
+ result: PropTypes.object.isRequired,
67
+ updateQueryState: PropTypes.func.isRequired,
68
+ currentQueryState: PropTypes.object.isRequired,
69
+ };
70
+
71
+ export const SearchResultItemLayout = withState(SearchResultItemComponent);
@@ -0,0 +1,9 @@
1
+ /*
2
+ * This file is part of Invenio.
3
+ * Copyright (C) 2025 CERN.
4
+ *
5
+ * Invenio 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
+ */
8
+
9
+ export { SearchResultItemLayout } from "./SearchResultItemLayout";
@@ -481,7 +481,7 @@ export class RDMDepositForm extends Component {
481
481
  includesPaths={this.sectionsConfig["funding-section"]}
482
482
  severityChecks={this.severityChecks}
483
483
  active
484
- label="Funding"
484
+ label={i18next.t("Funding")}
485
485
  ui={this.accordionStyle}
486
486
  id="funding-section"
487
487
  >
@@ -3,30 +3,29 @@
3
3
  #
4
4
  # Copyright (C) 2022 CERN.
5
5
  # Copyright (C) 2024 KTH Royal Institute of Technology.
6
+ # Copyright (C) 2025 Northwestern University.
6
7
  #
7
8
  # Invenio App RDM is free software; you can redistribute it and/or modify it
8
9
  # under the terms of the MIT License; see LICENSE file for more details.
9
10
  -#}
10
11
 
11
- {% set BASE_URL = config.SITE_UI_URL %}
12
-
13
12
  {{ _("The following files were flagged as 'unhealthy'. This means that the checksum check failed or timed out. Please take any action if needed.") }}
14
13
 
15
14
  {% for entry in entries -%}
16
- {{ _("ID: %s") | format(entry.file.id) }}
17
- {{ _("URI: %s") | format(entry.file.uri) }}
15
+ {{ _("ID: {}").format(entry.file.id) }}
16
+ {{ _("URI: {}").format(entry.file.uri) }}
18
17
  {%- if 'filename' in entry %}
19
- {{ _("Name: %s") | format(entry.filename) }}
18
+ {{ _("Name: {}").format(entry.filename) }}
20
19
  {%- endif %}
21
- {{ _("Created: %s") | format(entry.file.created) }}
22
- {{ _("Checksum: %s") | format(entry.file.checksum) }}
23
- {{ _("Last check date: %s") | format(entry.file.last_check_at) }}
24
- {{ _("Last check FAILED with result: %s") | format(entry.file.last_check) }}
20
+ {{ _("Created: {}").format(entry.file.created) }}
21
+ {{ _("Checksum: {}").format(entry.file.checksum) }}
22
+ {{ _("Last check date: {}").format(entry.file.last_check_at) }}
23
+ {{ _("Last check FAILED with result: {}").format(entry.file.last_check) }}
25
24
  {%- if 'record' in entry %}
26
- {{ _("Record: %s/records/%s") | format(BASE_URL, entry.record.id) }}
25
+ {{ _("Record: {}").format(invenio_url_for("invenio_app_rdm_records.record_detail", pid_value=entry.record.id)) }}
27
26
  {%- endif %}
28
27
  {%- if 'draft' in entry %}
29
- {{ _("Draft: %s/uploads/%s") | format(BASE_URL, entry.draft.id) }}
28
+ {{ _("Draft: {}").format(invenio_url_for("invenio_app_rdm_records.deposit_edit", pid_value=entry.draft.id)) }}
30
29
  {%- endif %}
31
30
  {{ "-" * 80 }}
32
31
  {% endfor %}
@@ -38,6 +38,7 @@ theme = WebpackThemeBundle(
38
38
  "invenio-records-administration": "./js/invenio_app_rdm/administration/records/index.js",
39
39
  "invenio-drafts-administration": "./js/invenio_app_rdm/administration/drafts/index.js",
40
40
  "invenio-domains-administration": "./js/invenio_app_rdm/administration/domains/index.js",
41
+ "invenio-audit-logs-administration": "./js/invenio_app_rdm/administration/auditLogs/index.js",
41
42
  "invenio-communities-browse": "./js/invenio_app_rdm/subcommunity/browse.js",
42
43
  },
43
44
  dependencies={
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invenio-app-rdm
3
- Version: 13.0.0b3.dev5
3
+ Version: 13.0.0b3.dev7
4
4
  Summary: Invenio Research Data Management.
5
5
  Home-page: https://github.com/inveniosoftware/invenio-app-rdm
6
6
  Author: CERN
@@ -11,8 +11,8 @@ Platform: any
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Requires-Python: >=3.7
13
13
  License-File: LICENSE
14
- Requires-Dist: invenio-app<3.0.0,>=2.0.0
15
- Requires-Dist: invenio-base<3.0.0,>=2.0.0
14
+ Requires-Dist: invenio-app<3.0.0,>=2.1.0
15
+ Requires-Dist: invenio-base<3.0.0,>=2.1.0
16
16
  Requires-Dist: invenio-cache<3.0.0,>=2.0.0
17
17
  Requires-Dist: invenio-celery<3.0.0,>=2.0.0
18
18
  Requires-Dist: invenio-config<2.0.0,>=1.0.3
@@ -40,14 +40,15 @@ Requires-Dist: invenio-search-ui<5.0.0,>=4.0.0
40
40
  Requires-Dist: invenio-files-rest<4.0.0,>=3.0.0
41
41
  Requires-Dist: invenio-previewer<4.0.0,>=3.0.0
42
42
  Requires-Dist: invenio-records-files<2.0.0,>=1.2.1
43
- Requires-Dist: invenio-communities<19.0.0,>=18.0.0
44
- Requires-Dist: invenio-rdm-records<19.0.0,>=18.1.0
43
+ Requires-Dist: invenio-communities<19.0.0,>=18.2.0
44
+ Requires-Dist: invenio-rdm-records<19.0.0,>=18.4.0
45
45
  Requires-Dist: CairoSVG<3.0.0,>=2.5.2
46
46
  Requires-Dist: invenio-banners<5.0.0,>=4.0.0
47
47
  Requires-Dist: invenio-pages<6.0.0,>=5.0.0
48
+ Requires-Dist: invenio-audit-logs<1.0.0,>=0.1.0
48
49
  Provides-Extra: tests
49
50
  Requires-Dist: pytest-black-ng>=0.4.0; extra == "tests"
50
- Requires-Dist: pytest-invenio<4.0.0,>=3.0.0; extra == "tests"
51
+ Requires-Dist: pytest-invenio<4.0.0,>=3.3.0; extra == "tests"
51
52
  Requires-Dist: Sphinx>=4.5.0; extra == "tests"
52
53
  Provides-Extra: elasticsearch7
53
54
  Requires-Dist: invenio-search[elasticsearch7]<4.0.0,>=3.0.0; extra == "elasticsearch7"
@@ -97,6 +98,19 @@ https://inveniordm.docs.cern.ch
97
98
  Changes
98
99
  =======
99
100
 
101
+ Version v13.0.0b3.dev7 (released 2025-05-08)
102
+
103
+ - fix: community request page missing context variable
104
+
105
+ Version v13.0.0b3.dev6 (released 2025-05-07)
106
+
107
+ - records-ui: add error handler for NoResultFound exceptions
108
+ - tests: add tests for draft file download with and without preview flag
109
+ - i18n: mark string for translation
110
+ - urls: integrate link generation (invenio_url_for)
111
+ - templates: add thesis details display
112
+ - administration: Add Audit Logs Admin Panel UI (experimental feature, behind a flag)
113
+
100
114
  Version v13.0.0b3.dev5 (released 2025-04-25)
101
115
 
102
116
  - deposit: add copyright field
@@ -1,15 +1,17 @@
1
- invenio_app_rdm/__init__.py,sha256=nZGats5Z8q2BrxygVDy_X_sJZ2hM_mN8SDbdJgBFHao,699
1
+ invenio_app_rdm/__init__.py,sha256=XOVVBuu5KIjDGC4Z4qFjNx_OmuljD_F20pcco0NFczQ,699
2
2
  invenio_app_rdm/cli.py,sha256=G6QqNU2W6n6ICtTMnpeKFXIsdorncDmVXwwwsGH5F2k,2746
3
3
  invenio_app_rdm/config.py,sha256=Sv6Q8VEP-KYNuvC6kASLOYC-gYRByYN-hwPmbCxQWKc,50624
4
4
  invenio_app_rdm/ext.py,sha256=PkZhATGJDgYqBJQh41NdvBZWR83mgI3Eej6rj10UVJE,5278
5
5
  invenio_app_rdm/tasks.py,sha256=FyrIQXVuPjms-dNEnLrVmmdwrX_IykJ87gcSNgOR6O0,1373
6
- invenio_app_rdm/urls.py,sha256=8S95QSs4yS0rtORsd4veo--rF3LSLwZenoowJ5ubbmM,2496
7
6
  invenio_app_rdm/views.py,sha256=SDr9NwZEWQcgT_3GFRYdDf6eUaK9DfnoafIkhUf9nSI,785
8
7
  invenio_app_rdm/administration/__init__.py,sha256=8r9LeoE9fNHZSVS5QsCfVhRU7MAiEOWJk9MA3Y--4F8,251
8
+ invenio_app_rdm/administration/audit_logs/__init__.py,sha256=jsBXeKSY5YNn1juF9sFyHPYo_XYpwdP3Dye-de7cMK0,318
9
+ invenio_app_rdm/administration/audit_logs/audit_logs.py,sha256=vgrGQ1lLE5uSuoJCx9kIMQPshjxJzZlCZ4v5cZEcKus,2156
9
10
  invenio_app_rdm/administration/domains/__init__.py,sha256=Qob5kqjRPxpuSE5yDV2tesN6tmaKp5JcxCxGA8Mrcak,487
10
11
  invenio_app_rdm/administration/domains/domains.py,sha256=vafLa-mqkg_tQLjx328E64P_4mksB5kjBlsfunvdatg,5599
11
12
  invenio_app_rdm/administration/records/__init__.py,sha256=WpNHBm_Mk9FF8GzvrXWjL79URMSgBhpqgxvrLXNooqg,434
12
13
  invenio_app_rdm/administration/records/records.py,sha256=BFxxnB4acN8Fd3A6CWuyRUjo3bvdWmNSkcxuVZYzTaA,5357
14
+ invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/audit_logs.html,sha256=kLP3lfZiwLB8N-78_xINymnD8TIjGMzeJIO1a1zO130,346
13
15
  invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/domains_search.html,sha256=NP8HPfOQPIR9psNDMFRXJH8fjok2AbXCentD_3Q1wWw,338
14
16
  invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/user_moderation.html,sha256=koXd8lV_KBgAl1Wll7aM3xR0NgYcOl2PiFqD2Xkcp2w,348
15
17
  invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/user_moderation_details.html,sha256=g6YZh9yWqDfzd5kMgIrO4c5k4iKa61y04RsuT_ZbMJI,350
@@ -63,7 +65,7 @@ invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details
63
65
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/subjects.html,sha256=Wu7MCXzGj41K38-VXi-kStvc8fm3Ck68uJ1egfFK0dw,1509
64
66
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/citations.html,sha256=5Sdg_gwGOgm0qLRv4-CUmDNx7PYlOsa5XPGjDdx7k20,258
65
67
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/communities.html,sha256=GxxKbXxVg5lKcSEzVeYJXyz7qUL7yhsZnhXY3lE-vNU,1561
66
- invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/details.html,sha256=zQCx5x5AE_T8nywICNBzo-9K4vNtfPSyz_F_7cC6L8Y,1974
68
+ invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/details.html,sha256=Ip3wBnfpS8U9fBH2q9nZbt7znAzUKvVrbz3QkeBUMUM,1988
67
69
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/export.html,sha256=DoDrQFd_OJcJzqBEQ2FRbcfJpD3TLRjESU68mBLWVyU,1392
68
70
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/external_resources.html,sha256=Ia92YMRlxuGRTMsWU4hQTLJDeRy8BX8bnSSaLXKst4Y,1934
69
71
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/keywords_subjects.html,sha256=LvsA6JiosfwSSwUUg6--3xx1OZepvN7mxQdIWyl9caE,708
@@ -74,14 +76,14 @@ invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details
74
76
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/technical_metadata.html,sha256=N5VUwCygKG-zXvyu0YeFJb7A0eHIcKWuMAVm9Kbrb_M,658
75
77
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/versions.html,sha256=sPYG81XFcU1ND6roeYmN4SY419iyvQj9ozCv9Pctw5E,932
76
78
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/creatibutors.html,sha256=N53nwtlEZjxweCx70qFJa1s6uL1pPSzWZgk8p1vodE4,4277
77
- invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/detail.html,sha256=1XF9DgxLADlpKY7vAcdXhtaOAyrCCupgi5p0AmnqDiQ,10447
79
+ invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/detail.html,sha256=YEF5tR22t2QUO-tKpjsS4gKhYMZpfyjbO6wWf8LKhJw,10598
78
80
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/doi.html,sha256=NZUtJSCwQScJsmz7vxglslEVqPwG7Ldul_wl7yce0fg,1489
79
81
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/files.html,sha256=Xsq_HiM_cTGKgmoE1HAXje8iYe3d0wIJoesk22Xn7vU,8761
80
82
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/locations.html,sha256=27-KyPqb05pu-yRXHvxCgZWRSi5bFP6xf7XBn91sbeA,1741
81
83
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/stats_popup.html,sha256=5SVzfIS15Aro2Itd2BiaLbMXm0cvvwk6ZCdYjuSwhBw,625
82
84
  invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/version.html,sha256=eA8-n81XUezkwPXvcG5v2sgLPQNTgr7hB36-_Gr-tVI,758
83
- invenio_app_rdm/records_ui/views/__init__.py,sha256=fzRrhYImQg13KnHfxJgzDlJPe-DzE02GifP2j_Wt480,5726
84
- invenio_app_rdm/records_ui/views/decorators.py,sha256=XUSPpstYyh72IdNaIO4wz3sw1fjI_2DNGsKdUu3ueL4,15769
85
+ invenio_app_rdm/records_ui/views/__init__.py,sha256=9DaDls04IQv7fYttDjLofIWGsRRjk-FwUYnIfxV3OWk,5840
86
+ invenio_app_rdm/records_ui/views/decorators.py,sha256=MUUmgGuigmFMigcpyJbNvM0yPKUyeAw5LE6MJ69BoFA,15883
85
87
  invenio_app_rdm/records_ui/views/deposits.py,sha256=KRR_fpl98EhNZie0Q-_uZtgG2QKsQAW2fhb-kvnQ4s4,21561
86
88
  invenio_app_rdm/records_ui/views/filters.py,sha256=Rm55fXJv97cqq8B_6KCe-3PxxHUH4VRu-jd9fTea6r4,6994
87
89
  invenio_app_rdm/records_ui/views/records.py,sha256=GdxG3JnXrV1QT0KxOqeYWoDGIiZRZ9w5poihPPd-wTo,16428
@@ -99,13 +101,17 @@ invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/subcommunity/
99
101
  invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/subcommunity-invitation/index.html,sha256=347Saf8Fv78uevCbK2nFiOtNFUT0QyZSZS7lgoNzHdQ,1448
100
102
  invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/user-access-request/index.html,sha256=ltSP9sYPnpmCKMDYpZYU25Wxr3Dfqe2RNDxm6bVjX_I,1779
101
103
  invenio_app_rdm/requests_ui/views/__init__.py,sha256=7QiAyRq8Eu84IXwjzxK63vNeTZnowZ5P85xtw7XgRjs,397
102
- invenio_app_rdm/requests_ui/views/requests.py,sha256=grC2bQWF36xzeftdOSjy_vBWAGSUbupOliPervenF6s,16048
104
+ invenio_app_rdm/requests_ui/views/requests.py,sha256=Ww3ewDciAvcrheOwtirS6Qewj21FQ8R1b42Og0_r_3w,16149
103
105
  invenio_app_rdm/requests_ui/views/ui.py,sha256=DBysYQa__gOCg-pikO6HmoVLmRmMAVWeTBiYhPa7PmA,2359
104
106
  invenio_app_rdm/theme/__init__.py,sha256=QbkxNjjOmGKRlie96HfTXgnFeVQjOX0GdiZnHP7pIhs,277
105
107
  invenio_app_rdm/theme/views.py,sha256=Ucn6o7mF8qR7gtOeliKC73gIBhy5LgY65rvteC1Uiog,4312
106
- invenio_app_rdm/theme/webpack.py,sha256=5ucwIJQgM1y9EFdRcsxv_ThPs7hXRzdmipVBxoU1McI,5090
108
+ invenio_app_rdm/theme/webpack.py,sha256=5oi0YBD84y8246JYGtSNmNbRUH-bz_G8WrSRRDEyplw,5201
107
109
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/theme.js,sha256=LSswk1IkVgUxTT3Arw_QF-mNQ16vVYDf2B90h6FZfFw,4431
108
110
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/utils.js,sha256=oTCQkmr7j6FwVsOfM6hsKrPHfadW7m9IurrqYVDgxDw,3628
111
+ invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/auditLogs/ViewAction.js,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
+ invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/auditLogs/index.js,sha256=7glSWacd986Zu6l_Ob7IXp4uNWA0f51FBfRv4HZxjYU,965
113
+ invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/auditLogs/search/SearchResultItemLayout.js,sha256=wXwMfu8u02A3KT_xZbalntDGvLwVFc9AlorUTuiSqdQ,2268
114
+ invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/auditLogs/search/index.js,sha256=q1WaQ-kNNMvNdnsONMw2c4GWobizoPWsmIdWL2fRCPc,283
109
115
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/CompareRevisionsDropdown.js,sha256=BNdMxfFYPhlrGKPg47oc4X8ILllfCngN61L1b7Yt_8Q,2483
110
116
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/ImpersonateUser.js,sha256=1tnHc9PaCQvVS1PVsDWtfpWzVxJJ5_eltn9WJEYygiY,2366
111
117
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/ImpersonateUserForm.js,sha256=Z21KrMyAJ1rKF5IEzM088-pTvTga21LBnG4GPYvh5g4,6890
@@ -160,7 +166,7 @@ invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/CopyButto
160
166
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/DisplayPartOfCommunities.js,sha256=bSmhuwSWk4CW80mqa4ZZbjfa-_NsiJ1IxQ9veDy6sAM,2751
161
167
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/DisplayVerifiedCommunity.js,sha256=m3rT8jJ3440ZX22zZog8bHatWltYXwvmw_tu6tJUM9k,1506
162
168
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordsResultsListItem.js,sha256=RTSQBlGQY7ww7fVGHDvDfWQNyJ07mnEoyboTGIcCgD4,6694
163
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/RDMDepositForm.js,sha256=TRuglTvAn2Iz_vPewNpRp473VnYXzPgso1XkzG9u5gA,32282
169
+ invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/RDMDepositForm.js,sha256=92Mg5PAjOHZ8pMNHJjyqdHMXLXuaAF6FT12UTJzy9XQ,32295
164
170
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/ShareDraftButton.js,sha256=ICMV4Ixe-nTe6q7COZ0oyAQf2nVp2cez_-iUZobwUD0,1998
165
171
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/config.js,sha256=a9Im0S72ne6LGDzM8C-pP9fIhcMihIbOJ5Ez5SwSn9A,1456
166
172
  invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/index.js,sha256=gA0BaUFpaJr9nkmHSWoxuwKDfSD3JUTPcadj2TKHN24,1587
@@ -374,7 +380,7 @@ invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/page.html,sha256=zwI
374
380
  invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/page_cover.html,sha256=D-NNfCX38wRjHOq1kzPPncKKUd0b9U1al-NMy48dKvU,357
375
381
  invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/searchbar.html,sha256=tvzez_2CE86wl_lipD44MD1swZcZw5uYqXEvvPMkBIo,2222
376
382
  invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/site_footer.html,sha256=C0xluZe7KCkvRKeeMIt6P8v_EfWyuMCQ_JQvlI0M9Xw,1037
377
- invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/files_integrity_report/email/files_integrity_report.html,sha256=rKkdAZIyJoXp_ayE_8nemEvBvBnDaHEoorklmDS5UWA,1157
383
+ invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/files_integrity_report/email/files_integrity_report.html,sha256=mwbLrf2xabxq0dLQzUNWGHeYVoySo9GaaydT-FKC9g8,1237
378
384
  invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/help/search.de.html,sha256=gefXaGv-ylkBUbvXXJe5EcTS66qoY0-v0XesXICX5_E,9310
379
385
  invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/help/search.en.html,sha256=-EZdjhc4JslSq37_QN3KRL01LBwLJENkRtwBJ-PLSzc,8792
380
386
  invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/help/search.sv.html,sha256=PHSLAuYtRTtsDgArshe5SIDs_M-dw8MUrSIrnNky2gw,9123
@@ -469,10 +475,10 @@ invenio_app_rdm/users_ui/views/__init__.py,sha256=SMdY2NJj9GICfr3Xuok7qdNYVtA2bJ
469
475
  invenio_app_rdm/users_ui/views/dashboard.py,sha256=iUn2PrODAwb8ugmMosJKAjPhUzjCiWiAWoXQr9RUFuc,1793
470
476
  invenio_app_rdm/users_ui/views/ui.py,sha256=W_eXM8dLVIrNHQB2UEh37C9BYoHauft6RyvcDNFHovA,1742
471
477
  invenio_app_rdm/utils/files.py,sha256=CruDyO2gDVadSlWEJD-WHpWHeOQ0juh-Ei9jz3D9yjc,3923
472
- invenio_app_rdm-13.0.0b3.dev5.dist-info/licenses/LICENSE,sha256=AZXFHRrZa5s4m9DV7zZr4bPGTMUvcEPCodeV_AmFI8k,1204
478
+ invenio_app_rdm-13.0.0b3.dev7.dist-info/licenses/LICENSE,sha256=AZXFHRrZa5s4m9DV7zZr4bPGTMUvcEPCodeV_AmFI8k,1204
473
479
  tests/__init__.py,sha256=yKVf0yYRuxmXvyAtLjmfpHGVCsEkZOhs_FojAAM_w-8,244
474
- tests/conftest.py,sha256=6iR-l-DIpQDxN2LLMu6kbHnLsmAW1m8Lq-j8rNNucf8,8956
475
- tests/test_tasks.py,sha256=6l25lcMjL3ZuQr4hsxbAEjSTu_J1aKkOB3ZXqOZZIy0,3712
480
+ tests/conftest.py,sha256=FQ_aSBxGo0gNfV64RJ117GcmgjVbZKwpxF9_3VNdNJY,7931
481
+ tests/test_tasks.py,sha256=YAf2mryFK6Vfzk2053XLBA2e92vbNCTWqJ6ThWhGOuQ,6645
476
482
  tests/test_utils.py,sha256=nvj59DibjRZgLzwnch83tyCw9R1DEuDSQhU7v7mEOT0,1897
477
483
  tests/test_version.py,sha256=c_ayM-WLlD0Q6z939sBnFpMTY160TmFg-9-hGsPa7FE,410
478
484
  tests/test_views.py,sha256=Q0mSOqd6xVrAxQMdc0MAdZRUnxv7gFAkKcBQY-gQtUI,1420
@@ -490,20 +496,21 @@ tests/fixtures/app_data/pages.yaml,sha256=t4pyjwMBVI6MxEkwCtg9urMXo5fh0mzWy-Zr8N
490
496
  tests/fixtures/app_data/pages/about.html,sha256=obB_g3uL1ligwguh0a0-tRLRBB-eGXN1NE-3odp2j-E,11
491
497
  tests/mock_module/__init__.py,sha256=gu6vRcXqYTKCXZjvAIHZRv2w1IvpGiDs7atQJbij5M0,236
492
498
  tests/mock_module/views.py,sha256=TWJQfcql5XaBNtdR9phVl4tHB0YG6g5IG5n08VZTzXE,770
493
- tests/mock_module/templates/mock_mail.html,sha256=H_ya78ip4xS-9ZpBuzVpwD-QE8EKEZpoLayKe0u5rHQ,845
499
+ tests/mock_module/templates/mock_mail.html,sha256=w7eiZbbs1gErCXelGN7NeiTavcDM9yUM702Mygz0oAc,943
494
500
  tests/redirector/__init__.py,sha256=yKVf0yYRuxmXvyAtLjmfpHGVCsEkZOhs_FojAAM_w-8,244
495
501
  tests/redirector/conftest.py,sha256=KAbp0R8tgGoNvMr8mJ1G2AZX6PzLw-PChtbucrLn5_o,1676
496
502
  tests/redirector/test_redirector.py,sha256=iATYglIw3QSoUKpspQCAM4SaG_WmzjL7r1Nmew_KlGY,1002
497
503
  tests/ui/__init__.py,sha256=yKVf0yYRuxmXvyAtLjmfpHGVCsEkZOhs_FojAAM_w-8,244
498
- tests/ui/conftest.py,sha256=id6A9VbwHQtnr8bzQyQXDBDoKUHx8uufuhRVk02mqbk,3015
504
+ tests/ui/conftest.py,sha256=3Msw0lfBoboQ7X-oZv_wGN7UF6StUpHPRVzRvbjhpoI,3472
499
505
  tests/ui/test_deposits.py,sha256=BehQzo1r3_f4Uc9jcXRddd9bS5GfQ3jRRYOM0AMbi3w,3792
500
506
  tests/ui/test_export_formats.py,sha256=pCXJCTp9ykEWb2oB-ynGjQDhFaVsOs31ym0stwfWCaQ,909
507
+ tests/ui/test_file_download.py,sha256=o4JHkFyJxZDaQ5NHRZR_PV98jS9UtklbOZPpduzwWKw,2436
501
508
  tests/ui/test_filters.py,sha256=Q90wsJffjMVir7wNX8taGf2KZleLtPbXZXHLTkBpzLA,284
502
509
  tests/ui/test_signposting_ui.py,sha256=KCSjQlMD2VKlwQCyZYDwYjtVNL35x3u-ZC4ceD5y21w,3847
503
510
  tests/ui/test_static.py,sha256=vO3OQAOhrQESJifnQfM1pw7JYz3J874O8BAb7Cc_PPA,868
504
511
  tests/ui/test_stats_ui.py,sha256=LHa_0hjvpYvliSk_jknWy-90CO82jVElUfK5Ua_ZmfA,3554
505
- invenio_app_rdm-13.0.0b3.dev5.dist-info/METADATA,sha256=6YT-pB8fpd-7ynabhsShq9rIO7ovbMsXWZVElMhDZr0,12696
506
- invenio_app_rdm-13.0.0b3.dev5.dist-info/WHEEL,sha256=AeO2BvogYWm3eGaHCvhzmUYt8ia7KfURiHzO_1atlys,109
507
- invenio_app_rdm-13.0.0b3.dev5.dist-info/entry_points.txt,sha256=r1vTqYNABeWqRMWitzyR9FnBsAy-KYZKZCp95IziyLY,2070
508
- invenio_app_rdm-13.0.0b3.dev5.dist-info/top_level.txt,sha256=NqTqrntInEAci7EXcNBvouXFMqwyjVQhEI0b7izYRBY,22
509
- invenio_app_rdm-13.0.0b3.dev5.dist-info/RECORD,,
512
+ invenio_app_rdm-13.0.0b3.dev7.dist-info/METADATA,sha256=eGia2PPptzucD3ImEyEI9TMwfsODbAZnXk87kHBtlv4,13241
513
+ invenio_app_rdm-13.0.0b3.dev7.dist-info/WHEEL,sha256=oSJJyWjO7Z2XSScFQUpXG1HL-N0sFMqqeKVVbZTPkWc,109
514
+ invenio_app_rdm-13.0.0b3.dev7.dist-info/entry_points.txt,sha256=rfzEeOEdtGy99NlpWzeW-32CoO5XrEpBvlZwLD2Th88,2158
515
+ invenio_app_rdm-13.0.0b3.dev7.dist-info/top_level.txt,sha256=NqTqrntInEAci7EXcNBvouXFMqwyjVQhEI0b7izYRBY,22
516
+ invenio_app_rdm-13.0.0b3.dev7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -2,6 +2,7 @@
2
2
  rdm = invenio_app_rdm.cli:rdm
3
3
 
4
4
  [invenio_administration.views]
5
+ invenio_app_rdm_audit_logs = invenio_app_rdm.administration.audit_logs:AuditLogListView
5
6
  invenio_app_rdm_drafts_list = invenio_app_rdm.administration.records:DraftAdminListView
6
7
  invenio_app_rdm_records_list = invenio_app_rdm.administration.records:RecordAdminListView
7
8
  invenio_requests_user_moderation_detail = invenio_app_rdm.administration.user_moderation:UserModerationRequestDetailView
tests/conftest.py CHANGED
@@ -27,8 +27,6 @@ except AttributeError:
27
27
 
28
28
  security.safe_str_cmp = hmac.compare_digest
29
29
 
30
- import shutil
31
- import tempfile
32
30
  from collections import namedtuple
33
31
 
34
32
  import pytest
@@ -41,7 +39,6 @@ from invenio_accounts.proxies import current_datastore
41
39
  from invenio_accounts.testutils import login_user_via_session
42
40
  from invenio_app.factory import create_app as _create_app
43
41
  from invenio_db import db
44
- from invenio_files_rest.models import Bucket, FileInstance, Location, ObjectVersion
45
42
  from invenio_records_resources.proxies import current_service_registry
46
43
  from invenio_vocabularies.contrib.subjects.api import Subject
47
44
  from invenio_vocabularies.proxies import current_service as vocabulary_service
@@ -273,41 +270,3 @@ RunningApp = namedtuple(
273
270
  def running_app(app, location, resource_type_item, language_item, subject_item):
274
271
  """Fixture mimicking a running app."""
275
272
  return RunningApp(app, location, resource_type_item, language_item, subject_item)
276
-
277
-
278
- @pytest.yield_fixture()
279
- def dummy_location(db):
280
- """File system location."""
281
- tmppath = tempfile.mkdtemp()
282
-
283
- loc = Location(name="testloc", uri=tmppath, default=True)
284
- db.session.add(loc)
285
- db.session.commit()
286
-
287
- yield loc
288
-
289
- shutil.rmtree(tmppath)
290
-
291
-
292
- @pytest.fixture
293
- def invalid_file_instance(db, dummy_location):
294
- """Creates a file instance."""
295
- # Create a Bucket and ObjectVersion
296
- b1 = Bucket.create(location=dummy_location)
297
- with open("README.rst", "rb") as fp:
298
- obj = ObjectVersion.create(b1, "README.rst", stream=fp)
299
- db.session.commit()
300
- file_id = obj.file_id
301
-
302
- # Get FileInstance from file ID
303
- f = FileInstance.query.get(file_id)
304
-
305
- # Force an invalid checksum
306
- f.checksum = "invalid"
307
- f.verify_checksum()
308
- db.session.commit()
309
-
310
- # Retrieve the file instance (with updated last_check)
311
- f = FileInstance.query.get(file_id)
312
-
313
- return f
@@ -2,13 +2,12 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  #
4
4
  # Copyright (C) 2022 CERN.
5
+ # Copyright (C) 2025 Northwestern University.
5
6
  #
6
7
  # Invenio App RDM is free software; you can redistribute it and/or modify it
7
8
  # under the terms of the MIT License; see LICENSE file for more details.
8
9
  -#}
9
10
 
10
- {% set BASE_URL = config.SITE_UI_URL %}
11
-
12
11
  {%- for entry in entries -%}
13
12
  {{ "ID: %s" | format(entry.file.id) }}
14
13
  {{ "URI: %s" | format(entry.file.uri) }}
@@ -19,10 +18,10 @@
19
18
  {{ "Checksum: %s" | format(entry.file.checksum) }}
20
19
  {{ "Last check: %s" | format(entry.file.last_check_at) }}
21
20
  {%- if 'record' in entry %}
22
- {{ "Record: %s/records/%s" | format(BASE_URL, entry.record.id) }}
21
+ {{ "Record: %s" | format(invenio_url_for("invenio_app_rdm_records.record_detail", pid_value=entry.record.id)) }}
23
22
  {%- endif %}
24
23
  {%- if 'draft' in entry %}
25
- {{ "Draft: %s/uploads/%s" | format(BASE_URL, entry.draft.id) }}
24
+ {{ "Draft: %s" | format(invenio_url_for("invenio_app_rdm_records.deposit_edit", pid_value=entry.draft.id)) }}
26
25
  {%- endif %}
27
26
  {{ "-" * 80 }}
28
27
  {% endfor %}
tests/test_tasks.py CHANGED
@@ -1,16 +1,102 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
3
  # Copyright (C) 2022-2024 CERN.
4
+ # Copyright (C) 2025 Northwestern University.
4
5
  #
5
6
  # Invenio App RDM is free software; you can redistribute it and/or modify it
6
7
  # under the terms of the MIT License; see LICENSE file for more details.
7
8
  """Test invenio-app-rdm celery tasks."""
8
9
 
9
- from invenio_app_rdm.tasks import file_integrity_report
10
+ from io import BytesIO
11
+
12
+ import pytest
13
+ from invenio_access.permissions import system_identity
14
+ from invenio_db import db
15
+ from invenio_files_rest.models import Bucket, FileInstance, Location, ObjectVersion
16
+ from invenio_rdm_records.proxies import current_rdm_records
10
17
 
18
+ from invenio_app_rdm.tasks import file_integrity_report
11
19
 
12
- def test_task_file_integrity_report(app, invalid_file_instance):
20
+ # Fixtures
21
+
22
+
23
+ @pytest.fixture
24
+ def draft_with_file_instance(running_app, minimal_record):
25
+ """Create draft with file and return (Draft, FileInstance)."""
26
+ minimal_record["files"] = {"enabled": True}
27
+
28
+ record_service = current_rdm_records.records_service
29
+ file_service = record_service.draft_files
30
+
31
+ draft = record_service.create(system_identity, minimal_record)
32
+ file_to_initialise = [
33
+ {
34
+ "key": "article.txt",
35
+ "checksum": "md5:c785060c866796cc2a1708c997154c8e",
36
+ "size": 17, # 2kB
37
+ "metadata": {
38
+ "description": "Published article PDF.",
39
+ },
40
+ }
41
+ ]
42
+ file_service.init_files(system_identity, draft.id, file_to_initialise)
43
+ content = BytesIO(b"test file content")
44
+ file_service.set_file_content(
45
+ system_identity,
46
+ draft.id,
47
+ file_to_initialise[0]["key"],
48
+ content,
49
+ content.getbuffer().nbytes,
50
+ )
51
+ file_item = file_service.commit_file(system_identity, draft.id, "article.txt")
52
+ # fmt: off
53
+ id_of_file_instance = (
54
+ file_item
55
+ ._record
56
+ .files
57
+ .entries["article.txt"]
58
+ .object_version.file_id
59
+ )
60
+ # fmt: on
61
+ f = FileInstance.query.get(id_of_file_instance)
62
+ return draft, f
63
+
64
+
65
+ @pytest.fixture
66
+ def draft_with_invalid_file_instance(draft_with_file_instance):
67
+ # Force an invalid checksum
68
+ draft, f = draft_with_file_instance
69
+ f.checksum = "invalid"
70
+ f.verify_checksum()
71
+ db.session.commit()
72
+
73
+ # Retrieve the file instance (with updated last_check)
74
+ f = FileInstance.query.get(f.id)
75
+
76
+ return draft, f
77
+
78
+
79
+ @pytest.fixture
80
+ def record_with_invalid_file_instance(draft_with_invalid_file_instance):
81
+ draft, f = draft_with_invalid_file_instance
82
+ record_service = current_rdm_records.records_service
83
+ return record_service.publish(system_identity, draft.id), f
84
+
85
+
86
+ @pytest.fixture
87
+ def invalid_file_instance(record_with_invalid_file_instance):
88
+ draft, f = record_with_invalid_file_instance
89
+ return f
90
+
91
+
92
+ # Tests
93
+
94
+
95
+ def test_task_file_integrity_report(
96
+ app, record_with_invalid_file_instance, set_app_config_fn_scoped
97
+ ):
13
98
  """Test celery task for file integrity reports."""
99
+ record, invalid_file_instance = record_with_invalid_file_instance
14
100
  assert invalid_file_instance.last_check is False
15
101
 
16
102
  # A report must be generated for the file
@@ -20,7 +106,6 @@ def test_task_file_integrity_report(app, invalid_file_instance):
20
106
  recipients = "test@invenio.org"
21
107
  sender = "test@invenio.org"
22
108
 
23
- file_name = invalid_file_instance.objects[0].key
24
109
  uri = invalid_file_instance.uri
25
110
  file_id = invalid_file_instance.id
26
111
  file_name = invalid_file_instance.objects[0].key
@@ -28,9 +113,13 @@ def test_task_file_integrity_report(app, invalid_file_instance):
28
113
 
29
114
  with mail.record_messages() as outbox:
30
115
  # Configure email and validate that email was sent
31
- app.config["APP_RDM_FILES_INTEGRITY_REPORT_TEMPLATE"] = "mock_mail.html"
32
- app.config["APP_RDM_ADMIN_EMAIL_RECIPIENT"] = recipients
33
- app.config["MAIL_DEFAULT_SENDER"] = sender
116
+ set_app_config_fn_scoped(
117
+ {
118
+ "APP_RDM_FILES_INTEGRITY_REPORT_TEMPLATE": "mock_mail.html",
119
+ "APP_RDM_ADMIN_EMAIL_RECIPIENT": recipients,
120
+ "MAIL_DEFAULT_SENDER": sender,
121
+ }
122
+ )
34
123
 
35
124
  file_integrity_report()
36
125
  assert len(outbox) == 1
@@ -41,9 +130,12 @@ def test_task_file_integrity_report(app, invalid_file_instance):
41
130
  assert "URI: {}".format(uri) in mail_sent.body
42
131
  assert "ID: {}".format(str(file_id)) in mail_sent.body
43
132
  assert "Name: {}".format(file_name) in mail_sent.body
133
+ assert f"Record: https://127.0.0.1:5000/records/{record.id}" in mail_sent.body
44
134
 
45
135
 
46
- def test_integrity_report_invalid_template(app, invalid_file_instance):
136
+ def test_integrity_report_invalid_template(
137
+ app, invalid_file_instance, set_app_config_fn_scoped
138
+ ):
47
139
  """Test non-existant e-mail template."""
48
140
  assert invalid_file_instance.last_check is False
49
141
 
@@ -52,12 +144,15 @@ def test_integrity_report_invalid_template(app, invalid_file_instance):
52
144
 
53
145
  with mail.record_messages() as outbox:
54
146
  # Remove the template, no e-mail is sent
55
- app.config["APP_RDM_FILES_INTEGRITY_REPORT_TEMPLATE"] = None
147
+ set_app_config_fn_scoped({"APP_RDM_FILES_INTEGRITY_REPORT_TEMPLATE": None})
148
+
56
149
  file_integrity_report()
57
150
  assert len(outbox) == 0
58
151
 
59
152
 
60
- def test_integrity_report_invalid_addresses(app, invalid_file_instance):
153
+ def test_integrity_report_invalid_addresses(
154
+ app, invalid_file_instance, set_app_config_fn_scoped
155
+ ):
61
156
  """Test invalid recipient address."""
62
157
  assert invalid_file_instance.last_check is False
63
158
 
@@ -65,17 +160,24 @@ def test_integrity_report_invalid_addresses(app, invalid_file_instance):
65
160
  assert mail
66
161
 
67
162
  with mail.record_messages() as outbox:
68
- # Use mock template
69
- app.config["APP_RDM_FILES_INTEGRITY_REPORT_TEMPLATE"] = "mock_mail.html"
163
+ set_app_config_fn_scoped(
164
+ {
165
+ # Use mock template
166
+ "APP_RDM_FILES_INTEGRITY_REPORT_TEMPLATE": "mock_mail.html",
167
+ # Recipient is not set, mail is not sent.
168
+ "APP_RDM_ADMIN_EMAIL_RECIPIENT": None,
169
+ }
170
+ )
70
171
 
71
- # Recipient is not set, mail is not sent.
72
- app.config["APP_RDM_ADMIN_EMAIL_RECIPIENT"] = None
73
172
  file_integrity_report()
74
173
  assert len(outbox) == 0
75
174
 
76
175
 
77
- def test_integrity_report_default_template(app, invalid_file_instance):
176
+ def test_integrity_report_default_template(
177
+ app, draft_with_invalid_file_instance, set_app_config_fn_scoped
178
+ ):
78
179
  """Test invalid recipient address."""
180
+ draft, invalid_file_instance = draft_with_invalid_file_instance
79
181
  assert invalid_file_instance.last_check is False
80
182
 
81
183
  mail = app.extensions.get("mail")
@@ -91,8 +193,10 @@ def test_integrity_report_default_template(app, invalid_file_instance):
91
193
 
92
194
  with mail.record_messages() as outbox:
93
195
  # Use default template
94
- app.config["APP_RDM_ADMIN_EMAIL_RECIPIENT"] = recipients
95
- app.config["MAIL_DEFAULT_SENDER"] = sender
196
+ set_app_config_fn_scoped(
197
+ {"APP_RDM_ADMIN_EMAIL_RECIPIENT": recipients, "MAIL_DEFAULT_SENDER": sender}
198
+ )
199
+
96
200
  file_integrity_report()
97
201
  assert len(outbox) == 1
98
202
  mail_sent = outbox[0]
@@ -102,3 +206,4 @@ def test_integrity_report_default_template(app, invalid_file_instance):
102
206
  assert "URI: {}".format(uri) in mail_sent.body
103
207
  assert "ID: {}".format(str(file_id)) in mail_sent.body
104
208
  assert "Name: {}".format(file_name) in mail_sent.body
209
+ assert f"Draft: https://127.0.0.1:5000/uploads/{draft.id}" in mail_sent.body
tests/ui/conftest.py CHANGED
@@ -12,12 +12,13 @@
12
12
  from io import BytesIO
13
13
 
14
14
  import pytest
15
+ from flask_principal import Identity
15
16
  from flask_webpackext.manifest import (
16
17
  JinjaManifest,
17
18
  JinjaManifestEntry,
18
19
  JinjaManifestLoader,
19
20
  )
20
- from invenio_access.permissions import system_identity
21
+ from invenio_access.permissions import any_user, authenticated_user, system_identity
21
22
  from invenio_app.factory import create_ui
22
23
  from invenio_rdm_records.proxies import current_rdm_records
23
24
  from invenio_search import current_search
@@ -74,14 +75,18 @@ def record(running_app, minimal_record):
74
75
 
75
76
 
76
77
  @pytest.fixture()
77
- def record_with_file(running_app, minimal_record):
78
- """Create and publish a record with file."""
78
+ def draft_with_file(running_app, minimal_record, users):
79
+ """Create a draft with a file."""
79
80
  minimal_record["files"] = {"enabled": True}
80
81
 
82
+ # Use a user's identity to make sure the record has an owner
83
+ user_identity = Identity(users["user1"].id)
84
+ user_identity.provides.add(any_user)
85
+ user_identity.provides.add(authenticated_user)
81
86
  record_service = current_rdm_records.records_service
82
87
  file_service = record_service.draft_files
83
88
 
84
- draft = record_service.create(system_identity, minimal_record)
89
+ draft = record_service.create(user_identity, minimal_record)
85
90
  file_to_initialise = [
86
91
  {
87
92
  "key": "article.txt",
@@ -102,4 +107,11 @@ def record_with_file(running_app, minimal_record):
102
107
  content.getbuffer().nbytes,
103
108
  )
104
109
  file_service.commit_file(system_identity, draft.id, "article.txt")
105
- return record_service.publish(system_identity, draft.id)
110
+ return draft
111
+
112
+
113
+ @pytest.fixture()
114
+ def record_with_file(draft_with_file):
115
+ """Create and publish a record with file."""
116
+ record_service = current_rdm_records.records_service
117
+ return record_service.publish(system_identity, draft_with_file.id)
@@ -0,0 +1,73 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2025 CERN.
4
+ # Copyright (C) 2025 KTH Royal Institute of Technology.
5
+ #
6
+ # Invenio-App-RDM is free software; you can redistribute it and/or modify
7
+ # it under the terms of the MIT License; see LICENSE file for more details.
8
+
9
+ from flask import url_for
10
+
11
+
12
+ def test_file_download_with_and_without_preview_flag(
13
+ client_with_login, draft_with_file
14
+ ):
15
+ """Test file download both with and without preview flag."""
16
+ client = client_with_login
17
+ draft_id = draft_with_file["id"]
18
+ file_name = "article.txt"
19
+
20
+ # The draft's owner should be able to download its file from the preview page
21
+ url_with_preview = url_for(
22
+ "invenio_app_rdm_records.record_file_download",
23
+ pid_value=draft_id,
24
+ filename=file_name,
25
+ download=1,
26
+ preview=1,
27
+ )
28
+ response = client.get(url_with_preview)
29
+ assert (
30
+ response.status_code == 200
31
+ ), "File download with preview flag should return 200"
32
+
33
+ # But since the draft isn't published yet, it can't be found without preview=1
34
+ url_without_preview = url_for(
35
+ "invenio_app_rdm_records.record_file_download",
36
+ pid_value=draft_id,
37
+ filename=file_name,
38
+ download=1,
39
+ )
40
+ response = client.get(url_without_preview)
41
+ assert (
42
+ response.status_code == 404
43
+ ), "File download without preview flag should return 404"
44
+
45
+
46
+ def test_nonexistent_file_returns_404(client_with_login, draft_with_file):
47
+ """Test that requesting a non-existent file returns 404 via the NoResultFound handler."""
48
+ client = client_with_login
49
+ draft_id = draft_with_file["id"]
50
+ fake_file = "nonexistent-file.pdf"
51
+
52
+ # Downloads for files that don't exist should return a 404, both without...
53
+ url_without_preview = url_for(
54
+ "invenio_app_rdm_records.record_file_download",
55
+ pid_value=draft_id,
56
+ filename=fake_file,
57
+ download=1,
58
+ )
59
+ response = client.get(url_without_preview)
60
+ assert response.status_code == 404, "Non-existent file should return 404"
61
+
62
+ # ... as well as with the preview=1 flag
63
+ url_with_preview = url_for(
64
+ "invenio_app_rdm_records.record_file_download",
65
+ pid_value=draft_id,
66
+ filename=fake_file,
67
+ download=1,
68
+ preview=1,
69
+ )
70
+ response = client.get(url_with_preview)
71
+ assert (
72
+ response.status_code == 404
73
+ ), "Non-existent file with preview flag should return 404"
invenio_app_rdm/urls.py DELETED
@@ -1,72 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Copyright (C) 2023 Northwestern University.
4
- #
5
- # This file is lifted from Invenio-RDM-Records .
6
- #
7
- # Invenio-RDM-Records is free software; you can redistribute it and/or modify
8
- # it under the terms of the MIT License; see LICENSE file for more details.
9
-
10
- """Convenient URL generation.
11
-
12
- InvenioRDM poses challenges to url generation that Flask's url_for cannot handle out
13
- of the gate.
14
-
15
- - InvenioRDM is actually 2 applications mounted on different url_prefixes:
16
- `url_for` in the API application isn't aware of the UI application endpoints
17
- - The endpoint names are relatively hidden / spread out and APP_RDM_ROUTES is usually
18
- the interface to name them.
19
- - Need to be able to generate urls outside of request context without thinking about it,
20
-
21
- This module contains minimal methods to generate URLs correctly without much
22
- engineering. Over time, it can be made more abstract, complex and powerful and even
23
- extracted into its own package to solve url generation across InvenioRDM once and for
24
- all.
25
-
26
- Design decisions:
27
-
28
- - Generated urls are absolute for now
29
- """
30
-
31
- import unicodedata
32
- from urllib.parse import quote
33
-
34
- from flask import current_app
35
-
36
-
37
- def record_url_for(_app="ui", pid_value=""):
38
- """Return url for record route."""
39
- assert _app in ["ui", "api"]
40
-
41
- site_app = _app.upper()
42
- url_prefix = current_app.config.get(f"SITE_{site_app}_URL", "")
43
-
44
- # We use [] so that this fails and brings to attention the configuration
45
- # problem if APP_RDM_ROUTES.record_detail is missing
46
- url_path = current_app.config["APP_RDM_ROUTES"]["record_detail"].replace(
47
- "<pid_value>", pid_value
48
- )
49
-
50
- return "/".join(p.strip("/") for p in [url_prefix, url_path])
51
-
52
-
53
- def download_url_for(pid_value="", filename=""):
54
- """Return url for download route."""
55
- url_prefix = current_app.config.get("SITE_UI_URL", "")
56
-
57
- # see https://github.com/pallets/werkzeug/blob/main/src/werkzeug/utils.py#L456-L465
58
- try:
59
- filename.encode("ascii")
60
- except UnicodeEncodeError:
61
- # safe = RFC 5987 attr-char
62
- filename = quote(filename, safe="!#$&+-.^_`|~")
63
-
64
- # We use [] so that this fails and brings to attention the configuration
65
- # problem if APP_RDM_ROUTES.record_file_download is missing
66
- url_path = (
67
- current_app.config["APP_RDM_ROUTES"]["record_file_download"]
68
- .replace("<pid_value>", pid_value)
69
- .replace("<path:filename>", filename)
70
- )
71
-
72
- return "/".join(p.strip("/") for p in [url_prefix, url_path])