import datetime import feedparser import re import requests import sys from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.models import User, Group, Permission from django.core.mail import EmailMessage from django.core.urlresolvers import reverse from django.db import transaction from django.db.models import Avg from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render, redirect from django.utils import timezone from django.views.decorators.csrf import csrf_protect from guardian.decorators import permission_required_or_403 from guardian.shortcuts import assign_perm from .models import * from .forms import * from .utils import SubmissionUtils from comments.models import Comment from journals.models import journals_submit_dict from scipost.models import Contributor, title_dict, RegistrationInvitation from scipost.utils import Utils from comments.forms import CommentForm ############### # SUBMISSIONS: ############### @permission_required('scipost.can_submit_manuscript', raise_exception=True) def prefill_using_identifier(request): if request.method == "POST": identifierform = SubmissionIdentifierForm(request.POST) if identifierform.is_valid(): # we allow 1 or 2 digits for version identifierpattern = re.compile("^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$") errormessage = '' if not identifierpattern.match(identifierform.cleaned_data['identifier']): errormessage = ('The identifier you entered is improperly formatted ' '(did you forget the version number?)') elif (Submission.objects #.filter(arxiv_link__contains=identifierform.cleaned_data['identifier']) .filter(arxiv_identifier_w_vn_nr=identifierform.cleaned_data['identifier']) .exists()): errormessage = 'This preprint version has already been submitted to SciPost.' if errormessage != '': form = SubmissionForm() return render(request, 'submissions/submit_manuscript.html', {'identifierform': identifierform, 'form': form, 'errormessage': errormessage}) # Otherwise we query arXiv for the information: identifier_without_vn_nr = identifierform.cleaned_data['identifier'].rpartition('v')[0] arxiv_vn_nr = int(identifierform.cleaned_data['identifier'].rpartition('v')[2]) is_resubmission = False resubmessage = '' previous_submissions = Submission.objects.filter( arxiv_identifier_wo_vn_nr=identifier_without_vn_nr).order_by('-arxiv_vn_nr') if previous_submissions.exists(): is_resubmission = True resubmessage = ('There already exists a preprint with this arXiv identifier ' 'but a different version number. \nYour Submission will be ' 'handled as a resubmission.') try: queryurl = ('http://export.arxiv.org/api/query?id_list=%s' % identifierform.cleaned_data['identifier']) arxivquery = feedparser.parse(queryurl) # Flag error if preprint doesn't exist try: test = arxivquery['entries'][0]['title'] except KeyError: errormessage = 'A preprint associated to this identifier does not exist.' except: pass # If paper has been published, should comment on published version try: arxiv_journal_ref = arxivquery['entries'][0]['arxiv_journal_ref'] errormessage = ('This paper has been published as ' + arxiv_journal_ref + '. You cannot submit it to SciPost anymore.') except: pass try: arxiv_doi = arxivquery['entries'][0]['arxiv_doi'] errormessage = ('This paper has been published under DOI ' + arxiv_DOI + '. You cannot submit it to SciPost anymore.') except: pass if errormessage != '': form = SubmissionForm() context = {'identifierform': identifierform, 'form': form, 'errormessage': errormessage} return render(request, 'submissions/submit_manuscript.html', context) # otherwise prefill the form: # metadata = arxivquery # title = arxivquery['entries'][0]['title'] # authorlist = arxivquery['entries'][0]['authors'][0]['name'] # for author in arxivquery['entries'][0]['authors'][1:]: # authorlist += ', ' + author['name'] # arxiv_link = arxivquery['entries'][0]['id'] # abstract = arxivquery['entries'][0]['summary'] # form = SubmissionForm( # initial={'is_resubmission': is_resubmission, # 'metadata': metadata, # 'title': title, 'author_list': authorlist, # 'arxiv_identifier_w_vn_nr': identifierform.cleaned_data['identifier'], # 'arxiv_identifier_wo_vn_nr': identifier_without_vn_nr, # 'arxiv_vn_nr': arxiv_vn_nr, # 'arxiv_link': arxiv_link, 'abstract': abstract}) metadata = arxivquery title = arxivquery['entries'][0]['title'] authorlist = arxivquery['entries'][0]['authors'][0]['name'] for author in arxivquery['entries'][0]['authors'][1:]: authorlist += ', ' + author['name'] arxiv_link = arxivquery['entries'][0]['id'] abstract = arxivquery['entries'][0]['summary'] initialdata={'is_resubmission': is_resubmission, 'metadata': metadata, 'title': title, 'author_list': authorlist, 'arxiv_identifier_w_vn_nr': identifierform.cleaned_data['identifier'], 'arxiv_identifier_wo_vn_nr': identifier_without_vn_nr, 'arxiv_vn_nr': arxiv_vn_nr, 'arxiv_link': arxiv_link, 'abstract': abstract} if is_resubmission: initialdata['submitted_to_journal'] = previous_submissions[0].submitted_to_journal initialdata['submission_type'] = previous_submissions[0].submission_type initialdata['discipline'] = previous_submissions[0].discipline initialdata['domain'] = previous_submissions[0].domain initialdata['specialization'] = previous_submissions[0].specialization initialdata['subject_area'] = previous_submissions[0].subject_area initialdata['secondary_areas'] = previous_submissions[0].secondary_areas initialdata['referees_suggested'] = previous_submissions[0].referees_suggested initialdata['referees_flagged'] = previous_submissions[0].referees_flagged form = SubmissionForm(initial=initialdata) context = {'identifierform': identifierform, 'form': form, 'resubmessage': resubmessage} return render(request, 'submissions/submit_manuscript.html', context) except: print("Unexpected error in prefill_using_identifier:", sys.exc_info()[0]) context = {'identifierform': identifierform, 'form': SubmissionForm(), 'errormessage': errormessage,} return render(request, 'submissions/submit_manuscript.html', context) else: pass return redirect(reverse('submissions:submit_manuscript')) @login_required @permission_required('scipost.can_submit_manuscript', raise_exception=True) @transaction.atomic def submit_manuscript(request): if request.method == 'POST': form = SubmissionForm(request.POST) if form.is_valid(): submitted_by = Contributor.objects.get(user=request.user) # Verify if submitter is among the authors if not submitted_by.user.last_name in form.cleaned_data['author_list']: errormessage = ('Your name does not match that of any of the authors. ' 'You are not authorized to submit this preprint.') identifierform = SubmissionIdentifierForm() return render(request, 'submissions/submit_manuscript.html', {'identifierform': identifierform, 'form': form, 'errormessage': errormessage}) submission = Submission ( is_current = True, is_resubmission = form.cleaned_data['is_resubmission'], submitted_by = submitted_by, submitted_to_journal = form.cleaned_data['submitted_to_journal'], submission_type = form.cleaned_data['submission_type'], discipline = form.cleaned_data['discipline'], domain = form.cleaned_data['domain'], specialization = form.cleaned_data['specialization'], subject_area = form.cleaned_data['subject_area'], secondary_areas = form.cleaned_data['secondary_areas'], status = 'unassigned', title = form.cleaned_data['title'], author_list = form.cleaned_data['author_list'], abstract = form.cleaned_data['abstract'], arxiv_identifier_w_vn_nr = form.cleaned_data['arxiv_identifier_w_vn_nr'], arxiv_identifier_wo_vn_nr = form.cleaned_data['arxiv_identifier_wo_vn_nr'], arxiv_vn_nr = form.cleaned_data['arxiv_vn_nr'], arxiv_link = form.cleaned_data['arxiv_link'], metadata = form.cleaned_data['metadata'], submission_date = timezone.now(), referees_suggested = form.cleaned_data['referees_suggested'], referees_flagged = form.cleaned_data['referees_flagged'], ) submission.save() submission.authors.add(submitted_by) # must be author to be able to submit submission.save() # If this is a resubmission, mark previous submissions as deprecated: if form.cleaned_data['is_resubmission']: previous_submissions = Submission.objects.filter( arxiv_identifier_wo_vn_nr=form.cleaned_data['arxiv_identifier_wo_vn_nr'] ).exclude(pk=submission.id).order_by('-arxiv_vn_nr') for sub in previous_submissions: sub.is_current = False sub.open_for_reporting = False sub.save() # Handle this submission in same way as if assignment had been accepted submission.open_for_reporting = True deadline = timezone.now() + datetime.timedelta(days=28) # for papers if submission.submitted_to_journal == 'SciPost Physics Lecture Notes': deadline += datetime.timedelta(days=28) submission.reporting_deadline = deadline submission.open_for_commenting = True submission.latest_activity = timezone.now() # We keep the same (most recent) Editor-in-charge by default submission.editor_in_charge = previous_submissions[0].editor_in_charge submission.status = 'EICassigned' submission.save() assignment = EditorialAssignment( submission=submission, to=submission.editor_in_charge, accepted=True, date_created=timezone.now(), date_answered=timezone.now(), ) assignment.save() SubmissionUtils.load({'submission': submission}) SubmissionUtils.send_authors_resubmission_ack_email() 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) SubmissionUtils.send_EIC_reappointment_email() else: SubmissionUtils.load({'submission': submission}) SubmissionUtils.send_authors_submission_ack_email() return HttpResponseRedirect(reverse('submissions:submit_manuscript_ack')) else: # form is invalid pass else: form = SubmissionForm() identifierform = SubmissionIdentifierForm() return render(request, 'submissions/submit_manuscript.html', {'identifierform': identifierform, 'form': form}) def submissions(request): """ Main method for viewing Submissions. """ if request.method == 'POST': form = SubmissionSearchForm(request.POST) if form.is_valid() and form.has_changed(): submission_search_list = Submission.objects.filter( title__icontains=form.cleaned_data['title_keyword'], author_list__icontains=form.cleaned_data['author'], abstract__icontains=form.cleaned_data['abstract_keyword'], ).exclude(status__in=['unassigned', 'assignment_failed'], ).order_by('-submission_date') else: submission_search_list = [] else: form = SubmissionSearchForm() submission_search_list = [] submission_recent_list = Submission.objects.filter( latest_activity__gte=timezone.now() + datetime.timedelta(days=-28) ).exclude(status__in=['unassigned', 'assignment_failed'] ).exclude(is_current=False).order_by('-submission_date') context = {'form': form, 'submission_search_list': submission_search_list, 'submission_recent_list': submission_recent_list } return render(request, 'submissions/submissions.html', context) def browse(request, discipline, nrweeksback): if request.method == 'POST': form = SubmissionSearchForm(request.POST) if form.is_valid() and form.has_changed(): submission_search_list = Submission.objects.filter( title__icontains=form.cleaned_data['title_keyword'], author_list__icontains=form.cleaned_data['author'], abstract__icontains=form.cleaned_data['abstract_keyword'], ).exclude(status__in=['unassigned', 'assignment_failed'], ).order_by('-submission_date') else: submission_search_list = [] context = {'form': form, 'submission_search_list': submission_search_list } return HttpResponseRedirect(request, 'submissions/submissions.html', context) else: form = SubmissionSearchForm() submission_browse_list = Submission.objects.filter( discipline=discipline, latest_activity__gte=timezone.now() + datetime.timedelta(weeks=-int(nrweeksback)) ).exclude(status__in=['unassigned', 'assignment_failed'] ).exclude(is_current=False).order_by('-submission_date') context = {'form': form, 'discipline': discipline, 'nrweeksback': nrweeksback, 'submission_browse_list': submission_browse_list } return render(request, 'submissions/submissions.html', context) def submission_detail_wo_vn_nr(request, arxiv_identifier_wo_vn_nr): submission = get_object_or_404(Submission, arxiv_identifier_wo_vn_nr=arxiv_identifier_wo_vn_nr, is_current=True) return(submission_detail(request, submission.arxiv_identifier_w_vn_nr)) def submission_detail(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) other_versions = Submission.objects.filter( arxiv_identifier_wo_vn_nr=submission.arxiv_identifier_wo_vn_nr ).exclude(pk=submission.id) comments = submission.comment_set.all() if request.method == 'POST': form = CommentForm(request.POST) if form.is_valid(): author = Contributor.objects.get(user=request.user) newcomment = Comment ( submission = submission, author = author, is_rem = form.cleaned_data['is_rem'], is_que = form.cleaned_data['is_que'], is_ans = form.cleaned_data['is_ans'], is_obj = form.cleaned_data['is_obj'], is_rep = form.cleaned_data['is_rep'], is_val = form.cleaned_data['is_val'], is_lit = form.cleaned_data['is_lit'], is_sug = form.cleaned_data['is_sug'], comment_text = form.cleaned_data['comment_text'], remarks_for_editors = form.cleaned_data['remarks_for_editors'], date_submitted = timezone.now(), ) newcomment.save() author.nr_comments = Comment.objects.filter(author=author).count() author.save() request.session['arxiv_identifier_w_vn_nr'] = submission.arxiv_identifier_w_vn_nr return HttpResponseRedirect(reverse('comments:comment_submission_ack')) else: form = CommentForm() reports = submission.report_set.all() try: author_replies = Comment.objects.filter(submission=submission, is_author_reply=True) except Comment.DoesNotExist: author_replies = () # To check in template whether the user can submit a report: try: is_author = request.user.contributor in submission.authors.all() is_author_unchecked = (not is_author and not (request.user.contributor in submission.authors_false_claims.all()) and (request.user.last_name in submission.author_list)) except AttributeError: is_author = False is_author_unchecked = False try: recommendation = EICRecommendation.objects.get(submission=submission) except EICRecommendation.DoesNotExist: recommendation = None context = {'submission': submission, 'other_versions': other_versions, 'recommendation': recommendation, 'comments': (comments.filter(status__gte=1, is_author_reply=False) .order_by('-date_submitted')), 'invited_reports': reports.filter(status__gte=1, invited=True), 'contributed_reports': reports.filter(status__gte=1, invited=False), 'author_replies': author_replies, 'form': form, 'is_author': is_author, 'is_author_unchecked': is_author_unchecked} return render(request, 'submissions/submission_detail.html', context) ###################### # Editorial workflow # ###################### @login_required @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) def editorial_workflow(request): """ Summary page for Editorial Fellows, containing a digest of the actions to take to handle Submissions. """ return render(request, 'submissions/editorial_workflow.html') @login_required @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) def pool(request): """ The Submissions pool contains all submissions which are undergoing the editorial process, from submission to publication acceptance or rejection. All members of the Editorial College have access. """ submissions_in_pool=(Submission.objects.all() .exclude(status__in=SUBMISSION_STATUS_OUT_OF_POOL) .exclude(is_current=False) .order_by('-submission_date')) contributor = Contributor.objects.get(user=request.user) assignments_to_consider = EditorialAssignment.objects.filter( to=contributor, accepted=None, deprecated=False) consider_assignment_form = ConsiderAssignmentForm() recs_to_vote_on = EICRecommendation.objects.all().exclude( recommendation=-1).exclude(recommendation=-2) rec_vote_form = RecommendationVoteForm() context = {'submissions_in_pool': submissions_in_pool, 'assignments_to_consider': assignments_to_consider, 'consider_assignment_form': consider_assignment_form, 'recs_to_vote_on': recs_to_vote_on, 'rec_vote_form': rec_vote_form} return render(request, 'submissions/pool.html', context) @login_required @permission_required('scipost.can_assign_submissions', raise_exception=True) def assign_submission(request, arxiv_identifier_w_vn_nr): submission_to_assign = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) #form = AssignSubmissionForm(discipline=submission_to_assign.discipline, expertise=submission_to_assign.subject_area) # reactivate later on form = AssignSubmissionForm(discipline=submission_to_assign.discipline) context = {'submission_to_assign': submission_to_assign, 'form': form} return render(request, 'submissions/assign_submission.html', context) @login_required @permission_required('scipost.can_assign_submissions', raise_exception=True) def assign_submission_ack(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) if request.method == 'POST': form = AssignSubmissionForm(request.POST, discipline=submission.discipline) if form.is_valid(): suggested_editor_in_charge = form.cleaned_data['editor_in_charge'] if not suggested_editor_in_charge.is_currently_available(): errormessage = ('This Fellow is marked as currently unavailable. ' 'Please go back and select another one.') return render(request, 'scipost/error.html', {'errormessage': errormessage}) ed_assignment = EditorialAssignment(submission=submission, to=suggested_editor_in_charge, date_created=timezone.now()) ed_assignment.save() email_text = ('Dear ' + title_dict[ed_assignment.to.title] + ' ' + ed_assignment.to.user.last_name + ', \n\nWe have received a Submission to SciPost ' + 'for which we would like you to consider becoming Editor-in-charge:\n\n' + submission.title + ' by ' + submission.author_list + '.' + '\n\nPlease visit https://scipost.org/submissions/pool ' + 'in order to accept or decline (it is important for you to inform us ' 'even if you decline, since this affects the result ' 'of the pre-screening process). ' 'Note that this assignment request is automatically ' 'deprecated if another Fellow ' 'takes charge of this Submission or if pre-screening ' 'fails in the meantime.' '\n\nMany thanks in advance for your collaboration,' + '\n\nThe SciPost Team.') emailmessage = EmailMessage( 'SciPost: potential Submission assignment', email_text, 'SciPost Editorial Admin <submissions@scipost.org>', [ed_assignment.to.user.email], ['submissions@scipost.org'], reply_to=['submissions@scipost.org']) emailmessage.send(fail_silently=False) context = {} return render(request, 'submissions/assign_submission_ack.html', context) @login_required @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) @transaction.atomic def accept_or_decline_assignment_ack(request, assignment_id): contributor = Contributor.objects.get(user=request.user) assignment = get_object_or_404 (EditorialAssignment, pk=assignment_id) errormessage = None if assignment.submission.status == 'assignment_failed': errormessage = 'This Submission has failed pre-screening and has been rejected.' context = {'errormessage': errormessage} return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) if assignment.submission.editor_in_charge: errormessage = (title_dict[assignment.submission.editor_in_charge.title] + ' ' + assignment.submission.editor_in_charge.user.last_name + ' has already agreed to be Editor-in-charge of this Submission.') context = {'errormessage': errormessage} return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) if request.method == 'POST': form = ConsiderAssignmentForm(request.POST) if form.is_valid(): assignment.date_answered = timezone.now() if form.cleaned_data['accept'] == 'True': assignment.accepted = True assignment.to = contributor assignment.submission.status = 'EICassigned' assignment.submission.editor_in_charge = contributor assignment.submission.open_for_reporting = True deadline = timezone.now() + datetime.timedelta(days=28) # for papers if assignment.submission.submitted_to_journal == 'SciPost Physics Lecture Notes': deadline += datetime.timedelta(days=28) assignment.submission.reporting_deadline = deadline assignment.submission.open_for_commenting = True assignment.submission.latest_activity = timezone.now() SubmissionUtils.load({'assignment': assignment}) SubmissionUtils.deprecate_other_assignments() assign_perm('can_take_editorial_actions', contributor.user, assignment.submission) ed_admins = Group.objects.get(name='Editorial Administrators') assign_perm('can_take_editorial_actions', ed_admins, assignment.submission) SubmissionUtils.send_EIC_appointment_email() SubmissionUtils.send_author_prescreening_passed_email() else: assignment.accepted = False assignment.refusal_reason = form.cleaned_data['refusal_reason'] assignment.submission.status = 'unassigned' assignment.save() assignment.submission.save() context = {'assignment': assignment} return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) @login_required @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) @transaction.atomic def volunteer_as_EIC(request, arxiv_identifier_w_vn_nr): """ Called when a Fellow volunteers while perusing the submissions pool. This is an adapted version of the accept_or_decline_assignment_ack method. """ submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) errormessage = None if submission.status == 'assignment_failed': errormessage = 'This Submission has failed pre-screening and has been rejected.' context = {'errormessage': errormessage} return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) if submission.editor_in_charge: errormessage = (title_dict[submission.editor_in_charge.title] + ' ' + submission.editor_in_charge.user.last_name + ' has already agreed to be Editor-in-charge of this Submission.') context = {'errormessage': errormessage} return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) contributor = Contributor.objects.get(user=request.user) assignment = EditorialAssignment(submission=submission, to=contributor, accepted=True, date_created=timezone.now(), date_answered=timezone.now()) deadline = timezone.now() + datetime.timedelta(days=28) # for papers if submission.submitted_to_journal == 'SciPost Physics Lecture Notes': deadline += datetime.timedelta(days=28) submission.status = 'EICassigned' submission.editor_in_charge = contributor submission.open_for_reporting = True submission.reporting_deadline = deadline submission.open_for_commenting = True submission.latest_activity = timezone.now() assignment.save() submission.save() SubmissionUtils.load({'assignment': assignment}) SubmissionUtils.deprecate_other_assignments() assign_perm('can_take_editorial_actions', contributor.user, submission) ed_admins = Group.objects.get(name='Editorial Administrators') assign_perm('can_take_editorial_actions', ed_admins, submission) SubmissionUtils.send_EIC_appointment_email() context = {'assignment': assignment} return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) @login_required @permission_required('scipost.can_assign_submissions', raise_exception=True) @transaction.atomic def assignment_failed(request, arxiv_identifier_w_vn_nr): """ No Editorial Fellow has accepted or volunteered to become Editor-in-charge. The submission is rejected. This method is called from pool.html by an Editorial Administrator. """ submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) submission.status = 'assignment_failed' submission.latest_activity = timezone.now() submission.save() SubmissionUtils.load({'submission': submission}) SubmissionUtils.deprecate_all_assignments() SubmissionUtils.assignment_failed_email_authors() context = {'submission': submission} return render(request, 'submissions/assignment_failed_ack.html', context) @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) def editorial_page(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) other_versions = Submission.objects.filter( arxiv_identifier_wo_vn_nr=submission.arxiv_identifier_wo_vn_nr ).exclude(pk=submission.id) ref_invitations = RefereeInvitation.objects.filter(submission=submission) nr_reports_to_vet = (Report.objects .filter(status=0, submission__editor_in_charge=request.user.contributor) .count()) communications = (EditorialCommunication.objects .filter(submission=submission).order_by('timestamp')) try: recommendation = EICRecommendation.objects.get(submission=submission) except EICRecommendation.DoesNotExist: recommendation = None context = {'submission': submission, 'other_versions': other_versions, 'recommendation': recommendation, 'ref_invitations': ref_invitations, 'nr_reports_to_vet': nr_reports_to_vet, 'communications': communications} return render(request, 'submissions/editorial_page.html', context) @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) def select_referee(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) queryresults = '' if request.method == 'POST': ref_search_form = RefereeSelectForm(request.POST) if ref_search_form.is_valid(): contributors_found = Contributor.objects.filter( user__last_name__icontains=ref_search_form.cleaned_data['last_name']) # Check for recent co-authorship (thus referee disqualification) if submission.metadata is not None: sub_auth_boolean_str = '((' + (submission .metadata['entries'][0]['authors'][0]['name'] .split()[-1]) for author in submission.metadata['entries'][0]['authors'][1:]: sub_auth_boolean_str += '+OR+' + author['name'].split()[-1] sub_auth_boolean_str += ')+AND+' search_str = sub_auth_boolean_str + ref_search_form.cleaned_data['last_name'] + ')' queryurl = ('http://export.arxiv.org/api/query?search_query=au:%s' % search_str + '&sortBy=submittedDate&sortOrder=descending' '&max_results=5') arxivquery = feedparser.parse(queryurl) queryresults = arxivquery else: ref_search_form = RefereeSelectForm() contributors_found = None ref_recruit_form = RefereeRecruitmentForm() context = {'submission': submission, 'ref_search_form': ref_search_form, 'contributors_found': contributors_found, 'ref_recruit_form': ref_recruit_form, 'queryresults': queryresults} return render(request, 'submissions/select_referee.html', context) @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) @transaction.atomic def recruit_referee(request, arxiv_identifier_w_vn_nr): """ If the Editor-in-charge does not find the desired referee among Contributors (otherwise, the method send_refereeing_invitation below is used instead), he/she can invite somebody by providing name + contact details. This function emails a registration invitation to this person. The pending refereeing invitation is then recognized upon registration, using the invitation token. """ submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) if request.method == 'POST': ref_recruit_form = RefereeRecruitmentForm(request.POST) if ref_recruit_form.is_valid(): # TODO check if email already taken ref_invitation = RefereeInvitation( submission=submission, title=ref_recruit_form.cleaned_data['title'], first_name=ref_recruit_form.cleaned_data['first_name'], last_name=ref_recruit_form.cleaned_data['last_name'], email_address=ref_recruit_form.cleaned_data['email_address'], date_invited=timezone.now(), invited_by = request.user.contributor) ref_invitation.save() # Create and send a registration invitation ref_inv_message_head = ('On behalf of the Editor-in-charge ' + title_dict[submission.editor_in_charge.title] + ' ' + submission.editor_in_charge.user.last_name + ', we would like to invite you to referee a Submission to ' + journals_submit_dict[submission.submitted_to_journal] + ', namely\n\n' + submission.title + '\nby ' + submission.author_list + '.') reg_invitation = RegistrationInvitation ( title = ref_recruit_form.cleaned_data['title'], first_name = ref_recruit_form.cleaned_data['first_name'], last_name = ref_recruit_form.cleaned_data['last_name'], email = ref_recruit_form.cleaned_data['email_address'], invitation_type = 'R', invited_by = request.user.contributor, message_style = 'F', personal_message = ref_inv_message_head, ) reg_invitation.save() Utils.load({'invitation': reg_invitation}) Utils.send_registration_invitation_email() # Copy the key to the refereeing invitation: ref_invitation.invitation_key = reg_invitation.invitation_key ref_invitation.save() return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) @transaction.atomic def send_refereeing_invitation(request, arxiv_identifier_w_vn_nr, contributor_id): """ This method is called by the EIC from the submission's editorial_page, in the case where the referee is an identified Contributor. For a referee who isn't a Contributor yet, the method recruit_referee above is called instead. """ submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) contributor = get_object_or_404(Contributor, pk=contributor_id) if not contributor.is_currently_available(): errormessage = ('This Contributor is marked as currently unavailable. ' 'Please go back and select another referee.') return render(request, 'scipost/error.html', {'errormessage': errormessage}) invitation = RefereeInvitation(submission=submission, referee=contributor, title=contributor.title, first_name=contributor.user.first_name, last_name=contributor.user.last_name, email_address=contributor.user.email, # the key is only used for inviting unregistered users invitation_key='notused', date_invited=timezone.now(), invited_by=request.user.contributor) invitation.save() SubmissionUtils.load({'invitation': invitation}) SubmissionUtils.send_refereeing_invitation_email() return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) def ref_invitation_reminder(request, arxiv_identifier_w_vn_nr, invitation_id): """ This method is used by the Editor-in-charge from the editorial_page when a referee has been invited but hasn't answered yet. It can be used for registered as well as unregistered referees. """ invitation = get_object_or_404 (RefereeInvitation, pk=invitation_id) invitation.nr_reminders += 1 invitation.date_last_reminded = timezone.now() invitation.save() SubmissionUtils.load({'invitation': invitation}) SubmissionUtils.send_ref_reminder_email() return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @login_required @permission_required('scipost.can_referee', raise_exception=True) def accept_or_decline_ref_invitations(request): contributor = Contributor.objects.get(user=request.user) invitation = RefereeInvitation.objects.filter(referee=contributor, accepted=None).first() form = ConsiderRefereeInvitationForm() context = {'invitation_to_consider': invitation, 'form': form} return render(request, 'submissions/accept_or_decline_ref_invitations.html', context) @login_required @permission_required('scipost.can_referee', raise_exception=True) def accept_or_decline_ref_invitation_ack(request, invitation_id): contributor = Contributor.objects.get(user=request.user) invitation = get_object_or_404 (RefereeInvitation, pk=invitation_id) if request.method == 'POST': form = ConsiderRefereeInvitationForm(request.POST) if form.is_valid(): invitation.date_responded = timezone.now() if form.cleaned_data['accept'] == 'True': invitation.accepted = True else: invitation.accepted = False invitation.refusal_reason = form.cleaned_data['refusal_reason'] invitation.save() SubmissionUtils.load({'invitation': invitation}) SubmissionUtils.email_referee_response_to_EIC() context = {'invitation': invitation} return render(request, 'submissions/accept_or_decline_ref_invitation_ack.html', context) @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) def extend_refereeing_deadline(request, arxiv_identifier_w_vn_nr, days): submission = get_object_or_404 (Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) submission.reporting_deadline += datetime.timedelta(days=int(days)) submission.open_for_reporting = True submission.open_for_commenting = True submission.status = 'EICassigned' submission.latest_activity = timezone.now() submission.save() return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) def close_refereeing_round(request, arxiv_identifier_w_vn_nr): """ Called by the Editor-in-charge when a satisfactory number of reports have been gathered. Automatically emails the authors to ask them if they want to round off any replies to reports or comments before the editorial recommendation is formulated. """ submission = get_object_or_404 (Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) submission.open_for_reporting = False submission.open_for_commenting = False submission.status = 'review_closed' submission.reporting_deadline = timezone.now() submission.latest_activity = timezone.now() submission.save() return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @login_required def communication(request, arxiv_identifier_w_vn_nr, comtype, referee_id=None): """ Communication between editor-in-charge, author or referee occurring during the submission refereeing. """ submission = get_object_or_404 (Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) errormessage = None if not comtype in ed_comm_choices_dict.keys(): errormessage = 'Unknown type of cummunication.' # TODO: Verify that this is requested by an authorized contributor (eic, ref, author) elif (comtype in ['EtoA', 'EtoR', 'EtoS'] and not request.user.has_perm('can_take_editorial_actions', submission)): errormessage = 'Only the Editor-in-charge can perform this action.' elif (comtype in ['AtoE'] and not (request.user.contributor == submission.submitted_by)): errormessage = 'Only the corresponding author can perform this action.' elif (comtype in ['RtoE'] and not (RefereeInvitation.objects .filter(submission=submission, referee=request.user.contributor).exists())): errormessage = 'Only invited referees for this Submission can perform this action.' elif (comtype in ['StoE'] and not request.user.groups.filter(name='Editorial Administrators').exists()): errormessage = 'Only Editorial Administrators can perform this action.' if errormessage is not None: context = {'errormessage': errormessage, 'comtype': comtype} return render(request, 'submissions/communication.html', context) if request.method == 'POST': form = EditorialCommunicationForm(request.POST) if form.is_valid(): communication = EditorialCommunication(submission=submission, comtype=comtype, timestamp=timezone.now(), text=form.cleaned_data['text']) if referee_id is not None: referee = get_object_or_404(Contributor, pk=referee_id) communication.referee = referee communication.save() SubmissionUtils.load({'communication': communication}) SubmissionUtils.send_communication_email() if comtype == 'EtoA' or comtype == 'EtoR' or comtype == 'EtoS': return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) elif comtype == 'AtoE' or comtype == 'RtoE': return redirect(reverse('scipost:personal_page')) elif comtype == 'StoE': return redirect(reverse('submissions:pool')) else: form = EditorialCommunicationForm() context = {'submission': submission, 'comtype': comtype, 'referee_id': referee_id, 'form': form} return render(request, 'submissions/communication.html', context) @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) @transaction.atomic def eic_recommendation(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404 (Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) if request.method == 'POST': form = EICRecommendationForm(request.POST) if form.is_valid(): #recommendation = form.save() recommendation = EICRecommendation( submission = submission, date_submitted = timezone.now(), remarks_for_authors = form.cleaned_data['remarks_for_authors'], requested_changes = form.cleaned_data['requested_changes'], remarks_for_editorial_college = form.cleaned_data['remarks_for_editorial_college'], recommendation = form.cleaned_data['recommendation'], voting_deadline = timezone.now() + datetime.timedelta(days=7), ) recommendation.save() # If recommendation is to accept or reject, # it is forwarded to the Editorial College for voting # If it is to carry out minor or major revisions, # it is returned to the Author who is asked to resubmit if (recommendation.recommendation == 1 or recommendation.recommendation == 2 or recommendation.recommendation == 3 or recommendation.recommendation == -3): submission.status = 'put_to_EC_voting' elif (recommendation.recommendation == -1 or recommendation.recommendation == -2): submission.status = 'revision_requested' SubmissionUtils.load({'submission': submission, 'recommendation': recommendation}) SubmissionUtils.send_author_revision_requested_email() submission.open_for_reporting = False submission.save() # The EIC has fulfilled this editorial assignment. assignment = get_object_or_404(EditorialAssignment, submission=submission, to=request.user.contributor) assignment.completed = True assignment.save() return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) else: form = EICRecommendationForm() context = {'submission': submission, 'form': form} return render(request, 'submissions/eic_recommendation.html', context) ########### # Reports ########### @login_required @permission_required('scipost.can_referee', raise_exception=True) @transaction.atomic def submit_report(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404 (Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) # Check whether the user can submit a report: is_author = request.user.contributor in submission.authors.all() is_author_unchecked = (not is_author and not (request.user.contributor in submission.authors_false_claims.all()) and (request.user.last_name in submission.author_list)) errormessage = None if timezone.now() > submission.reporting_deadline: errormessage = ('The reporting deadline has passed. You cannot submit' ' a Report anymore.') if is_author: errormessage = 'You are an author of this Submission and cannot submit a Report.' if is_author_unchecked: errormessage = ('The system flagged you as a potential author of this Submission. ' 'Please go to your personal page under the Submissions tab to clarify this.') if errormessage: context = {'errormessage': errormessage} return render(request, 'submissions/submit_report_ack.html', context) if request.method == 'POST': form = ReportForm(request.POST) if form.is_valid(): author = Contributor.objects.get(user=request.user) invited = RefereeInvitation.objects.filter(submission=submission, referee=request.user.contributor).exists() if invited: invitation = RefereeInvitation.objects.get(submission=submission, referee=request.user.contributor) invitation.fulfilled = True invitation.save() flagged = False if submission.referees_flagged is not None: if author.user.last_name in submission.referees_flagged: flagged = True newreport = Report ( submission = submission, author = author, invited = invited, flagged = flagged, qualification = form.cleaned_data['qualification'], strengths = form.cleaned_data['strengths'], weaknesses = form.cleaned_data['weaknesses'], report = form.cleaned_data['report'], requested_changes = form.cleaned_data['requested_changes'], validity = form.cleaned_data['validity'], significance = form.cleaned_data['significance'], originality = form.cleaned_data['originality'], clarity = form.cleaned_data['clarity'], formatting = form.cleaned_data['formatting'], grammar = form.cleaned_data['grammar'], recommendation = form.cleaned_data['recommendation'], remarks_for_editors = form.cleaned_data['remarks_for_editors'], anonymous = form.cleaned_data['anonymous'], date_submitted = timezone.now(), ) newreport.save() author.nr_reports = Report.objects.filter(author=author).count() author.save() SubmissionUtils.load({'report': newreport}) SubmissionUtils.email_EIC_report_delivered() request.session['arxiv_identifier_w_vn_nr'] = arxiv_identifier_w_vn_nr return HttpResponseRedirect(reverse('submissions:submit_report_ack')) else: form = ReportForm() context = {'submission': submission, 'form': form } return render(request, 'submissions/submit_report.html', context) @login_required @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) def vet_submitted_reports(request): contributor = Contributor.objects.get(user=request.user) report_to_vet = Report.objects.filter(status=0, submission__editor_in_charge=contributor).first() form = VetReportForm() context = {'contributor': contributor, 'report_to_vet': report_to_vet, 'form': form } return(render(request, 'submissions/vet_submitted_reports.html', context)) @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) @transaction.atomic def vet_submitted_report_ack(request, report_id): if request.method == 'POST': form = VetReportForm(request.POST) report = Report.objects.get(pk=report_id) if form.is_valid(): report.vetted_by = request.user.contributor if form.cleaned_data['action_option'] == '1': # accept the report as is report.status = 1 report.save() report.submission.latest_activity = timezone.now() report.submission.save() elif form.cleaned_data['action_option'] == '2': # the report is simply rejected report.status = form.cleaned_data['refusal_reason'] report.save() # email report author SubmissionUtils.load({'report': report, 'email_response': form.cleaned_data['email_response_field']}) SubmissionUtils.acknowledge_report_email() # email report author, bcc EIC if report.status == 1: SubmissionUtils.send_author_report_received_email() context = {'submission': report.submission} return render(request, 'submissions/vet_submitted_report_ack.html', context) @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) @transaction.atomic def vote_on_rec(request, rec_id): if request.method == 'POST': recommendation = get_object_or_404(EICRecommendation, id=rec_id) form = RecommendationVoteForm(request.POST) if form.is_valid(): if form.cleaned_data['vote'] == 'agree': recommendation.voted_for.add(request.user.contributor) recommendation.voted_against.remove(request.user.contributor) recommendation.voted_abstain.remove(request.user.contributor) elif form.cleaned_data['vote'] == 'disagree': recommendation.voted_for.remove(request.user.contributor) recommendation.voted_against.add(request.user.contributor) recommendation.voted_abstain.remove(request.user.contributor) elif form.cleaned_data['vote'] == 'abstain': recommendation.voted_for.remove(request.user.contributor) recommendation.voted_against.remove(request.user.contributor) recommendation.voted_abstain.add(request.user.contributor) recommendation.save() return redirect(reverse('submissions:pool')) return redirect(reverse('submissions:pool'))