From 9612b926158e591891c519687426d354a1da4a91 Mon Sep 17 00:00:00 2001 From: Jorran de Wit <jorrandewit@outlook.com> Date: Mon, 14 May 2018 22:12:46 +0200 Subject: [PATCH] Try direct-recommendation 1. --- submissions/forms.py | 79 +++++++- submissions/managers.py | 4 + .../submissions/pool/submission_li.html | 2 +- .../pool/editorial_assignment.html | 61 ++++++ submissions/urls.py | 6 + submissions/views.py | 191 +++++++++++------- 6 files changed, 265 insertions(+), 78 deletions(-) create mode 100644 submissions/templates/submissions/pool/editorial_assignment.html diff --git a/submissions/forms.py b/submissions/forms.py index a1bc8675e..8c2be486e 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -17,7 +17,7 @@ from .constants import ( EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS, SUBMISSION_STATUS, PUT_TO_VOTING, CYCLE_UNDETERMINED, SUBMISSION_CYCLE_CHOICES, REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3, STATUS_VETTED, REPORT_MINOR_REV, REPORT_MAJOR_REV, REPORT_REJECT, STATUS_ACCEPTED, DECISION_FIXED, DEPRECATED, - STATUS_EIC_ASSIGNED) + STATUS_EIC_ASSIGNED, CYCLE_DEFAULT, CYCLE_DIRECT_REC) from . import exceptions, helpers from .models import ( Submission, RefereeInvitation, Report, EICRecommendation, EditorialAssignment, @@ -481,8 +481,8 @@ class SubmissionPrescreeningForm(forms.ModelForm): # Editorial workflow # ###################### -class EditorialAssignmentForm(forms.ModelForm): - """Create new EditorialAssignment for Submission.""" +class InviteEditorialAssignmentForm(forms.ModelForm): + """Invite new Fellow; create EditorialAssignment for Submission.""" class Meta: model = EditorialAssignment @@ -503,7 +503,80 @@ class EditorialAssignmentForm(forms.ModelForm): return super().save(commit) +class EditorialAssignmentForm(forms.ModelForm): + """Create and/or process new EditorialAssignment for Submission.""" + + DECISION_CHOICES = ( + ('accept', 'Accept'), + ('decline', 'Decline')) + CYCLE_CHOICES = ( + (CYCLE_DEFAULT, 'Normal refereeing cycle'), + (CYCLE_DIRECT_REC, 'Directly formulate Editorial Recommendation for rejection')) + + decision = forms.ChoiceField( + widget=forms.RadioSelect, choices=DECISION_CHOICES, + label="Are you willing to take charge of this Submission?") + refereeing_cycle = forms.ChoiceField( + widget=forms.RadioSelect, choices=CYCLE_CHOICES, initial=CYCLE_DEFAULT) + + class Meta: + model = EditorialAssignment + fields = ('refusal_reason',) + + def __init__(self, *args, **kwargs): + """Add related submission as argument.""" + self.submission = kwargs.pop('submission') + self.request = kwargs.pop('request', None) + super().__init__(*args, **kwargs) + if not self.instance.id: + del self.fields['decision'] + del self.fields['refusal_reason'] + + def save(self, commit=True): + """Save Submission to EditorialAssignment.""" + self.instance.submission = self.submission + self.instance.date_answered = timezone.now() + self.instance.to = self.request.user.contributor + + if self.cleaned_data['refereeing_cycle'] == CYCLE_DEFAULT: + deadline = timezone.now() + datetime.timedelta(days=28) + if self.instance.submission.submitted_to_journal == 'SciPostPhysLectNotes': + deadline += datetime.timedelta(days=28) + + # Update related Submission. + Submission.objects.filter(id=self.submission.id).update( + refereeing_cycle=CYCLE_DEFAULT, + status=STATUS_EIC_ASSIGNED, + editor_in_charge=self.request.user.contributor, + reporting_deadline=deadline, + open_for_reporting=True, + visible_public=True, + latest_activity=timezone.now()) + elif self.cleaned_data['refereeing_cycle'] == CYCLE_DIRECT_REC: + # Update related Submission. + Submission.objects.filter(id=self.submission.id).update( + refereeing_cycle=CYCLE_DIRECT_REC, + status=STATUS_EIC_ASSIGNED, + editor_in_charge=self.request.user.contributor, + reporting_deadline=timezone.now(), + open_for_reporting=False, + visible_public=False, + latest_activity=timezone.now()) + + if 'decision' not in self.cleaned_data or self.cleaned_data['decision'] == 'accept': + self.instance.accepted = True + # Deprecate old EditorialAssignments if Fellow accepts. + EditorialAssignment.objects.filter(submission=self.submission, accepted=None).exclude( + id=self.instance.id).update(deprecated=True) + else: + self.instance.accepted = False + self.instance.refusal_reason = self.cleaned_data['refusal_reason'] + return super().save(commit) + + class ConsiderAssignmentForm(forms.Form): + """Process open EditorialAssignment.""" + accept = forms.ChoiceField(widget=forms.RadioSelect, choices=ASSIGNMENT_BOOL, label="Are you willing to take charge of this Submission?") refusal_reason = forms.ChoiceField(choices=ASSIGNMENT_REFUSAL_REASONS, required=False) diff --git a/submissions/managers.py b/submissions/managers.py index b652be402..b73aad549 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -108,6 +108,10 @@ class SubmissionQuerySet(models.QuerySet): """Return submissions passed pre-screening, but unassigned.""" return self.filter(status=constants.STATUS_UNASSIGNED) + def without_eic(self): + """Return Submissions that still need Editorial Assignment.""" + return self.filter(status__in=[constants.STATUS_INCOMING, constants.STATUS_UNASSIGNED]) + def actively_refereeing(self): """Return submission currently in some point of the refereeing round.""" return self.filter(status=constants.STATUS_EIC_ASSIGNED) diff --git a/submissions/templates/partials/submissions/pool/submission_li.html b/submissions/templates/partials/submissions/pool/submission_li.html index 0c1c01854..b4c72bc84 100644 --- a/submissions/templates/partials/submissions/pool/submission_li.html +++ b/submissions/templates/partials/submissions/pool/submission_li.html @@ -25,7 +25,7 @@ <small class="text-muted">Editor-in-charge</small> <br> {% if submission.status == 'unassigned' %} - <span class="card-text text-danger">You can volunteer to become Editor-in-charge by <a href="{% url 'submissions:volunteer_as_EIC' submission.arxiv_identifier_w_vn_nr %}">clicking here</a>.</span> + <span class="card-text text-danger">You can volunteer to become Editor-in-charge by <a href="{% url 'submissions:editorial_assignment' submission.arxiv_identifier_w_vn_nr %}">clicking here</a>.</span> {% elif submission.editor_in_charge == request.user.contributor %} <strong>You are Editor-in-charge</strong> {% else %} diff --git a/submissions/templates/submissions/pool/editorial_assignment.html b/submissions/templates/submissions/pool/editorial_assignment.html new file mode 100644 index 000000000..e619e96d7 --- /dev/null +++ b/submissions/templates/submissions/pool/editorial_assignment.html @@ -0,0 +1,61 @@ +{% extends 'submissions/pool/base.html' %} + +{% load bootstrap %} +{% load guardian_tags %} +{% load scipost_extras %} +{% load submissions_extras %} + +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">Editorial Assignment</span> +{% endblock %} + +{% block pagetitle %}: Editorial Assignment{% endblock pagetitle %} + +{% block content %} + +<h1 class="highlight">Editorial Assignment</h1> +<h3 class="pt-0">Can you act as Editor-in-charge?{% if form.instance.id %} (see below to accept/decline){% endif %}</h3> +<br> + +<h3>Submission details</h3> +{% include 'partials/submissions/submission_summary.html' with submission=submission %} + +<br> +{% if form.instance.id %} + <h2 class="highlight">Accept or Decline this Assignment</h2> +{% else %} + <h2 class="highlight">Volunteer to become Editor-in-charge</h2> +{% endif %} +<h4 class="mb-2">By accepting, you will be required to start a refereeing round on the next screen.</h4> + +<form method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <p> + <strong>Clarification</strong> + <br> + If you choose the Normal refereeing cycle, you will be redirected to the Editorial Page to proceed further. The Submission will be publicly available and the authors will be informed that the refereeing process has started. + <br> + If you choose to directly formulate a Editorial Recommendation for rejection, the Submission will not become publicly available. After formulation of the Editorial Recommendation, it will put forward for voting as normal. + </p> + <input class="btn btn-primary" type="submit" value="Submit" /> +</form> + + +<script> + $(function() { + $('[name="decision"]').on('change', function() { + var val = $('[name="decision"]:checked').val(); + if(val == 'decline') { + $('[name="refusal_reason"]').closest('.form-group').show(); + $('[name="refereeing_cycle"]').closest('.form-group').hide(); + } else { + $('[name="refusal_reason"]').closest('.form-group').hide(); + $('[name="refereeing_cycle"]').closest('.form-group').show(); + } + }).trigger('change'); + }); +</script> + +{% endblock %} diff --git a/submissions/urls.py b/submissions/urls.py index ace829f43..5bc4cd1b7 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -62,6 +62,12 @@ urlpatterns = [ views.assign_submission, name='assign_submission'), url(r'^pool/assignment_request/(?P<assignment_id>[0-9]+)$', views.assignment_request, name='assignment_request'), + url(r'^pool/{regex}/editorial_assignment/$'.format( + regex=SUBMISSIONS_COMPLETE_REGEX), views.editorial_assignment, + name='editorial_assignment'), + url(r'^pool/{regex}/editorial_assignment/(?P<assignment_id>[0-9]+)/$'.format( + regex=SUBMISSIONS_COMPLETE_REGEX), views.editorial_assignment, + name='editorial_assignment'), url(r'^volunteer_as_EIC/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.volunteer_as_EIC, name='volunteer_as_EIC'), url(r'^assignment_failed/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), diff --git a/submissions/views.py b/submissions/views.py index f70391cd5..462bca46f 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -29,10 +29,10 @@ from .models import ( from .mixins import SubmissionAdminViewMixin from .forms import ( SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSearchForm, RecommendationVoteForm, - ConsiderAssignmentForm, EditorialAssignmentForm, SetRefereeingDeadlineForm, RefereeSelectForm, - RefereeRecruitmentForm, ConsiderRefereeInvitationForm, EditorialCommunicationForm, - EICRecommendationForm, ReportForm, VetReportForm, VotingEligibilityForm, - SubmissionCycleChoiceForm, ReportPDFForm, SubmissionReportsForm, iThenticateReportForm, + ConsiderAssignmentForm, InviteEditorialAssignmentForm, EditorialAssignmentForm, VetReportForm, + SetRefereeingDeadlineForm, RefereeSelectForm, iThenticateReportForm, VotingEligibilityForm, + RefereeRecruitmentForm, ConsiderRefereeInvitationForm, EditorialCommunicationForm, ReportForm, + SubmissionCycleChoiceForm, ReportPDFForm, SubmissionReportsForm, EICRecommendationForm, SubmissionPoolFilterForm, FixCollegeDecisionForm, SubmissionPrescreeningForm) from .utils import SubmissionUtils @@ -468,7 +468,7 @@ def assign_submission(request, arxiv_identifier_w_vn_nr): """ submission = get_object_or_404(Submission.objects.pool_editable(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) - form = EditorialAssignmentForm(request.POST or None, submission=submission) + form = InviteEditorialAssignmentForm(request.POST or None, submission=submission) if form.is_valid(): ed_assignment = form.save() @@ -490,81 +490,124 @@ def assign_submission(request, arxiv_identifier_w_vn_nr): @login_required @fellowship_required() -@transaction.atomic -def assignment_request(request, assignment_id): - """Process EditorialAssignment acceptance/rejection form or show if not submitted.""" - assignment = get_object_or_404(EditorialAssignment.objects.open(), - to=request.user.contributor, pk=assignment_id) - - errormessage = None - if assignment.submission.status == STATUS_ASSIGNMENT_FAILED: - # This status can be reached without assigned editor. - errormessage = 'This Submission has failed pre-screening and has been rejected.' - elif assignment.submission.editor_in_charge: - errormessage = (assignment.submission.editor_in_charge.get_title_display() + ' ' + - assignment.submission.editor_in_charge.user.last_name + - ' has already agreed to be Editor-in-charge of this Submission.') - - if errormessage: - # Don't open the assignment. - messages.warning(request, errormessage) - return redirect(reverse('submissions:pool')) +def editorial_assignment(request, arxiv_identifier_w_vn_nr, assignment_id=None): + """Editorial Assignment form view.""" + submission = get_object_or_404( + Submission.objects.without_eic(), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + + if assignment_id: + # Process existing EditorialAssignment. + assignment = get_object_or_404( + EditorialAssignment.objects.open(), to=request.user.contributor, pk=assignment_id) + else: + # Create new EditorialAssignment. + assignment = EditorialAssignment() - form = ConsiderAssignmentForm(request.POST or None) + form = EditorialAssignmentForm(request.POST or None, submission=submission, instance=assignment) if form.is_valid(): - assignment.date_answered = timezone.now() - if form.cleaned_data['accept'] == 'True': - submission = assignment.submission - - # Sign Editorial Assigment - EditorialAssignment.objects.filter(id=assignment.id).update( - accepted=True, to=request.user.contributor) - EditorialAssignment.objects.filter(submission=submission, accepted=None).exclude( - id=assignment.id).update(deprecated=True) - - # Update related Submission - submission = assignment.submission - submission.status = STATUS_EIC_ASSIGNED - submission.editor_in_charge = request.user.contributor - submission.open_for_reporting = True - deadline = timezone.now() + datetime.timedelta(days=28) # for papers - if submission.submitted_to_journal == 'SciPostPhysLectNotes': - deadline += datetime.timedelta(days=28) - submission.reporting_deadline = deadline - submission.open_for_commenting = True - submission.latest_activity = timezone.now() - submission.save() - - # Send out mails - SubmissionUtils.load({'assignment': assignment}) - SubmissionUtils.send_EIC_appointment_email() - SubmissionUtils.send_author_prescreening_passed_email() - - # Add SubmissionEvents - submission.add_general_event('The Editor-in-charge has been assigned.') - msg = 'Thank you for becoming Editor-in-charge of this submission.' - url = reverse('submissions:editorial_page', - args=(submission.arxiv_identifier_w_vn_nr,)) - else: - assignment.accepted = False - assignment.refusal_reason = form.cleaned_data['refusal_reason'] - assignment.submission.status = 'unassigned' - - # Save assignment and submission - assignment.save() - assignment.submission.save() - msg = 'Thank you for considering' - url = reverse('submissions:pool') - - # Form submitted, redirect user - messages.success(request, msg) - return redirect(url) + form.save() + return redirect('submissions:pool') context = { + 'form': form, + 'submission': submission, 'assignment': assignment, - 'form': form } - return render(request, 'submissions/pool/assignment_request.html', context) + return render(request, 'submissions/pool/editorial_assignment.html', context) + + +@login_required +@fellowship_required() +def assignment_request(request, assignment_id): + """Redirect to Editorial Assignment form view. + + Exists for historical reasons; email are send with this url construction. + """ + assignment = get_object_or_404(EditorialAssignment.objects.open(), + to=request.user.contributor, pk=assignment_id) + return redirect(reverse('submissions:editorial_assignment', kwargs={ + 'arxiv_identifier_w_vn_nr': assignment.submission.arxiv_identifier_w_vn_nr, + 'assignment_id': assignment.id + })) + + +# @login_required +# @fellowship_required() +# @transaction.atomic +# def assignment_request(request, assignment_id): +# """Process EditorialAssignment acceptance/rejection form or show if not submitted.""" +# assignment = get_object_or_404(EditorialAssignment.objects.open(), +# to=request.user.contributor, pk=assignment_id) +# +# errormessage = None +# if assignment.submission.status == STATUS_ASSIGNMENT_FAILED: +# # This status can be reached without assigned editor. +# errormessage = 'This Submission has failed pre-screening and has been rejected.' +# elif assignment.submission.editor_in_charge: +# errormessage = (assignment.submission.editor_in_charge.get_title_display() + ' ' + +# assignment.submission.editor_in_charge.user.last_name + +# ' has already agreed to be Editor-in-charge of this Submission.') +# +# if errormessage: +# # Don't open the assignment. +# messages.warning(request, errormessage) +# return redirect(reverse('submissions:pool')) +# +# form = ConsiderAssignmentForm(request.POST or None) +# if form.is_valid(): +# assignment.date_answered = timezone.now() +# if form.cleaned_data['accept'] == 'True': + # submission = assignment.submission + + # # Sign Editorial Assigment + # EditorialAssignment.objects.filter(id=assignment.id).update( + # accepted=True, to=request.user.contributor) + # EditorialAssignment.objects.filter(submission=submission, accepted=None).exclude( + # id=assignment.id).update(deprecated=True) +# + # # Update related Submission + # submission = assignment.submission + # submission.status = STATUS_EIC_ASSIGNED + # submission.editor_in_charge = request.user.contributor + # submission.open_for_reporting = True + # deadline = timezone.now() + datetime.timedelta(days=28) # for papers + # if submission.submitted_to_journal == 'SciPostPhysLectNotes': + # deadline += datetime.timedelta(days=28) + # submission.reporting_deadline = deadline + # submission.open_for_commenting = True + # submission.latest_activity = timezone.now() + # submission.save() +# +# # Send out mails +# SubmissionUtils.load({'assignment': assignment}) +# SubmissionUtils.send_EIC_appointment_email() +# SubmissionUtils.send_author_prescreening_passed_email() +# +# # Add SubmissionEvents +# submission.add_general_event('The Editor-in-charge has been assigned.') +# msg = 'Thank you for becoming Editor-in-charge of this submission.' +# url = reverse('submissions:editorial_page', +# args=(submission.arxiv_identifier_w_vn_nr,)) +# else: + # assignment.accepted = False + # assignment.refusal_reason = form.cleaned_data['refusal_reason'] + # assignment.submission.status = 'unassigned' +# +# # Save assignment and submission +# assignment.save() +# assignment.submission.save() +# msg = 'Thank you for considering' +# url = reverse('submissions:pool') +# +# # Form submitted, redirect user +# messages.success(request, msg) +# return redirect(url) +# +# context = { +# 'assignment': assignment, +# 'form': form +# } +# return render(request, 'submissions/pool/assignment_request.html', context) @login_required -- GitLab