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
79 changes: 79 additions & 0 deletions src/submission/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from django.template import Context, Template
from django.template.loader import render_to_string
from django.templatetags.static import static
from django.db.models import QuerySet
from django.db.models.signals import pre_delete, m2m_changed
from django.dispatch import receiver
from django.core import exceptions
Expand Down Expand Up @@ -2617,6 +2618,84 @@ def best_large_image_alt_text(self):
)
return default_text

def ancestors(self, link_type: str) -> QuerySet["Article"]:
"""
Return articles related to self, where self is the "to-article".

This can be used, for instance, to refer to corrections in self's landing page.
"""
return self._related(link_type=link_type, direction="ancestors")

def descendants(self, link_type: str) -> QuerySet["Article"]:
"""
Return articles related to self, where self is the "from-article".

This can be used, for instance, to refer to corrections in self's landing page.
"""
return self._related(link_type=link_type, direction="descendants")

def _related(self, link_type: str, direction: str) -> QuerySet["Article"]:
"""
Return articles related to self.

Direction:
- descendants -> where self is the "from-article"
- ancenstors -> where self. is the "to-article"
"""
if not link_type:
return Article.objects.none()

# Silently return nothing if Hydra plugin is not available
try:
from plugins.hydra.models import LinkedArticle # noqa: F401
except ImportError:
return Article.objects.none()

if direction == "descendants":
# self is the "from-article"; return the "to-articles"
return Article.objects.filter(
linked_to__from_article=self, linked_to__relationship=link_type
)
if direction == "ancestors":
# self is the "to-article"; return the "from-articles"
return Article.objects.filter(
linked_from__to_article=self, linked_from__relationship=link_type
)

raise ValueError(
f"Unknown relationship direction '{direction}' requested for {self.id}"
)

def update_of(self):
"""
Return the LinkedArticle relation whose "from-article" is the
"parent" this article is an "update" of.

We consider only a well-known list of relations / link-types.
Only the first relation is returned; the idea is that, if, for instance,
self is an erratum of X, then it cannot be an addendum of Y at the same time.

This can be used, for instance, to refer to errata in
templates/common/identifiers/crossref_article.xml
"""
# Silently return nothing if Hydra plugin is not available
try:
from plugins.hydra.models import CROSSREF_UPDATES, LinkedArticle
except ImportError:
return None

relations = LinkedArticle.objects.filter(
to_article=self,
relationship__in=CROSSREF_UPDATES,
)
if relations.count() > 1:
# If this happens, we prefer to continue the process, but someone should check what's happening
logger.error(
f"Article {self.pk} has multiple personalities: {[(r.from_article.id, r.relationship) for r in relations]}"
)

return relations.first()


class FrozenAuthorQueryset(model_utils.AffiliationCompatibleQueryset):
AFFILIATION_RELATED_NAME = "frozen_author"
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.object.update_of %}
<crossmark>
<crossmark_policy>{{ article.object.journal|setting:'crossref_prefix' }}/not-used</crossmark_policy>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ according to XML specs, when recording an <update>, then a <crossmark_policy> DOI should also be provided, but, according to crossref support, that element is not used and any DOI would do. They suggested to use the article DOI, but I'm hardcoding a 10.11111/no-used fake DOI (I feel it's less confusing...)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@mauromsl please feel free to resolve this thread if you have no objections

<updates>
<update type="{{ article.object.update_of.relationship }}" date="{{ now|date:"Y-m-d" }}">{{ article.object.update_of.from_article.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
Loading