Skip to content

Commit

Permalink
make DOI optional and add CLI command for DOI minting
Browse files Browse the repository at this point in the history
- Removed DOI minting code for video types
- Added a CLI command to mint DOI
- Updated FAQ section to request a DOI
- Updated tests to reflect the changes
  • Loading branch information
zubeydecivelek authored and zzacharo committed Nov 19, 2024
1 parent d7d7e47 commit 6b0e39d
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 46 deletions.
6 changes: 1 addition & 5 deletions cds/modules/deposit/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,11 +899,7 @@ def _publish_edited(self):
# dump again renamed subtitles
self["_files"] = self.files.dumps()

from cds.modules.records.permissions import is_public

if is_public(self, "read"):
# Mint the doi if necessary
doi_minter(record_uuid=self.id, data=self)
# Call the 'doi_minter' function if needed and if is_public

return super(Video, self)._publish_edited()

Expand Down
2 changes: 2 additions & 0 deletions cds/modules/fixtures/data/pages/faq.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ <h2 id="cds-videos">CDS Videos</h2>
</p><p>
<strong><em>Please insert the list of e-mails and e-groups, one after the other, with &quot;@cern.ch&quot; at the end.</em></strong>
</p>
<p><a href="#how-to-get-doi" name="how-to-get-doi" class="cds-anchor"><strong>How to request a DOI for a video?</strong></a></p>
<p>Videos are not automatically assigned a DOI. However, if you would like to obtain a DOI for your video, you can request one by creating a support ticket. Please use the following <a href="https://cern.service-now.com/service-portal/?id=service_element&name=MultiMedia-Service">link</a> to submit your request.</p>
<p><a href="#how-to-share" name="how-to-share" class="cds-anchor"><strong>How can I share my project with some colleagues?</strong></a></p>
<p>If you want to share the draft(s) video(s) with some colleagues, before publishing, to collaborate at the same time, edit the videos' project and use the &quot;Project editors&quot; tab: input the list of e-mail and e-groups (with &quot;@cern.ch&quot;) in the &quot;Grant access to the project&quot; field as restricting access to a video.</p>
<p><a href="#how-to-add-subtitles" name="how-to-add-subtitles" class="cds-anchor"><strong>How can I upload subtitles for my video?</strong></a></p>
Expand Down
45 changes: 44 additions & 1 deletion cds/modules/maintenance/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from invenio_files_rest.models import ObjectVersion, ObjectVersionTag
from invenio_records_files.models import RecordsBuckets

from cds.modules.deposit.api import Video
from cds.modules.flows.deposit import index_deposit_project
from cds.modules.flows.models import FlowMetadata
from cds.modules.flows.tasks import ExtractFramesTask
Expand All @@ -38,13 +37,57 @@
create_subformat,
)
from cds.modules.records.api import CDSVideosFilesIterator
from cds.modules.deposit.api import Video
from cds.modules.records.resolver import record_resolver
from cds.modules.records.minters import doi_minter
from cds.modules.deposit.tasks import datacite_register
from invenio_db import db
from cds.modules.records.permissions import is_public
from invenio_indexer.api import RecordIndexer



def abort_if_false(ctx, param, value):
if not value:
ctx.abort()

@click.command()
@click.option("--recid", "recid", help="ID of the video record", default=None)
@with_appcontext
def create_doi(recid):
if not recid:
raise ClickException('Missing option "--recid')

try:
# Get the video object with recid
_, record = record_resolver.resolve(recid)
depid = record.depid
video_deposit = Video.get_record(depid.object_uuid)
except Exception as exc:
raise ClickException("Failed to fetch the video record")

if is_public(video_deposit, "read") and video_deposit.is_published():
try:
# sync deposit files <--> record files
edit_record = video_deposit.edit()
doi_minter(record_uuid=edit_record.id, data=edit_record)
edit_record.publish().commit()

# save changes
db.session.commit()

# Register the doi to datacite
datacite_register(recid, str(record.id))
except Exception as exc:
db.session.rollback()
# index the record again
_, record_video = edit_record.fetch_published()
RecordIndexer().index(record_video)
click.echo(f"DOI created, registered, and indexed successfully for record '{recid}'")
else:
click.echo(f"Record '{recid}' is either not public or not published. Skipping DOI creation.")



@click.group()
def subformats():
Expand Down
13 changes: 4 additions & 9 deletions cds/modules/records/minters.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,10 @@ def cds_record_minter(record_uuid, data):
"""Mint record identifiers."""
provider = _rec_minter(record_uuid, data)

from cds.modules.deposit.api import Project

from .permissions import is_public

project_schema = current_jsonschemas.path_to_url(Project._schema)

# We shouldn't mint the DOI for the project (CDS#996)
if data.get("$schema") != project_schema and is_public(data, "read"):
doi_minter(record_uuid, data)
# from cds.modules.deposit.api import Project
# from .permissions import is_public
# project_schema = current_jsonschemas.path_to_url(Project._schema)
# Call the 'doi_minter' function if needed (not project and published)

return provider.pid

Expand Down
22 changes: 10 additions & 12 deletions tests/unit/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import pytest
from invenio_pidstore.models import PersistentIdentifier, PIDStatus

from cds.modules.records.minters import cds_record_minter, is_local_doi
from cds.modules.records.minters import cds_record_minter, doi_minter, is_local_doi


def test_recid_provider(db):
Expand All @@ -57,14 +57,9 @@ def test_recid_provider(db):
object_uuid=uuid,
status=PIDStatus.REGISTERED,
)
pid_create.assert_any_call(
"doi",
"10.0000/videos.1",
object_type="rec",
object_uuid=uuid,
pid_provider="datacite",
status=PIDStatus.RESERVED,
)
# Verify the video has no DOI after publishing
assert "doi" not in data



@pytest.mark.parametrize(
Expand All @@ -80,6 +75,7 @@ def test_doi_minting(db, doi_in, doi_out):
rec_uuid = uuid4()
data = dict(doi=doi_in)
cds_record_minter(rec_uuid, data)
doi_minter(rec_uuid, data)
db.session.commit()

pid = PersistentIdentifier.get("doi", doi_out)
Expand All @@ -99,7 +95,7 @@ def test_invalid_doi(db, doi):
uuid = uuid4()
data = dict(doi=doi)
with pytest.raises(AssertionError):
cds_record_minter(uuid, data)
doi_minter(uuid, data)


def test_no_doi_minted_for_projects(db, api_project):
Expand All @@ -111,6 +107,9 @@ def test_no_doi_minted_for_projects(db, api_project):
# Project shouldn't have a DOI
assert project.get("doi") is None
cds_record_minter(uuid2, video_1)
# Video shouldn't have a DOI
assert "doi" not in video_1
doi_minter(uuid2, video_1)
# Video should have a DOI
assert video_1.get("doi")

Expand All @@ -122,7 +121,7 @@ def test_recid_provider_exception(db):


def test_minting_recid(db):
"""Test reminting doi for published record."""
"""Test reminting recid for published record."""
data = dict()
# Assert registration of recid.
rec_uuid = uuid4()
Expand All @@ -131,7 +130,6 @@ def test_minting_recid(db):
assert pid.pid_value == "1"
assert pid.status == PIDStatus.REGISTERED
assert pid.object_uuid == rec_uuid
assert data["doi"] == "10.0000/videos.1"
with pytest.raises(AssertionError):
cds_record_minter(rec_uuid, data)

Expand Down
97 changes: 78 additions & 19 deletions tests/unit/test_video_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
from time import sleep

import mock
from cds.modules.maintenance.cli import create_doi
from cds.modules.records.minters import doi_minter
from click.testing import CliRunner

import pytest
from celery.exceptions import Retry
from flask import url_for
Expand Down Expand Up @@ -90,22 +94,18 @@ def test_video_publish_registering_the_datacite(
sender=api_app, action="publish", deposit=video_1
)

assert datacite_mock.called is True
assert datacite_mock().metadata_post.call_count == 1
datacite_mock().doi_post.assert_called_once_with(
"10.0000/videos.1", "https://videos.cern.ch/record/1"
)
# [[ CONFIRM THERE'S NO DOI ]]
assert datacite_mock.called is False
assert datacite_mock().metadata_post.call_count == 0
datacite_mock().doi_post.assert_not_called()

# [[ UPDATE DATACITE ]]
datacite_register_after_publish(
sender=api_app, action="publish", deposit=video_1
)

assert datacite_mock.called is True
assert datacite_mock().metadata_post.call_count == 2
datacite_mock().doi_post.assert_called_with(
"10.0000/videos.1", "https://videos.cern.ch/record/1"
)
assert datacite_mock().metadata_post.call_count == 0
datacite_mock().doi_post.assert_not_called()


@mock.patch("invenio_pidstore.providers.datacite.DataCiteMDSClient")
Expand Down Expand Up @@ -151,12 +151,10 @@ def test_video_publish_registering_the_datacite_if_fail(
):
datacite_register.s(pid_value="1", record_uuid=str(video_1.id)).apply()

assert datacite_mock.called is True
assert datacite_mock().metadata_post.call_count == 1
datacite_mock().doi_post.assert_called_once_with(
"10.0000/videos.1", "https://videos.cern.ch/record/1"
)
assert datacite_mock.call_count == 3
# [[ CONFIRM THERE'S NO DOI ]]
assert datacite_mock.called is False
assert datacite_mock().metadata_post.call_count == 0
datacite_mock().doi_post.assert_not_called()


@mock.patch("invenio_pidstore.providers.datacite.DataCiteMDSClient")
Expand Down Expand Up @@ -403,8 +401,8 @@ def test_video_publish_edit_publish_again(
# [[ MODIFY DOI -> SAVE ]]
video_1 = deposit_video_resolver(video_1_depid)
video_1_dict = copy.deepcopy(video_1)
old_doi = video_1_dict["doi"]
video_1_dict["doi"] = "10.1123/doi"
old_doi = video_1_dict.get("doi")
# video_1_dict["doi"] = "10.1123/doi"
del video_1_dict["_files"]
res = client.put(
url_for(
Expand All @@ -418,7 +416,7 @@ def test_video_publish_edit_publish_again(
assert res.status_code == 200
data = json.loads(res.data.decode("utf-8"))
# Ensure that doi once minted cannot be changed to another value
assert data["metadata"]["doi"] == old_doi
assert data["metadata"].get("doi") == old_doi

video_1 = deposit_video_resolver(video_1_depid)
# video_1['doi'] = old_doi
Expand Down Expand Up @@ -528,6 +526,67 @@ def test_record_video_links(
}


@mock.patch("invenio_pidstore.providers.datacite.DataCiteMDSClient")
def test_mint_doi_with_cli(
datacite_mock,
api_app,
users,
location,
json_headers,
json_partial_project_headers,
json_partial_video_headers,
deposit_metadata,
video_deposit_metadata,
project_deposit_metadata,
):
"""Test video publish without DOI, then mint DOI using CLI."""
api_app.config["DEPOSIT_DATACITE_MINTING_ENABLED"] = True

with api_app.test_client() as client:
# Log in as the first user
login_user(User.query.get(users[0]))

# Create a new project
project_dict = _create_new_project(
client, json_partial_project_headers, project_deposit_metadata
)

# Add a new empty video
video_1_dict = _add_video_info_to_project(
client, json_partial_video_headers, project_dict, video_deposit_metadata
)

video_1_depid = video_1_dict["metadata"]["_deposit"]["id"]
video_1 = deposit_video_resolver(video_1_depid)
prepare_videos_for_publish([video_1])

# Publish the video
video_1.publish()

# Verify the video has no DOI after publishing
assert "doi" not in video_1

# Use the CLI command to mint the DOI
recid = video_1['_deposit']['pid']['value']
runner = CliRunner()
result = runner.invoke(create_doi, ["--recid", recid])

assert result.exit_code == 0, f"CLI command failed: {result.output}"

# Fetch the updated record
_, updated_video = video_1.fetch_published()

# Verify that the DOI was minted successfully
doi = updated_video.get("doi")
assert doi is not None, "DOI was not minted"

# Check that the DOI was registered with DataCite
assert datacite_mock.called is True
datacite_mock().doi_post.assert_called_once_with(
doi, f"https://videos.cern.ch/record/{recid}"
)


def _deposit_edit(client, json_headers, id):
"""Post action to edit deposit."""
res = client.post(
Expand Down

0 comments on commit 6b0e39d

Please sign in to comment.