-
Notifications
You must be signed in to change notification settings - Fork 90
Errata backend #5249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Errata backend #5249
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # Generated by Django 4.2.29 on 2026-04-27 11:51 | ||
|
|
||
| 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="Original or main paper", | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
| ) | ||
|
|
@@ -2630,6 +2631,26 @@ 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 | ||
| if not self.ancestors.exists(): | ||
| return None | ||
|
Comment on lines
+2641
to
+2644
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While it makes sense on the context of this PR, section.name is a customizable (and translatable) entry. Since we are adding a new model to register the relationships between articles, it would make sense to codify this (and other relationships) as part of that model, rather than relying on the indirection of the section name. |
||
|
|
||
| # 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" | ||
|
|
@@ -3400,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=_("Original or main paper"), | ||
| 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" | ||
|
|
||
|
|
||
|
Comment on lines
+3424
to
+3445
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the community are asking for a few more relationship types such as addendum, correction and so on. We also have a model on the hydra plugin for registering relationships such as translations. Would it work for your use case if we were to port the LinkedArticle model from Hydra plugin instead? It behaves as a linking table, so there is one less join required when querying both sides of the relationship. |
||
| # Signals | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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>{{ article.object.journal|setting:'crossref_prefix' }}/not-used</crossmark_policy> | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <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 %} | ||
|
|
@@ -69,8 +94,7 @@ | |
| {% endfor %} | ||
| </fr:program> | ||
| {% endif %} | ||
|
|
||
|
|
||
| {% endif %} | ||
|
|
||
| <doi_data> | ||
| <doi>{{ article.doi }}</doi> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we go with the approach I proposed about registering a model that behaves as a linking table, we could also use our M2MOrderedThroughField. It would avoid the additional dependency and also maintain consistency with other ordered many to many relationships in the codebase.