diff --git a/notifications/migrations/0002_notification_url_code.py b/notifications/migrations/0002_notification_url_code.py new file mode 100644 index 0000000000000000000000000000000000000000..b8922d06cd68ebd87772913a30232203c46fe343 --- /dev/null +++ b/notifications/migrations/0002_notification_url_code.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-04 19:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='url_code', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/notifications/models.py b/notifications/models.py index 3f0d1dd553cfac820324b9030c688b2d5558b251..eb0947cf320bfb04f7ddb13a5b8b84a2697251f3 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -71,6 +71,8 @@ class Notification(models.Model): # of notifications. internal_type = models.CharField(max_length=255, blank=True, choices=NOTIFICATION_TYPES) + url_code = models.CharField(max_length=255, blank=True) + objects = NotificationQuerySet.as_manager() class Meta: diff --git a/preprints/helpers.py b/preprints/helpers.py index d5437b287c6539ca03ce34265425af7b6438b19c..1af7fba21670c593835cbbb0a1f1a3a6360490d8 100644 --- a/preprints/helpers.py +++ b/preprints/helpers.py @@ -5,24 +5,40 @@ __license__ = "AGPL v3" from django.db.models import Max from django.utils import timezone +from submissions.models import Submission + from .models import Preprint -def generate_new_scipost_identifier(): - """Return an identifier for a new SciPost preprint series without version number.""" +def generate_new_scipost_identifier(old_preprint=None): + """ + Return an identifier for a new SciPost preprint series without version number. + + TODO: This method will explode as soon as it will be used similtaneously by two or more people. + """ now = timezone.now() - existing_identifier = Preprint.objects.filter( - created__year=now.year, created__month=now.month).aggregate( - identifier=Max('scipost_preprint_identifier'))['identifier'] - if not existing_identifier: - existing_identifier = '1' - else: - existing_identifier = str(existing_identifier + 1) - return '{year}{month}_{identifier}'.format( - year=now.year, month=str(now.month).rjust(2, '0'), - identifier=existing_identifier.rjust(5, '0')), int(existing_identifier) + if isinstance(old_preprint, Submission): + old_preprint = old_preprint.preprint + + if old_preprint: + # Generate new version number of existing series. + preprint_series = Preprint.objects.filter( + scipost_preprint_identifier=old_preprint.scipost_preprint_identifier).values_list( + 'vn_nr', flat=True) + identifier = '{}v{}'.format(old_preprint.identifier_wo_vn_nr, max(preprint_series) + 1) + return identifier, old_preprint.scipost_preprint_identifier + else: + # New series of Preprints. + existing_identifier = Preprint.objects.filter( + created__year=now.year, created__month=now.month).aggregate( + identifier=Max('scipost_preprint_identifier'))['identifier'] + if not existing_identifier: + existing_identifier = '1' + else: + existing_identifier = str(existing_identifier + 1) -def format_scipost_identifier(identifier, version=1): - return 'scipost_{identifier}v{version}'.format( - identifier=identifier, version=version) + identifier = 'scipost_{year}{month}_{identifier}v1'.format( + year=now.year, month=str(now.month).rjust(2, '0'), + identifier=existing_identifier.rjust(5, '0')) + return identifier, int(existing_identifier) diff --git a/submissions/admin.py b/submissions/admin.py index 7e02b3362b0bd7fd1cb3ca0f49d5e5b76538d1c5..78df5387cad6306dcc075d99576e120b1f57b910 100644 --- a/submissions/admin.py +++ b/submissions/admin.py @@ -65,8 +65,10 @@ class SubmissionAdmin(GuardedModelAdmin): }), ('Versioning', { 'fields': ( + 'thread_hash', 'is_current', - 'is_resubmission', + '_is_resubmission', + 'is_resubmission_of', 'list_of_changes'), }), ('Submission details', { diff --git a/submissions/constants.py b/submissions/constants.py index d32c9d654fc4079dac178a3d6c4ca503c04f69b2..a10841d53e78759400bd4d21928c8ae90648a9f4 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -46,6 +46,7 @@ NO_REQUIRED_ACTION_STATUSES = [ ] SUBMISSION_TYPE = ( + # ('', None), ('Letter', 'Letter (broad-interest breakthrough results)'), ('Article', 'Article (in-depth reports on specialized research)'), ('Review', 'Review (candid snapshot of current research in a given area)'), diff --git a/submissions/forms.py b/submissions/forms.py index 8709a00de4f9191f40ebc0e73c238a105324ebd0..af9e2eecb349d165df82c2aa579deea86f8e2d33 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -31,7 +31,7 @@ from colleges.models import Fellowship from journals.models import Journal from journals.constants import SCIPOST_JOURNAL_PHYSICS_PROC, SCIPOST_JOURNAL_PHYSICS from mails.utils import DirectMailUtil -from preprints.helpers import generate_new_scipost_identifier, format_scipost_identifier +from preprints.helpers import generate_new_scipost_identifier from preprints.models import Preprint from production.utils import get_or_create_production_stream from scipost.constants import SCIPOST_SUBJECT_AREAS @@ -95,180 +95,237 @@ class SubmissionPoolFilterForm(forms.Form): # Submission and resubmission # ############################### -class SubmissionChecks: - """Mixin with checks run at least the Submission creation form.""" +class SubmissionService: + """ + Object to run checks for prefiller and submit manuscript forms. + """ - use_arxiv_preprint = True - arxiv_data = {} - is_resubmission = False - last_submission = None + metadata = {} - def __init__(self, *args, **kwargs): - self.requested_by = kwargs.pop('requested_by', None) - 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(preprint__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 __init__(self, requested_by, preprint_server, identifier=None, resubmission_of_id=None): + self.requested_by = requested_by + self.preprint_server = preprint_server + self.identifier = identifier + self.resubmission_of_id = resubmission_of_id + self._arxiv_data = None + + @property + def latest_submission(self): + """ + Return latest version of preprint series or None. + """ + if hasattr(self, '_latest_submission'): + return self._latest_submission + + if self.identifier: + # Check if is resubmission when identifier data is submitted. + identifier = self.identifier.rpartition('v')[0] + self._latest_submission = Submission.objects.filter( + preprint__identifier_wo_vn_nr=identifier).order_by( + '-preprint__vn_nr').first() + elif self.resubmission_of_id: + # Resubmission (submission id) is selected by user. + try: + self._latest_submission = Submission.objects.filter( + id=int(self.resubmission_of_id)).order_by('-preprint__vn_nr').first() + except ValueError: + self._latest_submission = None + return self._latest_submission + + @property + def arxiv_data(self): + if self._arxiv_data is None: + self._call_arxiv() + return self._arxiv_data + + def run_checks(self): + """ + Do several pre-checks (using the arXiv API if needed). + + This is needed for both the prefill and submission forms. + """ + self._submission_already_exists() + self._submission_previous_version_is_valid_for_submission() + + if self.preprint_server == 'arxiv': + self._submission_is_already_published() + + def _call_arxiv(self): + """ + Retrieve all data from the ArXiv database for `identifier`. + """ + if self.preprint_server != 'arxiv': + # Do the call here to prevent multiple calls to the arXiv API in one request. + self._arxiv_data = {} + return + if not self.identifier: + print('crap', self.identifier) + return + + caller = ArxivCaller(self.identifier) - def _call_arxiv(self, identifier): - caller = ArxivCaller(identifier) if caller.is_valid: - self.arxiv_data = caller.data + self._arxiv_data = caller.data self.metadata = caller.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'] + def get_latest_submission_data(self): + """ + Return initial form data originating from earlier Submission. + """ + if self.is_resubmission(): + return { + 'title': self.latest_submission.title, + 'abstract': self.latest_submission.abstract, + 'author_list': self.latest_submission.author_list, + 'discipline': self.latest_submission.discipline, + 'domain': self.latest_submission.domain, + 'referees_flagged': self.latest_submission.referees_flagged, + 'referees_suggested': self.latest_submission.referees_suggested, + 'secondary_areas': self.latest_submission.secondary_areas, + 'subject_area': self.latest_submission.subject_area, + 'submitted_to': self.latest_submission.submitted_to, + 'submission_type': self.latest_submission.submission_type, + } + return {} - 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 is_resubmission(self): + """ + Check if Submission is a SciPost or arXiv resubmission. + """ + return self.latest_submission is not None - 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(preprint__identifier_wo_vn_nr=identifiers['identifier_wo_vn_nr']) - .order_by('preprint__vn_nr').last()) - - # If submissions are found; check their statuses - if submission: - self.last_submission = submission - if submission.open_for_resubmission: - self.is_resubmission = True - if self.requested_by.contributor not in submission.authors.all(): - error_message = ('There exists a preprint with this arXiv identifier ' - 'but an earlier version number. Resubmission is only possible' - ' if you are a registered author of this manuscript.') - raise forms.ValidationError(error_message) - elif submission.status == STATUS_REJECTED: - 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 identifier_matches_regex(self, journal_code): + """ + Check if identifier is valid for the Journal submitting to. + """ + if self.preprint_server != 'arxiv': + # Only check arXiv identifiers + return - def identifier_matches_regex(self, identifier, journal_code): - """Check if arXiv identifier is valid for the Journal submitting to.""" if journal_code in EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS.keys(): regex = EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS[journal_code] else: regex = EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS['default'] pattern = re.compile(regex) - if not pattern.match(identifier): + if not pattern.match(self.identifier): # No match object returned, identifier is invalid error_message = ('The journal you want to submit to does not allow for this' - ' arXiv identifier. Please contact SciPost if you have' + ' identifier. Please contact SciPost if you have' ' any further questions.') raise forms.ValidationError(error_message, code='submitted_to') - def submission_is_resubmission(self): - """Check if the Submission is a resubmission.""" - return self.is_resubmission + def process_resubmission_procedure(self, submission): + """ + Update all fields for new and old Submission and EditorialAssignments to comply with + the resubmission procedures. - def identifier_into_parts(self, identifier): - """Split the preprint identifier into parts.""" - data = { - 'identifier_w_vn_nr': identifier, - 'identifier_wo_vn_nr': identifier.rpartition('v')[0], - 'vn_nr': int(identifier.rpartition('v')[2]) - } - return data + -- submission: the new version of the Submission series. + """ + if not self.latest_submission: + raise Submission.DoesNotExist - def do_pre_checks(self, identifier): - """Group call of different checks.""" - self._submission_already_exists(identifier) - if self.use_arxiv_preprint: - self._call_arxiv(identifier) - self._submission_is_already_published(identifier) - self._submission_previous_version_is_valid_for_submission(identifier) + # Close last submission + Submission.objects.filter(id=self.latest_submission.id).update( + is_current=False, open_for_reporting=False, status=STATUS_RESUBMITTED) + # Copy Topics + submission.topics.add(*self.latest_submission.topics.all()) -class SubmissionIdentifierForm(SubmissionChecks, forms.Form): - """Prefill SubmissionForm using this form that takes an arXiv ID only.""" + # Open for comment and reporting and copy EIC info + Submission.objects.filter(id=submission.id).update( + open_for_reporting=True, + open_for_commenting=True, + is_resubmission_of=self.latest_submission, + visible_pool=True, + refereeing_cycle=CYCLE_UNDETERMINED, + editor_in_charge=self.latest_submission.editor_in_charge, + status=STATUS_EIC_ASSIGNED, + thread_hash=self.latest_submission.thread_hash) - IDENTIFIER_PLACEHOLDER = 'new style (with version nr) ####.####(#)v#(#)' + # Add author(s) (claim) fields + submission.authors.add(*self.latest_submission.authors.all()) + submission.authors_claims.add(*self.latest_submission.authors_claims.all()) + submission.authors_false_claims.add(*self.latest_submission.authors_false_claims.all()) - identifier_w_vn_nr = forms.RegexField( - regex=IDENTIFIER_PATTERN_NEW, strip=True, - error_messages={'invalid': strings.arxiv_query_invalid}, - widget=forms.TextInput({'placeholder': IDENTIFIER_PLACEHOLDER})) + # Create new EditorialAssigment for the current Editor-in-Charge + EditorialAssignment.objects.create( + submission=submission, + to=self.latest_submission.editor_in_charge, + status=STATUS_ACCEPTED) - def clean_identifier_w_vn_nr(self): - """Do basic prechecks based on the arXiv ID only.""" - identifier = self.cleaned_data['identifier_w_vn_nr'] - self.do_pre_checks(identifier) - return identifier + def _submission_already_exists(self): + """ + Check if preprint has already been submitted before. + """ + if Submission.objects.filter(preprint__identifier_w_vn_nr=self.identifier).exists(): + error_message = 'This preprint version has already been submitted to SciPost.' + raise forms.ValidationError(error_message, code='duplicate') - 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': self.last_submission.submitted_to, - 'submission_type': self.last_submission.submission_type, - } - return data or {} + def _submission_previous_version_is_valid_for_submission(self): + """ + Check if previous submitted versions have the appropriate status. + """ - def request_arxiv_preprint_form_prefill_data(self): - """Return dictionary to prefill `RequestSubmissionForm`.""" - form_data = self.arxiv_data - form_data['identifier_w_vn_nr'] = self.cleaned_data['identifier_w_vn_nr'] - if self.submission_is_resubmission(): - form_data.update(self._gather_data_from_last_submission()) - return form_data + if self.latest_submission: + if self.latest_submission.status == STATUS_REJECTED: + # Explicitly give rejected status warning. + error_message = ('This preprint has previously undergone refereeing ' + 'and has been rejected. Resubmission is only possible ' + 'if the manuscript has been substantially reworked into ' + 'a new submission with distinct identifier.') + raise forms.ValidationError(error_message) + elif self.latest_submission.open_for_resubmission: + # Check if verified author list contains current user. + if self.requested_by.contributor not in self.latest_submission.authors.all(): + error_message = ('There exists a preprint with this identifier ' + 'but an earlier version number. Resubmission is only possible' + ' if you are a registered author of this manuscript.') + raise forms.ValidationError(error_message) + else: + # Submission has not an appropriate status for resubmission. + error_message = ('There exists a preprint with this 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_already_published(self): + """ + Check if preprint number is already registered with a DOI in the *ArXiv* database. + """ + 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}) -class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): - """Form to submit a new Submission.""" - scipost_identifier = None +class SubmissionForm(forms.ModelForm): + """ + Form to submit a new (re)Submission. + """ identifier_w_vn_nr = forms.CharField(widget=forms.HiddenInput()) - arxiv_link = forms.URLField( - widget=forms.TextInput(attrs={'placeholder': 'ex.: arxiv.org/abs/1234.56789v1'})) preprint_file = forms.FileField() class Meta: model = Submission fields = [ - 'is_resubmission', + 'is_resubmission_of', 'discipline', 'submitted_to', 'proceedings', @@ -283,43 +340,72 @@ class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): 'list_of_changes', 'remarks_for_editors', 'referees_suggested', - 'referees_flagged' + 'referees_flagged', + 'arxiv_link', ] widgets = { - 'is_resubmission': forms.HiddenInput(), + 'is_resubmission_of': forms.HiddenInput(), 'secondary_areas': forms.SelectMultiple(choices=SCIPOST_SUBJECT_AREAS), - 'remarks_for_editors': forms.TextInput( - attrs={'placeholder': 'Any private remarks (for the editors only)', 'rows': 3}), - 'referees_suggested': forms.TextInput( - attrs={'placeholder': 'Optional: names of suggested referees', 'rows': 3}), - 'referees_flagged': forms.TextInput( - attrs={'placeholder': 'Optional: names of referees whose reports should be treated with caution (+ short reason)', 'rows': 3}), + 'arxiv_link': forms.TextInput( + attrs={'placeholder': 'ex.: arxiv.org/abs/1234.56789v1'}), + 'remarks_for_editors': forms.Textarea( + attrs={'placeholder': 'Any private remarks (for the editors only)', 'rows': 5}), + 'referees_suggested': forms.Textarea( + attrs={'placeholder': 'Optional: names of suggested referees', 'rows': 5}), + 'referees_flagged': forms.Textarea( + attrs={ + 'placeholder': 'Optional: names of referees whose reports should be treated with caution (+ short reason)', + 'rows': 5 + }), + 'author_comments': forms.Textarea( + attrs={'placeholder': 'Your resubmission letter (will be viewable online)'}), + 'list_of_changes': forms.Textarea( + attrs={'placeholder': 'Give a point-by-point list of changes (will be viewable online)'}), } def __init__(self, *args, **kwargs): - self.use_arxiv_preprint = kwargs.pop('use_arxiv_preprint', True) + self.requested_by = kwargs.pop('requested_by') + self.preprint_server = kwargs.pop('preprint_server', 'arxiv') + self.resubmission_preprint = kwargs['initial'].get('resubmission', False) + + data = args[0] if len(args) > 1 else kwargs.get('data', {}) + identifier = kwargs['initial'].get('identifier_w_vn_nr', None) or data.get('identifier_w_vn_nr') + + self.service = SubmissionService( + self.requested_by, self.preprint_server, + identifier=identifier, + resubmission_of_id=self.resubmission_preprint) + if self.preprint_server == 'scipost': + kwargs['initial'] = self.service.get_latest_submission_data() super().__init__(*args, **kwargs) - # Alter resubmission-dependent fields - if not self.submission_is_resubmission(): - # These fields are only available for resubmissions + if not self.preprint_server == 'arxiv': + # No arXiv-specific data required. + del self.fields['identifier_w_vn_nr'] + del self.fields['arxiv_link'] + elif not self.preprint_server == 'scipost': + # No need for a file upload if user is not using the SciPost preprint server. + del self.fields['preprint_file'] + + # Find all submission allowed to be resubmitted by current user. + self.fields['is_resubmission_of'].queryset = Submission.objects.candidate_for_resubmission( + self.requested_by) + + # Fill resubmission-dependent fields + if self.is_resubmission(): + self.fields['is_resubmission_of'].initial = self.service.latest_submission + else: + # 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)'}) - # ArXiv or SciPost preprint fields - if self.use_arxiv_preprint: - del self.fields['preprint_file'] - else: - del self.fields['arxiv_link'] - del self.fields['identifier_w_vn_nr'] + if not self.fields['is_resubmission_of'].initial: + # No intial nor submitted data found. + del self.fields['is_resubmission_of'] - self.fields['submitted_to'].queryset = Journal.objects.filter(active=True) + # Select Journal instances. + self.fields['submitted_to'].queryset = Journal.objects.active() self.fields['submitted_to'].label = 'Journal: submit to' # Proceedings submission fields @@ -332,20 +418,27 @@ class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): doi_label=SCIPOST_JOURNAL_PHYSICS_PROC) del self.fields['proceedings'] - # Submission type is optional - self.fields['submission_type'].required = False + def is_resubmission(self): + return self.service.is_resubmission() def clean(self, *args, **kwargs): - """Do all prechecks which are also done in the prefiller.""" + """ + Do all general checks for Submission. + """ cleaned_data = super().clean(*args, **kwargs) + + # SciPost preprints are auto-generated here. + self.scipost_identifier = None if 'identifier_w_vn_nr' not in cleaned_data: - # New series of SciPost preprints - identifier_str, self.scipost_identifier = generate_new_scipost_identifier() - cleaned_data['identifier_w_vn_nr'] = format_scipost_identifier(identifier_str) + self.service.identifier, self.scipost_identifier = generate_new_scipost_identifier( + cleaned_data.get('is_resubmission_of', None)) + # Also copy to the form data + self.cleaned_data['identifier_w_vn_nr'] = self.service.identifier - self.do_pre_checks(cleaned_data['identifier_w_vn_nr']) - self.identifier_matches_regex( - cleaned_data['identifier_w_vn_nr'], cleaned_data['submitted_to'].doi_label) + # Run checks again to clean any possible human intervention and run checks again + # with possibly newly generated identifier. + self.service.run_checks() + self.service.identifier_matches_regex(cleaned_data['submitted_to'].doi_label) if self.cleaned_data['submitted_to'].doi_label != SCIPOST_JOURNAL_PHYSICS_PROC: try: @@ -353,31 +446,22 @@ class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): except KeyError: # No proceedings returned to data return cleaned_data - return cleaned_data def clean_author_list(self): - """Check if author list matches the Contributor submitting. - - 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. + """ + Check if author list matches the Contributor submitting. """ author_list = self.cleaned_data['author_list'] - if not self.use_arxiv_preprint: - # Using SciPost preprints, there is nothing to check with. - return 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') + self.add_error('author_list', error_message) return author_list def clean_submission_type(self): - """Validate Submission type. - - The SciPost Physics journal requires a Submission type to be specified. + """ + Validate Submission type for the SciPost Physics journal. """ submission_type = self.cleaned_data['submission_type'] journal_doi_label = self.cleaned_data['submitted_to'].doi_label @@ -385,39 +469,10 @@ class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): self.add_error('submission_type', 'Please specify the submission type.') return submission_type - @transaction.atomic - def copy_and_save_data_from_resubmission(self, submission): - """Fill given Submission with data coming from last_submission.""" - if not self.last_submission: - raise Submission.DoesNotExist - - # Close last submission - Submission.objects.filter(id=self.last_submission.id).update( - is_current=False, open_for_reporting=False, status=STATUS_RESUBMITTED) - - # Copy Topics - submission.topics.add(*self.last_submission.topics.all()) - - # Open for comment and reporting and copy EIC info - Submission.objects.filter(id=submission.id).update( - open_for_reporting=True, - open_for_commenting=True, - is_resubmission=True, - visible_pool=True, - editor_in_charge=self.last_submission.editor_in_charge, - status=STATUS_EIC_ASSIGNED) - - # Add author(s) (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()) - - # Create new EditorialAssigment for the current Editor-in-Charge - EditorialAssignment.objects.create( - submission=submission, to=self.last_submission.editor_in_charge, status=STATUS_ACCEPTED) - def set_pool(self, submission): - """Set the default set of (guest) Fellows for this Submission.""" + """ + Set the default set of (guest) Fellows for this Submission. + """ qs = Fellowship.objects.active() fellows = qs.regular().filter( contributor__discipline=submission.discipline).return_active_for_submission(submission) @@ -431,46 +486,85 @@ class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): @transaction.atomic def save(self): - """Fill, create and transfer data to the new Submission. - - 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. + """ + Create the new Submission and Preprint instances. """ submission = super().save(commit=False) submission.submitted_by = self.requested_by.contributor # Save identifiers - identifiers = self.identifier_into_parts(self.cleaned_data['identifier_w_vn_nr']) + identifiers = self.cleaned_data['identifier_w_vn_nr'].rpartition('v') preprint, __ = Preprint.objects.get_or_create( - identifier_w_vn_nr=identifiers['identifier_w_vn_nr'], - identifier_wo_vn_nr=identifiers['identifier_wo_vn_nr'], - vn_nr=identifiers['vn_nr'], + identifier_w_vn_nr=self.cleaned_data['identifier_w_vn_nr'], + identifier_wo_vn_nr=identifiers[0], + vn_nr=identifiers[2], url=self.cleaned_data.get('arxiv_link', ''), scipost_preprint_identifier=self.scipost_identifier, _file=self.cleaned_data.get('preprint_file', None), ) # Save metadata directly from ArXiv call without possible user interception - submission.metadata = self.metadata if hasattr(self, 'metadata') else {} + submission.metadata = self.service.metadata submission.preprint = preprint - if self.submission_is_resubmission(): - # Reset Refereeing Cycle. EIC needs to pick a cycle on resubmission. - submission.refereeing_cycle = CYCLE_UNDETERMINED - submission.save() # Save before filling from old Submission. - - self.copy_and_save_data_from_resubmission(submission) - else: - # Save! - submission.save() + submission.save() + if self.is_resubmission(): + self.service.process_resubmission_procedure(submission) # Gather first known author and Fellows. submission.authors.add(self.requested_by.contributor) self.set_pool(submission) # Return latest version of the Submission. It could be outdated by now. - return Submission.objects.get(id=submission.id) + submission.refresh_from_db() + return submission + + +class SubmissionIdentifierForm(forms.Form): + """ + Prefill SubmissionForm using this form that takes an arXiv ID only. + """ + + IDENTIFIER_PLACEHOLDER = 'new style (with version nr) ####.####(#)v#(#)' + + identifier_w_vn_nr = forms.RegexField( + label='arXiv identifier with version number', + regex=IDENTIFIER_PATTERN_NEW, strip=True, + error_messages={'invalid': strings.arxiv_query_invalid}, + widget=forms.TextInput({'placeholder': IDENTIFIER_PLACEHOLDER})) + + def __init__(self, *args, **kwargs): + self.requested_by = kwargs.pop('requested_by') + return super().__init__(*args, **kwargs) + + + def clean_identifier_w_vn_nr(self): + """ + Do basic prechecks based on the arXiv ID only. + """ + identifier = self.cleaned_data.get('identifier_w_vn_nr', None) + + self.service = SubmissionService(self.requested_by, 'arxiv', identifier=identifier) + self.service.run_checks() + return identifier + + def get_initial_submission_data(self): + """ + Return dictionary to prefill `SubmissionForm`. + """ + form_data = self.service.arxiv_data + form_data['identifier_w_vn_nr'] = self.cleaned_data['identifier_w_vn_nr'] + if self.service.is_resubmission(): + form_data.update({ + 'discipline': self.service.latest_submission.discipline, + 'domain': self.service.latest_submission.domain, + 'referees_flagged': self.service.latest_submission.referees_flagged, + 'referees_suggested': self.service.latest_submission.referees_suggested, + 'secondary_areas': self.service.latest_submission.secondary_areas, + 'subject_area': self.service.latest_submission.subject_area, + 'submitted_to': self.service.latest_submission.submitted_to, + 'submission_type': self.service.latest_submission.submission_type, + }) + return form_data class SubmissionReportsForm(forms.ModelForm): diff --git a/submissions/managers.py b/submissions/managers.py index edb98934913597f183e1c515ae3d331bbcb0f8cf..dc73320955c2e4f1e1cc2c8578c6c7e0fd7f1a9a 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -17,6 +17,8 @@ now = timezone.now() class SubmissionQuerySet(models.QuerySet): def _newest_version_only(self, queryset): """ + TODO: Make more efficient... with agregation or whatever. + The current Queryset should return only the latest version of the Arxiv submissions known to SciPost. @@ -157,7 +159,7 @@ class SubmissionQuerySet(models.QuerySet): (including subsequent resubmissions, even if those came in later). """ identifiers = [] - for sub in self.filter(is_resubmission=False, + for sub in self.filter(is_resubmission_of__isnull=True, submission_date__range=(from_date, until_date)): identifiers.append(sub.preprint.identifier_wo_vn_nr) return self.filter(preprint__identifier_wo_vn_nr__in=identifiers) @@ -209,6 +211,19 @@ class SubmissionQuerySet(models.QuerySet): """Return Submissions that have EditorialAssignments that still need to be sent.""" return self.filter(editorial_assignments__status=constants.STATUS_PREASSIGNED) + def candidate_for_resubmission(self, user): + """ + Return all Submissions that are open for resubmission specialised for a certain User. + """ + if not hasattr(user, 'contributor'): + return self.none() + + return self.filter(is_current=True, status__in=[ + constants.STATUS_INCOMING, + constants.STATUS_UNASSIGNED, + constants.STATUS_EIC_ASSIGNED, + ], submitted_by=user.contributor) + class SubmissionEventQuerySet(models.QuerySet): def for_author(self): diff --git a/submissions/migrations/0045_submission_is_resubmission_of.py b/submissions/migrations/0045_submission_is_resubmission_of.py new file mode 100644 index 0000000000000000000000000000000000000000..a0ea4d2ebea14f5bcd7a740717c053fc4624bb36 --- /dev/null +++ b/submissions/migrations/0045_submission_is_resubmission_of.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-30 09:13 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0044_auto_20181115_1009'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='is_resubmission_of', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='successor', to='submissions.Submission'), + ), + ] diff --git a/submissions/migrations/0046_auto_20181130_1013.py b/submissions/migrations/0046_auto_20181130_1013.py new file mode 100644 index 0000000000000000000000000000000000000000..41769a5dbba9f9eb31028ebaa50a8379bfe39623 --- /dev/null +++ b/submissions/migrations/0046_auto_20181130_1013.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-30 09:13 +from __future__ import unicode_literals + +from django.db import migrations + + +def populate_explicit_resubmission_links(apps, schema_editor): + Submission = apps.get_model('submissions', 'Submission') + + for resubmission in Submission.objects.filter(preprint__vn_nr__gt=1): + resub_of = Submission.objects.filter( + preprint__identifier_wo_vn_nr=resubmission.preprint.identifier_wo_vn_nr).order_by( + '-preprint__vn_nr').exclude(id=resubmission.id).first() + Submission.objects.filter(id=resubmission.id).update(is_resubmission_of=resub_of) + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0045_submission_is_resubmission_of'), + ] + + operations = [ + migrations.RunPython(populate_explicit_resubmission_links, reverse_code=migrations.RunPython.noop), + ] diff --git a/submissions/migrations/0047_auto_20181204_2011.py b/submissions/migrations/0047_auto_20181204_2011.py new file mode 100644 index 0000000000000000000000000000000000000000..15fe0d2cb0de419eb07beb1d6f8954efdf8a3bfc --- /dev/null +++ b/submissions/migrations/0047_auto_20181204_2011.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-04 19:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0046_auto_20181130_1013'), + ] + + operations = [ + migrations.RenameField( + model_name='submission', + old_name='is_resubmission', + new_name='_is_resubmission', + ), + migrations.AlterField( + model_name='submission', + name='submission_type', + field=models.CharField(blank=True, choices=[('Letter', 'Letter (broad-interest breakthrough results)'), ('Article', 'Article (in-depth reports on specialized research)'), ('Review', 'Review (candid snapshot of current research in a given area)')], max_length=10), + ), + ] diff --git a/submissions/migrations/0048_submission_thread_hash.py b/submissions/migrations/0048_submission_thread_hash.py new file mode 100644 index 0000000000000000000000000000000000000000..ddf50c99d378afb379eedca6278da7bf875a29b6 --- /dev/null +++ b/submissions/migrations/0048_submission_thread_hash.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-04 19:37 +from __future__ import unicode_literals + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0047_auto_20181204_2011'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='thread_hash', + field=models.UUIDField(default=uuid.uuid4), + ), + ] diff --git a/submissions/migrations/0049_auto_20181204_2040.py b/submissions/migrations/0049_auto_20181204_2040.py new file mode 100644 index 0000000000000000000000000000000000000000..bbcd903d8ecd1538e68965795fcfce6a74be4305 --- /dev/null +++ b/submissions/migrations/0049_auto_20181204_2040.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-04 19:40 +from __future__ import unicode_literals + +import uuid + +from django.db import migrations + + +def get_thread_ids(parent, submissions_list=[]): + successor = parent.successor.first() + if not successor: + return submissions_list + + submissions_list.append(successor.id) + return get_thread_ids(successor, submissions_list) + + +def populate_thread_hashes(apps, schema_editor): + Submission = apps.get_model('submissions', 'Submission') + + for original_submission in Submission.objects.filter(is_resubmission_of__isnull=True): + children_ids = get_thread_ids(original_submission, [original_submission.id]) + Submission.objects.filter(id__in=children_ids).update(thread_hash=uuid.uuid4()) + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0048_submission_thread_hash'), + ] + + operations = [ + migrations.RunPython(populate_thread_hashes, reverse_code=migrations.RunPython.noop), + ] diff --git a/submissions/models.py b/submissions/models.py index 58acca54932f2fceb81313a167eb661b54918470..f7348cfb610ade1639421d2518b357eb5885d74e 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -4,6 +4,7 @@ __license__ = "AGPL v3" import datetime import feedparser +import uuid from django.contrib.postgres.fields import JSONField from django.contrib.contenttypes.fields import GenericRelation @@ -75,7 +76,10 @@ class Submission(models.Model): is_current = models.BooleanField(default=True) visible_public = models.BooleanField("Is publicly visible", default=False) visible_pool = models.BooleanField("Is visible in the Pool", default=False) - is_resubmission = models.BooleanField(default=False) + is_resubmission_of = models.ForeignKey( + 'self', blank=True, null=True, related_name='successor') + thread_hash = models.UUIDField(default=uuid.uuid4) + _is_resubmission = models.BooleanField(default=False) refereeing_cycle = models.CharField( max_length=30, choices=SUBMISSION_CYCLES, default=CYCLE_DEFAULT, blank=True) @@ -84,7 +88,7 @@ class Submission(models.Model): subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS, verbose_name='Primary subject area', default='Phys:QP') - submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE) + submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE, blank=True) submitted_by = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, related_name='submitted_submissions') voting_fellows = models.ManyToManyField('colleges.Fellowship', blank=True, @@ -194,6 +198,10 @@ class Submission(models.Model): """Return url of the Submission detail page.""" return reverse('submissions:submission', args=(self.preprint.identifier_w_vn_nr,)) + @property + def is_resubmission(self): + return self.is_resubmission_of is not None + @property def notification_name(self): """Return string representation of this Submission as shown in Notifications.""" @@ -230,13 +238,12 @@ class Submission(models.Model): def original_submission_date(self): """Return the submission_date of the first Submission in the thread.""" return Submission.objects.filter( - preprint__identifier_wo_vn_nr=self.preprint.identifier_wo_vn_nr).first().submission_date + thread_hash=self.thread_hash, is_resubmission_of__isnull=True).first().submission_date @property def thread(self): """Return all (public) Submissions in the database in this ArXiv identifier series.""" - return Submission.objects.public().filter( - preprint__identifier_wo_vn_nr=self.preprint.identifier_wo_vn_nr).order_by( + return Submission.objects.public().filter(thread_hash=self.thread_hash).order_by( '-preprint__vn_nr') @cached_property @@ -251,8 +258,7 @@ class Submission(models.Model): def get_other_versions(self): """Return queryset of other Submissions with this ArXiv identifier series.""" - return Submission.objects.filter( - preprint__identifier_wo_vn_nr=self.preprint.identifier_wo_vn_nr).exclude(pk=self.id) + return Submission.objects.filter(thread_hash=self.thread_hash).exclude(pk=self.id) def count_accepted_invitations(self): """Count number of accepted RefereeInvitations for this Submission.""" diff --git a/submissions/templates/submissions/submission_form.html b/submissions/templates/submissions/submission_form.html index 1e314ea136b45e02b96556a9fe77cba68bee94b4..433ff7ed59cb0ccf78b218259a9b3eded44f7e89 100644 --- a/submissions/templates/submissions/submission_form.html +++ b/submissions/templates/submissions/submission_form.html @@ -48,6 +48,24 @@ <p> Please prepare your manuscript according to the <a href="{% url 'submissions:author_guidelines' %}">author guidelines</a>. </p> + + + {% if form.is_resubmission %} + <hr> + <p class="mb-1">You are submitting a new version of your manuscript:</p> + <h3><a href="{{ form.fields.is_resubmission_of.initial.get_absolute_url }}" target="_blank">{{ form.fields.is_resubmission_of.initial.title }}</a></h3> + <p> + by {{ form.fields.is_resubmission_of.initial.author_list }} + <br> + Preprint number: {{ form.fields.is_resubmission_of.initial.preprint.identifier_w_vn_nr }} + </p> + {% endif %} + + {% comment %} + {% if not form.form_complete %} + <h3 class="text-danger">Please check the preprint identifier used for resubmission. Current identifier {{ form.resubmission_preprint }} is not valid.</h3> + {% endif %} + {% endcomment %} </div> </div> diff --git a/submissions/templates/submissions/submission_prefill_form.html b/submissions/templates/submissions/submission_prefill_form.html index 0b9cdb911805cff65781fe9c9b3d0c56e5b3573d..9a4e96e80ede61ecd90371fcd627eed1037514da 100644 --- a/submissions/templates/submissions/submission_prefill_form.html +++ b/submissions/templates/submissions/submission_prefill_form.html @@ -35,7 +35,8 @@ <div class='card-body'> <h3>Please provide the arXiv identifier for your Submission</h3> <p><em>(give the identifier without prefix but with version number, as per the placeholder)</em></p> - <form action="{% url 'submissions:submit_manuscript_arxiv' %}" method="get"> + <form action="{% url 'submissions:prefill_using_identifier' %}" method="post"> + {% csrf_token %} {{ form|bootstrap }} <input type="submit" class="btn btn-outline-secondary" value="Query arXiv"/> <br> diff --git a/submissions/templates/submissions/submission_resubmission_candidates.html b/submissions/templates/submissions/submission_resubmission_candidates.html new file mode 100644 index 0000000000000000000000000000000000000000..0a26172c5ff5943c7571244b373f03f51e7cd184 --- /dev/null +++ b/submissions/templates/submissions/submission_resubmission_candidates.html @@ -0,0 +1,39 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: submit manuscript{% endblock pagetitle %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <form method="post"> + {% csrf_token %} + <h1 class="highlight">Possible Resubmissions</h1> + <p>The system has found {{ submissions|length|pluralize:'a Submission,Submissions' }} for which you are a verified author. If you wish to submit a new version for {{ submissions|length|pluralize:'this Submission,one of these Submissions' }}, please use the "Resubmit this Submission" buttton below.</p> + <ul> + {% for submission in submissions %} + <li class="py-2"> + <strong>{{ submission.title }}</strong> + <br> + {{ submission.author_list }} + <br> + Preprint number: {{ submission.preprint.identifier_w_vn_nr }} + <br> + {% if not submission.open_for_resubmission %} + <strong class="text-danger">This submission 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.</strong> + {% else %} + <button type="submit" name="submission" value="{{ submission.id }}"class="btn btn-primary py-1 mt-1">Resubmit this Submission</button> + {% endif %} + </li> + {% endfor %} + </ul> + <p> + If you wish to submit a new Submission, please <button type="submit" name="submission" value="new" class="btn btn-primary py-1 mr-1">submit a new Submission here</button>. + </p> + </form> + </div> +</div> + +{% endblock content %} diff --git a/submissions/test_views.py b/submissions/test_views.py index 6f4b9fbdf9d9cc43b6c27a32b4db70bb9efa1fad..c2bf1dba5b4cb2cc410c96699a10c3925e4a2f40 100644 --- a/submissions/test_views.py +++ b/submissions/test_views.py @@ -15,7 +15,7 @@ from .factories import UnassignedSubmissionFactory, EICassignedSubmissionFactory ResubmittedSubmissionFactory, ResubmissionFactory,\ PublishedSubmissionFactory, DraftReportFactory,\ AcceptedRefereeInvitationFactory -from .forms import RequestSubmissionForm, SubmissionIdentifierForm, ReportForm +from .forms import SubmissionIdentifierForm, ReportForm. SubmissionForm from .models import Submission, Report, RefereeInvitation from journals.models import Journal @@ -96,7 +96,7 @@ class PrefillUsingIdentifierTest(BaseContributorTestCase): {'identifier': TEST_SUBMISSION['identifier_w_vn_nr']}) self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], RequestSubmissionForm) + # self.assertIsInstance(response.context['form'], SubmissionForm) # Explicitly compare fields instead of assertDictEqual as metadata field may be outdated # self.assertEqual(TEST_SUBMISSION['is_resubmission'], @@ -189,7 +189,7 @@ class SubmitManuscriptTest(BaseContributorTestCase): # Submit new Submission form response = client.post(reverse('submissions:submit_manuscript'), params) self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], RequestSubmissionForm) + self.assertIsInstance(response.context['form'], SubmissionForm) self.assertFalse(response.context['form'].is_valid()) self.assertIn('author_list', response.context['form'].errors.keys()) diff --git a/submissions/urls.py b/submissions/urls.py index 9b2f9985cbf8c22e4f8c9bd2600efb1c94925991..5591c3ff0cb1661be5172ecc0563d6057c21da2e 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -68,6 +68,7 @@ urlpatterns = [ url(r'^admin/reports/(?P<report_id>[0-9]+)/compile$', views.report_pdf_compile, name='report_pdf_compile'), + url(r'^resubmit_manuscript$', views.resubmit_manuscript, name='resubmit_manuscript'), url(r'^submit_manuscript$', views.prefill_using_arxiv_identifier, name='submit_manuscript'), url(r'^submit_manuscript/scipost$', views.RequestSubmissionUsingSciPostView.as_view(), name='submit_manuscript_scipost'), diff --git a/submissions/views.py b/submissions/views.py index 51e476f0b54edde37a25674c51d4bf5ec3d77366..3ae50a5702a8882bbcf74922559395f21f415c3a 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -1,10 +1,8 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" - import datetime import feedparser -import json import strings from django.contrib import messages @@ -32,7 +30,7 @@ from .models import ( Submission, EICRecommendation, EditorialAssignment, RefereeInvitation, Report, SubmissionEvent) from .mixins import SubmissionAdminViewMixin from .forms import ( - SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSearchForm, RecommendationVoteForm, + SubmissionIdentifierForm, SubmissionForm, SubmissionSearchForm, RecommendationVoteForm, ConsiderAssignmentForm, InviteEditorialAssignmentForm, EditorialAssignmentForm, VetReportForm, SetRefereeingDeadlineForm, RefereeSearchForm, #RefereeSelectForm, iThenticateReportForm, VotingEligibilityForm, @@ -57,24 +55,56 @@ from production.forms import ProofsDecisionForm from profiles.models import Profile from profiles.forms import SimpleProfileForm, ProfileEmailForm from scipost.constants import INVITATION_REFEREEING +from scipost.decorators import is_contributor_user from scipost.forms import RemarkForm from scipost.mixins import PaginationMixin from scipost.models import Contributor, Remark -from submissions.models import RefereeInvitation - -# from notifications.views import is_test_user # Temporarily until release ############### # SUBMISSIONS: ############### +@login_required +@is_contributor_user() +def resubmit_manuscript(request): + """ + Choose which Submission to resubmit if Submission is available. + + On POST, redirect to submit page. + """ + submissions = get_list_or_404( + Submission.objects.candidate_for_resubmission(request.user)) + if request.POST and request.POST.get('submission'): + if request.POST['submission'] == 'new': + return redirect(reverse('submissions:submit_manuscript_scipost') + '?resubmission=false') + + last_submission = Submission.objects.candidate_for_resubmission(request.user).filter( + id=request.POST['submission']).first() + + if last_submission: + if last_submission.preprint.scipost_preprint_identifier: + # Determine right preprint-view. + extra_param = '?resubmission={}'.format(request.POST['submission']) + return redirect(reverse('submissions:submit_manuscript_scipost') + extra_param) + else: + extra_param = '?identifier_w_vn_nr={}'.format( + last_submission.preprint.identifier_w_vn_nr) + return redirect(reverse('submissions:submit_manuscript') + extra_param) + else: + # POST request invalid. Try again with GET request. + return redirect('submissions:resubmit_manuscript') + context = { + 'submissions': submissions, + } + return render(request, 'submissions/submission_resubmission_candidates.html', context) + class RequestSubmissionView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """Formview to submit a new manuscript (Submission).""" permission_required = 'scipost.can_submit_manuscript' success_url = reverse_lazy('scipost:personal_page') - form_class = RequestSubmissionForm + form_class = SubmissionForm template_name = 'submissions/submission_form.html' def get_context_data(self, *args, **kwargs): @@ -87,8 +117,8 @@ class RequestSubmissionView(LoginRequiredMixin, PermissionRequiredMixin, CreateV """Form requires extra kwargs.""" kwargs = super().get_form_kwargs() kwargs['requested_by'] = self.request.user - if hasattr(self, 'initial_data'): - kwargs['initial'] = self.initial_data + kwargs['initial'] = getattr(self, 'initial_data', {}) + kwargs['initial']['resubmission'] = self.request.GET.get('resubmission') return kwargs @transaction.atomic @@ -102,7 +132,7 @@ class RequestSubmissionView(LoginRequiredMixin, PermissionRequiredMixin, CreateV 'Your Submission will soon be handled by an Editor.') messages.success(self.request, text) - if form.submission_is_resubmission(): + if form.is_resubmission(): # Send emails SubmissionUtils.load({'submission': submission}, self.request) SubmissionUtils.send_authors_resubmission_ack_email() @@ -124,35 +154,41 @@ class RequestSubmissionUsingArXivView(RequestSubmissionView): """Formview to submit a new Submission using arXiv.""" def get(self, request): - """Redirect to the arXiv prefill form if arXiv ID is not known.""" + """ + Redirect to the arXiv prefill form if arXiv ID is not known. + """ form = SubmissionIdentifierForm(request.GET or None, requested_by=self.request.user) if form.is_valid(): # Gather data from ArXiv API if prefill form is valid - self.initial_data = form.request_arxiv_preprint_form_prefill_data() + self.initial_data = form.get_initial_submission_data() return super().get(request) else: + for code, err in form.errors.items(): + messages.warning(request, err[0]) return redirect('submissions:prefill_using_identifier') def get_form_kwargs(self): """Form requires extra kwargs.""" kwargs = super().get_form_kwargs() - kwargs['use_arxiv_preprint'] = True + kwargs['preprint_server'] = 'arxiv' return kwargs class RequestSubmissionUsingSciPostView(RequestSubmissionView): """Formview to submit a new Submission using SciPost's preprint server.""" - def dispatch(self, request, *args, **kwargs): - """TEMPORARY: Not accessible unless in test group.""" - # if not is_test_user(request.user): - # raise Http404 - return super().dispatch(request, *args, **kwargs) + def get(self, request): + """Check for possible Resubmissions before dispatching.""" + if Submission.objects.candidate_for_resubmission(request.user).exists(): + if not request.GET.get('resubmission'): + return redirect('submissions:resubmit_manuscript') + return super().get(request) def get_form_kwargs(self): """Form requires extra kwargs.""" kwargs = super().get_form_kwargs() - kwargs['use_arxiv_preprint'] = False + # kwargs['use_arxiv_preprint'] = False + kwargs['preprint_server'] = 'scipost' return kwargs @@ -162,13 +198,10 @@ def prefill_using_arxiv_identifier(request): """Form view asking for the arXiv ID related to the new Submission to submit.""" query_form = SubmissionIdentifierForm(request.POST or None, initial=request.GET or None, requested_by=request.user) - if query_form.is_valid(): - prefill_data = query_form.request_arxiv_preprint_form_prefill_data() - form = RequestSubmissionForm( - initial=prefill_data, requested_by=request.user, use_arxiv_preprint=True) + if query_form.is_valid(): # Submit message to user - if query_form.submission_is_resubmission(): + if query_form.service.is_resubmission(): resubmessage = ('There already exists a preprint with this arXiv identifier ' 'but a different version number. \nYour Submission will be ' 'handled as a resubmission.') @@ -176,14 +209,10 @@ def prefill_using_arxiv_identifier(request): else: messages.success(request, strings.acknowledge_arxiv_query, fail_silently=True) - context = { - 'form': form, - } response = redirect('submissions:submit_manuscript_arxiv') response['location'] += '?identifier_w_vn_nr={}'.format( query_form.cleaned_data['identifier_w_vn_nr']) - # return render(request, 'submissions/submission_form.html', context) - return reponse + return response context = { 'form': query_form,