Source code for commentaries.forms

__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"


from django import forms
from django.utils.safestring import mark_safe
from django.template.loader import get_template

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Div
from crispy_bootstrap5.bootstrap5 import FloatingField

from .models import Commentary
from .constants import COMMENTARY_PUBLISHED, COMMENTARY_PREPRINT

from comments.forms import CommentForm
from scipost.services import DOICaller, ArxivCaller

import strings


[docs]class DOIToQueryForm(forms.Form): VALID_DOI_REGEXP = r"(?i)10.\d{4,9}/[-._;()/:A-Z0-9]+$" doi = forms.RegexField( regex=VALID_DOI_REGEXP, strip=True, help_text=strings.doi_query_help_text, error_messages={"invalid": strings.doi_query_invalid}, widget=forms.TextInput( {"label": "DOI", "placeholder": strings.doi_query_placeholder} ), )
[docs] def clean_doi(self): input_doi = self.cleaned_data["doi"] commentary = Commentary.objects.filter(pub_DOI=input_doi) if commentary.exists(): error_message = get_template( "commentaries/_doi_query_commentary_exists.html" ).render({"arxiv_or_DOI_string": commentary[0].arxiv_or_DOI_string}) raise forms.ValidationError(mark_safe(error_message)) caller = DOICaller(input_doi) if caller.is_valid: self.crossref_data = DOICaller(input_doi).data else: error_message = "Could not find a resource for that DOI." raise forms.ValidationError(error_message) return input_doi
[docs] def request_published_article_form_prefill_data(self): additional_form_data = {"pub_DOI": self.cleaned_data["doi"]} return {**self.crossref_data, **additional_form_data}
[docs]class ArxivQueryForm(forms.Form): IDENTIFIER_PATTERN_NEW = r"^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$" IDENTIFIER_PATTERN_OLD = r"^[-.a-z]+/[0-9]{7,}v[0-9]{1,2}$" VALID_ARXIV_IDENTIFIER_REGEX = "(?:{})|(?:{})".format( IDENTIFIER_PATTERN_NEW, IDENTIFIER_PATTERN_OLD ) identifier = forms.RegexField( regex=VALID_ARXIV_IDENTIFIER_REGEX, strip=True, help_text=strings.arxiv_query_help_text, error_messages={"invalid": strings.arxiv_query_invalid}, widget=forms.TextInput({"placeholder": strings.arxiv_query_placeholder}), )
[docs] def clean_identifier(self): identifier = self.cleaned_data["identifier"] commentary = Commentary.objects.filter(arxiv_identifier=identifier) if commentary.exists(): error_message = get_template( "commentaries/_doi_query_commentary_exists.html" ).render({"arxiv_or_DOI_string": commentary[0].arxiv_or_DOI_string}) raise forms.ValidationError(mark_safe(error_message)) caller = ArxivCaller(identifier) if caller.is_valid: self.arxiv_data = ArxivCaller(identifier).data else: error_message = "Could not find a resource for that arXiv identifier." raise forms.ValidationError(error_message) return identifier
[docs] def request_arxiv_preprint_form_prefill_data(self): additional_form_data = {"arxiv_identifier": self.cleaned_data["identifier"]} return {**self.arxiv_data, **additional_form_data}
[docs]class RequestCommentaryForm(forms.ModelForm):
[docs] class Meta: model = Commentary fields = [ "acad_field", "specialties", "approaches", "title", "author_list", "pub_date", "pub_abstract", ] placeholders = {"pub_date": "Format: YYYY-MM-DD"}
def __init__(self, *args, **kwargs): self.requested_by = kwargs.pop("requested_by", None) super().__init__(*args, **kwargs)
[docs] def save(self, *args, **kwargs): self.instance.parse_links_into_urls(commit=False) if self.requested_by: self.instance.requested_by = self.requested_by return super().save(*args, **kwargs)
[docs]class RequestArxivPreprintForm(RequestCommentaryForm):
[docs] class Meta(RequestCommentaryForm.Meta): model = Commentary fields = RequestCommentaryForm.Meta.fields + ["arxiv_identifier"]
def __init__(self, *args, **kwargs): super(RequestArxivPreprintForm, self).__init__(*args, **kwargs) # We want arxiv_identifier to be a required field. # Since it can be blank on the model, we have to override this property here. self.fields["arxiv_identifier"].required = True # TODO: add regex here?
[docs] def clean_arxiv_identifier(self): arxiv_identifier = self.cleaned_data["arxiv_identifier"] commentary = Commentary.objects.filter(arxiv_identifier=arxiv_identifier) if commentary.exists(): error_message = get_template( "commentaries/_doi_query_commentary_exists.html" ).render({"arxiv_or_DOI_string": commentary[0].arxiv_or_DOI_string}) raise forms.ValidationError(mark_safe(error_message)) return arxiv_identifier
[docs] def save(self, *args, **kwargs): self.instance.type = COMMENTARY_PREPRINT return super().save(*args, **kwargs)
[docs]class RequestPublishedArticleForm(RequestCommentaryForm):
[docs] class Meta(RequestCommentaryForm.Meta): fields = RequestCommentaryForm.Meta.fields + [ "journal", "volume", "pages", "pub_DOI", ] placeholders = { **RequestCommentaryForm.Meta.placeholders, **{"pub_DOI": "ex.: 10.21468/00.000.000000"}, }
def __init__(self, *args, **kwargs): super(RequestPublishedArticleForm, self).__init__(*args, **kwargs) # We want pub_DOI to be a required field. # Since it can be blank on the model, we have to override this property here. self.fields["pub_DOI"].required = True
[docs] def clean_pub_DOI(self): input_doi = self.cleaned_data["pub_DOI"] commentary = Commentary.objects.filter(pub_DOI=input_doi) if commentary.exists(): error_message = get_template( "commentaries/_doi_query_commentary_exists.html" ).render({"arxiv_or_DOI_string": commentary[0].arxiv_or_DOI_string}) raise forms.ValidationError(mark_safe(error_message)) return input_doi
[docs] def save(self, *args, **kwargs): self.instance.type = COMMENTARY_PUBLISHED return super().save(*args, **kwargs)
[docs]class VetCommentaryForm(forms.Form): """Process an unvetted Commentary request. This form will provide fields to let the user process a Commentary that is unvetted. On success, the Commentary is either accepted or deleted from the database. Keyword arguments: * commentary_id -- the Commentary.id to process (required) * user -- User instance of the vetting user (required) """ ACTION_MODIFY = 0 ACTION_ACCEPT = 1 ACTION_REFUSE = 2 COMMENTARY_ACTION_CHOICES = ( (ACTION_MODIFY, "modify"), (ACTION_ACCEPT, "accept"), (ACTION_REFUSE, "refuse (give reason below)"), ) REFUSAL_EMPTY = 0 REFUSAL_PAPER_EXISTS = -1 REFUSAL_UNTRACEBLE = -2 REFUSAL_ARXIV_EXISTS = -3 COMMENTARY_REFUSAL_CHOICES = ( (REFUSAL_EMPTY, "-"), (REFUSAL_PAPER_EXISTS, "a commentary on this paper already exists"), (REFUSAL_UNTRACEBLE, "this paper cannot be traced"), ( REFUSAL_ARXIV_EXISTS, "there exists a more revent version of this arXiv preprint", ), ) COMMENTARY_REFUSAL_DICT = dict(COMMENTARY_REFUSAL_CHOICES) action_option = forms.ChoiceField( widget=forms.RadioSelect, choices=COMMENTARY_ACTION_CHOICES, required=True, label="Action", ) refusal_reason = forms.ChoiceField( choices=COMMENTARY_REFUSAL_CHOICES, required=False ) email_response_field = forms.CharField( widget=forms.Textarea(attrs={"rows": 5, "cols": 40}), label="Justification (optional)", required=False, ) def __init__(self, *args, **kwargs): """Pop and save keyword arguments if set, return form instance""" self.commentary_id = kwargs.pop("commentary_id", None) self.user = kwargs.pop("user", None) self.is_cleaned = False return super(VetCommentaryForm, self).__init__(*args, **kwargs)
[docs] def clean(self, *args, **kwargs): """Check valid form and keyword arguments given""" cleaned_data = super(VetCommentaryForm, self).clean(*args, **kwargs) # Check valid `commentary_id` if not self.commentary_id: self.add_error(None, "No `commentary_id` provided") return cleaned_data else: self.commentary = Commentary.objects.select_related( "requested_by__user" ).get(pk=self.commentary_id) # Check valid `user` if not self.user: self.add_error(None, "No `user` provided") return cleaned_data self.is_cleaned = True return cleaned_data
def _form_is_cleaned(self): """Raise ValueError if form isn't validated""" if not self.is_cleaned: raise ValueError( ( "VetCommentaryForm could not be processed " "because the data didn't validate" ) )
[docs] def clean_refusal_reason(self): """`refusal_reason` field is required if action==refuse.""" if self.commentary_is_refused(): if int(self.cleaned_data["refusal_reason"]) == self.REFUSAL_EMPTY: self.add_error( "refusal_reason", "Please, choose a reason for rejection." ) return self.cleaned_data["refusal_reason"]
[docs] def get_commentary(self): """Return Commentary if available""" self._form_is_cleaned() return self.commentary
[docs] def get_refusal_reason(self): """Return refusal reason""" if self.commentary_is_refused(): return self.COMMENTARY_REFUSAL_DICT[ int(self.cleaned_data["refusal_reason"]) ]
[docs] def commentary_is_accepted(self): return int(self.cleaned_data["action_option"]) == self.ACTION_ACCEPT
[docs] def commentary_is_modified(self): return int(self.cleaned_data["action_option"]) == self.ACTION_MODIFY
[docs] def commentary_is_refused(self): return int(self.cleaned_data["action_option"]) == self.ACTION_REFUSE
[docs] def process_commentary(self): """Vet the commentary or delete it from the database""" # Modified actions are not doing anything. Users are redirected to an edit page instead. if self.commentary_is_accepted(): self.commentary.vetted = True self.commentary.vetted_by = self.user.contributor self.commentary.save() return self.commentary elif self.commentary_is_refused(): self.commentary.delete() return None
[docs]class CommentarySearchForm(forms.Form): author = forms.CharField( max_length=100, required=False, label="On publication with author(s)" ) title = forms.CharField( max_length=100, required=False, label="On publication with title" ) abstract = forms.CharField( max_length=1000, required=False, label="On publication with abstract" ) 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) self.helper = FormHelper() self.helper.layout = Layout( Div( FloatingField("author"), FloatingField("title"), FloatingField("abstract"), ), )
[docs] def search_results(self): """Return all Commentary objects according to search""" commentaries = Commentary.objects.vetted() if self.acad_field_slug and self.acad_field_slug != "all": commentaries = commentaries.filter(acad_field__slug=self.acad_field_slug) if self.specialty_slug and self.specialty_slug != "all": commentaries = commentaries.filter( specialties__slug=self.specialty_slug ) if hasattr(self, "cleaned_data"): if "title" in self.cleaned_data: commentaries = commentaries.filter( title__icontains=self.cleaned_data["title"], ) if "abstract" in self.cleaned_data: commentaries = commentaries.filter( pub_abstract__icontains=self.cleaned_data["abstract"], ) if "author" in self.cleaned_data: commentaries = commentaries.filter( author_list__icontains=self.cleaned_data["author"], ) return commentaries.order_by("-pub_date")
[docs]class CommentaryListSearchForm(forms.Form): """Search for Commentary specified by user (for old CommentaryListView)""" author = forms.CharField(max_length=100, required=False, label="Author(s)") title = forms.CharField(max_length=100, required=False, label="Title") abstract = forms.CharField(max_length=1000, required=False, label="Abstract")
[docs] def search_results(self): """Return all Commentary objects according to search""" return Commentary.objects.vetted( title__icontains=self.cleaned_data["title"], pub_abstract__icontains=self.cleaned_data["abstract"], author_list__icontains=self.cleaned_data["author"], ).order_by("-pub_date")
[docs]class CommentSciPostPublication(CommentForm): """ This Form will let authors of a SciPost publication comment on their Publication using the Commentary functionalities. It will create a Commentary page if it does not exist yet. It inherits from ModelForm: CommentForm and thus will, by default, return a Comment! """ def __init__(self, *args, **kwargs): self.publication = kwargs.pop("publication") self.current_user = kwargs.pop("current_user") super().__init__(*args, **kwargs)
[docs] def save(self, commit=True): """ Create (vetted) Commentary page if not exist and do save actions as per original CommentForm. """ if not commit: raise AssertionError( "CommentSciPostPublication can only be used with commit=True" ) try: commentary = self.publication.commentary except Commentary.DoesNotExist: submission = self.publication.accepted_submission commentary = Commentary( **{ # 'vetted_by': None, "requested_by": self.current_user.contributor, "vetted": True, "type": COMMENTARY_PUBLISHED, "acad_field": self.publication.acad_field, "specialties": self.publication.specialties, "approaches": self.publication.approaches, "title": self.publication.title, "arxiv_identifier": submission.preprint.identifier_w_vn_nr, "arxiv_link": submission.preprint.url, "pub_DOI": self.publication.doi_string, "metadata": self.publication.metadata, "scipost_publication": self.publication, "author_list": self.publication.author_list, "journal": self.publication.in_issue.in_volume.in_journal.name, "pages": self.publication.in_issue.number, "volume": self.publication.in_issue.in_volume.number, "pub_date": self.publication.publication_date, "pub_abstract": self.publication.abstract, } ) commentary.parse_links_into_urls(commit=False) commentary.save() commentary.authors.add(*self.publication.authors.all()) # Original saving steps comment = super().save(commit=False) comment.author = self.current_user.contributor comment.is_author_reply = True comment.content_object = commentary comment.save() comment.grant_permissions() return comment