diff --git a/scipost_django/journals/forms.py b/scipost_django/journals/forms.py index 04760df64342ad60caf6a1792d614a75cf77d81b..4c22f8114ee1b4ee75d7a71b1f3c120b71ed6ed7 100644 --- a/scipost_django/journals/forms.py +++ b/scipost_django/journals/forms.py @@ -44,6 +44,7 @@ from .models import ( PublicationAuthorsTable, ) from .utils import JournalUtils +from .validators import doi_validator from common.utils import get_current_domain, jatsify_tags @@ -133,8 +134,57 @@ class PublicationSearchForm(forms.Form): return publications +class CitationListItemForm(forms.ModelForm): + doi = forms.CharField( + required=False, + widget=forms.TextInput(attrs={"placeholder": "DOI"}), + validators=[doi_validator], + ) + + 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()) + 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 @@ -653,9 +703,9 @@ class DraftPublicationForm(forms.ModelForm): 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["submission_date"].initial = ( + self.submission.original_submission_date + ) self.fields["acceptance_date"].initial = self.submission.acceptance_date self.fields["publication_date"].initial = timezone.now() diff --git a/scipost_django/journals/regexes.py b/scipost_django/journals/regexes.py index a8275410dfb68773343a42c25f68d0a4ffebb228..db6ed340c3f2e62b38c51e1d333601249734662c 100644 --- a/scipost_django/journals/regexes.py +++ b/scipost_django/journals/regexes.py @@ -15,3 +15,5 @@ PUBLICATION_DOI_LABEL_REGEX += ( DOI_DISPATCH_PATTERN = r"(?P<journal_tag>{})".format(JOURNAL_DOI_LABEL_REGEX) DOI_DISPATCH_PATTERN += r"(\.(?P<part_1>\w+)(\.(?P<part_2>[0-9]+)(\.(?P<part_3>[0-9]{3,}))?)?)?(-(?P<suffix>[0-9]+(\.[0-9]+)?))?" + +CROSSREF_DOI_REGEX = r"^10.\d{4,9}/[-._;()/:a-zA-Z0-9]+$" diff --git a/scipost_django/journals/templates/journals/_hx_citation_list_bibitems_form.html b/scipost_django/journals/templates/journals/_hx_citation_list_bibitems_form.html new file mode 100644 index 0000000000000000000000000000000000000000..c8ffda114df14c7b319659d3798d8f77bfead1db --- /dev/null +++ b/scipost_django/journals/templates/journals/_hx_citation_list_bibitems_form.html @@ -0,0 +1,8 @@ +{% load bootstrap %} + +<form action="{% url 'journals:create_citation_list_metadata' publication.doi_label %}" + method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" class="btn btn-primary" value="Submit" /> +</form> diff --git a/scipost_django/journals/templates/journals/_hx_citation_list_item.html b/scipost_django/journals/templates/journals/_hx_citation_list_item.html new file mode 100644 index 0000000000000000000000000000000000000000..86ef11916ffbb5cb75071bc1dc44908316006573 --- /dev/null +++ b/scipost_django/journals/templates/journals/_hx_citation_list_item.html @@ -0,0 +1,20 @@ +<tr> + <td>{{ citation.key }}</td> + <td>{{ citation.doi }}</td> + <td> + <button class="btn btn-light btn-sm" + title="Edit" + hx-target="closest tr" + hx-swap="outerHTML" + hx-get="{% url "journals:_hx_citation_list_item_form" doi_label=publication.doi_label index=index %}"> + <span>{% include "bi/pencil-square.html" %}</span> + </button> + <button class="btn btn-light btn-sm" + title="Delete" + hx-target="closest tr" + hx-swap="outerHTML" + hx-get="{% url "journals:_hx_citation_list_item_delete" doi_label=publication.doi_label index=index %}"> + <span class="text-danger">{% include "bi/trash-fill.html" %}</span> + </button> + </td> +</tr> diff --git a/scipost_django/journals/templates/journals/_hx_citation_list_item_form.html b/scipost_django/journals/templates/journals/_hx_citation_list_item_form.html new file mode 100644 index 0000000000000000000000000000000000000000..39b3366f64df1322fc4be129f1dfc1970459be7a --- /dev/null +++ b/scipost_django/journals/templates/journals/_hx_citation_list_item_form.html @@ -0,0 +1,18 @@ +<tr hx-post="{{ request.get_full_path }}" + hx-swap="outerHTML" + hx-trigger="click from:closest tr target:button[type='submit']" + hx-include="this"> + + <td> + ref{{ index|add:1 }} + </td> + {% for field in form %}<td>{{ field }} + {% for error in field.errors %} + <div class="text-danger">{{ error }}</div> + {% endfor %} + </td>{% endfor %} + + <td> + <button class="btn btn-sm btn-primary" type="submit">Save</button> + </td> +</tr> diff --git a/scipost_django/journals/templates/journals/create_citation_list_metadata.html b/scipost_django/journals/templates/journals/create_citation_list_metadata.html index 985181fc5786c1bb226957b9200861e9e6668451..7b97a52966bc8422c9c6804fd88f6b23d7b47337 100644 --- a/scipost_django/journals/templates/journals/create_citation_list_metadata.html +++ b/scipost_django/journals/templates/journals/create_citation_list_metadata.html @@ -1,6 +1,8 @@ {% extends 'scipost/base.html' %} -{% block pagetitle %}: Create citation list metadata{% endblock pagetitle %} +{% block pagetitle %} + : Create citation list metadata +{% endblock pagetitle %} {% block breadcrumb %} <div class="breadcrumb-container"> @@ -14,37 +16,39 @@ </div> {% endblock %} -{% load bootstrap %} - {% block content %} - <div class="row"> - <div class="col-12"> - <h1 class="highlight">Create citation list metadata page for <a href="{{ publication.get_absolute_url }}">{{ publication.doi_label }}</a></h1> - <p> - The following field is prefilled with the current citation list of the Publication object. Once you submit, it will overwrite the current citation list, shown below. - </p> - <br> - - <form action="{% url 'journals:create_citation_list_metadata' publication.doi_label %}" method="post"> - {% csrf_token %} - {{ form|bootstrap }} - <input type="submit" class="btn btn-primary" value="Submit"> - <a href="{% url 'journals:manage_metadata' %}" class="ms-3 btn btn-link">Back to Admin</a> - </form> - - <hr class="divider"> - - <h3>Current citation list metadata:</h3> - <br> - <table class="table"> - {% for citation in publication.metadata.citation_list %} - <tr> - <td>{{ citation.key }}</td><td>{{ citation.doi }}</td> - </tr> - {% endfor %} - </table> - </div> - </div> + <h1 class="highlight d-flex justify-content-between align-items-center"> + <span>Create citation list metadata page for <a href="{{ publication.get_absolute_url }}">{{ publication.doi_label }}</a></span> + <a class="fs-6 btn-link" href="{% url 'journals:manage_metadata' %}">Back to Admin</a> + </h1> + + <div id="paste-bibtex-form" + hx-get="{% url "journals:_hx_citation_list_bibitems_form" doi_label=publication.doi_label %}" + hx-trigger="load once"></div> + + <h3 class="mt-4">Current citation list metadata:</h3> + + <table class="table table-sm align-middle"> + + {% for citation in publication.metadata.citation_list %} + {% with index=forloop.counter|add:-1 %} + {% include "journals/_hx_citation_list_item.html" %} + {% endwith %} + {% endfor %} + + <tr> + <td colspan="20" class="p-1 bg-opacity-10 bg-info text-center"> + <button class="btn btn-link" + hx-target="closest tr" + hx-swap="beforebegin" + hx-get="{% url "journals:_hx_citation_list_item_form" doi_label=publication.doi_label %}"> + <span class="me-1">{% include "bi/plus-square-fill.html" %}</span> + Add Item + </button> + </td> + </tr> + + </table> {% endblock %} diff --git a/scipost_django/journals/urls/general.py b/scipost_django/journals/urls/general.py index ffdb76f006533f062d57dcc0074311966391b874..d80b7979ff2f1dc01920c7fe8f2b32656e49e4dd 100644 --- a/scipost_django/journals/urls/general.py +++ b/scipost_django/journals/urls/general.py @@ -128,6 +128,26 @@ urlpatterns = [ journals_views.CitationUpdateView.as_view(), name="create_citation_list_metadata", ), + path( + "admin/<publication_doi_label:doi_label>/_hx_citation_list_bibitems_form", + journals_views._hx_citation_list_bibitems_form, + name="_hx_citation_list_bibitems_form", + ), + path( + "admin/<publication_doi_label:doi_label>/_hx_citation_list_item_form", + journals_views._hx_citation_list_item_form, + name="_hx_citation_list_item_form", + ), + path( + "admin/<publication_doi_label:doi_label>/_hx_citation_list_item_form/<int:index>", + journals_views._hx_citation_list_item_form, + name="_hx_citation_list_item_form", + ), + path( + "admin/<publication_doi_label:doi_label>/_hx_citation_list_item_delete/<int:index>", + journals_views._hx_citation_list_item_delete, + name="_hx_citation_list_item_delete", + ), path( "admin/<publication_doi_label:doi_label>/abstract_jats", journals_views.AbstractJATSUpdateView.as_view(), diff --git a/scipost_django/journals/validators.py b/scipost_django/journals/validators.py index 5e0ecabed4095077ea33da8d12abcc486e88bb4a..45305d148ed03162e3504c30e3f296cb3d5cec97 100644 --- a/scipost_django/journals/validators.py +++ b/scipost_django/journals/validators.py @@ -9,6 +9,7 @@ from .regexes import ( VOLUME_DOI_LABEL_REGEX, ISSUE_DOI_LABEL_REGEX, PUBLICATION_DOI_LABEL_REGEX, + CROSSREF_DOI_REGEX, ) doi_journal_validator = RegexValidator( @@ -27,3 +28,7 @@ doi_publication_validator = RegexValidator( r"^{regex}$".format(regex=PUBLICATION_DOI_LABEL_REGEX), "Only expressions with regex %s are allowed." % PUBLICATION_DOI_LABEL_REGEX, ) +doi_validator = RegexValidator( + r"^{regex}$".format(regex=CROSSREF_DOI_REGEX), + "Only expressions with regex %s are allowed." % CROSSREF_DOI_REGEX, +) diff --git a/scipost_django/journals/views.py b/scipost_django/journals/views.py index f9f4580180390d98d950a87be1bb46c554ac5295..dae0d5ab58a31e314892f588d978a937b4b879b9 100644 --- a/scipost_django/journals/views.py +++ b/scipost_django/journals/views.py @@ -14,6 +14,8 @@ import requests import matplotlib +from scipost.permissions import HTMXPermissionsDenied, HTMXResponse + matplotlib.use("Agg") import matplotlib.pyplot as plt import io, base64 @@ -61,6 +63,7 @@ from .models import ( ) from .forms import ( AbstractJATSForm, + CitationListItemForm, DraftPublicationUpdateForm, FundingInfoForm, HTMXInlinePublicationResourceForm, @@ -938,6 +941,118 @@ class CitationUpdateView( ) +def _hx_citation_list_item_delete(request, doi_label, index: int): + """ + Deletes a citation entry at the given index. + """ + if not request.user.has_perm("scipost.can_draft_publication"): + return HTMXPermissionsDenied( + "You do not have permission to delete a citation in this Publication" + ) + + publication = get_object_or_404(Publication, doi_label=doi_label) + if ( + not request.user.has_perm("scipost.can_publish_accepted_submission") + and not publication.is_draft + ): + return HTMXPermissionsDenied( + "You do not have permission to delete a citation in this non-draft Publication" + ) + + publication.metadata["citation_list"].pop(index) + publication.save() + + return HttpResponse("") + + +def _hx_citation_list_item_form(request, doi_label, index: int | None = None): + """ + Renders a form to create or edit a citation entry at the given index. + """ + if not request.user.has_perm("scipost.can_draft_publication"): + return HTMXPermissionsDenied( + "You do not have permission to edit this Publication" + ) + + publication = get_object_or_404(Publication, doi_label=doi_label) + if ( + not request.user.has_perm("scipost.can_publish_accepted_submission") + and not publication.is_draft + ): + return HTMXPermissionsDenied( + "You do not have permission to edit this non-draft Publication" + ) + + if index is not None: + if index >= len(publication.metadata["citation_list"]): + return HTMXResponse("Index out of range", tag="danger") + else: + form = CitationListItemForm( + request.POST or None, + instance=publication, + index=index, + ) + else: + index = len(publication.metadata["citation_list"]) + form = CitationListItemForm( + request.POST or None, instance=publication, index=index, + ) + + if request.method == "POST": + if form.is_valid(): + form.save() + doi = form.cleaned_data.get("doi") + return TemplateResponse( + request, + "journals/_hx_citation_list_item.html", + { + "citation": {"key": "ref" + str(index + 1), "doi": doi}, + "publication": publication, + "index": index, + }, + ) + + return TemplateResponse( + request, + "journals/_hx_citation_list_item_form.html", + { + "form": form, + "publication": publication, + "index": index, + }, + ) + + +def _hx_citation_list_bibitems_form(request, doi_label): + if not request.user.has_perm("scipost.can_draft_publication"): + return HTMXPermissionsDenied( + "You do not have permission to edit this Publication" + ) + + publication = get_object_or_404(Publication, doi_label=doi_label) + if ( + not request.user.has_perm("scipost.can_publish_accepted_submission") + and not publication.is_draft + ): + return HTMXPermissionsDenied( + "You do not have permission to edit this non-draft Publication" + ) + + form = CitationListBibitemsForm(request.POST or None, instance=publication) + if form.is_valid(): + form.save() + messages.success(request, "Citation list updated") + return HttpResponse("") + + context = { + "form": form, + "publication": publication, + } + return TemplateResponse( + request, "journals/_hx_citation_list_bibitems_form.html", context + ) + + class AbstractJATSUpdateView( PublicationMixin, ProdSupervisorPublicationPermissionMixin, UpdateView ): @@ -1406,9 +1521,7 @@ def adjust_pubfracs(request, doi_label): pubfrac, created = PubFrac.objects.get_or_create( publication=publication, organization=org ) - formset = PubFracsFormSet( - request.POST or None, queryset=publication.pubfracs.all() - ) + formset = PubFracsFormSet(request.POST or None, queryset=publication.pubfracs.all()) if formset.is_valid(): formset.save() messages.success(request, "Funding fractions successfully allocated.")