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
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ python-magic==0.4.27
pytz==2024.1
requests==2.32.4
six==1.16.0
django-sortedm2m~=3.1
sqlparse==0.4.4
swapper==1.3.0
tqdm==4.66.3
Expand Down
7 changes: 7 additions & 0 deletions src/identifiers/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ def create_crossref_journal_context(
if journal.doi:
journal_data["doi"] = journal.doi
journal_data["url"] = journal.site_url()
if crossmark_policy_doi := setting_handler.get_setting(
"Identifiers",
"crossref_crossmark_policy_doi",
journal,
).processed_value:
journal_data["crossmark_policy_doi"] = crossmark_policy_doi

return journal_data

Expand All @@ -425,6 +431,7 @@ def create_crossref_article_context(article, identifier=None):
"other_pages": article.page_numbers,
"scheduled": article.scheduled_for_publication,
"object": article,
"erratum_of": article.erratum_of(),
}

# append citations for i4oc compatibility
Expand Down
2 changes: 2 additions & 0 deletions src/identifiers/tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def test_create_crossref_article_context_published(self):
"date_accepted": None,
"date_published": self.article_published.date_published,
"doi": f"10.0000/TST.{self.article_published.id}",
"erratum_of": None,
"id": self.article_published.id,
"license": "",
"object": self.article_published,
Expand All @@ -267,6 +268,7 @@ def test_create_crossref_article_context_not_published(self):
"date_accepted": None,
"date_published": None,
"doi": self.doi_one.identifier,
"erratum_of": None,
"id": self.article_one.id,
"license": submission_models.Licence.objects.filter(
journal=self.journal_one,
Expand Down
45 changes: 45 additions & 0 deletions src/submission/migrations/0090_genealogy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 4.2.29 on 2026-03-30 15:05

from django.db import migrations, models
import django.db.models.deletion
import sortedm2m.fields


class Migration(migrations.Migration):
dependencies = [
("submission", "0089_merge_20260226_1524"),
]

operations = [
migrations.CreateModel(
name="Genealogy",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"children",
sortedm2m.fields.SortedManyToManyField(
help_text=None,
related_name="ancestors",
to="submission.article",
),
),
(
"parent",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="genealogy",
to="submission.article",
verbose_name="Introduction",
),
),
],
),
]
47 changes: 47 additions & 0 deletions src/submission/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
from utils.orcid import validate_orcid, COMPILED_ORCID_REGEX
from utils.forms import plain_text_validator
from journal import models as journal_models
from sortedm2m.fields import SortedManyToManyField
from review.const import (
ReviewerDecisions as RD,
)
Expand Down Expand Up @@ -2626,6 +2627,30 @@ def best_large_image_alt_text(self):
)
return default_text

def erratum_of(self):
"""
Return the "parent" article for which this article is an erratum.

This is intended to be used in templates/common/identifiers/crossref_article.xml
"""
if self.section.name != "Erratum":
return None

# Most articles do not have a "Genealogy"
if not hasattr(self, "ancestors"):
return None

if not self.ancestors.exists():
return None

# We can safely assume that an erratum refers to only one other paper
# so we just return the first "ancestor".
#
# Also, there is no need to check if the "parent" was published:
# the business logic should ensure that we cannot publish an erratum
# to a non-published paper.
return self.ancestors.first().parent


class FrozenAuthorQueryset(model_utils.AffiliationCompatibleQueryset):
AFFILIATION_RELATED_NAME = "frozen_author"
Expand Down Expand Up @@ -3396,6 +3421,28 @@ def handle_defaults(self, article):
article.save()


class Genealogy(models.Model):
"""
Maintain relations of type parent/children between articles.

This can be used, for instance, to link erratum to the original paper.
"""

parent = models.OneToOneField(
Article,
verbose_name=_("Introduction"),
on_delete=models.CASCADE,
related_name="genealogy",
)
children = SortedManyToManyField(
Article,
related_name="ancestors",
)

def __str__(self):
return f"Genealogy: {self.parent} has {self.children.count()} kids"


# Signals


Expand Down
28 changes: 26 additions & 2 deletions src/templates/common/identifiers/crossref_article.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@
<item_number item_number_type="article_number">{{ article.object.pk }}</item_number>
</publisher_item>

{% if article.erratum_of %}
<crossmark>
<crossmark_policy>{{ crossmark_policy_doi }}</crossmark_policy>
<updates>
<update type="erratum" date="{{ now|date:"Y-m-d" }}">{{ article.erratum_of.get_doi }}</update>
</updates>
{% if article.object.funders.exists %}
<custom_metadata>
<fr:program name="fundref">
{% for funder in article.object.funders.all %}
<fr:assertion name="fundgroup">
<fr:assertion name="funder_name">{{ funder.name }}</fr:assertion>
{% if funder.fundref_id %}
<fr:assertion name="funder_identifier">{{ funder.fundref_id }}</fr:assertion>
{% endif %}
{% if funder.funding_id %}
<fr:assertion name="award_number">{{ funder.funding_id }}</fr:assertion>
{% endif %}
</fr:assertion>
{% endfor %}
</fr:program>
</custom_metadata>
{% endif %}
</crossmark>
{% else %}
{% if article.object.funders.exists %}
<fr:program name="fundref">
{% for funder in article.object.funders.all %}
Expand All @@ -69,8 +94,7 @@
{% endfor %}
</fr:program>
{% endif %}


{% endif %}

<doi_data>
<doi>{{ article.doi }}</doi>
Expand Down
19 changes: 19 additions & 0 deletions src/utils/install/journal_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -5672,5 +5672,24 @@
"editor",
"journal-manager"
]
},
{
"group": {
"name": "Identifiers"
},
"setting": {
"description": "A DOI which points to a publisher's CrossMark policy document.",
"is_translatable": false,
"name": "crossref_crossmark_policy_doi",
"pretty_name": "Crossmark policy",
"type": "char"
},
"value": {
"default": ""
},
"editable_by": [
"editor",
"journal-manager"
]
}
]
Loading