From 8ecb592a8d9064ede900df44afd231ef9cfc918a Mon Sep 17 00:00:00 2001
From: "J.-S. Caux" <J.S.Caux@uva.nl>
Date: Wed, 16 Oct 2019 20:15:30 +0200
Subject: [PATCH] Add SubmissionTiering

---
 submissions/constants.py                      | 25 ++++++++++
 submissions/forms.py                          | 12 +++--
 .../migrations/0071_submissiontiering.py      | 26 +++++++++++
 .../migrations/0072_populate_tiering.py       | 37 +++++++++++++++
 submissions/models.py                         | 13 +++++-
 .../submissions/pool/recommendation.html      | 46 +++++++++++++++++++
 submissions/views.py                          | 18 ++++++--
 7 files changed, 170 insertions(+), 7 deletions(-)
 create mode 100644 submissions/migrations/0071_submissiontiering.py
 create mode 100644 submissions/migrations/0072_populate_tiering.py

diff --git a/submissions/constants.py b/submissions/constants.py
index de2edbe3e..c2a8fe93c 100644
--- a/submissions/constants.py
+++ b/submissions/constants.py
@@ -196,6 +196,31 @@ EVENT_TYPES = (
     (EVENT_FOR_AUTHOR, 'Comment for author'),
 )
 
+
+
+# Editorial recommendations
+EIC_REC_PUBLISH = 1
+EIC_REC_MINOR_REVISION = -1
+EIC_REC_MAJOR_REVISION = -2
+EIC_REC_REJECT = -3
+EIC_REC_CHOICES = (
+    (EIC_REC_PUBLISH, 'Publish'),
+    (EIC_REC_MINOR_REVISION, 'Ask for minor revision'),
+    (EIC_REC_MAJOR_REVISION, 'Ask for major revision'),
+    (EIC_REC_REJECT, 'Reject'),
+)
+
+# Tiering
+TIER_I = 1
+TIER_II = 2
+TIER_III = 3
+SUBMISSION_TIERS = (
+    (TIER_I, 'Tier I (surpasses expectations and criteria for this Journal; among top 10%)'),
+    (TIER_II, 'Tier II (easily meets expectations and criteria for this Journal; among top 50%)'),
+    (TIER_III, 'Tier III (meets expectations and criteria for this Journal)'),
+)
+
+
 VOTING_IN_PREP, PUT_TO_VOTING, VOTE_COMPLETED = 'voting_in_prep', 'put_to_voting', 'vote_completed'
 DECISION_FIXED, DEPRECATED = 'decision_fixed', 'deprecated'
 EIC_REC_STATUSES = (
diff --git a/submissions/forms.py b/submissions/forms.py
index 9aeb6fc59..54619bfdc 100644
--- a/submissions/forms.py
+++ b/submissions/forms.py
@@ -21,10 +21,12 @@ 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,
-    REPORT_MINOR_REV, REPORT_MAJOR_REV, REPORT_REJECT, REPORT_REC,
+    REPORT_MINOR_REV, REPORT_MAJOR_REV, REPORT_REJECT,
+    EIC_REC_CHOICES, SUBMISSION_TIERS,
     STATUS_VETTED, DECISION_FIXED, DEPRECATED, STATUS_COMPLETED,
     STATUS_EIC_ASSIGNED, CYCLE_DEFAULT, CYCLE_DIRECT_REC, STATUS_PREASSIGNED, STATUS_REPLACED,
-    STATUS_FAILED_PRESCREENING, STATUS_DEPRECATED, STATUS_ACCEPTED, STATUS_DECLINED, STATUS_WITHDRAWN)
+    STATUS_FAILED_PRESCREENING, STATUS_DEPRECATED,
+    STATUS_ACCEPTED, STATUS_DECLINED, STATUS_WITHDRAWN)
 from . import exceptions, helpers
 from .helpers import to_ascii_only
 from .models import (
@@ -1351,6 +1353,10 @@ class RecommendationVoteForm(forms.Form):
     vote = forms.ChoiceField(
         widget=forms.RadioSelect, choices=[
             ('agree', 'Agree'), ('disagree', 'Disagree'), ('abstain', 'Abstain')])
+    tier = forms.ChoiceField(
+        widget=forms.RadioSelect,
+        choices=SUBMISSION_TIERS,
+        required=False)
     alternative_for_journal = forms.ModelChoiceField(
         label='Alternative recommendation: for which Journal?',
         widget=forms.Select,
@@ -1359,7 +1365,7 @@ class RecommendationVoteForm(forms.Form):
     )
     alternative_recommendation = forms.ChoiceField(
         label='Which action do you recommend?',
-        widget=forms.Select, choices=REPORT_REC,
+        widget=forms.Select, choices=EIC_REC_CHOICES,
         required=False)
     remark = forms.CharField(widget=forms.Textarea(attrs={
         'rows': 3,
diff --git a/submissions/migrations/0071_submissiontiering.py b/submissions/migrations/0071_submissiontiering.py
new file mode 100644
index 000000000..e3bd21e24
--- /dev/null
+++ b/submissions/migrations/0071_submissiontiering.py
@@ -0,0 +1,26 @@
+# Generated by Django 2.1.8 on 2019-10-16 14:33
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('journals', '0084_journal_minimal_nr_of_reports'),
+        ('scipost', '0033_auto_20191005_1142'),
+        ('submissions', '0070_alternativerecommendation'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SubmissionTiering',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('tier', models.SmallIntegerField(choices=[(1, 'Tier I (surpasses expectations and criteria for this Journal; among top 10%)'), (2, 'Tier II (easily meets expectations and criteria for this Journal; among top 50%)'), (3, 'Tier III (meets expectations and criteria for this Journal)')])),
+                ('fellow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor')),
+                ('for_journal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='journals.Journal')),
+                ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tierings', to='submissions.Submission')),
+            ],
+        ),
+    ]
diff --git a/submissions/migrations/0072_populate_tiering.py b/submissions/migrations/0072_populate_tiering.py
new file mode 100644
index 000000000..4747b3c01
--- /dev/null
+++ b/submissions/migrations/0072_populate_tiering.py
@@ -0,0 +1,37 @@
+# Generated by Django 2.1.8 on 2019-10-16 14:35
+
+from django.db import migrations
+
+from submissions.constants import (
+    REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3,
+    EIC_REC_PUBLISH,
+    TIER_I, TIER_II, TIER_III
+)
+
+def populate_tiering(apps, schema_editor):
+    EICRecommendation = apps.get_model('submissions', 'EICRecommendation')
+    SubmissionTiering = apps.get_model('submissions', 'SubmissionTiering')
+
+    for eicrec in EICRecommendation.objects.all():
+        if eicrec.recommendation in [REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3]:
+            tiering = SubmissionTiering(
+                submission=eicrec.submission,
+                fellow = eicrec.submission.editor_in_charge,
+                for_journal = eicrec.for_journal,
+                tier=eicrec.recommendation) # works: REPORT... and TIER... constants have same value
+            tiering.save()
+    EICRecommendation.objects.filter(
+        recommendation__in=[REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3]
+        ).update(recommendation=EIC_REC_PUBLISH)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('submissions', '0071_submissiontiering'),
+    ]
+
+    operations = [
+        migrations.RunPython(populate_tiering,
+                             reverse_code=migrations.RunPython.noop),
+    ]
diff --git a/submissions/models.py b/submissions/models.py
index e7b191250..5fbc20c5b 100644
--- a/submissions/models.py
+++ b/submissions/models.py
@@ -22,7 +22,9 @@ from .constants import (
     REPORT_STATUSES, STATUS_UNVETTED, STATUS_INCOMING, STATUS_EIC_ASSIGNED,
     SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, DECISION_FIXED, ASSIGNMENT_STATUSES,
     CYCLE_DIRECT_REC, EVENT_GENERAL, EVENT_TYPES, EVENT_FOR_AUTHOR, EVENT_FOR_EIC, REPORT_TYPES,
-    REPORT_NORMAL, STATUS_DRAFT, STATUS_VETTED, EIC_REC_STATUSES, VOTING_IN_PREP, STATUS_UNASSIGNED,
+    REPORT_NORMAL,
+    EIC_REC_CHOICES, SUBMISSION_TIERS,
+    STATUS_DRAFT, STATUS_VETTED, EIC_REC_STATUSES, VOTING_IN_PREP, STATUS_UNASSIGNED,
     STATUS_INCORRECT, STATUS_UNCLEAR, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC, DEPRECATED,
     STATUS_FAILED_PRESCREENING, STATUS_RESUBMITTED, STATUS_REJECTED, STATUS_WITHDRAWN, REPORT_REC,
     STATUS_PUBLISHED, STATUS_REPLACED, STATUS_ACCEPTED, STATUS_DEPRECATED, STATUS_COMPLETED,
@@ -1026,6 +1028,15 @@ class EICRecommendation(SubmissionRelatedObjectMixin, models.Model):
         return _str
 
 
+class SubmissionTiering(models.Model):
+    """A Fellow's quality tiering of a Submission for a given Journal, given during voting."""
+    submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE,
+                                   related_name='tierings')
+    fellow = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE)
+    for_journal = models.ForeignKey('journals.Journal', on_delete=models.CASCADE)
+    tier = models.SmallIntegerField(choices=SUBMISSION_TIERS)
+
+
 class AlternativeRecommendation(models.Model):
     """Alternative recommendation from voting Fellow who disagrees with EICRec."""
     eicrec = models.ForeignKey('submissions.EICRecommendation', on_delete=models.CASCADE)
diff --git a/submissions/templates/submissions/pool/recommendation.html b/submissions/templates/submissions/pool/recommendation.html
index b3e154c0f..0cfa1f3bd 100644
--- a/submissions/templates/submissions/pool/recommendation.html
+++ b/submissions/templates/submissions/pool/recommendation.html
@@ -77,6 +77,14 @@
 		{% endfor %}
               </li>
             </ul>
+	    {% if old_rec.submission.tierings.all|length > 0 %}
+	      <h3>Tierings specified by voting Fellows</h3>
+	      <ul>
+	      {% for tiering in old_rec.submission.tierings.all %}
+		<li>{{ tiering.fellow }}: for Journal <em>{{ tiering.for_journal }}</em>: {{ tiering.get_tier_display }}</li>
+	      {% endfor %}
+	      </ul>
+	    {% endif %}
 	    {% if old_rec.alternativerecommendation_set.all|length > 0 %}
 	      <h3>Alternative recommendations offered during voting by Fellows who disagreed:</h3>
 	      <ul>
@@ -134,6 +142,28 @@
         </li>
       </ul>
 
+      {% if recommendation.submission.tierings.all|length > 0 %}
+	<h3>Tierings for this Submission, as indicated by voting Fellows who agreed with publication</h3>
+	<table class="table table-bordered">
+	  <thead>
+	    <tr>
+	      <th class="py-1">Fellow</th>
+	      <th class="py-1">For Journal</th>
+	      <th class="py-1">Tier</th>
+	    </tr>
+	  </thead>
+	  <tbody>
+	    {% for tiering in recommendation.submission.tierings.all %}
+	      <tr>
+		<td>{{ tiering.fellow }}</td>
+		<td>{{ tiering.for_journal }}</td>
+		<td>{{ tiering.get_tier_display }}</td>
+	      </tr>
+	    {% endfor %}
+	  </tbody>
+	</table>
+      {% endif %}
+
       {% if recommendation.alternativerecommendation_set.all|length > 0 %}
 	<h3>Alternative recommendations offered during voting by Fellows who disagreed:</h3>
 	<ul>
@@ -180,6 +210,7 @@
       <p>You had previously voted <span class="text-danger">{{ previous_vote }}</span>; you can use the form below to change your vote and/or add a remark:</p>
     {% endif %}
     <form action="{% url 'submissions:vote_on_rec' rec_id=recommendation.id %}" method="post">
+      <p id="agree_instructions"><strong class="text-success">If you agree with a recommendation to publish, you can provide your ballpark quality tiering below</strong><br>(this is not compulsory, but most welcome)</p>
       <p id="disagree_instructions" class="text-danger"><strong>If you vote disagree, please provide an alternative recommendation below</strong></p>
       {% csrf_token %}
       {{ voting_form|bootstrap }}
@@ -192,19 +223,34 @@
 {% block footer_script %}
   <script nonce="{{ request.csp_nonce }}">
    $(document).ready(function(){
+       $("#agree_instructions").hide()
        $("#disagree_instructions").hide()
+       $("input[name=tier]").parents('.form-group').hide()
        $("#id_alternative_for_journal").parents('.form-group').hide()
        $("#id_alternative_recommendation").parents('.form-group').hide()
        $('input[name=vote]').on('change', function(){
 	   var selection = $('input[name=vote]:checked').val();
 	   switch(selection){
+	       case "agree":
+		   $("#agree_instructions").show()
+		   $("#disagree_instructions").hide()
+		   {% if recommendation.recommendation == 1 %}
+		   $("input[name=tier]").parents('.form-group').show()
+		   {% endif %}
+		   $("#id_alternative_for_journal").parents('.form-group').hide()
+		   $("#id_alternative_recommendation").parents('.form-group').hide()
+		   break;
 	       case "disagree":
+		   $("#agree_instructions").hide()
 		   $("#disagree_instructions").show()
+		   $("input[name=tier]").parents('.form-group').hide()
 		   $("#id_alternative_for_journal").parents('.form-group').show()
 		   $("#id_alternative_recommendation").parents('.form-group').show()
 		   break;
 	       default:
+		   $("#agree_instructions").hide()
 		   $("#disagree_instructions").hide()
+		   $("input[name=tier]").parents('.form-group').hide()
 		   $("#id_alternative_for_journal").parents('.form-group').hide()
 		   $("#id_alternative_recommendation").parents('.form-group').hide()
 		   break;
diff --git a/submissions/views.py b/submissions/views.py
index 4479f4d4c..8171f7f95 100644
--- a/submissions/views.py
+++ b/submissions/views.py
@@ -27,10 +27,10 @@ from django.views.generic.list import ListView
 from .constants import (
     STATUS_VETTED, STATUS_EIC_ASSIGNED, SUBMISSION_STATUS, STATUS_ASSIGNMENT_FAILED,
     STATUS_WITHDRAWN, STATUS_DRAFT, CYCLE_DIRECT_REC, STATUS_COMPLETED, STATUS_DEPRECATED,
-    DEPRECATED)
+    DEPRECATED, EIC_REC_PUBLISH)
 from .helpers import check_verified_author, check_unverified_author
 from .models import (
-    Submission, EICRecommendation, AlternativeRecommendation,
+    Submission, EICRecommendation, SubmissionTiering, AlternativeRecommendation,
     EditorialAssignment, RefereeInvitation, Report, SubmissionEvent)
 from .mixins import SubmissionAdminViewMixin
 from .forms import (
@@ -1716,7 +1716,10 @@ def vote_on_rec(request, rec_id):
     else:
         form = RecommendationVoteForm(initial=initial)
     if form.is_valid():
-        # Delete previous alternative recs, irrespective of the vote
+        # Delete previous tiernings and alternative recs, irrespective of the vote
+        SubmissionTiering.objects.filter(
+            submission=recommendation.submission,
+            fellow=request.user.contributor).delete()
         AlternativeRecommendation.objects.filter(
             eicrec=recommendation, fellow=request.user.contributor).delete()
         if form.cleaned_data['vote'] == 'agree':
@@ -1726,6 +1729,15 @@ def vote_on_rec(request, rec_id):
                 messages.warning(request, 'You have already voted for this Recommendation.')
             recommendation.voted_against.remove(request.user.contributor)
             recommendation.voted_abstain.remove(request.user.contributor)
+            # Add a tiering if form filled in:
+            if (recommendation.recommendation == EIC_REC_PUBLISH and
+                form.cleaned_data['tier'] != ''):
+                tiering = SubmissionTiering(
+                    submission=recommendation.submission,
+                    fellow=request.user.contributor,
+                    for_journal=recommendation.for_journal,
+                    tier=form.cleaned_data['tier'])
+                tiering.save()
         elif form.cleaned_data['vote'] == 'disagree':
             recommendation.voted_for.remove(request.user.contributor)
             try:
-- 
GitLab