diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index d5d7a8f606ab5cd27ab52f6036260623c1fe7cac..963a519298520825a11f6c956e2f8541572e0f10 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -84,6 +84,7 @@ INSTALLED_APPS = ( 'haystack', 'rest_framework', 'sphinxdoc', + 'colleges', 'commentaries', 'comments', 'finances', diff --git a/colleges/__init__.py b/colleges/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/colleges/admin.py b/colleges/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/colleges/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/colleges/apps.py b/colleges/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..e1d74cff7d6248bb7702cad64f2ba1c406061042 --- /dev/null +++ b/colleges/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CollegesConfig(AppConfig): + name = 'colleges' diff --git a/colleges/managers.py b/colleges/managers.py new file mode 100644 index 0000000000000000000000000000000000000000..9904333acde715621bade47f6e31414592c9eda7 --- /dev/null +++ b/colleges/managers.py @@ -0,0 +1,15 @@ +import datetime + +from django.db import models +from django.db.models import Q + + +class FellowQuerySet(models.QuerySet): + def active(self): + today = datetime.date.today() + return self.filter( + Q(start_date__lte=today, until_date__isnull=True) | + Q(start_date__isnull=True, until_date__gte=today) | + Q(start_date__lte=today, until_date__gte=today) | + Q(start_date__isnull=True, until_date__isnull=True) + ).order_by('contributor__user__last_name') diff --git a/colleges/migrations/0001_initial.py b/colleges/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..2b4a744ee6e8399c9a1e9a5f047bea3ccff29a53 --- /dev/null +++ b/colleges/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-10-14 07:37 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import scipost.db.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('scipost', '0065_authorshipclaim_publication'), + ] + + operations = [ + migrations.CreateModel( + name='EditorialCollegeFellowship', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('latest_activity', scipost.db.fields.AutoDateTimeField(blank=True, default=django.utils.timezone.now, editable=False)), + ('affiliation', models.CharField(blank=True, max_length=255)), + ('start_date', models.DateField(blank=True, null=True)), + ('until_date', models.DateField(blank=True, null=True)), + ('contributor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fellowships', to='scipost.Contributor')), + ], + ), + migrations.AlterUniqueTogether( + name='editorialcollegefellowship', + unique_together=set([('contributor', 'start_date', 'until_date')]), + ), + ] diff --git a/colleges/migrations/0002_auto_20171014_0938.py b/colleges/migrations/0002_auto_20171014_0938.py new file mode 100644 index 0000000000000000000000000000000000000000..287565c6096c2c99dee572243c2706bcde090981 --- /dev/null +++ b/colleges/migrations/0002_auto_20171014_0938.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-10-14 07:38 +from __future__ import unicode_literals + +from django.db import migrations + + +def fill_editorial_college(apps, schema_editor): + Fellowship = apps.get_model('colleges', 'EditorialCollegeFellowship') + Contributor = apps.get_model('scipost', 'Contributor') + for contributor in Contributor.objects.filter(user__groups__name='Editorial College'): + Fellowship.objects.get_or_create(contributor=contributor) + + +class Migration(migrations.Migration): + + dependencies = [ + ('colleges', '0001_initial'), + ] + + operations = [ + migrations.RunPython(fill_editorial_college), + ] diff --git a/colleges/migrations/__init__.py b/colleges/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/colleges/models.py b/colleges/models.py new file mode 100644 index 0000000000000000000000000000000000000000..175665d5802c4a971af7beb635e5cb194311ac0a --- /dev/null +++ b/colleges/models.py @@ -0,0 +1,40 @@ +import datetime + +from django.db import models + +from scipost.behaviors import TimeStampedModel + +from .managers import FellowQuerySet + + +class EditorialCollegeFellowship(TimeStampedModel): + """ + Editorial College Fellowship connecting Editorial College and Contributors, + possibly with a limiting start/until date. + + The date range will effectively be used while determining 'the pool' for a specific + Submission, so it has a direct effect on the submission date. + """ + contributor = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, + related_name='fellowships') + affiliation = models.CharField(max_length=255, blank=True) + start_date = models.DateField(null=True, blank=True) + until_date = models.DateField(null=True, blank=True) + + objects = FellowQuerySet.as_manager() + + class Meta: + unique_together = ('contributor', 'start_date', 'until_date') + + def __str__(self): + return self.contributor.__str__() + + def is_active(self): + today = datetime.date.today() + if not self.start_date: + if not self.until_date: + return True + return today <= self.until_date + elif not self.until_date: + return today >= self.start_date + return today >= self.start_date and today <= self.until_date diff --git a/colleges/tests.py b/colleges/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/colleges/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/colleges/views.py b/colleges/views.py new file mode 100644 index 0000000000000000000000000000000000000000..91ea44a218fbd2f408430959283f0419c921093e --- /dev/null +++ b/colleges/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/submissions/admin.py b/submissions/admin.py index 252b1d19bf854f93693cce13743ce505c905c48e..3c01d6a344f7b9e16ea48953b096bf7746e02e53 100644 --- a/submissions/admin.py +++ b/submissions/admin.py @@ -111,7 +111,8 @@ class SubmissionAdmin(GuardedModelAdmin): 'remarks_for_editors', 'submitted_to_journal', 'pdf_refereeing_pack', - 'plagiarism_report'), + 'plagiarism_report', + 'pool'), }), ('Meta', { 'classes': ('collapse',), diff --git a/submissions/managers.py b/submissions/managers.py index e3e15f6c51d24452110bbf3add0da166f5118642..683eaf0b6d2300678d4ef43289cfe2dd1998b498 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -12,7 +12,9 @@ from .constants import SUBMISSION_STATUS_OUT_OF_POOL, SUBMISSION_STATUS_PUBLICLY STATUS_REJECTED, STATUS_REJECTED_VISIBLE,\ STATUS_ACCEPTED, STATUS_RESUBMITTED, STATUS_RESUBMITTED_REJECTED_VISIBLE,\ EVENT_FOR_EIC, EVENT_GENERAL, EVENT_FOR_AUTHOR,\ - STATUS_UNASSIGNED, STATUS_ASSIGNMENT_FAILED, STATUS_WITHDRAWN + STATUS_UNASSIGNED, STATUS_ASSIGNMENT_FAILED, STATUS_WITHDRAWN,\ + STATUS_PUT_TO_EC_VOTING, STATUS_VOTING_IN_PREPARATION,\ + SUBMISSION_STATUS_VOTING_DEPRECATED class SubmissionQuerySet(models.QuerySet): @@ -44,8 +46,47 @@ class SubmissionQuerySet(models.QuerySet): except AttributeError: return self.none() + def _pool(self, user): + """ + This filter creates 'the complete pool' for an user. + + This new-style pool does explicitly not have the author filter. + """ + if not hasattr(user, 'contributor'): + return self.none() + + if user.has_perm('scipost.can_oversee_refereeing'): + # Editorial Administators do have permission to see all submissions + # without being one of the College Fellows. Therefore, use the 'old' author + # filter to still filter out their conflicts of interests. + return self.user_filter(user) + else: + qs = user.contributor.fellowships.active() + return self.filter(fellows__in=qs) + + def pool(self, user): + """ + Return the pool for a certain user. + """ + qs = self._pool(user) + qs = qs.exclude(is_current=False).exclude(status__in=SUBMISSION_STATUS_OUT_OF_POOL) + return qs.order_by('-submission_date') + + def overcomplete_pool(self, user): + """ + Return the overcomplete pool for a certain user. + This is similar to the regular pool, however it also contains submissions that are + hidden in the regular pool, but should still be able to be opened by for example + the Editor-in-charge. + """ + qs = self._pool(user) + qs = qs.exclude(status__in=SUBMISSION_HTTP404_ON_EDITORIAL_PAGE) + return qs.order_by('-submission_date') + def get_pool(self, user): """ + -- DEPRECATED -- + Return subset of active and newest 'alive' submissions. """ return (self.user_filter(user) @@ -55,6 +96,8 @@ class SubmissionQuerySet(models.QuerySet): def filter_editorial_page(self, user): """ + -- DEPRECATED -- + Return Submissions currently 'alive' (being refereed, not published). It is meant to allow opening and editing certain submissions that are officially @@ -121,19 +164,15 @@ class SubmissionQuerySet(models.QuerySet): identifiers.append(sub.arxiv_identifier_wo_vn_nr) return self.filter(arxiv_identifier_wo_vn_nr__in=identifiers) - def accepted(self): return self.filter(status=STATUS_ACCEPTED) - def published(self): return self.filter(status=STATUS_PUBLISHED) - def assignment_failed(self): return self.filter(status=STATUS_ASSIGNMENT_FAILED) - def rejected(self): return self._newest_version_only(self.filter(status__in=[STATUS_REJECTED, STATUS_REJECTED_VISIBLE])) @@ -141,7 +180,6 @@ class SubmissionQuerySet(models.QuerySet): def withdrawn(self): return self._newest_version_only(self.filter(status=STATUS_WITHDRAWN)) - def open_for_reporting(self): """ Return Submissions that have appriopriate status for reporting. @@ -203,7 +241,7 @@ class EditorialAssignmentQuerySet(models.QuerySet): return self.filter(accepted=None, deprecated=False) -class EICRecommendationManager(models.Manager): +class EICRecommendationQuerySet(models.QuerySet): def get_for_user_in_pool(self, user): """ -- DEPRECATED -- @@ -221,6 +259,8 @@ class EICRecommendationManager(models.Manager): def filter_for_user(self, user, **kwargs): """ + -- DEPRECATED -- + Return list of EICRecommendation's which are owned/assigned author through the related submission. """ @@ -229,6 +269,23 @@ class EICRecommendationManager(models.Manager): except AttributeError: return self.none() + def user_may_vote_on(self, user): + if not hasattr(user, 'contributor'): + return self.none() + + return (self.filter(eligible_to_vote=user.contributor) + .exclude(recommendation__in=[-1, -2]) + .exclude(voted_for=user.contributor) + .exclude(voted_against=user.contributor) + .exclude(voted_abstain=user.contributor) + .exclude(submission__status__in=SUBMISSION_STATUS_VOTING_DEPRECATED)) + + def put_to_voting(self): + return self.filter(submission__status=STATUS_PUT_TO_EC_VOTING) + + def voting_in_preparation(self): + return self.filter(submission__status=STATUS_VOTING_IN_PREPARATION) + class ReportQuerySet(models.QuerySet): def accepted(self): diff --git a/submissions/migrations/0077_submission_pool.py b/submissions/migrations/0077_submission_pool.py new file mode 100644 index 0000000000000000000000000000000000000000..00704fb18fb754b1adfad9756de867cdd43c1551 --- /dev/null +++ b/submissions/migrations/0077_submission_pool.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-10-14 07:37 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('colleges', '0001_initial'), + ('submissions', '0076_auto_20170928_2024'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='pool', + field=models.ManyToManyField(blank=True, related_name='pool', to='colleges.EditorialCollegeFellowship'), + ), + ] diff --git a/submissions/migrations/0078_auto_20171014_0945.py b/submissions/migrations/0078_auto_20171014_0945.py new file mode 100644 index 0000000000000000000000000000000000000000..9ecef592c0482d974922ca74a295ffa170ac0a24 --- /dev/null +++ b/submissions/migrations/0078_auto_20171014_0945.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-10-14 07:45 +from __future__ import unicode_literals + +from django.db import migrations + + +def fill_editorial_college(apps, schema_editor): + Fellowship = apps.get_model('colleges', 'EditorialCollegeFellowship') + Submission = apps.get_model('submissions', 'Submission') + fellows = Fellowship.objects.all() + + for submission in Submission.objects.all(): + for fellow in fellows: + cont = fellow.contributor + if cont not in submission.authors.all() and cont not in submission.authors_claims.all() and (cont.user.last_name not in submission.author_list or cont in submission.authors_false_claims.all()): + submission.pool.add(fellow) + + +def no_going_back(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0077_submission_pool'), + ('colleges', '0002_auto_20171014_0938'), + ] + + operations = [ + migrations.RunPython(fill_editorial_college, no_going_back), + ] diff --git a/submissions/migrations/0079_auto_20171014_0951.py b/submissions/migrations/0079_auto_20171014_0951.py new file mode 100644 index 0000000000000000000000000000000000000000..d31aae4dda351922b48fdb6aae597fc57bb64fe2 --- /dev/null +++ b/submissions/migrations/0079_auto_20171014_0951.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-10-14 07:51 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0078_auto_20171014_0945'), + ] + + operations = [ + migrations.RenameField( + model_name='submission', + old_name='pool', + new_name='fellows', + ), + ] diff --git a/submissions/mixins.py b/submissions/mixins.py index 4f84bb7065c9863fe725e231cb6cb02eb08a1a48..e1a09834fcadb060d26bd739c63b4f4ff276b079 100644 --- a/submissions/mixins.py +++ b/submissions/mixins.py @@ -69,8 +69,8 @@ class SubmissionAdminViewMixin(FriendlyPermissionMixin, SubmissionFormViewMixin) """ qs = super().get_queryset() if self.pool: - return qs.get_pool(self.request.user) - return qs.filter_editorial_page(self.request.user) + return qs.pool(self.request.user) + return qs.overcomplete_pool(self.request.user) def get_object(self): """ diff --git a/submissions/models.py b/submissions/models.py index e5fb47eb4424289c7bd3dd6f6d1f1d629694db8a..96d0dae677c472b4076e9c19191c29ad4273bec5 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -15,7 +15,7 @@ from .constants import ASSIGNMENT_REFUSAL_REASONS, ASSIGNMENT_NULLBOOL,\ REPORT_STATUSES, STATUS_UNVETTED, SUBMISSION_EIC_RECOMMENDATION_REQUIRED,\ SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, CYCLE_DIRECT_REC,\ EVENT_GENERAL, EVENT_TYPES, EVENT_FOR_AUTHOR, EVENT_FOR_EIC -from .managers import SubmissionQuerySet, EditorialAssignmentQuerySet, EICRecommendationManager,\ +from .managers import SubmissionQuerySet, EditorialAssignmentQuerySet, EICRecommendationQuerySet,\ ReportQuerySet, SubmissionEventQuerySet, RefereeInvitationQuerySet from .utils import ShortSubmissionCycle, DirectRecommendationSubmissionCycle,\ GeneralSubmissionCycle @@ -24,7 +24,6 @@ from comments.models import Comment from scipost.behaviors import TimeStampedModel from scipost.constants import TITLE_CHOICES from scipost.fields import ChoiceArrayField -from scipost.models import Contributor from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS from journals.constants import SCIPOST_JOURNALS_SUBMIT, SCIPOST_JOURNALS_DOMAINS from journals.models import Publication @@ -58,6 +57,8 @@ class Submission(models.Model): status = models.CharField(max_length=30, choices=SUBMISSION_STATUS, default=STATUS_UNASSIGNED) refereeing_cycle = models.CharField(max_length=30, choices=SUBMISSION_CYCLES, default=CYCLE_DEFAULT) + fellows = models.ManyToManyField('colleges.EditorialCollegeFellowship', blank=True, + related_name='pool') subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS, verbose_name='Primary subject area', default='Phys:QP') submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE, @@ -520,7 +521,7 @@ class EICRecommendation(SubmissionRelatedObjectMixin, models.Model): related_name='voted_abstain') voting_deadline = models.DateTimeField('date submitted', default=timezone.now) - objects = EICRecommendationManager() + objects = EICRecommendationQuerySet.as_manager() def __str__(self): return (self.submission.title[:20] + ' by ' + self.submission.author_list[:30] + diff --git a/submissions/templates/submissions/pool.html b/submissions/templates/submissions/pool.html index 632bb4fe8b832e60ace7193cee4052cfb862c128..af4c97ff48b010362327f1a1b43852224d1f6704 100644 --- a/submissions/templates/submissions/pool.html +++ b/submissions/templates/submissions/pool.html @@ -21,7 +21,7 @@ <div class="col-lg-8"> {% if is_ECAdmin %} - {% if recommendations_undergoing_voting %} + {% if recommendations.put_to_voting.exists %} <div class="row"> <div class="col-12"> <h3 class="highlight mt-0">Administrative actions on recommendations undergoing voting:</h3> @@ -38,7 +38,7 @@ </div> <div class="row"> - {% for rec in recommendations_undergoing_voting %} + {% for rec in recommendations.put_to_voting %} {% if not forloop.first %} <hr> {% endif %} @@ -116,14 +116,14 @@ <hr> {% endif %} - {% if recommendations_to_prepare_for_voting %} + {% if recommendations.voting_in_preparation.exists %} <div class="row"> <div class="col-12"> <h1 class="highlight mt-0">Recommendations to prepare for voting</h1> </div> </div> - {% for rec in recommendations_to_prepare_for_voting %} + {% for rec in recommendations.voting_in_preparation %} {% if not forloop.first %} <hr> {% endif %} @@ -229,7 +229,7 @@ <div class="row"> <div class="col-12"> <!-- Submissions list --> - {% for sub in submissions_in_pool %} + {% for sub in submissions %} <div class="card card-outline-secondary mt-1" id="pool_submission_{{sub.id}}"> {% include 'submissions/_submission_card_in_pool.html' with submission=sub remark_form=remark_form is_ECAdmin=is_ECAdmin user=request.user %} </div> @@ -258,14 +258,14 @@ </div><!-- end status --> {% if is_ECAdmin %} - {% if recommendations_undergoing_voting %} + {% if recommendations.put_to_voting.exists %} <!-- Preparing --> <a href="#rec_filter_voting" data-toggle="collapse" class="collapsed"> <h3 class="card-title text-gray-dark">Recommendations undergoing voting ({{recommendations_undergoing_voting|length}})</h3> </a> <div id="rec_filter_voting" class="collapse"> <ul class="list-group list-group-flush"> - {% for recommendation in recommendations_undergoing_voting %} + {% for recommendation in recommendations.put_to_voting %} <li class="list-group-item"> <div class="card-body"> <a href="#undergoing_rec_{{recommendation.id}}">{{recommendation.submission.title}}</a> @@ -278,14 +278,14 @@ </div><!-- end preparing --> {% endif %} - {% if recommendations_to_prepare_for_voting %} + {% if recommendations.voting_in_preparation.exists %} <!-- Preparing --> <a href="#rec_filter_prepare" data-toggle="collapse" class="collapsed"> <h3 class="card-title text-gray-dark">Recommendations to prepare ({{recommendations_to_prepare_for_voting|length}})</h3> </a> <div id="rec_filter_prepare" class="collapse"> <ul class="list-group list-group-flush"> - {% for recommendation in recommendations_to_prepare_for_voting %} + {% for recommendation in recommendations.voting_in_preparation %} <li class="list-group-item"> <div class="card-body"> <a href="#prepare_rec_{{recommendation.id}}">{{recommendation.submission.title}}</a> @@ -301,11 +301,11 @@ <!-- Pool --> <a href="#pool_filter_submissions" data-toggle="collapse"> - <h3 class="card-title text-gray-dark">Submissions in pool ({{submissions_in_pool|length}})</h3> + <h3 class="card-title text-gray-dark">Submissions in pool ({{submissions|length}})</h3> </a> <div id="pool_filter_submissions" class="collapse show"> <ul class="list-group list-group-flush"> - {% for submission in submissions_in_pool %} + {% for submission in submissions %} <li class="list-group-item"> <div class="card-body" style="overflow: auto;"> <a href="#pool_submission_{{submission.id}}">{{submission.title}}</a> diff --git a/submissions/templates/submissions/pool/pool.html b/submissions/templates/submissions/pool/pool.html index 97758bf7d200b5414411822a62b3c25fa99375bd..3a279cdbec43381ca1614c2ffa4cb8d8ae188c08 100644 --- a/submissions/templates/submissions/pool/pool.html +++ b/submissions/templates/submissions/pool/pool.html @@ -27,14 +27,14 @@ <h1>SciPost Submissions Pool</h1> {% if is_ECAdmin %} - {% if recommendations_to_prepare_for_voting or recommendations_undergoing_voting %} + {% if recommendations.voting_in_preparation.exists or recommendations.put_to_voting.exists %} <div class="quote-border"> <h2 class="text-primary">Administrative Tasks</h2> - {% if recommendations_to_prepare_for_voting %} + {% if recommendations.voting_in_preparation.exists %} <h3>Recommendations to prepare for voting <i class="fa fa-exclamation-circle text-warning"></i></h3> <ul> - {% for recommendation in recommendations_to_prepare_for_voting %} + {% for recommendation in recommendations.voting_in_preparation %} <li>On Editorial Recommendation: {{ recommendation }}<br> <a href="{% url 'submissions:prepare_for_voting' rec_id=recommendation.id %}">Prepare for voting</a> </li> @@ -42,10 +42,10 @@ </ul> {% endif %} - {% if recommendations_undergoing_voting %} + {% if recommendations.put_to_voting.exists %} <h3>Recommendations undergoing voting <i class="fa fa-exclamation-circle text-warning"></i></h3> <ul class="fa-ul"> - {% for recommendation in recommendations_undergoing_voting %} + {% for recommendation in recommendations.put_to_voting %} <li>{% include 'partials/submissions/admin/recommendation_tooltip.html' with classes='fa-li' recommendation=recommendation %} On Editorial Recommendation: {{ recommendation }}<br> <a href="{% url 'submissions:admin_recommendation' recommendation.submission.arxiv_identifier_w_vn_nr %}">See Editorial Recommendation</a> @@ -91,7 +91,7 @@ <ul class="list-unstyled" data-target="active-list"> <!-- Submissions list --> - {% for sub in submissions_in_pool %} + {% for sub in submissions %} <li class="p-2{% if sub == submission %} active{% endif %}"> {% if sub == submission %} {% include 'partials/submissions/pool/submission_li.html' with submission=sub is_current=1 %} diff --git a/submissions/views.py b/submissions/views.py index b0fbd035edfcb72d960c8a2889e03ea2e9d41e1f..ebc08813668b401f5d0e7d1ee87bfe06a190024e 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -339,39 +339,26 @@ def pool(request, arxiv_identifier_w_vn_nr=None): to publication acceptance or rejection. All members of the Editorial College have access. """ - submissions_in_pool = (Submission.objects.get_pool(request.user) - .prefetch_related('referee_invitations', 'remarks', 'comments')) - recommendations_undergoing_voting = (EICRecommendation.objects - .get_for_user_in_pool(request.user) - .filter(submission__status='put_to_EC_voting')) - recommendations_to_prepare_for_voting = (EICRecommendation.objects - .get_for_user_in_pool(request.user) - .filter( - submission__status='voting_in_preparation')) - contributor = Contributor.objects.get(user=request.user) - assignments_to_consider = EditorialAssignment.objects.open().filter(to=contributor) + # Objects + submissions = Submission.objects.pool(request.user) + recommendations = EICRecommendation.objects.filter(submission__in=submissions) + recs_to_vote_on = recommendations.user_may_vote_on(request.user) + assignments_to_consider = EditorialAssignment.objects.open().filter(to=request.user.contributor) + + # Forms consider_assignment_form = ConsiderAssignmentForm() - recs_to_vote_on = (EICRecommendation.objects.get_for_user_in_pool(request.user) - .filter(eligible_to_vote=contributor) - .exclude(recommendation__in=[-1, -2]) - .exclude(voted_for=contributor) - .exclude(voted_against=contributor) - .exclude(voted_abstain=contributor) - .exclude(submission__status__in=SUBMISSION_STATUS_VOTING_DEPRECATED)) rec_vote_form = RecommendationVoteForm() remark_form = RemarkForm() context = { - 'submissions_in_pool': submissions_in_pool, + 'submissions': submissions, 'submission_status': SUBMISSION_STATUS, - 'recommendations_undergoing_voting': recommendations_undergoing_voting, - 'recommendations_to_prepare_for_voting': recommendations_to_prepare_for_voting, + 'recommendations': recommendations, '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, 'remark_form': remark_form, - 'submission': None } # The following is in test phase. Update if test is done @@ -380,20 +367,21 @@ def pool(request, arxiv_identifier_w_vn_nr=None): # Search search_form = SubmissionPoolFilterForm(request.GET or None) if search_form.is_valid(): - context['submissions_in_pool'] = search_form.search(context['submissions_in_pool'], - request.user.contributor) + context['submissions'] = search_form.search(context['submissions'], + request.user.contributor) context['search_form'] = search_form # Show specific submission in the pool + context['submission'] = None if arxiv_identifier_w_vn_nr: try: - context['submission'] = context['submissions_in_pool'].get( + context['submission'] = context['submissions'].get( arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) except Submission.DoesNotExist: pass # EdColAdmin related variables - if request.user.contributor.is_EdCol_Admin(): + if request.user.has_perm('scipost.can_oversee_refereeing'): context['latest_events'] = SubmissionEvent.objects.for_eic().last_hours() # Temporary test logic: only testers see the new Pool @@ -683,7 +671,7 @@ def assignments(request): @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.objects.filter_editorial_page(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) context = { @@ -698,8 +686,9 @@ def editorial_page(request, arxiv_identifier_w_vn_nr): @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) def cycle_form_submit(request, arxiv_identifier_w_vn_nr): - submission = get_object_or_404(Submission.objects.get_pool(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + form = SubmissionCycleChoiceForm(request.POST or None, instance=submission) if form.is_valid(): submission = form.save() @@ -720,8 +709,9 @@ def cycle_form_submit(request, arxiv_identifier_w_vn_nr): @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.objects.filter_editorial_page(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + context = {} queryresults = '' @@ -770,7 +760,7 @@ def recruit_referee(request, arxiv_identifier_w_vn_nr): The pending refereeing invitation is then recognized upon registration, using the invitation token. """ - submission = get_object_or_404(Submission.objects.filter_editorial_page(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) if request.method == 'POST': ref_recruit_form = RefereeRecruitmentForm(request.POST) @@ -830,7 +820,7 @@ def send_refereeing_invitation(request, arxiv_identifier_w_vn_nr, contributor_id For a referee who isn't a Contributor yet, the method recruit_referee above is called instead. """ - submission = get_object_or_404(Submission.objects.get_pool(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), 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: @@ -873,7 +863,9 @@ def ref_invitation_reminder(request, arxiv_identifier_w_vn_nr, invitation_id): 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) + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), + arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + invitation = get_object_or_404(submission.referee_invitations.all(), pk=invitation_id) invitation.nr_reminders += 1 invitation.date_last_reminded = timezone.now() invitation.save() @@ -882,6 +874,7 @@ def ref_invitation_reminder(request, arxiv_identifier_w_vn_nr, invitation_id): SubmissionUtils.send_ref_reminder_email() else: SubmissionUtils.send_unreg_ref_reminder_email() + messages.success(request, 'Reminder sent succesfully.') return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @@ -992,7 +985,7 @@ def cancel_ref_invitation(request, arxiv_identifier_w_vn_nr, invitation_id): @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.objects.get_pool(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) submission.reporting_deadline += datetime.timedelta(days=int(days)) submission.open_for_reporting = True @@ -1010,7 +1003,7 @@ def extend_refereeing_deadline(request, arxiv_identifier_w_vn_nr, days): @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) def set_refereeing_deadline(request, arxiv_identifier_w_vn_nr): - submission = get_object_or_404(Submission.objects.get_pool(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) form = SetRefereeingDeadlineForm(request.POST or None) @@ -1047,7 +1040,7 @@ def close_refereeing_round(request, arxiv_identifier_w_vn_nr): round off any replies to reports or comments before the editorial recommendation is formulated. """ - submission = get_object_or_404(Submission.objects.get_pool(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) submission.open_for_reporting = False submission.open_for_commenting = False @@ -1064,7 +1057,9 @@ def close_refereeing_round(request, arxiv_identifier_w_vn_nr): @permission_required('scipost.can_oversee_refereeing', raise_exception=True) def refereeing_overview(request): - submissions_under_refereeing = (Submission.objects.filter(status=STATUS_EIC_ASSIGNED) + submissions_under_refereeing = (Submission.objects + .overcomplete_pool(request.user) + .filter(status=STATUS_EIC_ASSIGNED) .order_by('submission_date')) context = {'submissions_under_refereeing': submissions_under_refereeing} return render(request, 'submissions/refereeing_overview.html', context) @@ -1076,7 +1071,8 @@ 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) + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), + arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) errormessage = None if comtype not in dict(ED_COMM_CHOICES).keys(): errormessage = 'Unknown type of cummunication.' @@ -1126,7 +1122,7 @@ def communication(request, arxiv_identifier_w_vn_nr, comtype, referee_id=None): (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.objects.filter_editorial_page(request.user), + submission = get_object_or_404(Submission.objects.overcomplete_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) if submission.eic_recommendation_required(): messages.warning(request, ('<h3>An Editorial Recommendation is not required</h3>' @@ -1342,11 +1338,15 @@ def vet_submitted_report(request, report_id): @permission_required('scipost.can_prepare_recommendations_for_voting', raise_exception=True) @transaction.atomic def prepare_for_voting(request, rec_id): - recommendation = get_object_or_404((EICRecommendation.objects - .get_for_user_in_pool(request.user)), id=rec_id) - Fellows_with_expertise = Contributor.objects.filter( - user__groups__name__in=['Editorial College'], - expertises__contains=[recommendation.submission.subject_area]) + submissions = Submission.objects.overcomplete_pool(request.user) + recommendation = get_object_or_404(EICRecommendation.objects.filter(submission__in=submissions), + id=rec_id) + + # Fellows_with_expertise = Contributor.objects.filter( + # user__groups__name__in=['Editorial College'], + # expertises__contains=[recommendation.submission.subject_area]) + fellows_with_expertise = (recommendation.submission.fellows + .filter(expertises__contains=recommendation.submission.subject_area)) coauthorships = {} if request.method == 'POST': eligibility_form = VotingEligibilityForm( @@ -1371,7 +1371,7 @@ def prepare_for_voting(request, rec_id): else: # Identify possible co-authorships in last 3 years, disqualifying Fellow from voting: if recommendation.submission.metadata is not None: - for Fellow in Fellows_with_expertise: + for Fellow in fellows_with_expertise: sub_auth_boolean_str = '((' + (recommendation.submission .metadata['entries'][0]['authors'][0]['name'] .split()[-1]) @@ -1393,7 +1393,7 @@ def prepare_for_voting(request, rec_id): context = { 'recommendation': recommendation, - 'Fellows_with_expertise': Fellows_with_expertise, + 'Fellows_with_expertise': fellows_with_expertise, 'coauthorships': coauthorships, 'eligibility_form': eligibility_form, } @@ -1403,8 +1403,10 @@ def prepare_for_voting(request, rec_id): @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) @transaction.atomic def vote_on_rec(request, rec_id): - recommendation = get_object_or_404((EICRecommendation.objects - .get_for_user_in_pool(request.user)), id=rec_id) + submissions = Submission.objects.overcomplete_pool(request.user) + recommendation = get_object_or_404(EICRecommendation.objects.filter(submission__in=submissions), + id=rec_id) + form = RecommendationVoteForm(request.POST or None) if form.is_valid(): if form.cleaned_data['vote'] == 'agree': @@ -1453,13 +1455,15 @@ def remind_Fellows_to_vote(request): """ This method sends an email to all Fellow with pending voting duties. It must be called by and Editorial Administrator. + + TODO: This reminder function doesn't filter per submission?! """ - recommendations_undergoing_voting = (EICRecommendation.objects - .get_for_user_in_pool(request.user) - .filter(submission__status__in=['put_to_EC_voting'])) + submissions = Submission.objects.overcomplete_pool(request.user) + recommendations = EICRecommendation.objects.filter(submission__in=submissions).put_to_voting() + Fellow_emails = [] Fellow_names = [] - for rec in recommendations_undergoing_voting: + for rec in recommendations: for Fellow in rec.eligible_to_vote.all(): if (Fellow not in rec.voted_for.all() and Fellow not in rec.voted_against.all() @@ -1487,10 +1491,15 @@ def fix_College_decision(request, rec_id): Terminates the voting on a Recommendation. Called by an Editorial Administrator. + # TODO - 2 bugs: + TO FIX: If multiple recommendations are submitted; decisions may be overruled unexpectedly. + TO FIX: A college decision can be fixed multiple times, there is no already-fixed mechanism!!! """ - recommendation = get_object_or_404((EICRecommendation.objects - .get_for_user_in_pool(request.user)), pk=rec_id) + submissions = Submission.objects.overcomplete_pool(request.user) + recommendation = get_object_or_404(EICRecommendation.objects.filter(submission__in=submissions), + id=rec_id) + submission = recommendation.submission if recommendation.recommendation in [1, 2, 3]: # Publish as Tier I, II or III