Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
{% if record_deletion %}
<input type="hidden" name="deposits-record-deletion" value='{{ record_deletion | tojson }}'>
{% endif %}
{% if file_modification %}
<input type="hidden" name="deposits-file-modification" value='{{ file_modification | tojson }}'>
{% endif %}
<input type="hidden" name="config-groups-enabled"
value='{{ config.USERS_RESOURCES_GROUPS_ENABLED | tojson }}'>
<input type="hidden" name="records-resources-allow-empty-files"
Expand Down
39 changes: 32 additions & 7 deletions invenio_app_rdm/records_ui/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@

"""Utility functions."""

from datetime import datetime, timedelta, timezone
from itertools import chain

from flask import current_app
from invenio_access.permissions import system_identity
from invenio_rdm_records.records.api import RDMRecord
from invenio_rdm_records.requests.record_deletion import RecordDeletion
from invenio_rdm_records.services.config import RDMRecordDeletionPolicy
from invenio_rdm_records.services.config import (
FileModificationPolicyEvaluator,
RDMRecordDeletionPolicy,
)
from invenio_records.dictutils import dict_set
from invenio_records.errors import MissingModelError
from invenio_records_files.api import FileObject
Expand Down Expand Up @@ -119,10 +123,7 @@ def evaluate_record_deletion(record: RDMRecord, identity):

immediate, request = rec_del["immediate_deletion"], rec_del["request_deletion"]
rd_enabled = immediate.enabled or request.enabled
rd_valid_user = (
rec_del["immediate_deletion"].valid_user
or rec_del["request_deletion"].valid_user
)
rd_valid_user = immediate.valid_user or request.valid_user
rd_allowed = immediate.allowed or request.allowed
existing_request = get_existing_deletion_request(record.id)

Expand All @@ -138,8 +139,8 @@ def evaluate_record_deletion(record: RDMRecord, identity):
else current_app.config["RDM_REQUEST_RECORD_DELETION_CHECKLIST"]
),
"context": {
"files": record.files.count,
"internalDoi": record.pids["doi"]["provider"] != "external",
"files": record["files"]["count"],
"internalDoi": record["pids"]["doi"]["provider"] != "external",
Comment on lines +142 to +143
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Could you share why was this required here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for when we were having different behaviour for internal vs external DOIs. Lars has said we're changing policy and treating them all the same and now no longer required

},
}
else:
Expand All @@ -153,3 +154,27 @@ def evaluate_record_deletion(record: RDMRecord, identity):
)

return record_deletion


def evaluate_file_modification(record, identity):
"""Evaluate whether a given record file's can be edited by an identity."""
file_mod = FileModificationPolicyEvaluator().evaluate(identity, record._record)

file_mod = file_mod["immediate_file_modification"]

file_modification = {
"enabled": file_mod.enabled,
"valid_user": file_mod.valid_user,
"allowed": file_mod.allowed,
}

if file_mod.allowed:
file_modification["fileModification"] = file_mod
created = record._record.created.replace(tzinfo=timezone.utc)
modification_until = created + current_app.config.get(
"RDM_FILE_MODIFICATION_PERIOD"
)
days_until = (modification_until - datetime.now(timezone.utc)).days
file_modification["context"] = {"days_until": days_until}

return file_modification
6 changes: 1 addition & 5 deletions invenio_app_rdm/records_ui/views/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ def view(**kwargs):
expand=expand,
)
kwargs["draft"] = draft
kwargs["files_locked"] = (
record_service.config.lock_edit_published_files(
record_service, g.identity, draft=draft, record=draft._record
)
)
kwargs["files_locked"] = draft._record.files.bucket.locked
return f(**kwargs)
except PIDDoesNotExistError:
# Redirect to /records/:id because users are interchangeably
Expand Down
11 changes: 10 additions & 1 deletion invenio_app_rdm/records_ui/views/deposits.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@
from marshmallow_utils.fields.babel import gettext_from_dict
from sqlalchemy.orm import load_only

from ..utils import evaluate_record_deletion, set_default_value
from ..utils import (
evaluate_file_modification,
evaluate_record_deletion,
set_default_value,
)
from .decorators import (
no_cache_response,
pass_draft,
Expand Down Expand Up @@ -550,8 +554,12 @@ def deposit_edit(pid_value, draft=None, draft_files=None, files_locked=True):
published_record = ui_serializer.dump_obj(published_record_result.to_dict())

record_deletion = evaluate_record_deletion(published_record_result, g.identity)
file_modification = evaluate_file_modification(
published_record_result, g.identity
)
else:
record_deletion = {}
file_modification = {}

community_ui = None
community_theme = None
Expand Down Expand Up @@ -627,6 +635,7 @@ def deposit_edit(pid_value, draft=None, draft_files=None, files_locked=True):
]
),
record_deletion=record_deletion,
file_modification=file_modification,
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{# -*- coding: utf-8 -*-

This file is part of Invenio.
Copyright (C) 2016-2025 CERN.

Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
#}

{% extends "invenio_requests/details/index.html" %}

{% set active_dashboard_menu_item = 'requests' %}
{% set active_community_header_menu_item = 'requests' %}

{%- block request_header %}
{% set back_button_url = url_for("invenio_app_rdm_users.requests") %}
{% from "invenio_requests/macros/request_header.html" import inclusion_request_header %}
{{ inclusion_request_header(
request=invenio_request,
record=record,
accepted=request_is_accepted,
back_button_url=back_button_url,
back_button_text=_("Back to requests")
) }}
{%- endblock request_header %}

{% block request_timeline %}
<div
class="ui container rdm-tab-container fluid rel-pt-2 ml-0-mobile mr-0-mobile"
id="request-request-deletion-tab-container"
>
<div
class="ui secondary pointing menu rdm-tab-menu"
id="request-deletion-request-tab"
>
<a
class="active item"
data-tab="conversation"
role="tab"
aria-selected="true"
aria-controls="conversation-tab-panel"
id="conversation-tab"
>
{{ _("Conversation") }}
</a>

{% if record_ui %}
<a
role="tab"
class="item"
data-tab="record"
aria-selected="false"
aria-controls="record-tab-panel"
id="record-tab"
>
{{ _("Record") }}
</a>
{% endif %}
</div>

<div
class="ui bottom attached tab segment active borderless p-0"
data-tab="conversation"
role="tabpanel"
aria-labelledby="conversation-tab"
id="conversation-tab-panel"
>
{{ super() }}
</div>

{# The record tab content needs to be last since the HTML structure is complex and breaks following tab contents #}
{% if record_ui %}
<div
class="ui bottom attached tab segment borderless"
data-tab="record"
role="tabpanel"
aria-labelledby="record-tab"
id="record-tab-panel"
hidden="hidden"
>
{% set use_theme_basic_template = false %}
{% set preview_submission_request = true %}
{% include config.APP_RDM_RECORD_LANDING_PAGE_TEMPLATE %}
</div>
{% endif %}

</div>
{% endblock request_timeline %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// This file is part of InvenioRDM
// Copyright (C) 2025 CERN.
//
// Invenio RDM Records is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.

import React, { Component } from "react";
import PropTypes from "prop-types";
import { i18next } from "@translations/invenio_app_rdm/i18next";

export class FileModificationUntil extends Component {
render() {
const { filesLocked, fileModification, record } = this.props;

if (!fileModification.fileModification?.enabled) {
return null;
}

const isPublished = record.is_published;
const filesUnlocked = !filesLocked;
const daysUntil = fileModification.context?.days_until;
if (isPublished && filesUnlocked && daysUntil) {
return (
<>
{" "}
{i18next.t("– Unlocked, {{ daysUntil }} days to publish changes", {
daysUntil: fileModification.context.days_until,
})}
</>
);
}

return null;
}
}

FileModificationUntil.propTypes = {
filesLocked: PropTypes.bool.isRequired,
fileModification: PropTypes.object,
record: PropTypes.object.isRequired,
};

FileModificationUntil.defaultProps = {
fileModification: {},
};
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,11 @@ RecordDeletion.propTypes = {
disabled: PropTypes.bool,
record: PropTypes.object.isRequired,
permissions: PropTypes.object.isRequired,
recordDeletion: PropTypes.object.isRequired,
recordDeletion: PropTypes.object,
options: PropTypes.array.isRequired,
};

RecordDeletion.defaultProps = {
disabled: false,
recordDeletion: {},
};
Original file line number Diff line number Diff line change
Expand Up @@ -109,27 +109,27 @@ export class DeletionModal extends Component {
reason: values.reason,
comment: values.comment,
};
if ("request_deletion" in record.links) {
this.cancellableAction = withCancel(
http.post(record.links.request_deletion, payload)
);
try {
const response = await this.cancellableAction.promise;
const data = response.data;
if (!("request_deletion" in record.links)) {
this.setState({ error: "Could not submit deletion request", loading: false });
return;
}
this.cancellableAction = withCancel(
http.post(record.links.request_deletion, payload)
);
try {
const response = await this.cancellableAction.promise;
const data = response.data;

if (response.status === 200) {
window.location.reload();
} else if (response.status === 201) {
window.location.href = data.links.self_html;
}
} catch (error) {
this.setState({ error: error });
console.error(error);
} finally {
this.setState({ loading: false });
if (response.status === 200) {
window.location.reload();
} else if (response.status === 201) {
window.location.href = data.links.self_html;
}
} else {
this.setState({ error: "Could not submit deletion request", loading: false });
} catch (error) {
this.setState({ error: error });
console.error(error);
} finally {
this.setState({ loading: false });
}
};

Expand Down Expand Up @@ -189,17 +189,15 @@ export class DeletionModal extends Component {
{immediateDeletionAllowed ? (
<p>
{
recordDeletion["recordDeletion"]["immediate_deletion"][
"policy"
]["description"]
recordDeletion.recordDeletion?.immediate_deletion?.policy
?.description
}
</p>
) : (
<p>
{
recordDeletion["recordDeletion"]["request_deletion"]["policy"][
"description"
]
recordDeletion.recordDeletion?.request_deletion?.policy
?.description
}
</p>
)}
Expand Down
Loading