Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
31287b7
Adding Crossref support
Engerrs Oct 8, 2024
b156350
add missing dependency
Engerrs Oct 8, 2024
1a7c3d2
ci: update workflow files
alycejenni Oct 29, 2024
d4c7db8
ci: add pull request validation workflow
alycejenni Oct 29, 2024
eb5433a
ci: update tool config
alycejenni Oct 29, 2024
1734e66
chore: update tool details in contributing guide
alycejenni Oct 29, 2024
2164f95
chore: add pull request template
alycejenni Oct 29, 2024
6e38919
build: remove version from docker compose file
alycejenni Oct 29, 2024
60ec5b8
build: update ckantools version
alycejenni Oct 29, 2024
fcced50
ci: only apply auto-fixes in pre-commit
alycejenni Oct 30, 2024
31424b2
ci: add docformatter args and dependency
alycejenni Oct 30, 2024
d2acd39
style: automatic reformat
alycejenni Oct 30, 2024
f904325
docs: standardise returns field
alycejenni Oct 31, 2024
10ef129
merge: #112 from ginger/standardise
alycejenni Oct 31, 2024
41c8bf3
ci: add merge to valid commit types
alycejenni Nov 4, 2024
370c561
merge: #113 from ginger/merge-commit-type
alycejenni Nov 4, 2024
916b252
ci: fix python setup action version
alycejenni Nov 4, 2024
b55fd06
merge: #114 from ginger/python-setup-version
alycejenni Nov 4, 2024
f671985
merge: PR #115 from dev
alycejenni Nov 4, 2024
60df234
bump: version 3.1.15 → 3.1.16
github-actions[bot] Nov 4, 2024
5250978
docs: fix tests badge
alycejenni Nov 4, 2024
9f25dce
docs: use variable logo based on colour scheme
alycejenni Nov 4, 2024
8aa8b07
merge: #116 from ginger/readme
alycejenni Nov 4, 2024
2dd89e5
merge: #117 from patch
alycejenni Nov 4, 2024
9624c6b
bump: version 3.1.16 → 3.1.17
github-actions[bot] Nov 4, 2024
eba9a0c
refactor(db)!: switch to alembic migration scripts
alycejenni Mar 14, 2025
5e07229
merge: #119 from ginger/2.11
alycejenni Mar 14, 2025
ebb3b80
merge: PR #120 from dev
alycejenni Mar 17, 2025
3c03d2d
bump: version 3.1.17 → 4.0.0
github-actions[bot] Mar 17, 2025
f98181a
ci: update pre-commit repo versions
alycejenni Jun 5, 2025
2f876b7
build: update ckantools
alycejenni Jun 5, 2025
ba86aab
build: remove pylint, add ruff lint ignore rules
alycejenni Jun 5, 2025
072a340
build: add ruff lint select rules
alycejenni Jun 5, 2025
13bb7b2
ci: set ruff target py version, add more ignores
alycejenni Jun 6, 2025
cfd5440
merge: #121 from ginger/standardisation
alycejenni Jun 6, 2025
eea3068
merge: PR #122 from dev
alycejenni Jun 9, 2025
fc320d2
bump: version 4.0.0 → 4.0.1
github-actions[bot] Jun 9, 2025
d9ca75f
fix: remove auto-added pyproject fields
alycejenni Jun 9, 2025
ba13cae
merge: #123 from patch
alycejenni Jun 9, 2025
cda4161
bump: version 4.0.1 → 4.0.2
github-actions[bot] Jun 9, 2025
b7022cb
build: include db migration files
alycejenni Aug 8, 2025
e26908a
merge: #124 from ginger/include-migration
alycejenni Aug 8, 2025
5702c43
merge: PR #125 from dev
alycejenni Aug 11, 2025
a158956
bump: version 4.0.2 → 4.0.3
github-actions[bot] Aug 11, 2025
4d2f64a
fix(conflict): make sure crossref branch is up to date with main
Engerrs Sep 10, 2025
fc34e62
fix(tests): fix api tests
Engerrs Sep 11, 2025
5e03579
fix: move dependency to pyproject
Engerrs Sep 11, 2025
e1310c6
fix: remove duplicated migration file
Engerrs Sep 11, 2025
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
50 changes: 38 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ _A CKAN extension for assigning a digital object identifier (DOI) to datasets, u
# Overview

<!--overview-start-->
This extension assigns a digital object identifier (DOI) to datasets, using the DataCite DOI service.
This extension assigns a digital object identifier (DOI) to datasets, using the DataCite/Crossref DOI service.

When a new dataset is created it is assigned a new DOI. This DOI will be in the format:

`https://doi.org/[prefix]/[8 random alphanumeric characters]`

If the new dataset is active and public, the DOI and metadata will be registered with DataCite.
If the new dataset is active and public, the DOI and metadata will be registered with DataCite/Crossref.

If the dataset is draft or private, the DOI will not be registered with DataCite. When the dataset is made active & public, the DOI will be registered.
If the dataset is draft or private, the DOI will not be registered with DataCite/Crossref. When the dataset is made active & public, the DOI will be registered.
This allows datasets to be embargoed, but still provides a DOI to be referenced in publications.

You will need a DataCite account to use this extension.
You will need a DataCite/Crossref account to use this extension.

## DOI Metadata

This extension currently uses [DataCite Metadata Schema v4.2](https://schema.datacite.org/meta/kernel-4.2/index.html).
DataCite currently uses [DataCite Metadata Schema v4.2](https://schema.datacite.org/meta/kernel-4.2/index.html).
Crossref currently uses [Crossref Metadata Schema v5.3.1](https://data.crossref.org/reports/help/schema_doc/5.3.1/index.html).

Dataset package fields and CKAN config settings are mapped to the DataCite Schema with default values, but these can be overwritten by [implementing `IDoi` interface methods](https://ckanext-doi.readthedocs.io/en/latest/usage/#interfaces).

Expand All @@ -47,6 +48,13 @@ Dataset package fields and CKAN config settings are mapped to the DataCite Schem

See [`metadata.py`](https://github.com/NaturalHistoryMuseum/ckanext-doi/blob/main/ckanext/doi/lib/metadata.py) for full mapping details.

| CKAN Field | Crossref Schema |
|-------------------------------|-----------------|
| dataset:title | title |
| config:ckanext.doi.publisher | publisher |
| config:ckanext.doi.account_name | email_address |


<!--overview-end-->

# Installation
Expand Down Expand Up @@ -93,10 +101,10 @@ Installing from a `pyproject.toml` in editable mode (i.e. `pip install -e`) requ

2. Initialise the database:
```shell
ckan -c $CONFIG_FILE doi initdb
ckan -c $CONFIG_FILE db upgrade -p doi
```

3. This extension will only work if you have signed up for an account with [DataCite](https://datacite.org). You will need a development/test account to use this plugin in test mode, and a live account to mint active DOIs.
3. This extension will only work if you have signed up for an account with [DataCite](https://datacite.org)/[Crossref](https://www.crossref.org). You will need a development/test account to use this plugin in test mode, and a live account to mint active DOIs.

<!--installation-end-->

Expand All @@ -111,9 +119,9 @@ DataCite Repository account credentials are used to register DOIs. A Repository

| Name | Description | Example |
|--------------------------------|---------------------------------------------------------------------------------------------------------|------------|
| `ckanext.doi.account_name` | Your DataCite Repository account name | `ABC.DEFG` |
| `ckanext.doi.account_password` | Your DataCite Repository account password | |
| `ckanext.doi.prefix` | The prefix taken from your DataCite Repository account (from your test account if running in test mode) | `10.1234` |
| `ckanext.doi.account_name` | Your DataCite/Crossref Repository account name | `ABC.DEFG` |
| `ckanext.doi.account_password` | Your DataCite/Crossref Repository account password | |
| `ckanext.doi.prefix` | The prefix taken from your DataCite/Crossref Repository account (from your test account if running in test mode) | `10.1234` |

## Institution Name **[REQUIRED]**

Expand All @@ -123,15 +131,33 @@ You also need to provide the name of the institution publishing the DOIs (e.g. N
|-------------------------|------------------------------------------------|
| `ckanext.doi.publisher` | The name of the institution publishing the DOI |

## Platform Name **[OPTIONAL]**

You also need to provide the name of the platform for publishing the DOIs (e.g. `crossref` or `datacite`). By default it is set to `datacite`.

| Name | Description |
|-------------------------|------------------------------------------------|
| `ckanext.doi.platform` | The name of the platform for publishing the DOI |

## Disable DOI minting to Dataset update **[OPTIONAL]**

Disable the minting trigger while updating Dataset. Needed for more custom ways of triggering DOI minting process.

| Name | Description |
|-------------------------|------------------------------------------------|
| `ckanext.doi.disable_on_update` | True/False |

## Test/Debug Mode **[REQUIRED]**

If test mode is set to true, the DOIs will use the DataCite test site. The test site uses a separate account, so you must also change your credentials and prefix.
If test mode is set to true, the DOIs will use the DataCite/Crossref test site. The test site uses a separate account, so you must also change your credentials and prefix.

| Name | Description | Options |
|-------------------------|----------------------|------------|
| `ckanext.doi.test_mode` | Enable dev/test mode | True/False |

Note that the DOIs will still display on your web interface as `https://doi.org/YOUR-DOI`, but they _will not resolve_. Log in to your test account to view all your minted test DOIs, or replace `https://doi.org/` with `https://doi.test.datacite.org/dois/` in a single URL to view a specific DOI.
Note that the DataCite DOIs will still display on your web interface as `https://doi.org/YOUR-DOI`, but they _will not resolve_. Log in to your test account to view all your minted test DOIs, or replace `https://doi.org/` with `https://doi.test.datacite.org/dois/` in a single URL to view a specific DOI.

Crossref TBA

## Other options

Expand Down
15 changes: 0 additions & 15 deletions ckanext/doi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,6 @@ def doi():
pass


@doi.command(name='initdb')
def init_db():
if not model.package_table.exists():
click.secho(
'Package table must exist before initialising the DOI table', fg='red'
)
raise click.Abort()

if doi_model.doi_table.exists():
click.secho('DOI table already exists', fg='green')
else:
doi_model.doi_table.create()
click.secho('DOI table created', fg='green')


@doi.command(name='delete-dois')
def delete_dois():
"""
Expand Down
103 changes: 97 additions & 6 deletions ckanext/doi/lib/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,29 @@
import logging
import random
import xmltodict
from ckan.common import asbool
from ckan.plugins import toolkit
from ckanext.doi.model.crud import DOIQuery
from datacite import DataCiteMDSClient, schema42
from datacite.errors import DataCiteError, DataCiteNotFoundError
from datetime import datetime as dt

from ckanext.doi.lib.helpers import doi_test_mode
from crossref.restful import Depositor

from ckanext.doi.lib.helpers import doi_test_mode, get_doi_platform

log = logging.getLogger(__name__)

DEPRECATED_TEST_PREFIX = '10.5072'


class DataciteClient:
test_url = 'https://mds.test.datacite.org'
class DOIClient:
test_url = None
client_name = ""

def __init__(self):
self.username = toolkit.config.get('ckanext.doi.account_name')
self.password = toolkit.config.get('ckanext.doi.account_password')
self._test_mode = None
self._test_mode = False
self.prefix = self.get_prefix()
client_config = {
'username': self.username,
Expand All @@ -50,7 +52,7 @@ def test_mode(self):
Defaults to true.
:return: test mode enabled as boolean (true=enabled)
"""
if self._test_mode is None:
if self._test_mode is False:
self._test_mode = doi_test_mode()
return self._test_mode

Expand Down Expand Up @@ -106,6 +108,11 @@ def generate_doi(self):
attempts -= 1
raise Exception('Failed to generate a DOI')


class DataciteClient(DOIClient):
test_url = 'https://mds.test.datacite.org'
client_name = "DataCite"

def mint_doi(self, doi, package_id):
"""
Mints the given DOI on datacite. Does not add metadata, just creates the DOI.
Expand Down Expand Up @@ -187,3 +194,87 @@ def check_for_update(self, doi, xml_dict):
else:
# if the original doesn't have any dates, it's definitely different
return False


class CrossrefClient(DOIClient):
test_url = 'https://test.crossref.org'
client_name = "Crossref"

def set_metadata(self, doi, xml_dict):
"""
Prepares and send metadata to Crossref to generate DOI.

:param doi: DOI identifier
:param xml_dict: the metadata as an xml dict (generated from build_xml_dict)
:return:
"""
self.make_crossref_request(self, doi, xml_dict)

def make_crossref_request(self, doi, xml_dict):
error_msg = None
elements = doi.split("/")

if len(elements) == 2:
ds_doi = elements[1]
depositor = Depositor(
self.get_prefix,
self.username,
self.password,
use_test_server=self._test_mode,
)

# Temporary solution until "register_doi" method is fixed by the library.
endpoint = depositor.get_endpoint("deposit")
files = {"mdFile": ("%s.xml" % ds_doi, xml_dict)}

params = {
"operation": "doMDUpload",
"login_id": self.username,
"login_passwd": self.password,
}

request = depositor.do_http_request(
"post",
endpoint,
data=params,
files=files,
custom_header=depositor.custom_header,
timeout=1000,
)

if request.status_code == 200:
resp_text = request.text

if "Not Accessible" in resp_text:
error_msg = 'There is an issue with credentials,' \
' please re-check them and try again.'
elif "<title>FAILURE</title>" in resp_text:
error_msg = 'Response returend with a failure.'
else:
error_msg = (
f'Request was returned with a {request.status_code} status code.'
)

if error_msg:
log.warning(error_msg)
else:
DOIQuery.update_doi(doi, published=dt.now())

return error_msg

def mint_doi(self, doi, package_id):
# Crossref doesn't have this step. Passing.
pass

def check_for_update(self, doi, xml_dict):
# Will update each Dataset update
return False


def get_client():
platform = get_doi_platform()
clients = {
"datacite": DataciteClient,
"crossref": CrossrefClient,
}
return clients[platform]()
10 changes: 10 additions & 0 deletions ckanext/doi/lib/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,13 @@ def doi_test_mode():
:return: bool
"""
return toolkit.asbool(get_setting('ckanext.doi.test_mode', default=get_debug()))


def get_doi_platform():
"""
Get name of the Platform that is being used for DOI creation

:return: string
"""

return toolkit.config.get('ckanext.doi.platform', 'datacite')
Loading