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