diff --git a/submissions/constants.py b/submissions/constants.py index 4e8da2fefd15599063680e7711ffe62134cd4d2b..943de541f9e517ddce9b8ceb90bbaebda95e9cf5 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -1,3 +1,6 @@ +from journals.constants import SCIPOST_JOURNAL_PHYSICS + + STATUS_UNASSIGNED = 'unassigned' STATUS_RESUBMISSION_INCOMING = 'resubmitted_incoming' STATUS_REVISION_REQUESTED = 'revision_requested' @@ -216,3 +219,19 @@ EVENT_TYPES = ( (EVENT_FOR_EIC, 'Comment for Editor-in-charge'), (EVENT_FOR_AUTHOR, 'Comment for author'), ) + +# Use `.format()` https://docs.python.org/3.5/library/string.html#format-string-syntax +# In your regex multiple brackets may occur; +# Please make sure to double them in that case as per instructions in the reference! +SUBMISSIONS_NO_VN_REGEX = '(?P<arxiv_identifier_wo_vn_nr>[0-9]{4,}.[0-9]{4,})' +SUBMISSIONS_COMPLETE_REGEX = '(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{4,}v[0-9]{1,2})' + + +# `EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS` tracks the regex rules for the manuscripts +# submitted per journal. +# +# CAUTION: *triple* check whether the `default` regex also meets any other explicit journal regex! +EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS = { + SCIPOST_JOURNAL_PHYSICS: '(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})', + 'default': SUBMISSIONS_COMPLETE_REGEX +} diff --git a/submissions/forms.py b/submissions/forms.py deleted file mode 100644 index fd5fff6e7be2f8988fa5ddd6cd4755946be8363e..0000000000000000000000000000000000000000 --- a/submissions/forms.py +++ /dev/null @@ -1,742 +0,0 @@ -from django import forms -from django.conf import settings -from django.contrib.auth.models import Group -from django.db import transaction -from django.utils import timezone - -from guardian.shortcuts import assign_perm - -from .constants import ASSIGNMENT_BOOL, ASSIGNMENT_REFUSAL_REASONS, STATUS_RESUBMITTED,\ - REPORT_ACTION_CHOICES, REPORT_REFUSAL_CHOICES, STATUS_REVISION_REQUESTED,\ - STATUS_REJECTED, STATUS_REJECTED_VISIBLE, STATUS_RESUBMISSION_INCOMING,\ - STATUS_DRAFT, STATUS_UNVETTED, REPORT_ACTION_ACCEPT, REPORT_ACTION_REFUSE,\ - STATUS_VETTED -from . import exceptions, helpers -from .models import Submission, RefereeInvitation, Report, EICRecommendation, EditorialAssignment,\ - iThenticateReport - -from scipost.constants import SCIPOST_SUBJECT_AREAS -from scipost.services import ArxivCaller -from scipost.models import Contributor -import strings - -import iThenticate - - -class SubmissionSearchForm(forms.Form): - author = forms.CharField(max_length=100, required=False, label="Author(s)") - title = forms.CharField(max_length=100, required=False) - abstract = forms.CharField(max_length=1000, required=False) - subject_area = forms.CharField(max_length=10, required=False, widget=forms.Select( - choices=((None, 'Show all'),) + SCIPOST_SUBJECT_AREAS[0][1])) - - def search_results(self): - """Return all Submission objects according to search""" - return Submission.objects.public_newest().filter( - title__icontains=self.cleaned_data.get('title', ''), - author_list__icontains=self.cleaned_data.get('author', ''), - abstract__icontains=self.cleaned_data.get('abstract', ''), - subject_area__icontains=self.cleaned_data.get('subject_area', '') - ) - - -############################### -# Submission and resubmission # -############################### - -class SubmissionChecks: - """ - Use this class as a blueprint containing checks which should be run - in multiple forms. - """ - is_resubmission = False - last_submission = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Prefill `is_resubmission` property if data is coming from initial data - if kwargs.get('initial', None): - if kwargs['initial'].get('is_resubmission', None): - self.is_resubmission = kwargs['initial']['is_resubmission'] in ('True', True) - - # `is_resubmission` property if data is coming from (POST) request - if kwargs.get('data', None): - if kwargs['data'].get('is_resubmission', None): - self.is_resubmission = kwargs['data']['is_resubmission'] in ('True', True) - - def _submission_already_exists(self, identifier): - if Submission.objects.filter(arxiv_identifier_w_vn_nr=identifier).exists(): - error_message = 'This preprint version has already been submitted to SciPost.' - raise forms.ValidationError(error_message, code='duplicate') - - def _call_arxiv(self, identifier): - caller = ArxivCaller(identifier) - if caller.is_valid: - self.arxiv_data = ArxivCaller(identifier).data - self.metadata = ArxivCaller(identifier).metadata - else: - error_message = 'A preprint associated to this identifier does not exist.' - raise forms.ValidationError(error_message) - - def _submission_is_already_published(self, identifier): - published_id = None - if 'arxiv_doi' in self.arxiv_data: - published_id = self.arxiv_data['arxiv_doi'] - elif 'arxiv_journal_ref' in self.arxiv_data: - published_id = self.arxiv_data['arxiv_journal_ref'] - - if published_id: - error_message = ('This paper has been published under DOI %(published_id)s' - '. Please comment on the published version.'), - raise forms.ValidationError(error_message, code='published', - params={'published_id': published_id}) - - def _submission_previous_version_is_valid_for_submission(self, identifier): - '''Check if previous submitted versions have the appropriate status.''' - identifiers = self.identifier_into_parts(identifier) - submission = (Submission.objects - .filter(arxiv_identifier_wo_vn_nr=identifiers['arxiv_identifier_wo_vn_nr']) - .order_by('arxiv_vn_nr').last()) - - # If submissions are found; check their statuses - if submission: - self.last_submission = submission - if submission.status == STATUS_REVISION_REQUESTED: - self.is_resubmission = True - elif submission.status in [STATUS_REJECTED, STATUS_REJECTED_VISIBLE]: - error_message = ('This arXiv preprint has previously undergone refereeing ' - 'and has been rejected. Resubmission is only possible ' - 'if the manuscript has been substantially reworked into ' - 'a new arXiv submission with distinct identifier.') - raise forms.ValidationError(error_message) - else: - error_message = ('There exists a preprint with this arXiv identifier ' - 'but an earlier version number, which is still undergoing ' - 'peer refereeing. ' - 'A resubmission can only be performed after request ' - 'from the Editor-in-charge. Please wait until the ' - 'closing of the previous refereeing round and ' - 'formulation of the Editorial Recommendation ' - 'before proceeding with a resubmission.') - raise forms.ValidationError(error_message) - - def submission_is_resubmission(self): - return self.is_resubmission - - def identifier_into_parts(self, identifier): - data = { - 'arxiv_identifier_w_vn_nr': identifier, - 'arxiv_identifier_wo_vn_nr': identifier.rpartition('v')[0], - 'arxiv_vn_nr': int(identifier.rpartition('v')[2]) - } - return data - - def do_pre_checks(self, identifier): - self._submission_already_exists(identifier) - self._call_arxiv(identifier) - self._submission_is_already_published(identifier) - self._submission_previous_version_is_valid_for_submission(identifier) - - -class SubmissionIdentifierForm(SubmissionChecks, forms.Form): - IDENTIFIER_PATTERN_NEW = r'^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$' - IDENTIFIER_PLACEHOLDER = 'new style (with version nr) ####.####(#)v#(#)' - - identifier = forms.RegexField(regex=IDENTIFIER_PATTERN_NEW, strip=True, - # help_text=strings.arxiv_query_help_text, - error_messages={'invalid': strings.arxiv_query_invalid}, - widget=forms.TextInput({'placeholder': IDENTIFIER_PLACEHOLDER})) - - def clean_identifier(self): - identifier = self.cleaned_data['identifier'] - self.do_pre_checks(identifier) - return identifier - - def _gather_data_from_last_submission(self): - '''Return dictionary with data coming from previous submission version.''' - if self.submission_is_resubmission(): - data = { - 'is_resubmission': True, - 'discipline': self.last_submission.discipline, - 'domain': self.last_submission.domain, - 'referees_flagged': self.last_submission.referees_flagged, - 'referees_suggested': self.last_submission.referees_suggested, - 'secondary_areas': self.last_submission.secondary_areas, - 'subject_area': self.last_submission.subject_area, - 'submitted_to_journal': self.last_submission.submitted_to_journal, - 'submission_type': self.last_submission.submission_type, - } - return data or {} - - def request_arxiv_preprint_form_prefill_data(self): - '''Return dictionary to prefill `RequestSubmissionForm`.''' - form_data = self.arxiv_data - form_data.update(self.identifier_into_parts(self.cleaned_data['identifier'])) - if self.submission_is_resubmission(): - form_data.update(self._gather_data_from_last_submission()) - return form_data - - -class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): - class Meta: - model = Submission - fields = [ - 'is_resubmission', - 'discipline', - 'submitted_to_journal', - 'submission_type', - 'domain', - 'subject_area', - 'secondary_areas', - 'title', - 'author_list', - 'abstract', - 'arxiv_identifier_w_vn_nr', - 'arxiv_link', - 'author_comments', - 'list_of_changes', - 'remarks_for_editors', - 'referees_suggested', - 'referees_flagged' - ] - widgets = { - 'is_resubmission': forms.HiddenInput(), - 'arxiv_identifier_w_vn_nr': forms.HiddenInput(), - 'secondary_areas': forms.SelectMultiple(choices=SCIPOST_SUBJECT_AREAS) - } - - def __init__(self, *args, **kwargs): - self.requested_by = kwargs.pop('requested_by', None) - super().__init__(*args, **kwargs) - - if not self.submission_is_resubmission(): - # These fields are only available for resubmissions - del self.fields['author_comments'] - del self.fields['list_of_changes'] - else: - self.fields['author_comments'].widget.attrs.update({ - 'placeholder': 'Your resubmission letter (will be viewable online)', }) - self.fields['list_of_changes'].widget.attrs.update({ - 'placeholder': 'Give a point-by-point list of changes (will be viewable online)'}) - - # Update placeholder for the other fields - self.fields['arxiv_link'].widget.attrs.update({ - 'placeholder': 'ex.: arxiv.org/abs/1234.56789v1'}) - self.fields['abstract'].widget.attrs.update({'cols': 100}) - self.fields['remarks_for_editors'].widget.attrs.update({ - 'placeholder': 'Any private remarks (for the editors only)', }) - self.fields['referees_suggested'].widget.attrs.update({ - 'placeholder': 'Optional: names of suggested referees', - 'rows': 3}) - self.fields['referees_flagged'].widget.attrs.update({ - 'placeholder': ('Optional: names of referees whose reports should' - ' be treated with caution (+ short reason)'), - 'rows': 3}) - - def clean(self, *args, **kwargs): - """ - Do all prechecks which are also done in the prefiller. - """ - cleaned_data = super().clean(*args, **kwargs) - self.do_pre_checks(cleaned_data['arxiv_identifier_w_vn_nr']) - return cleaned_data - - def clean_author_list(self): - """ - Important check! - - The submitting user must be an author of the submission. - Also possibly may be extended to check permissions and give ultimate submission - power to certain user groups. - """ - author_list = self.cleaned_data['author_list'] - if not self.requested_by.last_name.lower() in author_list.lower(): - error_message = ('Your name does not match that of any of the authors. ' - 'You are not authorized to submit this preprint.') - raise forms.ValidationError(error_message, code='not_an_author') - return author_list - - @transaction.atomic - def copy_and_save_data_from_resubmission(self, submission): - """ - Fill given Submission with data coming from last_submission in the SubmissionChecks - blueprint. - """ - if not self.last_submission: - raise Submission.DoesNotExist - - # Open for comment and reporting - submission.open_for_reporting = True - submission.open_for_commenting = True - - # Close last submission - self.last_submission.is_current = False - self.last_submission.open_for_reporting = False - self.last_submission.status = STATUS_RESUBMITTED - self.last_submission.save() - - # Editor-in-charge - submission.editor_in_charge = self.last_submission.editor_in_charge - submission.status = STATUS_RESUBMISSION_INCOMING - - # Author claim fields - submission.authors.add(*self.last_submission.authors.all()) - submission.authors_claims.add(*self.last_submission.authors_claims.all()) - submission.authors_false_claims.add(*self.last_submission.authors_false_claims.all()) - submission.save() - return submission - - @transaction.atomic - def reassign_eic_and_admins(self, submission): - # Assign permissions - assign_perm('can_take_editorial_actions', submission.editor_in_charge.user, submission) - ed_admins = Group.objects.get(name='Editorial Administrators') - assign_perm('can_take_editorial_actions', ed_admins, submission) - - # Assign editor - assignment = EditorialAssignment( - submission=submission, - to=submission.editor_in_charge, - accepted=True - ) - assignment.save() - submission.save() - return submission - - @transaction.atomic - def save(self): - """ - Prefill instance before save. - - Because of the ManyToManyField on `authors`, commit=False for this form - is disabled. Saving the form without the database call may loose `authors` - data without notice. - """ - submission = super().save(commit=False) - submission.submitted_by = self.requested_by.contributor - - # Save metadata directly from ArXiv call without possible user interception - submission.metadata = self.metadata - - # Update identifiers - identifiers = self.identifier_into_parts(submission.arxiv_identifier_w_vn_nr) - submission.arxiv_identifier_wo_vn_nr = identifiers['arxiv_identifier_wo_vn_nr'] - submission.arxiv_vn_nr = identifiers['arxiv_vn_nr'] - - # Save - submission.save() - if self.submission_is_resubmission(): - submission = self.copy_and_save_data_from_resubmission(submission) - submission = self.reassign_eic_and_admins(submission) - submission.authors.add(self.requested_by.contributor) - return submission - - -class SubmissionReportsForm(forms.ModelForm): - class Meta: - model = Submission - fields = ['pdf_refereeing_pack'] - - -###################### -# Editorial workflow # -###################### - -class AssignSubmissionForm(forms.Form): - - def __init__(self, *args, **kwargs): - discipline = kwargs.pop('discipline') - super(AssignSubmissionForm, self).__init__(*args, **kwargs) - self.fields['editor_in_charge'] = forms.ModelChoiceField( - queryset=Contributor.objects.filter(user__groups__name='Editorial College', - user__contributor__discipline=discipline, - ).order_by('user__last_name'), - required=True, label='Select an Editor-in-charge') - - -class ConsiderAssignmentForm(forms.Form): - accept = forms.ChoiceField(widget=forms.RadioSelect, choices=ASSIGNMENT_BOOL, - label="Are you willing to take charge of this Submission?") - refusal_reason = forms.ChoiceField(choices=ASSIGNMENT_REFUSAL_REASONS, required=False) - - -class RefereeSelectForm(forms.Form): - last_name = forms.CharField() - - def __init__(self, *args, **kwargs): - super(RefereeSelectForm, self).__init__(*args, **kwargs) - self.fields['last_name'].widget.attrs.update( - {'size': 20, 'placeholder': 'Search in contributors database'}) - - -class RefereeRecruitmentForm(forms.ModelForm): - class Meta: - model = RefereeInvitation - fields = ['title', 'first_name', 'last_name', 'email_address'] - - def __init__(self, *args, **kwargs): - super(RefereeRecruitmentForm, self).__init__(*args, **kwargs) - self.fields['first_name'].widget.attrs.update({'size': 20}) - self.fields['last_name'].widget.attrs.update({'size': 20}) - - -class ConsiderRefereeInvitationForm(forms.Form): - accept = forms.ChoiceField(widget=forms.RadioSelect, choices=ASSIGNMENT_BOOL, - label="Are you willing to referee this Submission?") - refusal_reason = forms.ChoiceField(choices=ASSIGNMENT_REFUSAL_REASONS, required=False) - - -class SetRefereeingDeadlineForm(forms.Form): - deadline = forms.DateField(required=False, label='', widget=forms.SelectDateWidget) - - def clean_deadline(self): - if not self.cleaned_data.get('deadline'): - self.add_error('deadline', 'Please use a valid date.') - return self.cleaned_data.get('deadline') - - -class VotingEligibilityForm(forms.Form): - - def __init__(self, *args, **kwargs): - discipline = kwargs.pop('discipline') - subject_area = kwargs.pop('subject_area') - super(VotingEligibilityForm, self).__init__(*args, **kwargs) - self.fields['eligible_Fellows'] = forms.ModelMultipleChoiceField( - queryset=Contributor.objects.filter( - user__groups__name__in=['Editorial College'], - user__contributor__discipline=discipline, - user__contributor__expertises__contains=[subject_area] - ).order_by('user__last_name'), - widget=forms.CheckboxSelectMultiple({'checked': 'checked'}), - required=True, label='Eligible for voting', - ) - - -############ -# Reports: -############ - -class ReportPDFForm(forms.ModelForm): - class Meta: - model = Report - fields = ['pdf_report'] - - -class ReportForm(forms.ModelForm): - class Meta: - model = Report - fields = ['qualification', 'strengths', 'weaknesses', 'report', 'requested_changes', - 'validity', 'significance', 'originality', 'clarity', 'formatting', 'grammar', - 'recommendation', 'remarks_for_editors', 'anonymous'] - - def __init__(self, *args, **kwargs): - if kwargs.get('instance'): - if kwargs['instance'].is_followup_report: - # Prefill data from latest report in the series - latest_report = kwargs['instance'].latest_report_from_series() - kwargs.update({ - 'initial': { - 'qualification': latest_report.qualification, - 'anonymous': latest_report.anonymous - } - }) - - super(ReportForm, self).__init__(*args, **kwargs) - self.fields['strengths'].widget.attrs.update({ - 'placeholder': ('Give a point-by-point ' - '(numbered 1-, 2-, ...) list of the paper\'s strengths'), - 'rows': 10, - 'cols': 100 - }) - self.fields['weaknesses'].widget.attrs.update({ - 'placeholder': ('Give a point-by-point ' - '(numbered 1-, 2-, ...) list of the paper\'s weaknesses'), - 'rows': 10, - 'cols': 100 - }) - self.fields['report'].widget.attrs.update({'placeholder': 'Your general remarks', - 'rows': 10, 'cols': 100}) - self.fields['requested_changes'].widget.attrs.update({ - 'placeholder': 'Give a numbered (1-, 2-, ...) list of specifically requested changes', - 'cols': 100 - }) - - # If the Report is not a followup: Explicitly assign more fields as being required! - if not self.instance.is_followup_report: - required_fields = [ - 'strengths', - 'weaknesses', - 'requested_changes', - 'validity', - 'significance', - 'originality', - 'clarity', - 'formatting', - 'grammar' - ] - for field in required_fields: - self.fields[field].required = True - - # Let user know the field is required! - for field in self.fields: - if self.fields[field].required: - self.fields[field].label += ' *' - - def save(self, submission): - """ - Update meta data if ModelForm is submitted (non-draft). - Possibly overwrite the default status if user asks for saving as draft. - """ - report = super().save(commit=False) - - report.submission = submission - report.date_submitted = timezone.now() - - # Save with right status asked by user - if 'save_draft' in self.data: - report.status = STATUS_DRAFT - elif 'save_submit' in self.data: - report.status = STATUS_UNVETTED - - # Update invitation and report meta data if exist - invitation = submission.referee_invitations.filter(referee=report.author).first() - if invitation: - invitation.fulfilled = True - invitation.save() - report.invited = True - - # Check if report author if the report is being flagged on the submission - if submission.referees_flagged: - if report.author.user.last_name in submission.referees_flagged: - report.flagged = True - report.save() - return report - - -class VetReportForm(forms.Form): - action_option = forms.ChoiceField(widget=forms.RadioSelect, - choices=REPORT_ACTION_CHOICES, - required=True, label='Action') - refusal_reason = forms.ChoiceField(choices=REPORT_REFUSAL_CHOICES, required=False) - email_response_field = forms.CharField(widget=forms.Textarea(), - label='Justification (optional)', required=False) - report = forms.ModelChoiceField(queryset=Report.objects.awaiting_vetting(), required=True, - widget=forms.HiddenInput()) - - def __init__(self, *args, **kwargs): - super(VetReportForm, self).__init__(*args, **kwargs) - self.fields['email_response_field'].widget.attrs.update({ - 'placeholder': ('Optional: give a textual justification ' - '(will be included in the email to the Report\'s author)'), - 'rows': 5 - }) - - def clean_refusal_reason(self): - '''Require a refusal reason if report is rejected.''' - reason = self.cleaned_data['refusal_reason'] - if self.cleaned_data['action_option'] == REPORT_ACTION_REFUSE: - if not reason: - self.add_error('refusal_reason', 'A reason must be given to refuse a report.') - return reason - - def process_vetting(self, current_contributor): - '''Set the right report status and update submission fields if needed.''' - report = self.cleaned_data['report'] - report.vetted_by = current_contributor - if self.cleaned_data['action_option'] == REPORT_ACTION_ACCEPT: - # Accept the report as is - report.status = STATUS_VETTED - report.submission.latest_activity = timezone.now() - report.submission.save() - elif self.cleaned_data['action_option'] == REPORT_ACTION_REFUSE: - # The report is rejected - report.status = self.cleaned_data['refusal_reason'] - else: - raise exceptions.InvalidReportVettingValue(self.cleaned_data['action_option']) - report.save() - return report - - -################### -# Communications # -################### - -class EditorialCommunicationForm(forms.Form): - text = forms.CharField(widget=forms.Textarea(), label='') - - def __init__(self, *args, **kwargs): - super(EditorialCommunicationForm, self).__init__(*args, **kwargs) - self.fields['text'].widget.attrs.update( - {'rows': 5, 'cols': 50, 'placeholder': 'Write your message in this box.'}) - - -###################### -# EIC Recommendation # -###################### - -class EICRecommendationForm(forms.ModelForm): - class Meta: - model = EICRecommendation - fields = ['recommendation', - 'remarks_for_authors', 'requested_changes', - 'remarks_for_editorial_college'] - - def __init__(self, *args, **kwargs): - super(EICRecommendationForm, self).__init__(*args, **kwargs) - self.fields['remarks_for_authors'].widget.attrs.update( - {'placeholder': 'Your general remarks for the authors', - 'rows': 10, 'cols': 100}) - self.fields['requested_changes'].widget.attrs.update( - {'placeholder': 'If you request revisions, give a numbered (1-, 2-, ...) list of specifically requested changes', - 'cols': 100}) - self.fields['remarks_for_editorial_college'].widget.attrs.update( - {'placeholder': 'If you recommend to accept or refuse, the Editorial College will vote; write any relevant remarks for the EC here.'}) - - -############### -# Vote form # -############### - -class RecommendationVoteForm(forms.Form): - vote = forms.ChoiceField(widget=forms.RadioSelect, - choices=[('agree', 'Agree'), - ('disagree', 'Disagree'), - ('abstain', 'Abstain')], - label='', - ) - remark = forms.CharField(widget=forms.Textarea(), label='', required=False) - - def __init__(self, *args, **kwargs): - super(RecommendationVoteForm, self).__init__(*args, **kwargs) - self.fields['remark'].widget.attrs.update( - {'rows': 3, 'cols': 30, 'placeholder': 'Your remarks (optional)'}) - - -class SubmissionCycleChoiceForm(forms.ModelForm): - referees_reinvite = forms.ModelMultipleChoiceField(queryset=RefereeInvitation.objects.none(), - widget=forms.CheckboxSelectMultiple({ - 'checked': 'checked'}), - required=False, label='Reinvite referees') - - class Meta: - model = Submission - fields = ('refereeing_cycle',) - widgets = {'refereeing_cycle': forms.RadioSelect} - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['refereeing_cycle'].default = None - other_submission = self.instance.other_versions.first() - if other_submission: - self.fields['referees_reinvite'].queryset = other_submission.referee_invitations.all() - - -class iThenticateReportForm(forms.ModelForm): - class Meta: - model = iThenticateReport - fields = [] - - def __init__(self, submission, *args, **kwargs): - self.submission = submission - super().__init__(*args, **kwargs) - - if kwargs.get('files', {}).get('file'): - # Add file field if file data is coming in! - self.fields['file'] = forms.FileField() - - def clean(self): - cleaned_data = super().clean() - doc_id = self.instance.doc_id - if not doc_id and not self.fields.get('file'): - try: - cleaned_data['document'] = helpers.retrieve_pdf_from_arxiv( - self.submission.arxiv_identifier_w_vn_nr) - except exceptions.ArxivPDFNotFound: - self.add_error(None, ('The pdf could not be found at arXiv.' - ' Please upload the pdf manually.')) - self.fields['file'] = forms.FileField() - elif not doc_id and cleaned_data.get('file'): - cleaned_data['document'] = cleaned_data['file'].read() - elif doc_id: - self.document_id = doc_id - - # Login client to append login-check to form - self.client = self.get_client() - - if not self.client: - self.add_error(None, "Failed to login to iThenticate.") - return None - - # Document (id) is found - if cleaned_data.get('document'): - self.document = cleaned_data['document'] - self.response = self.call_ithenticate() - elif hasattr(self, 'document_id'): - self.response = self.call_ithenticate() - - if hasattr(self, 'response') and self.response: - return cleaned_data - - # Don't return anything as someone submitted invalid data for the form at this point! - return None - - def save(self, *args, **kwargs): - data = self.response - - report, created = iThenticateReport.objects.get_or_create(doc_id=data['id']) - - if not created: - try: - iThenticateReport.objects.filter(doc_id=data['id']).update( - uploaded_time=data['uploaded_time'], - processed_time=data['processed_time'], - percent_match=data['percent_match'], - part_id=data.get('parts', [{}])[0].get('id') - ) - except KeyError: - pass - else: - report.save() - self.submission.plagiarism_report = report - self.submission.save() - return report - - def call_ithenticate(self): - if hasattr(self, 'document_id'): - # Update iThenticate status - return self.update_status() - elif hasattr(self, 'document'): - # Upload iThenticate document first time - return self.upload_document() - - def get_client(self): - client = iThenticate.API.Client(settings.ITHENTICATE_USERNAME, - settings.ITHENTICATE_PASSWORD) - if client.login(): - return client - self.add_error(None, "Failed to login to iThenticate.") - return None - - def update_status(self): - client = self.client - response = client.documents.get(self.document_id) - if response['status'] == 200: - return response.get('data')[0].get('documents') - self.add_error(None, "Updating failed. iThenticate didn't return valid data [1]") - - for msg in client.messages: - self.add_error(None, msg) - return None - - def upload_document(self): - from .plagiarism import iThenticate - plagiarism = iThenticate() - data = plagiarism.upload_submission(self.document, self.submission) - - # Give feedback to the user - if not data: - self.add_error(None, "Updating failed. iThenticate didn't return valid data [3]") - for msg in plagiarism.get_messages(): - self.add_error(None, msg) - return None - return data diff --git a/submissions/urls.py b/submissions/urls.py index 431ae957cad857afb401cabd2a15b58a9f53fe67..c1f46c9ab3839e1758c764de519a0ae91a24eecf 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -2,6 +2,7 @@ from django.conf.urls import url from django.views.generic import TemplateView from . import views +from .constants import SUBMISSIONS_NO_VN_REGEX, SUBMISSIONS_COMPLETE_REGEX urlpatterns = [ # Submissions @@ -14,25 +15,25 @@ urlpatterns = [ url(r'^author_guidelines$', TemplateView.as_view(template_name='submissions/author_guidelines.html'), name='author_guidelines'), - url(r'^(?P<arxiv_identifier_wo_vn_nr>[0-9]{4,}.[0-9]{5,})/$', views.submission_detail_wo_vn_nr, + url(r'^{regex}/$'.format(regex=SUBMISSIONS_NO_VN_REGEX), views.submission_detail_wo_vn_nr, name='submission_wo_vn_nr'), - url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/$', + url(r'^{regex}/$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.submission_detail, name='submission'), - url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/(?P<report_nr>[0-9]+)/pdf$', + url(r'^{regex}/reports/(?P<report_nr>[0-9]+)/pdf$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.report_detail_pdf, name='report_detail_pdf'), - url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/pdf$', + url(r'^{regex}/reports/pdf$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.submission_refereeing_package_pdf, name='refereeing_package_pdf'), # Editorial Administration url(r'^admin$', views.EditorialSummaryView.as_view(), name='admin'), url(r'^admin/treated$', views.treated_submissions_list, name='treated_submissions_list'), - url(r'^admin/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/compile$', + url(r'^admin/{regex}/reports/compile$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.treated_submission_pdf_compile, name='treated_submission_pdf_compile'), - url(r'^admin/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/plagiarism$', + url(r'^admin/{regex}/plagiarism$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.PlagiarismView.as_view(), name='plagiarism'), - url(r'^admin/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/plagiarism/report$', + url(r'^admin/{regex}/plagiarism/report$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.PlagiarismReportPDFView.as_view(), name='plagiarism_report'), - url(r'^admin/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/recommendations/(?P<rec_id>[0-9]+)$', + url(r'^admin/{regex}/recommendations/(?P<rec_id>[0-9]+)$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.EICRecommendationView.as_view(), name='eic_recommendation_detail'), url(r'^admin/reports$', views.reports_accepted_list, name='reports_accepted_list'), url(r'^admin/reports/(?P<report_id>[0-9]+)/compile$', @@ -46,30 +47,30 @@ urlpatterns = [ url(r'^pool$', views.pool, name='pool'), url(r'^submissions_by_status/(?P<status>[a-zA-Z_]+)$', views.submissions_by_status, name='submissions_by_status'), - url(r'^add_remark/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^add_remark/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.add_remark, name='add_remark'), # Assignment of Editor-in-charge - url(r'^assign_submission/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^assign_submission/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.assign_submission, name='assign_submission'), - url(r'^assign_submission_ack/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^assign_submission_ack/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.assign_submission_ack, name='assign_submission_ack'), url(r'^accept_or_decline_assignment_ack/(?P<assignment_id>[0-9]+)$', views.accept_or_decline_assignment_ack, name='accept_or_decline_assignment_ack'), - url(r'^volunteer_as_EIC/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^volunteer_as_EIC/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.volunteer_as_EIC, name='volunteer_as_EIC'), - url(r'^assignment_failed/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^assignment_failed/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.assignment_failed, name='assignment_failed'), # Editorial workflow and refereeing url(r'^editorial_workflow$', views.editorial_workflow, name='editorial_workflow'), url(r'^assignments$', views.assignments, name='assignments'), - url(r'^editorial_page/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^editorial_page/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.editorial_page, name='editorial_page'), - url(r'^select_referee/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^select_referee/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.select_referee, name='select_referee'), - url(r'^recruit_referee/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^recruit_referee/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.recruit_referee, name='recruit_referee'), - url(r'^send_refereeing_invitation/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/(?P<contributor_id>[0-9]+)$', + url(r'^send_refereeing_invitation/{regex}/(?P<contributor_id>[0-9]+)$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.send_refereeing_invitation, name='send_refereeing_invitation'), url(r'^accept_or_decline_ref_invitations/$', views.accept_or_decline_ref_invitations, name='accept_or_decline_ref_invitations'), @@ -77,27 +78,28 @@ urlpatterns = [ views.accept_or_decline_ref_invitation_ack, name='accept_or_decline_ref_invitation_ack'), url(r'^decline_ref_invitation/(?P<invitation_key>.+)$', views.decline_ref_invitation, name='decline_ref_invitation'), - url(r'^ref_invitation_reminder/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/(?P<invitation_id>[0-9]+)$', views.ref_invitation_reminder, name='ref_invitation_reminder'), - url(r'^cancel_ref_invitation/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/(?P<invitation_id>[0-9]+)$', + url(r'^ref_invitation_reminder/{regex}/(?P<invitation_id>[0-9]+)$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), + views.ref_invitation_reminder, name='ref_invitation_reminder'), + url(r'^cancel_ref_invitation/{regex}/(?P<invitation_id>[0-9]+)$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.cancel_ref_invitation, name='cancel_ref_invitation'), - url(r'^extend_refereeing_deadline/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/(?P<days>[0-9]+)$', + url(r'^extend_refereeing_deadline/{regex}/(?P<days>[0-9]+)$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.extend_refereeing_deadline, name='extend_refereeing_deadline'), - url(r'^set_refereeing_deadline/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^set_refereeing_deadline/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.set_refereeing_deadline, name='set_refereeing_deadline'), - url(r'^close_refereeing_round/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^close_refereeing_round/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.close_refereeing_round, name='close_refereeing_round'), url(r'^refereeing_overview$', views.refereeing_overview, name='refereeing_overview'), - url(r'^communication/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/(?P<comtype>[a-zA-Z]{4,})$', + url(r'^communication/{regex}/(?P<comtype>[a-zA-Z]{{4,}})$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.communication, name='communication'), - url(r'^communication/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/(?P<comtype>[a-zA-Z]{4,})/(?P<referee_id>[0-9]+)$', + url(r'^communication/{regex}/(?P<comtype>[a-zA-Z]{{4,}})/(?P<referee_id>[0-9]+)$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.communication, name='communication'), - url(r'^eic_recommendation/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^eic_recommendation/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.eic_recommendation, name='eic_recommendation'), - url(r'^cycle/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/submit$', + url(r'^cycle/{regex}/submit$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.cycle_form_submit, name='cycle_confirmation'), # Reports - url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/submit$', + url(r'^{regex}/reports/submit$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.submit_report, name='submit_report'), url(r'^reports/vet$', views.vet_submitted_reports_list, name='vet_submitted_reports_list'), url(r'^reports/(?P<report_id>[0-9]+)/vet$', views.vet_submitted_report,