Newer
Older
__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"
import re
from django import forms
from django.db.models import Q, Max, prefetch_related_objects
from django.forms import BaseModelFormSet, modelformset_factory
from django.utils import timezone
import lxml.etree as ET
from html.entities import entitydefs
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Div, Field, ButtonHolder, Submit
from crispy_bootstrap5.bootstrap5 import FloatingField
from common.forms import HTMXInlineCRUDModelForm
from journals.models.resource import PublicationResource
from journals.models.update import PublicationUpdate
from .constants import (
STATUS_DRAFT,
STATUS_PUBLICLY_OPEN,
PUBLICATION_PREPUBLISHED,
PUBLICATION_PUBLISHED,
)
Issue,
Publication,
Reference,
Volume,
PublicationAuthorsTable,
)
from common.utils import get_current_domain, jatsify_tags
from finances.models import PubFrac
from organizations.models import Organization
from proceedings.models import Proceedings
from production.constants import PRODUCTION_STREAM_COMPLETED
from production.models import ProductionEvent
from scipost.forms import RequestFormMixin
from submissions.models import Submission
class PublicationSearchForm(forms.Form):
"""Simple search form to filter a Publication queryset."""
author = forms.CharField(max_length=100, required=False, label="Author(s)")
title = forms.CharField(max_length=100, required=False)
doi_label = forms.CharField(max_length=100, required=False)
journal = forms.ModelChoiceField(queryset=Journal.objects.all(), required=False)
proceedings = forms.ModelChoiceField(
queryset=Proceedings.objects.all(), required=False
)
def __init__(self, *args, **kwargs):
self.acad_field_slug = kwargs.pop("acad_field_slug")
self.specialty_slug = kwargs.pop("specialty_slug")
super().__init__(*args, **kwargs)
if self.acad_field_slug and self.acad_field_slug != "all":
self.fields["journal"].queryset = Journal.objects.filter(
college__acad_field__slug=self.acad_field_slug
)
self.helper = FormHelper()
self.helper.layout = Layout(
Div(
Div(FloatingField("author"), css_class="col-lg-6"),
Div(FloatingField("title"), css_class="col-lg-6"),
css_class="row mb-0",
Div(FloatingField("journal"), css_class="col-lg-6"),
Div(FloatingField("doi_label"), css_class="col-lg-6"),
css_class="row mb-0",
Div(FloatingField("proceedings"), css_class="col-lg-6"),
css_class="row mb-0",
css_id="row_proceedings",
style="display: none",
),
)
def search_results(self):
"""
Return all public Publication objects fitting search criteria.
"""
publications = Publication.objects.published()
if self.acad_field_slug and self.acad_field_slug != "all":
publications = publications.filter(acad_field__slug=self.acad_field_slug)
if self.specialty_slug and self.specialty_slug != "all":
publications = publications.filter(
specialties__slug=self.specialty_slug
)
if self.cleaned_data.get("author"):
publications = publications.filter(
author_list__icontains=self.cleaned_data.get("author")
)
if self.cleaned_data.get("title"):
publications = publications.filter(
title__icontains=self.cleaned_data.get("title")
if self.cleaned_data.get("doi_label"):
publications = publications.filter(
doi_label__icontains=self.cleaned_data.get("doi_label")
)
if self.cleaned_data.get("journal"):
publications = publications.for_journal(
self.cleaned_data.get("journal").name
)
if self.cleaned_data.get("proceedings"):
publications = publications.filter(
in_issue__proceedings=self.cleaned_data.get("proceedings")
)
return publications
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
class CitationListItemForm(forms.ModelForm):
doi = forms.CharField(
required=False,
widget=forms.TextInput(attrs={"placeholder": "DOI"}),
)
class Meta:
model = Publication
fields = []
def __init__(self, *args, **kwargs):
self.index = kwargs.pop("index")
citation_list = kwargs["instance"].metadata["citation_list"]
if self.index < len(citation_list):
kwargs["initial"] = {"doi": citation_list[self.index]["doi"]}
super().__init__(*args, **kwargs)
def clean_doi(self):
doi = self.cleaned_data.get("doi")
dois_in_list = [cite["doi"] for cite in self.instance.metadata["citation_list"]]
if doi in dois_in_list and doi != self.initial.get("doi"):
self.add_error("doi", "This DOI is already in the citation list.")
return doi
def save(self, *args, **kwargs):
doi = self.cleaned_data.get("doi")
entry = {"key": "ref" + str(self.index + 1), "doi": doi}
if self.index < len(self.instance.metadata["citation_list"]):
self.instance.metadata["citation_list"][self.index] = entry
else:
self.instance.metadata["citation_list"].append(entry)
# Resort the citation list
sorted_list = sorted(
self.instance.metadata["citation_list"], key=lambda x: int(x["key"][3:])
)
self.instance.metadata["citation_list"] = [
{"key": "ref" + str(n + 1), "doi": cite["doi"]}
for n, cite in enumerate(sorted_list)
]
return super().save(*args, **kwargs)
class CitationListBibitemsForm(forms.ModelForm):
latex_bibitems = forms.CharField(
widget=forms.Textarea(),
help_text="Once you submit, it will overwrite the current citation list, shown below.",
)
class Meta:
model = Publication
fields = ()
def __init__(self, *args, **kwargs):
self.fields["latex_bibitems"].widget.attrs.update(
{"placeholder": "Paste the .tex bibitems here"}
)
def extract_dois(self):
entries_list = self.cleaned_data["latex_bibitems"]
entries_list = re.sub(r"(?m)^\%.*\n?", "", entries_list)
entries_list = entries_list.split("\doi{")
dois = []
for entry in entries_list[1:]: # drop first bit before first \doi{
dois.append(
{
"key": "ref" + str(n_entry),
"doi": entry.partition("}")[0],
}
)
return dois
self.instance.metadata["citation_list"] = self.extract_dois()
class AbstractJATSForm(forms.ModelForm):
abstract_jats = forms.CharField(
widget=forms.Textarea(
{
"placeholder": "Paste the JATS abstract here (use pandoc to generate; see docs)"
}
class Meta:
model = Publication
fields = ()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["abstract_jats"].initial = self.instance.abstract_jats
def save(self, *args, **kwargs):
self.instance.abstract_jats = jatsify_tags(self.cleaned_data["abstract_jats"])
return super().save(*args, **kwargs)
funding_statement = forms.CharField(
required=False,
widget=forms.Textarea({"placeholder": "Paste the funding info statement here"}),
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["funding_statement"].initial = self.instance.metadata.get(
"funding_statement"
)
self.instance.metadata["funding_statement"] = self.cleaned_data[
"funding_statement"
]
class BasePublicationAuthorsTableFormSet(BaseModelFormSet):
def save(self, *args, **kwargs):
objects = super().save(*args, **kwargs)
for form in self.ordered_forms:
form.instance.order = form.cleaned_data["ORDER"]
form.instance.save()
return objects
PublicationAuthorOrderingFormSet = modelformset_factory(
PublicationAuthorsTable,
fields=(),
can_order=True,
extra=0,
formset=BasePublicationAuthorsTableFormSet,
)
class AuthorsTableOrganizationSelectForm(forms.ModelForm):
url="/organizations/organization-autocomplete", attrs={"data-html": True}
),
class Meta:
model = PublicationAuthorsTable
fields = []
schema = None
parser = None
@classmethod
def initialize_lxml(cls):
if cls.schema is None:
cls.schema = ET.XMLSchema(
file=settings.STATIC_ROOT + settings.CROSSREF_SCHEMA_FILE
)
if cls.parser is None:
cls.parser = ET.XMLParser(schema=cls.schema)
xml = self.generate_xml(kwargs.get("instance"))
self.xml_str = self.format_xml(self.decode_html_entities(xml))
kwargs["initial"] = {"metadata_xml": self.xml_str}
if self.schema is None or self.parser is None:
self.initialize_lxml()
@staticmethod
def decode_html_entities(xml: str):
# Replace any encoded HTML entities with their decoded counterparts
for entity, symbol in entitydefs.items():
if entity in ["lt", "gt", "amp", "quot", "apos"]:
continue
xml = xml.replace(f"&{entity};", symbol)
return xml
def clean_metadata_xml(self):
# Flatten the XML before saving
xml = self.cleaned_data["metadata_xml"]
xml = re.sub(r"\s*\n+\s*", "", xml, flags=re.MULTILINE)
return xml
def save(self, *args, **kwargs):
self.instance.latest_metadata_update = timezone.now()
return super().save(*args, **kwargs)
def generate_xml(self, object):
Create new XML structure, return as a string.
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
def format_xml(self, xml_str: str) -> str:
"""
Format XML by pretty printing it.
Returns the formatted XML as a string.
"""
# Try to parse the XML, if it fails, just return the string
try:
xml = ET.fromstring(bytes(xml_str, encoding="utf8"))
xml_str = ET.tostring(xml, pretty_print=True).decode("utf8")
except:
pass
return xml_str
def validate_xml(self, xml_str: str):
"""
Validate XML by running it through the schema.
Returns a tuple of (valid, errors, xml_str).
"""
# Try to parse the XML, if it fails, just return the string
try:
xml_str = self.format_xml(xml_str)
xml = ET.fromstring(bytes(xml_str, encoding="utf8"))
valid = self.schema.validate(xml)
errors = list(self.schema.error_log)
return valid, errors, xml_str
except ET.XMLSyntaxError as error:
return False, [str(error)], xml_str
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
class CreatePublicationMetadataXMLForm(CreateMetadataXMLForm):
class Meta:
model = Publication
fields = ["metadata_xml"]
def generate_xml(self, publication):
# Create a doi_batch_id
salt = ""
for i in range(5):
salt = salt + random.choice(string.ascii_letters)
salt = salt.encode("utf8")
idsalt = publication.title[:10]
idsalt = idsalt.encode("utf8")
doi_batch_id = hashlib.sha1(salt + idsalt).hexdigest()
prefetch_related_objects(
[publication],
"authors__profile",
"authors__affiliations",
"grants",
)
funders = (
Funder.objects.filter(grants__in=publication.grants.all())
| publication.funders_generic.all()
).distinct()
# Render from template
template = loader.get_template("xml/publication_crossref.html")
context = {
"domain": get_current_domain(),
"publication": publication,
"doi_batch_id": doi_batch_id,
"deposit_email": settings.CROSSREF_DEPOSIT_EMAIL,
"funders": funders,
}
return template.render(context)
class CreateProceedingsMetadataXMLForm(CreateMetadataXMLForm):
class Meta:
model = Proceedings
fields = ["metadata_xml"]
def generate_xml(self, proceedings: "Proceedings"):
# Create a doi_batch_id
salt = ""
for i in range(5):
salt = salt + random.choice(string.ascii_letters)
salt = salt.encode("utf8")
idsalt = proceedings.event_name[:10]
idsalt = idsalt.encode("utf8")
doi_batch_id = hashlib.sha1(salt + idsalt).hexdigest()
prefetch_related_objects(
[proceedings],
"fellowships__contributor__profile",
"issue__publications__authors__profile",
"issue__publications__authors__affiliations",
)
# Render from template
template = loader.get_template("xml/proceedings_crossref.html")
context = {
"domain": get_current_domain(),
"proceedings": proceedings,
"doi_batch_id": doi_batch_id,
"deposit_email": settings.CROSSREF_DEPOSIT_EMAIL,
}
return template.render(context)
class CreatePublicationMetadataDOAJForm(forms.ModelForm):
fields = ["metadata_DOAJ"]
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
kwargs["initial"] = {"metadata_DOAJ": self.generate(kwargs.get("instance"))}
super().__init__(*args, **kwargs)
def generate(self, publication):
issn = str(publication.get_journal().issn)
"bibjson": {
"author": [{"name": publication.author_list}],
"title": publication.title,
"abstract": publication.abstract,
"year": publication.publication_date.strftime("%Y"),
"month": publication.publication_date.strftime("%m"),
"identifier": [
{"type": "eissn", "id": issn},
{"type": "doi", "id": publication.doi_string},
"url": self.request.build_absolute_uri(
publication.get_absolute_url()
),
"type": "fulltext",
"journal": {
"start_page": publication.get_paper_nr(),
md["bibjson"]["journal"]["volume"] = str(
publication.in_issue.in_volume.number
)
md["bibjson"]["journal"]["number"] = str(publication.in_issue.number)
class BaseReferenceFormSet(BaseModelFormSet):
"""
BaseReferenceFormSet is used to help fill the Reference list for Publications
It is required to add the required keyword argument `publication` to this FormSet.
"""
initial_references = []
def __init__(self, *args, **kwargs):
self.publication = kwargs.pop("publication")
extra = kwargs.pop("extra")
self.extra = int(extra if extra else "0")
kwargs["form_kwargs"] = {"publication": self.publication}
super().__init__(*args, **kwargs)
def prefill(self):
citations = self.publication.metadata.get("citation_list", [])
caller = DOICaller(cite["doi"])
if caller.is_valid:
# Authors
author_list = []
for author in caller._crossref_data["author"][:3]:
author_list.append(
"{}. {}".format(author["given"][0], author["family"])
)
author_list.append(author["name"])
authors = ", ".join(author_list[:-1])
authors += " and " + author_list[-1]
authors = " and ".join(author_list)
citation = "<em>{}</em> {} <b>{}</b>, {} ({})".format(
caller.data["title"],
caller.data["journal"],
caller.data["volume"],
caller.data["pages"],
datetime.strptime(caller.data["pub_date"], "%Y-%m-%d").year,
)
self.initial_references.append(
{
"reference_number": cite["key"][3:],
"authors": authors,
"citation": citation,
"identifier": cite["doi"],
"link": "https://doi.org/{}".format(cite["doi"]),
}
)
self.initial_references.append(
{
"reference_number": cite["key"][3:],
"identifier": cite["doi"],
"link": "https://doi.org/{}".format(cite["doi"]),
}
)
# Add prefill information to the form
if not self.initial_extra:
self.initial_extra = self.initial_references
else:
self.initial_extra.extend(self.initial_references)
class ReferenceForm(forms.ModelForm):
class Meta:
model = Reference
fields = [
"reference_number",
"authors",
"citation",
"identifier",
"link",
self.publication = kwargs.pop("publication")
super().__init__(*args, **kwargs)
def save(self, *args, **kwargs):
self.instance.publication = self.publication
super().save(*args, **kwargs)
ReferenceFormSet = modelformset_factory(
Reference, formset=BaseReferenceFormSet, form=ReferenceForm, can_delete=True
)
"""
This Form is used by the Production Supervisors to create a new Publication object
and prefill all data. It is only able to create a `draft` version of a Publication object.
"""
class Meta:
model = Publication
fields = [
"doi_label",
"pdf_file",
"in_issue",
"paper_nr",
"title",
"author_list",
"abstract",
"acad_field",
"specialties",
"approaches",
"cc_license",
"submission_date",
"acceptance_date",
"publication_date",
]
widgets = {
"submission_date": forms.DateInput(attrs={"type": "date"}),
"acceptance_date": forms.DateInput(attrs={"type": "date"}),
"publication_date": forms.DateInput(attrs={"type": "date"}),
}
def __init__(
self, data=None, identifier_w_vn_nr=None, issue_id=None, *args, **kwargs
):
# Use separate instance to be able to prefill the form without any existing Publication
self.submission = None
self.issue = None
try:
self.submission = Submission.objects.accepted().get(
preprint__identifier_w_vn_nr=identifier_w_vn_nr
)
except Submission.DoesNotExist:
self.submission = None
# Check if the Submission is related to a Journal with individual Publications only
if self.submission:
try:
self.to_journal = Journal.objects.has_individual_publications().get(
name=self.submission.editorial_decision.for_journal.name
)
except Journal.DoesNotExist:
self.to_journal = None
# If the Journal is not for individual publications, choose a Issue for Publication
if issue_id and not self.to_journal:
try:
self.issue = self.get_possible_issues().get(id=issue_id)
except Issue.DoesNotExist:
self.issue = None
if kwargs.get("instance") or self.issue or self.to_journal:
# When updating: fix in_issue, because many fields are directly related to the issue.
del self.fields["in_issue"]
self.fields["in_issue"].queryset = self.get_possible_issues()
issues = Issue.objects.filter(until_date__gte=timezone.now())
if self.submission:
issues = (
issues.for_journal(self.submission.submitted_to.name)
| issues.for_journal(
self.submission.editorial_decision.for_journal.name
)
).distinct()
def delete_secondary_fields(self):
"""
Delete fields from the self.fields dictionary. Later on, this submitted sparse form can
be used to prefill these secondary fields.
"""
del self.fields["doi_label"]
del self.fields["pdf_file"]
del self.fields["paper_nr"]
del self.fields["title"]
del self.fields["author_list"]
del self.fields["abstract"]
del self.fields["acad_field"]
del self.fields["specialties"]
del self.fields["approaches"]
del self.fields["cc_license"]
del self.fields["submission_date"]
del self.fields["acceptance_date"]
del self.fields["publication_date"]
def clean(self):
data = super().clean()
if not self.instance.id:
if self.submission:
self.instance.accepted_submission = self.submission
if self.issue:
self.instance.in_issue = self.issue
if self.to_journal:
self.instance.in_journal = self.to_journal
return data
def save(self, *args, **kwargs):
"""
Save the Publication object always as a draft and prefill the Publication with
related Submission data only when appending the Publication.
"""
do_prefill = False
if not self.instance.id:
do_prefill = True
# Set the cf_citation to empty string to force recalculation
self.instance.cf_citation = ""
if do_prefill:
self.first_time_fill()
return self.instance
def first_time_fill(self):
"""
Take over fields from related Submission object. This can only be done after
the Publication object has been added to the database due to m2m relations.
"""
self.instance.status = STATUS_DRAFT
if self.submission:
# Copy all existing author and non-author relations to Publication
for submission_author in self.submission.authors.all():
PublicationAuthorsTable.objects.create(
publication=self.instance, profile=submission_author.profile
)
self.instance.topics.add(*self.submission.topics.all())
# Create supplementary information for any provided external links
#! Refactor: may be possible to check if url is present in related publications
is_codebase = (
self.issue is None and "codebase" in self.to_journal.name.lower()
)
if self.submission.code_repository_url and not is_codebase:
PublicationResource.objects.get_or_create(
publication=self.instance,
_type=PublicationResource.TYPE_SUP_INFO,
url=self.submission.code_repository_url,
comments="Code repository",
)
is_datasets = (
self.issue is None and "datasets" in self.to_journal.name.lower()
)
if self.submission.data_repository_url and not is_datasets:
PublicationResource.objects.get_or_create(
publication=self.instance,
_type=PublicationResource.TYPE_SUP_INFO,
url=self.submission.data_repository_url,
comments="Data repository",
)
def prefill_fields(self):
if self.submission:
self.fields["title"].initial = self.submission.title
self.fields["author_list"].initial = self.submission.author_list
self.fields["abstract"].initial = self.submission.abstract
self.fields["acad_field"].initial = self.submission.acad_field.id
self.fields["specialties"].initial = [
s.id for s in self.submission.specialties.all()
]
self.fields["approaches"].initial = self.submission.approaches
self.fields["submission_date"].initial = (
self.submission.original_submission_date
)
self.fields["acceptance_date"].initial = self.submission.acceptance_date
self.fields["publication_date"].initial = timezone.now()
# Fill data for Publications grouped by Issues (or Issue+Volume).
if hasattr(self.instance, "in_issue") and self.instance.in_issue:
self.issue = self.instance.in_issue
if self.issue:
self.prefill_with_issue(self.issue)
# Fill data for Publications ungrouped; directly linked to a Journal.
if hasattr(self.instance, "in_journal") and self.instance.in_journal:
self.to_journal = self.instance.in_issue
if self.to_journal:
self.prefill_with_journal(self.to_journal)
def prefill_with_issue(self, issue):
# Determine next available paper number:
paper_nr = (
Publication.objects.filter(in_issue__in_volume=issue.in_volume).count()
+ 1
)
elif issue.in_journal:
# Issue only
paper_nr = Publication.objects.filter(in_issue=issue).count() + 1
self.fields["paper_nr"].initial = str(paper_nr)
doi_label = "{journal}.{vol}.{issue}.{paper}".format(
journal=issue.in_volume.in_journal.doi_label,
paper=str(paper_nr).rjust(3, "0"),
)
doi_label = "{journal}.{issue}.{paper}".format(
journal=issue.in_journal.doi_label,
paper=str(paper_nr).rjust(3, "0"),
)
self.fields["doi_label"].initial = doi_label
doi_string = "10.21468/{doi}".format(doi=doi_label)
def prefill_with_journal(self, journal):
# Determine next available paper number:
# paper_nr = journal.publications.count() + 1
paper_nr = (
journal.publications.aggregate(Max("paper_nr"))["paper_nr__max"] or 0
) + 1
self.fields["paper_nr"].initial = str(paper_nr)
doi_label = "{journal}.{paper}".format(
journal=journal.doi_label, paper=paper_nr
)
self.fields["doi_label"].initial = doi_label
class DraftAccompanyingPublicationForm(forms.Form):
anchor = forms.ModelChoiceField(
queryset=Publication.objects.all(),
widget=forms.HiddenInput(),
)
title = forms.CharField(max_length=300)
abstract = forms.CharField(widget=forms.Textarea())
doi_label_suffix = forms.CharField(max_length=128)
pubtype = forms.ChoiceField(
choices=Publication.PUBTYPE_CHOICES,
widget=forms.RadioSelect(),
label="Publication type",
)
inherit = forms.MultipleChoiceField(
choices=[
("abstract_jats", "Abstract (JATS)"),
("metadata.funding_statement", "Funding statement"),
],
widget=forms.CheckboxSelectMultiple(attrs={"checked": "checked"}),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Field("anchor", css_class="mb-3"),
Field("pubtype", css_class="d-flex flex-wrap gap-3 mb-3"),
Field("title", css_class="mb-3"),
Field("abstract", css_class="mb-3"),
Field("doi_label_suffix", css_class="mb-3"),
Field(
"inherit",
css_class="d-flex flex-wrap gap-2 mb-3",
),
ButtonHolder(Submit("submit", "Submit", css_class="btn btn-primary")),
)
def save(self, *args, **kwargs):
anchor = self.cleaned_data["anchor"]
# Create a new Publication based on the anchor data
companion = Publication(
accepted_submission=anchor.accepted_submission,
in_issue=anchor.in_issue,
in_journal=anchor.in_journal,
paper_nr=anchor.paper_nr,
paper_nr_suffix=self.cleaned_data["doi_label_suffix"],
status=STATUS_DRAFT,
title=self.cleaned_data["title"],
author_list=anchor.author_list,
abstract=self.cleaned_data["abstract"],
acad_field=anchor.acad_field,
approaches=anchor.approaches,
doi_label=f"{anchor.doi_label}-{self.cleaned_data['doi_label_suffix']}",
submission_date=anchor.submission_date,
acceptance_date=anchor.acceptance_date,
publication_date=anchor.publication_date,
pubtype=self.cleaned_data["pubtype"],
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
companion.save()
# Handle ManyToMany fields
companion.specialties.add(*anchor.specialties.all())
companion.topics.add(*anchor.topics.all())
companion.grants.add(*anchor.grants.all())
companion.funders_generic.add(*anchor.funders_generic.all())
# Add authors, using anchor info
for author in anchor.authors.all():
pat = PublicationAuthorsTable.objects.create(
publication=companion,
profile=author.profile,
order=author.order,
)
pat.affiliations.add(*author.affiliations.all())
# Add References, using anchor info
for reference in anchor.references.all():
Reference.objects.create(
reference_number=reference.reference_number,
publication=companion,
authors=reference.authors,
citation=reference.citation,
identifier=reference.identifier,
link=reference.link,
)
# Add PubFracs
for pubfrac in anchor.pubfracs.all():
PubFrac.objects.create(
organization=pubfrac.organization,
publication=companion,
fraction=pubfrac.fraction,
)
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
# Add DOI of each companion to the anchor's metadata and vice versa
anchor.metadata["citation_list"].append(
{
"doi": companion.doi_string,
"key": "ref" + str(len(anchor.metadata["citation_list"]) + 1),
}
)
anchor.save()
companion.metadata.setdefault("citation_list", [])
companion.metadata["citation_list"].append(
{
"doi": anchor.doi_string,
"key": "ref" + str(len(companion.metadata["citation_list"]) + 1),
}
)
# Inherit the selected fields from the anchor
for field in self.cleaned_data["inherit"]:
if "." not in field:
setattr(companion, field, getattr(anchor, field))
# Inherit the selected fields from the anchor's field as a dict
else:
field, key = field.split(".")
field_dict = getattr(companion, field)
field_dict[key] = getattr(anchor, field).get(key)
setattr(companion, field, field_dict)
companion.save()
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
class DraftPublicationUpdateForm(forms.ModelForm):
class Meta:
model = PublicationUpdate
fields = [
"publication",
"update_type",
"text",
"number",
"publication_date",
"doi_label",
]
widgets = {
"publication": forms.HiddenInput(),
"number": forms.HiddenInput(),
"text": forms.Textarea(
attrs={"placeholder": "Describe the changes made to the publication."}
),
"publication_date": forms.DateInput(attrs={"type": "date"}),
}
def __init__(self, *args, **kwargs):
publication = kwargs.pop("publication", None)
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Field("publication", css_class="mb-3"),
Field("number", css_class="mb-3"),
Field("update_type", css_class="mb-3"),
Field("publication_date", css_class="mb-3"),
Field("text", css_class="mb-3"),
ButtonHolder(Submit("submit", "Submit", css_class="btn btn-primary")),
)
self.initial["publication"] = publication
self.initial["doi_label"] = publication.doi_label
self.initial["number"] = publication.updates.count() + 1