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 .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 = [
'discipline', 'subject_area', '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):
"""Search for Commentary specified by user"""
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 an SciPost publication comment on their Publication
using the Commentary functionalities. It will create an 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,
'discipline': self.publication.discipline,
'subject_area': self.publication.subject_area,
'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())
commentary.authors_claims.add(*self.publication.authors_claims.all())
commentary.authors_false_claims.add(*self.publication.authors_false_claims.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