diff --git a/package.json b/package.json index 27c87b864b31e483f9dfb6fea3e702ab646d9d36..d016723390225a29a1dd65e2e610e90218a7b962 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "homepage": "https://www.scipost.org", "devDependencies": { "ajv": "^5.2.2", - "bootstrap": "^4.0.0", + "bootstrap": "^4.1.0", "bootstrap-loader": "^2.1.0", "clean-webpack-plugin": "^0.1.15", "css-loader": "^0.28.4", @@ -32,7 +32,7 @@ "jquery-ui": "^1.12.1", "node-loader": "^0.6.0", "node-sass": "^4.4.0", - "popper.js": "^1.14.1", + "popper.js": "^1.14.3", "postcss-load-config": "^1.2.0", "postcss-loader": "^2.0.6", "resolve-url-loader": "^1.6.1", diff --git a/production/utils.py b/production/utils.py index 9e2b31de48d0109d0bb9e3e50bce8c8bb2bd29eb..1691347a2ea47f89243787959209630529872fe5 100644 --- a/production/utils.py +++ b/production/utils.py @@ -1,6 +1,8 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +from django.contrib.auth.models import Group +from guardian.shortcuts import assign_perm from common.utils import BaseMailUtil @@ -13,6 +15,18 @@ def proofs_slug_to_id(slug): return int(slug) - 8932 +def get_or_create_production_stream(submission): + """Get or create a ProductionStream for the given Submission.""" + from .models import ProductionStream + + prodstream, created = ProductionStream.objects.get_or_create(submission=submission) + if created: + ed_admins = Group.objects.get(name='Editorial Administrators') + assign_perm('can_perform_supervisory_actions', ed_admins, prodstream) + assign_perm('can_work_for_stream', ed_admins, prodstream) + return prodstream + + class ProductionUtils(BaseMailUtil): mail_sender = 'no-reply@scipost.org' mail_sender_title = 'SciPost Production' diff --git a/scipost/static/scipost/assets/config/preconfig.scss b/scipost/static/scipost/assets/config/preconfig.scss index 95228df4dc669a64279ee467a18269b6fe957322..f93259c883b47f75132b1a55b57f6cea897097e1 100644 --- a/scipost/static/scipost/assets/config/preconfig.scss +++ b/scipost/static/scipost/assets/config/preconfig.scss @@ -55,7 +55,6 @@ $alert-border-radius: $base-border-radius; // Cards // $card-border-radius: $base-border-radius; -$card-border-color: $gray-200; $card-spacer-x: 0.75rem; $card-spacer-y: 0.5rem; $card-shadow-color: #ccc; @@ -120,7 +119,7 @@ $close-font-weight: 100; // Tables // -$table-cell-padding: 0.25rem 0.5rem; +$table-body-cell-padding: 0.25rem 0.5rem; // Tooltip diff --git a/scipost/static/scipost/assets/css/_cards.scss b/scipost/static/scipost/assets/css/_cards.scss index 6e1e57b63d1eba55342a323af040498dd0d65d1e..2a927df994d200cac69f44f5dcfd6df64767e8a1 100644 --- a/scipost/static/scipost/assets/css/_cards.scss +++ b/scipost/static/scipost/assets/css/_cards.scss @@ -36,17 +36,6 @@ .card-outline-secondary { border-color: #f1f1f1; } - -// .card-header { -// padding: 0.5rem 0; -// margin: 0 0.75rem; -// } - -// .card-footer { -// padding: 0.75rem 0 0 0; -// margin: 0 0.75rem 0.75rem 0.75rem; -// } - .list-group-item > .card-body { padding: 0.5rem; } diff --git a/scipost/static/scipost/assets/css/_labels.scss b/scipost/static/scipost/assets/css/_labels.scss index 6105f7d481ac7b7498b1fa701ef59ffb0e17d294..9b475466ed430c8ef917cd8b8ed04cfe667e46ca 100644 --- a/scipost/static/scipost/assets/css/_labels.scss +++ b/scipost/static/scipost/assets/css/_labels.scss @@ -3,9 +3,9 @@ // -------------------------------------------------- // For each of Bootstrap's buttons, define text, background and border color. -$label-padding-x: 5px !default; -$label-padding-y: 3px !default; -$label-line-height: inherit; +$label-padding-x: 0.6rem !default; +$label-padding-y: 0.25rem !default; +$label-line-height: 1.2; $label-font-weight: $font-weight-normal !default; $label-box-shadow: none; $label-font-size: inherit; @@ -38,10 +38,10 @@ $label-danger-color: $white; $label-danger-bg: $red !default; $label-danger-border: $red !default; -$label-padding-x-sm: .5rem; -$label-padding-y-sm: .1rem; +$label-padding-x-sm: 0.25rem; +$label-padding-y-sm: 0.15rem; -$label-padding-x-lg: 1rem !default; +$label-padding-x-lg: 0.75rem !default; $label-padding-y-lg: 0.5rem !default; $label-label-spacing-y: .5rem !default; @@ -55,9 +55,9 @@ $label-border-radius-sm: 4px; $label-transition: all .2s ease-in-out !default; .label { - display: inline; + display: inline-block; font-weight: $label-font-weight; - line-height: $label-line-height; + line-height: $label-line-height !important; text-align: center; white-space: nowrap; vertical-align: middle; diff --git a/scipost/static/scipost/assets/css/_tables.scss b/scipost/static/scipost/assets/css/_tables.scss index 7c3d0fdc6c1d930fd04287fd8b90807a6e4410b8..8e01f59c44469fc9ed26b54e688f213d23859f88 100644 --- a/scipost/static/scipost/assets/css/_tables.scss +++ b/scipost/static/scipost/assets/css/_tables.scss @@ -1,9 +1,12 @@ .table { th, td { - padding: $table-cell-padding; vertical-align: middle; } + + td { + padding: $table-body-cell-padding; + } } diff --git a/submissions/constants.py b/submissions/constants.py index dee17b75b4224a4a4c8806e97082e133ef9e239f..61ec2e59e4478fd97c69d06b92b3ddfa44d6ce12 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -36,7 +36,6 @@ SUBMISSION_STATUS = ( # Submissions with these statuses never have required actions. NO_REQUIRED_ACTION_STATUSES = [ - STATUS_INCOMING, STATUS_UNASSIGNED, STATUS_ASSIGNMENT_FAILED, STATUS_REJECTED, @@ -103,14 +102,17 @@ RANKING_CHOICES = ( (0, 'poor') ) +REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3 = 1, 2, 3 +REPORT_MINOR_REV, REPORT_MAJOR_REV = -1, -2 +REPORT_REJECT = -3 REPORT_REC = ( (None, '-'), - (1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select)'), - (2, 'Publish as Tier II (top 50% of papers in this journal)'), - (3, 'Publish as Tier III (meets the criteria of this journal)'), - (-1, 'Ask for minor revision'), - (-2, 'Ask for major revision'), - (-3, 'Reject') + (REPORT_PUBLISH_1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select)'), + (REPORT_PUBLISH_2, 'Publish as Tier II (top 50% of papers in this journal)'), + (REPORT_PUBLISH_3, 'Publish as Tier III (meets the criteria of this journal)'), + (REPORT_MINOR_REV, 'Ask for minor revision'), + (REPORT_MAJOR_REV, 'Ask for major revision'), + (REPORT_REJECT, 'Reject') ) # @@ -158,12 +160,12 @@ REPORT_TYPES = ( CYCLE_UNDETERMINED = '' CYCLE_DEFAULT, CYCLE_SHORT, CYCLE_DIRECT_REC = 'default', 'short', 'direct_rec' -SUBMISSION_CYCLES = ( - (CYCLE_UNDETERMINED, 'Cycle undetermined'), +SUBMISSION_CYCLE_CHOICES = ( (CYCLE_DEFAULT, 'Default cycle'), (CYCLE_SHORT, 'Short cycle'), (CYCLE_DIRECT_REC, 'Direct editorial recommendation'), ) +SUBMISSION_CYCLES = ((CYCLE_UNDETERMINED, 'Cycle undetermined'),) + SUBMISSION_CYCLE_CHOICES EVENT_GENERAL = 'gen' EVENT_FOR_EIC = 'eic' diff --git a/submissions/forms.py b/submissions/forms.py index b578fc9321deae8818f87f18455acf7f65821d20..1037e388c0c63e8a4ea7fa6d7d27d4d79e131f4b 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -14,16 +14,20 @@ from .constants import ( ASSIGNMENT_BOOL, ASSIGNMENT_REFUSAL_REASONS, STATUS_RESUBMITTED, REPORT_ACTION_CHOICES, REPORT_REFUSAL_CHOICES, STATUS_REJECTED, STATUS_INCOMING, REPORT_POST_EDREC, REPORT_NORMAL, STATUS_DRAFT, STATUS_UNVETTED, REPORT_ACTION_ACCEPT, REPORT_ACTION_REFUSE, STATUS_VETTED, - EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS, SUBMISSION_STATUS, PUT_TO_VOTING, CYCLE_UNDETERMINED) + 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, STATUS_ACCEPTED, DECISION_FIXED, DEPRECATED) from . import exceptions, helpers from .models import ( Submission, RefereeInvitation, Report, EICRecommendation, EditorialAssignment, iThenticateReport, EditorialCommunication) +from .signals import notify_manuscript_accepted from common.helpers import get_new_secrets_key from colleges.models import Fellowship from invitations.models import RegistrationInvitation from journals.constants import SCIPOST_JOURNAL_PHYSICS_PROC, SCIPOST_JOURNAL_PHYSICS +from production.utils import get_or_create_production_stream from scipost.constants import SCIPOST_SUBJECT_AREAS, INVITATION_REFEREEING from scipost.services import ArxivCaller from scipost.models import Contributor @@ -547,6 +551,8 @@ class SetRefereeingDeadlineForm(forms.Form): class VotingEligibilityForm(forms.ModelForm): + """Assign Fellows to vote for EICRecommendation and open its status for voting.""" + eligible_fellows = forms.ModelMultipleChoiceField( queryset=Contributor.objects.none(), widget=forms.CheckboxSelectMultiple({'checked': 'checked'}), @@ -557,22 +563,23 @@ class VotingEligibilityForm(forms.ModelForm): fields = () def __init__(self, *args, **kwargs): + """Get queryset of Contributors eligibile for voting.""" super().__init__(*args, **kwargs) self.fields['eligible_fellows'].queryset = Contributor.objects.filter( - fellowships__pool=self.instance.submission, - expertises__contains=[self.instance.submission.subject_area] - ).order_by('user__last_name') + fellowships__pool=self.instance.submission, + expertises__contains=[ + self.instance.submission.subject_area]).order_by('user__last_name') def save(self, commit=True): - recommendation = self.instance - recommendation.eligible_to_vote = self.cleaned_data['eligible_fellows'] - recommendation.status = PUT_TO_VOTING + """Update EICRecommendation status and save its voters.""" + self.instance.eligible_to_vote = self.cleaned_data['eligible_fellows'] + self.instance.status = PUT_TO_VOTING if commit: - recommendation.save() - recommendation.submission.touch() - recommendation.voted_for.add(recommendation.submission.editor_in_charge) - return recommendation + self.instance.save() + self.instance.submission.touch() + self.instance.voted_for.add(self.instance.submission.editor_in_charge) + return self.instance ############ @@ -827,7 +834,7 @@ class EICRecommendationForm(forms.ModelForm): if commit: if self.earlier_recommendations: - self.earlier_recommendations.update(active=False) + self.earlier_recommendations.update(active=False, status=DEPRECATED) # All reports already submitted are now formulated *after* eic rec formulation Report.objects.filter( @@ -882,10 +889,10 @@ class RecommendationVoteForm(forms.Form): class SubmissionCycleChoiceForm(forms.ModelForm): - referees_reinvite = forms.ModelMultipleChoiceField(queryset=RefereeInvitation.objects.none(), - widget=forms.CheckboxSelectMultiple({ - 'checked': 'checked'}), - required=False, label='Reinvite referees') + referees_reinvite = forms.ModelMultipleChoiceField( + queryset=RefereeInvitation.objects.none(), + widget=forms.CheckboxSelectMultiple({'checked': 'checked'}), + required=False, label='Reinvite referees') class Meta: model = Submission @@ -894,7 +901,7 @@ class SubmissionCycleChoiceForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['refereeing_cycle'].default = None + self.fields['refereeing_cycle'].choices = SUBMISSION_CYCLE_CHOICES other_submissions = self.instance.other_versions.all() if other_submissions: self.fields['referees_reinvite'].queryset = RefereeInvitation.objects.filter( @@ -1008,3 +1015,88 @@ class iThenticateReportForm(forms.ModelForm): self.add_error(None, msg) return None return data + + +class FixCollegeDecisionForm(forms.ModelForm): + """Fix EICRecommendation decision.""" + + FIX, DEPRECATE = 'fix', 'deprecate' + action = forms.ChoiceField(choices=((FIX, FIX), (DEPRECATE, DEPRECATE))) + + class Meta: + model = EICRecommendation + fields = () + + def __init__(self, *args, **kwargs): + """Accept request as argument.""" + self.submission = kwargs.pop('submission', None) + self.request = kwargs.pop('request', None) + return super().__init__(*args, **kwargs) + + def clean(self): + """Check if EICRecommendation has the right decision.""" + data = super().clean() + if self.instance.recommendation in [REPORT_MINOR_REV, REPORT_MAJOR_REV]: + self.add_error(None, 'This EICRecommendation its decision can not be fixed.') + elif self.instance.status == DECISION_FIXED: + self.add_error(None, 'This EICRecommendation is already fixed.') + elif self.instance.status == DEPRECATED: + self.add_error(None, 'This EICRecommendation is deprecated.') + return data + + def is_fixed(self): + """Check if decision is fixed.""" + return self.cleaned_data['action'] == self.FIX + + def fix_decision(self, recommendation): + """Fix decision of EICRecommendation.""" + EICRecommendation.objects.filter(id=recommendation.id).update(status=DECISION_FIXED) + submission = recommendation.submission + if recommendation in [REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3]: + # Publish as Tier I, II or III + Submission.objects.filter(id=submission.id).update( + visible_public=True, status=STATUS_ACCEPTED, acceptance_date=datetime.date.today(), + latest_activity=timezone.now()) + + # Start a new ProductionStream + get_or_create_production_stream(submission) + + if self.request: + # Add SubmissionEvent for authors + notify_manuscript_accepted(self.request.user, submission, False) + elif recommendation.recommendation == REPORT_REJECT: + # Decision: Rejection. Auto hide from public and Pool. + Submission.objects.filter(id=submission.id).update( + visible_public=False, visible_pool=False, + status=STATUS_REJECTED, latest_activity=timezone.now()) + submission.get_other_versions().update(visible_public=False) + + # Add SubmissionEvent for authors + submission.add_event_for_author( + 'The Editorial Recommendation has been formulated: {0}.'.format( + recommendation.get_recommendation_display())) + submission.add_event_for_eic( + 'The Editorial Recommendation has been fixed: {0}.'.format( + recommendation.get_recommendation_display())) + return recommendation + + def deprecate_decision(self, recommendation): + """Deprecate decision of EICRecommendation.""" + EICRecommendation.objects.filter(id=recommendation.id).update( + status=DEPRECATED, active=False) + recommendation.submission.add_event_for_eic( + 'The Editorial Recommendation (v{version}) has been deprecated: {decision}.'.format( + version=recommendation.version, + decision=recommendation.get_recommendation_display())) + + return recommendation + + def save(self): + """Update EICRecommendation and related Submission.""" + if self.is_fixed(): + return self.fix_decision(self.instance) + elif self.cleaned_data['action'] == self.DEPRECATE: + return self.deprecate_decision(self.instance) + else: + raise ValueError('The decision given is invalid') + return self.instance diff --git a/submissions/managers.py b/submissions/managers.py index 62bf17e24202a8074a6a9cf9bfa72b6fe52a3622..d244fc2b666773a9ed08e2566e07f0d2073e9dd3 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -137,7 +137,7 @@ class SubmissionQuerySet(models.QuerySet): def actively_refereeing(self): """Return submission currently in some point of the refereeing round.""" - return self.exclude(status=constants.STATUS_EIC_ASSIGNED) + return self.filter(status=constants.STATUS_EIC_ASSIGNED) def public(self): """Return all publicly available Submissions.""" diff --git a/submissions/models.py b/submissions/models.py index 67b54769cf2cdb5cc0f7a39a995075b511c01aa9..c941daa111c9b4a6462a74d4725865f5456accd1 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -70,7 +70,7 @@ class Submission(models.Model): visible_pool = models.BooleanField("Is visible in the Pool", default=True) is_resubmission = models.BooleanField(default=False) refereeing_cycle = models.CharField( - max_length=30, choices=SUBMISSION_CYCLES, default=CYCLE_DEFAULT) + max_length=30, choices=SUBMISSION_CYCLES, default=CYCLE_DEFAULT, blank=True) fellows = models.ManyToManyField('colleges.Fellowship', blank=True, related_name='pool') diff --git a/submissions/templates/partials/submissions/pool/referee_invitations.html b/submissions/templates/partials/submissions/pool/referee_invitations.html index 8af3a7a1081f5007ba03fcb6be4d6a32a8322caf..8367c939ae8756c475e0344541930210d2842a9c 100644 --- a/submissions/templates/partials/submissions/pool/referee_invitations.html +++ b/submissions/templates/partials/submissions/pool/referee_invitations.html @@ -1,20 +1,28 @@ -<table class="table table-invitations"> +<table class="table bg-light table-hover"> + <thead> + <tr> + <th>Referee</th> + <th>Invitation date</th> + <th>Task status</th> + <th colspan="4">Actions</th> + </tr> + </thead> <tbody> {% for invitation in invitations %} <tr> - <td>{{invitation.first_name}} {{invitation.last_name}}</td> + <td>{{ invitation.get_title_display }} {{invitation.first_name}} {{invitation.last_name}}</td> <td> - invited <br/> + invited <br> {{invitation.date_invited}} </td> <td> {% if invitation.fulfilled %} - <strong style="color: green">task fulfilled</strong> + <strong class="text-success">task fulfilled</strong> {% elif invitation.cancelled %} <strong class="text-danger">cancelled</strong> {% elif invitation.accepted is not None %} {% if invitation.accepted %} - <strong style="color: green">task accepted</strong> + <strong class="text-success">task accepted</strong> {% else %} <strong class="text-danger">task declined</strong> {% endif %} @@ -56,7 +64,7 @@ </tr> {% empty %} <tr> - <td class="text-center py-3">You have not invited any referees yet.</td> + <td class="text-center py-3" colspan="7">You have not invited any referees yet.</td> </tr> {% endfor %} </tbody> diff --git a/submissions/templates/partials/submissions/pool/referee_invitations_status.html b/submissions/templates/partials/submissions/pool/referee_invitations_status.html index 225405c15291193e2e4c3bbdabb2a24d6ad3f67e..6d1099c8cc827f9bb82e43ac92fdb2510a27d871 100644 --- a/submissions/templates/partials/submissions/pool/referee_invitations_status.html +++ b/submissions/templates/partials/submissions/pool/referee_invitations_status.html @@ -1,7 +1,20 @@ {% if submission.refereeing_cycle != 'direct_rec' %} -<p> - Nr referees invited: {{submission.referee_invitations.count}} <span>[{{submission.count_accepted_invitations}} acccepted / {{submission.count_declined_invitations}} declined / {{submission.count_pending_invitations}} response pending]</span> - <br> - Nr reports obtained: {{submission.count_obtained_reports}} [{{submission.count_invited_reports}} invited / {{submission.count_contrib_reports}} contributed], nr refused: {{submission.reports.rejected.count}}, nr awaiting vetting: {{submission.reports.awaiting_vetting.count}} -</p> + <div class="table-responsive-md"> + <table class="table table-borderless"> + <tbody> + <tr> + <td>Nr referees invited:</td> + <td> + {{submission.referee_invitations.count}} <span>[{{submission.count_accepted_invitations}} acccepted / {{submission.count_declined_invitations}} declined / {{submission.count_pending_invitations}} response pending]</span> + </td> + </tr> + <tr> + <td>Nr reports obtained:</td> + <td> + {{submission.count_obtained_reports}} [{{submission.count_invited_reports}} invited / {{submission.count_contrib_reports}} contributed], nr refused: {{submission.reports.rejected.count}}, nr awaiting vetting: {{submission.reports.awaiting_vetting.count}} + </td> + </tr> + </tbody> + </table> + </div> {% endif %} diff --git a/submissions/templates/partials/submissions/pool/submission_li.html b/submissions/templates/partials/submissions/pool/submission_li.html index fdd9854706c11655521eb30a85470216a5f4aeef..0c1c01854082b75f377e46c726841cf681bb9da3 100644 --- a/submissions/templates/partials/submissions/pool/submission_li.html +++ b/submissions/templates/partials/submissions/pool/submission_li.html @@ -6,13 +6,22 @@ {% endif %} </div> <div class="pool-item"> - <p class="mb-1"> - <a href="{% url 'submissions:pool' submission.arxiv_identifier_w_vn_nr %}">{{ submission.title }}</a><br> - <em>by {{ submission.author_list }}</em> - </p> + <div class="row mb-1"> + <div class="col-md-7"> + <a href="{% url 'submissions:pool' submission.arxiv_identifier_w_vn_nr %}">{{ submission.title }}</a><br> + <em>by {{ submission.author_list }}</em> + </div> + <div class="col-md-5"> + {% if submission.eicrecommendations.active.first %} + <small class="text-muted">EIC Recommendation Status</small> + <br> + <span class="label label-sm label-secondary">{{ submission.eicrecommendations.active.first.get_status_display }}</span> + {% endif %} + </div> + </div> <div class="row mb-0"> - <div class="col-3"> + <div class="col-md-3"> <small class="text-muted">Editor-in-charge</small> <br> {% if submission.status == 'unassigned' %} @@ -23,7 +32,7 @@ {{ submission.editor_in_charge }} {% endif %} </div> - <div class="col-2"> + <div class="col-md-2"> <small class="text-muted">Actions</small> <br> <a href="{% url 'submissions:pool' submission.arxiv_identifier_w_vn_nr %}" data-toggle="dynamic" data-target="#container_{{ submission.id }}">See details</a> @@ -31,13 +40,13 @@ · <a href="{% url 'submissions:editorial_page' submission.arxiv_identifier_w_vn_nr %}">Editorial page</a> {% endif %} </div> - <div class="col-2"> + <div class="col-md-2"> <small class="text-muted">Original Submission date</small> <br> {{ submission.original_submission_date }} </div> - <div class="col-5"> - <small class="text-muted">Status</small> + <div class="col-md-5"> + <small class="text-muted">Submission Status</small> <br> <span class="label label-sm label-secondary">{{ submission.get_status_display }}</span> </div> diff --git a/submissions/templates/submissions/pool/editorial_page.html b/submissions/templates/submissions/pool/editorial_page.html index aa4f0bd57419435193db5f5015c684549e23f08e..1e0ddf49ef2dfc58ae92233ef8ff625ef7e54f46 100644 --- a/submissions/templates/submissions/pool/editorial_page.html +++ b/submissions/templates/submissions/pool/editorial_page.html @@ -76,23 +76,54 @@ </div> <div class="row"><!-- Status --> - <div class="col-md-12"> - {% include 'partials/submissions/submission_status.html' with submission=submission %} - {% if submission.plagiarism_report %} - <h4>Plagiarism report status: {% if submission.plagiarism_report.percent_match %}<b>{{submission.plagiarism_report.percent_match}}%</b>{% else %}<em>Scan in progress</em>{% endif %}</h4> - {% endif %} + <div class="col"> + <table class="table table-borderless"> + <tr> + <td>Current status:</td> + <td><span class="label label-secondary">{{ submission.get_status_display }}</span></td> + </tr> + {% if submission.eicrecommendations.active.first %} + <tr> + <td>Recommendation status:</td> + <td><span class="label label-secondary">{{ submission.eicrecommendations.active.first.get_status_display }}</span></td> + </tr> + {% endif %} + <tr> + <td>Cycle:</td> + <td>{{ submission.get_refereeing_cycle_display }}</td> + </tr> + <tr> + <td>Plagiarism report:</td> + <td> + {% if submission.plagiarism_report %} + {% if submission.plagiarism_report.percent_match %} + <b>{{submission.plagiarism_report.percent_match}}%</b> + {% else %} + <em>Scan in progress</em> + {% if perms.scipost.can_do_plagiarism_checks %} + <br> + <a href="{% url 'submissions:plagiarism' submission.arxiv_identifier_w_vn_nr %}">Update plagiarism score</a> + {% endif %} + {% endif %} + {% else %} + <em>No plagiarism report found.</em> + {% if perms.scipost.can_do_plagiarism_checks %} + <br> + <a href="{% url 'submissions:plagiarism' submission.arxiv_identifier_w_vn_nr %}">Run plagiarism check</a> + {% endif %} + {% endif %} + </td> + </tr> + </table> </div> -</div><!-- End status --> - -{% if full_access %} - <div class="row"> - <div class="col-md-10 col-lg-8"> + {% if full_access %} + <div class="col-12 col-lg-6"> {% include 'partials/submissions/pool/required_actions_block.html' with submission=submission %} </div> - </div> -{% endif %} + {% endif %} +</div><!-- End status --> -{% if submission.status == 'resubmitted_incoming' %} +{% if not submission.refereeing_cycle %} {% if full_access %} <div class="row"> <div class="col-12"> diff --git a/submissions/templates/submissions/pool/recommendation.html b/submissions/templates/submissions/pool/recommendation.html index 6a15dfd082c466208210e6e3bd4137f8abe36c53..7c15df4629984378f74a9424974bf4103fe53ee7 100644 --- a/submissions/templates/submissions/pool/recommendation.html +++ b/submissions/templates/submissions/pool/recommendation.html @@ -79,8 +79,16 @@ <div class="card-footer bg-light py-3"> <h3 class="card-title">Administrative actions on recommendations undergoing voting:</h3> <ul class="mb-0"> - <li>To send an email reminder to each Fellow with at least one voting duty: <a href="{% url 'submissions:remind_Fellows_to_vote' %}">click here</a></li> - <li>To fix the College decision and follow the Editorial Recommendation as is: <a href="{% url 'submissions:fix_College_decision' rec_id=recommendation.id %}">click here</a></li> + <li>To send an email reminder to each Fellow with at least one voting duty: <a href="{% url 'submissions:remind_Fellows_to_vote' %}">click here</a>.</li> + <li> + The current Editorial Recommendation: {{ recommendation.get_recommendation_display }}. + <br> + <form method="post" action="{% url 'submissions:eic_recommendation_detail' recommendation.submission.arxiv_identifier_w_vn_nr recommendation.id %}" class="d-inline-block my-2"> + {% csrf_token %} + <button type="submit" name="action" value="fix" class="btn btn-primary btn-sm mr-2">Fix College decision</button> + <button type="submit" name="action" value="deprecate" class="btn btn-danger btn-sm text-white">Deprecate College decision</button> + </form> + </li> <li>To request a modification of the Recommendation to request for revision: click here</li> </ul> </div> diff --git a/submissions/utils.py b/submissions/utils.py index 9073269e23f1a2fdb4406aa2bbc863c59e94a548..a96fcc8adaa5c0b01ed42c1c49c46bd30fed4941 100644 --- a/submissions/utils.py +++ b/submissions/utils.py @@ -49,15 +49,20 @@ class BaseSubmissionCycle: # Editor-in-charge has requested revision. return False - if self.submission.eicrecommendations.exists(): + if not self.submission.plagiarism_report: + # No plagiarism report is known yet. + self.required_actions.append(( + 'plagiarism_report', + 'No plagiarism report found. Please run the plagiarism check.')) + + if self.submission.eicrecommendations.active().exists(): # A Editorial Recommendation has already been submitted. Cycle done. return False - if self.submission.is_resubmission and self.submission.status == STATUS_INCOMING: - # Submission is a resubmission and the EIC still has to determine which - # cycle to proceed with. - self.required_actions.append(('choose_cycle', - 'Choose the submission cycle to proceed with.',)) + if not self.submission.refereeing_cycle: + # Submission is a resubmission: EIC has to determine which cycle to proceed with. + self.required_actions.append( + ('choose_cycle', 'Choose the submission cycle to proceed with.')) return False comments_to_vet = self.submission.comments.awaiting_vetting().count() diff --git a/submissions/views.py b/submissions/views.py index 0fe7246c7c6b042420f408a6959fbda099dfe489..6f789246c507f9f4463bb4f84d9ab3876a221212 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -18,7 +18,7 @@ from django.template import Template, Context from django.utils import timezone from django.utils.decorators import method_decorator from django.views.generic.base import RedirectView -from django.views.generic.detail import DetailView, SingleObjectMixin +from django.views.generic.detail import SingleObjectMixin from django.views.generic.edit import CreateView, UpdateView from django.views.generic.list import ListView @@ -36,7 +36,7 @@ from .forms import ( RefereeRecruitmentForm, ConsiderRefereeInvitationForm, EditorialCommunicationForm, EICRecommendationForm, ReportForm, VetReportForm, VotingEligibilityForm, SubmissionCycleChoiceForm, ReportPDFForm, SubmissionReportsForm, iThenticateReportForm, - SubmissionPoolFilterForm) + SubmissionPoolFilterForm, FixCollegeDecisionForm) from .signals import notify_manuscript_accepted from .utils import SubmissionUtils @@ -1539,65 +1539,113 @@ def fix_College_decision(request, rec_id): Called by an Editorial Administrator. """ - submissions = Submission.objects.pool_full(request.user) - recommendation = get_object_or_404(EICRecommendation.objects.filter( - submission__in=submissions).put_to_voting(), id=rec_id) - - submission = recommendation.submission - if recommendation.recommendation in [1, 2, 3]: - # Publish as Tier I, II or III - Submission.objects.filter(id=submission.id).update( - visible_public=True, status=STATUS_ACCEPTED, acceptance_date=datetime.date.today(), - latest_activity=timezone.now()) - - # Create a ProductionStream object - prodstream = ProductionStream(submission=submission) - prodstream.save() - ed_admins = Group.objects.get(name='Editorial Administrators') - assign_perm('can_perform_supervisory_actions', ed_admins, prodstream) - assign_perm('can_work_for_stream', ed_admins, prodstream) - - # Add SubmissionEvent for authors - notify_manuscript_accepted(request.user, submission, False) - submission.add_event_for_author('An Editorial Recommendation has been formulated: %s.' - % recommendation.get_recommendation_display()) - elif recommendation.recommendation == -3: - # Decision: Rejection - Submission.objects.filter(id=submission.id).update( - visible_public=False, status=STATUS_REJECTED, latest_activity=timezone.now()) - submission.get_other_versions().update(visible_public=False) - - # Add SubmissionEvent for authors - submission.add_event_for_author('An Editorial Recommendation has been formulated: %s.' - % recommendation.get_recommendation_display()) - - # Add SubmissionEvent for EIC - submission.add_event_for_eic('The Editorial College\'s decision has been fixed: %s.' - % recommendation.get_recommendation_display()) - - # Temporary: Update submission instance for utils email func. - # Won't be needed in new mail construct. - submission = Submission.objects.get(id=submission.id) - SubmissionUtils.load({'submission': submission, 'recommendation': recommendation}) - SubmissionUtils.send_author_College_decision_email() - messages.success(request, 'The Editorial College\'s decision has been fixed.') + # + # submissions = Submission.objects.pool_full(request.user) + # recommendation = get_object_or_404(EICRecommendation.objects.filter( + # submission__in=submissions).put_to_voting(), id=rec_id) + # + # form = FixCollegeDecisionForm(request.POST or None, instance=recommendation) + # if form.is_valid(): + # recommendation = form.save() + # submission = recommendation.submission + # + # # Temporary: Update submission instance for utils email func. + # # Won't be needed in new mail construct. + # submission = Submission.objects.get(id=recommendation.submission.id) + # SubmissionUtils.load({'submission': submission, 'recommendation': recommendation}) + # SubmissionUtils.send_author_College_decision_email() + # + # submission.add_event_for_eic( + # 'The Editorial College\'s decision has been fixed: {0}.'.format( + # recommendation.get_recommendation_display())) + # messages.success(request, 'The Editorial College\'s decision has been fixed.') + # return redirect(reverse('submissions:pool')) + # + # submission = recommendation.submission + # if recommendation.recommendation in [1, 2, 3]: + # # Publish as Tier I, II or III + # Submission.objects.filter(id=submission.id).update( + # visible_public=True, status=STATUS_ACCEPTED, acceptance_date=datetime.date.today(), + # latest_activity=timezone.now()) + # + # # Create a ProductionStream object + # prodstream = ProductionStream(submission=submission) + # prodstream.save() + # ed_admins = Group.objects.get(name='Editorial Administrators') + # assign_perm('can_perform_supervisory_actions', ed_admins, prodstream) + # assign_perm('can_work_for_stream', ed_admins, prodstream) + # + # # Add SubmissionEvent for authors + # notify_manuscript_accepted(request.user, submission, False) + # submission.add_event_for_author('An Editorial Recommendation has been formulated: %s.' + # % recommendation.get_recommendation_display()) + # elif recommendation.recommendation == -3: + # # Decision: Rejection + # Submission.objects.filter(id=submission.id).update( + # visible_public=False, status=STATUS_REJECTED, latest_activity=timezone.now()) + # submission.get_other_versions().update(visible_public=False) + # + # # Add SubmissionEvent for authors + # submission.add_event_for_author('An Editorial Recommendation has been formulated: %s.' + # % recommendation.get_recommendation_display()) + # + # # Add SubmissionEvent for EIC + # submission.add_event_for_eic('The Editorial College\'s decision has been fixed: %s.' + # % recommendation.get_recommendation_display()) + # # + # # # Temporary: Update submission instance for utils email func. + # # # Won't be needed in new mail construct. + # # submission = Submission.objects.get(id=submission.id) + # # SubmissionUtils.load({'submission': submission, 'recommendation': recommendation}) + # # SubmissionUtils.send_author_College_decision_email() + # # messages.success(request, 'The Editorial College\'s decision has been fixed.') return redirect(reverse('submissions:pool')) -class EICRecommendationView(SubmissionAdminViewMixin, DetailView): +class EICRecommendationView(SubmissionAdminViewMixin, UpdateView): """EICRecommendation detail view.""" permission_required = 'scipost.can_fix_College_decision' template_name = 'submissions/pool/recommendation.html' editorial_page = True + form_class = FixCollegeDecisionForm + success_url = reverse_lazy('submissions:pool') + + def get_object(self): + """Get EICRecommendation.""" + submission = super().get_object() + return get_object_or_404(submission.eicrecommendations.all(), id=self.kwargs['rec_id']) + + def get_form_kwargs(self): + """Form accepts request as argument.""" + kwargs = super().get_form_kwargs() + kwargs['request'] = self.request + return kwargs def get_context_data(self, *args, **kwargs): """Get the EICRecommendation as a submission-related instance.""" ctx = super().get_context_data(*args, **kwargs) - ctx['recommendation'] = get_object_or_404( - ctx['submission'].eicrecommendations.all(), id=self.kwargs['rec_id']) + ctx['recommendation'] = ctx['object'] return ctx + @transaction.atomic + def form_valid(self, form): + """Redirect and send out mails if decision is fixed.""" + recommendation = form.save() + if form.is_fixed(): + submission = recommendation.submission + + # Temporary: Update submission instance for utils email func. + # Won't be needed in new mail construct. + submission = Submission.objects.get(id=recommendation.submission.id) + SubmissionUtils.load({'submission': submission, 'recommendation': recommendation}) + SubmissionUtils.send_author_College_decision_email() + messages.success(self.request, 'The Editorial College\'s decision has been fixed.') + else: + messages.success( + self.request, 'The Editorial College\'s decision has been deprecated.') + return HttpResponseRedirect(self.success_url) + class PlagiarismView(SubmissionAdminViewMixin, UpdateView): """Administration detail page of Plagiarism report."""