From e7a4bfaa366d516584f6e7f7c5bb95cde92501a9 Mon Sep 17 00:00:00 2001 From: Jorran de Wit <jorrandewit@outlook.com> Date: Thu, 13 Dec 2018 07:32:34 +0100 Subject: [PATCH] .. --- submissions/models.py | 27 +- submissions/refereeing_cycles.py | 104 +++++- .../pool/required_actions_block.html | 4 +- .../pool/required_actions_tooltip.html | 4 +- .../submissions/pool/submission_details.html | 13 +- .../pool/submission_info_table.html | 17 +- .../pool/submission_info_table_extended.html | 32 ++ .../submissions/pool/submission_li.html | 7 +- .../submission_author_information.html | 16 +- .../submission_editorial_information.html | 4 +- .../submissions/submission_summary.html | 6 + .../submissions/submission_topics.html | 30 +- .../admin/submission_presassign_editors.html | 2 +- .../admin/submission_prescreening.html | 11 +- .../admin/submission_reassign.html | 2 +- submissions/utils.py | 297 +----------------- .../submission_cycle_reinvite_referee.html | 28 -- .../submission_cycle_reinvite_referee.txt | 15 - 18 files changed, 204 insertions(+), 415 deletions(-) create mode 100644 submissions/templates/partials/submissions/pool/submission_info_table_extended.html delete mode 100644 templates/email/submission_cycle_reinvite_referee.html delete mode 100644 templates/email/submission_cycle_reinvite_referee.txt diff --git a/submissions/models.py b/submissions/models.py index 426281dfa..4a1edea25 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -29,9 +29,7 @@ from .constants import ( from .managers import ( SubmissionQuerySet, EditorialAssignmentQuerySet, EICRecommendationQuerySet, ReportQuerySet, SubmissionEventQuerySet, RefereeInvitationQuerySet, EditorialCommunicationQueryset) -from .refereeing_cycles import RegularCycle -from .utils import ( - ShortSubmissionCycle, DirectRecommendationSubmissionCycle, GeneralSubmissionCycle) +from .refereeing_cycles import ShortCycle, DirectCycle, RegularCycle from comments.behaviors import validate_file_extension, validate_max_file_size from comments.models import Comment @@ -174,34 +172,25 @@ class Submission(models.Model): def comments_set_complete(self): """Return Comments on Submissions, Reports and other Comments.""" - return Comment.objects.filter(Q(submissions=self) | - Q(reports__submission=self) | - Q(comments__reports__submission=self) | - Q(comments__submissions=self)).distinct() + return Comment.objects.filter( + Q(submissions=self) | Q(reports__submission=self) | + Q(comments__reports__submission=self) | Q(comments__submissions=self)).distinct() @property def cycle(self): """Get cycle object that's relevant for the Submission.""" if not hasattr(self, '_cycle'): - self._cycle = RegularCycle(self) - return self._cycle - - @property - def cycle_old(self): - """Get cycle object that's relevant for the Submission.""" - if not hasattr(self, '__cycle'): self.set_cycle() - return self.__cycle + return self._cycle def set_cycle(self): """Set cycle to the Submission on request.""" if self.refereeing_cycle == CYCLE_SHORT: - cycle = ShortSubmissionCycle(self) + self._cycle = ShortCycle(self) elif self.refereeing_cycle == CYCLE_DIRECT_REC: - cycle = DirectRecommendationSubmissionCycle(self) + self._cycle = DirectCycle(self) else: - cycle = GeneralSubmissionCycle(self) - self.__cycle = cycle + self._cycle = RegularCycle(self) def get_absolute_url(self): """Return url of the Submission detail page.""" diff --git a/submissions/refereeing_cycles.py b/submissions/refereeing_cycles.py index 9feeb5fde..ea310ad15 100644 --- a/submissions/refereeing_cycles.py +++ b/submissions/refereeing_cycles.py @@ -2,13 +2,56 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" import datetime +import json from django.core.urlresolvers import reverse from django.utils import timezone -from django.utils.html import format_html +from django.utils.html import format_html, format_html_join, html_safe from . import constants -from .utils import RequiredActionsDict + + +@html_safe +class RequiredActionsDict(dict): + """A collection of required actions. + + The required action, meant for the editors-in-charge know how to display itself in + various formats. Dictionary keys are the action-codes, the values are the texts + to present to the user. + """ + + def as_data(self): + return {f: e.as_data() for f, e in self.items()} + + def as_list_text(self): + return [e.__str__() for e in self.values()] + + def get_json_data(self, escape_html=False): + return {f: e.get_json_data(escape_html) for f, e in self.items()} + + def as_json(self, escape_html=False): + return json.dumps(self.get_json_data(escape_html)) + + def as_ul(self): + if not self: + return '<div class="no-actions-msg">No required actions.</div>' + return format_html( + '<ul class="actions-list">{}</ul>', format_html_join('', '<li>{}</li>', self.values())) + + def as_text(self): + return ' '.join([action.as_text() for action in self.values()]) + + def __getitem__(self, action): + return super().__getitem__(action.id) + + def __setitem__(self, action, val): + super().__setitem__(action.id, val) + + def __contains__(self, value): + return value.id in list(self.keys()) + + def __str__(self): + return self.as_ul() class BaseAction: @@ -97,9 +140,11 @@ class ChoiceCycleAction(BaseAction): class NoEICRecommendationAction(BaseAction): + needs_referees = False + @property def txt(self): - if not self.submission.reports.non_draft().exists(): + if self.needs_referees: txt = ( 'The refereeing deadline has passed and you have received no Reports yet.' ' Please <a href="{url}">extend the reporting deadline</a> ' @@ -152,13 +197,11 @@ class BaseCycle: """ days_for_refereeing = 28 - minimum_number_of_referees = 3 can_invite_referees = True - def __init__(self, submission, current_user=None): + def __init__(self, submission): self._submission = submission self._required_actions = None - self._current_user = current_user @property def required_actions(self): @@ -166,6 +209,12 @@ class BaseCycle: self.update_required_actions() return self._required_actions + @property + def minimum_number_of_referees(self): + if self._submission.proceedings and self._submission.proceedings.minimum_referees: + return self._submission.proceedings.minimum_referees + return 3 # Three by default + def has_required_actions(self): return bool(self.required_actions) @@ -188,7 +237,9 @@ class BaseCycle: # The EIC is late with formulating a Recommendation. if self._submission.eic_recommendation_required: if self._submission.reporting_deadline_has_passed: - self.add_action(NoEICRecommendationAction()) + action = NoEICRecommendationAction() + action.needs_referees = not self._submission.reports.non_draft().exists() + self.add_action(action) # Submission is a resubmission: EIC has to determine which cycle to proceed with. comments_to_vet = self._submission.comments.awaiting_vetting().values_list('id') @@ -269,7 +320,9 @@ class BaseCycle: return self.as_text() def reset_refereeing_round(self): - """Set the Submission status to EIC_ASSIGNED and reset the reporting deadline.""" + """ + Set the Submission status to EIC_ASSIGNED and reset the reporting deadline. + """ from .models import Submission # Prevent circular import errors if self._submission.status in [constants.STATUS_INCOMING, constants.STATUS_UNASSIGNED]: Submission.objects.filter(id=self._submission.id).update( @@ -279,12 +332,13 @@ class BaseCycle: Submission.objects.filter(id=self._submission.id).update(reporting_deadline=deadline) def reinvite_referees(self, referees, request): - """Duplicate and reset RefereeInvitations and send `reinvite` mail. + """ + Duplicate and reset RefereeInvitations and send `reinvite` mail. referees - (list of) RefereeInvitation instances """ from mails.utils import DirectMailUtil - SubmissionUtils.load({'submission': self._submission}) + # SubmissionUtils.load({'submission': self._submission}) for referee in referees: invitation = referee invitation.pk = None # Duplicate, do not remove the old invitation @@ -296,9 +350,33 @@ class BaseCycle: mail_code='referees/reinvite_contributor_to_referee', instance=invitation) mail_sender.send() - # SubmissionUtils.load({'invitation': invitation}, request) - # SubmissionUtils.reinvite_referees_email() - class RegularCycle(BaseCycle): pass + + +class ShortCycle(BaseCycle): + """ + Short (two weeks) version of the regular refereeing cycle, used for resubmissions on request + by the editor. + """ + days_for_refereeing = 14 + + @property + def minimum_number_of_referees(self): + return 1 + + +class DirectCycle(BaseCycle): + """ + Refereeing cycle without refereeing. The editor directly formulates an EICRecommendation. + """ + can_invite_referees = False + + def update_required_actions(self): + """Gather the required actions list and populate self._required_actions.""" + super().update_required_actions() + + # Always show `EICRec required` action disregarding the refereeing deadline. + if self._submission.eic_recommendation_required: + self.add_action(NoEICRecommendationAction()) diff --git a/submissions/templates/partials/submissions/pool/required_actions_block.html b/submissions/templates/partials/submissions/pool/required_actions_block.html index 7b82edb3c..7ae18b18c 100644 --- a/submissions/templates/partials/submissions/pool/required_actions_block.html +++ b/submissions/templates/partials/submissions/pool/required_actions_block.html @@ -5,8 +5,8 @@ </div> <div class="card-body"> <ul class="mb-0"> - {% for action in submission.cycle.required_actions %} - <li>{{action.1}}</li> + {% for key, action in submission.cycle.required_actions.items %} + <li>{{ action|safe }}</li> {% empty %} <li>No actions required</li> {% endfor %} diff --git a/submissions/templates/partials/submissions/pool/required_actions_tooltip.html b/submissions/templates/partials/submissions/pool/required_actions_tooltip.html index 4b42c758a..b4fd08f77 100644 --- a/submissions/templates/partials/submissions/pool/required_actions_tooltip.html +++ b/submissions/templates/partials/submissions/pool/required_actions_tooltip.html @@ -2,8 +2,8 @@ <i class="fa fa-exclamation-circle {{ classes }}" data-toggle="tooltip" data-html="true" title=" Required Actions: <ul class='mb-0 pl-3 text-left'> - {% for action in submission.cycle.required_actions %} - <li>{{ action.1 }}</li> + {% for key, action in submission.cycle.required_actions.items %} + <li>{{ action }}</li> {% endfor %} </ul> "></i> diff --git a/submissions/templates/partials/submissions/pool/submission_details.html b/submissions/templates/partials/submissions/pool/submission_details.html index 85a54a447..05fe5dcc9 100644 --- a/submissions/templates/partials/submissions/pool/submission_details.html +++ b/submissions/templates/partials/submissions/pool/submission_details.html @@ -38,8 +38,17 @@ </ul> {% if is_editor_in_charge or is_editorial_admin or submission|is_voting_fellow:request.user %} - <br> - {% include 'partials/submissions/pool/required_actions_block.html' with submission=submission %} + <div class="my-4 p-3 border{% if submission.cycle.has_required_actions %} border-danger bg-light{% endif %}" id="required-actions" {% if submission.cycle.has_required_actions %}style="border-width: 2px !important;"{% endif %}> + <h3> + {% if submission.cycle.has_required_actions %} + <i class="fa fa-exclamation-triangle text-danger"></i> + {% else %} + <i class="fa fa-check-circle"></i> + {% endif %} + Required actions + </h3> + {{ submission.cycle.required_actions }} + </div> <h4> <a href="{% url 'submissions:editorial_page' submission.preprint.identifier_w_vn_nr %}">Go to this Submission's Editorial Page</a> </h4> diff --git a/submissions/templates/partials/submissions/pool/submission_info_table.html b/submissions/templates/partials/submissions/pool/submission_info_table.html index 3594325cb..72dedee19 100644 --- a/submissions/templates/partials/submissions/pool/submission_info_table.html +++ b/submissions/templates/partials/submissions/pool/submission_info_table.html @@ -5,7 +5,7 @@ </tr> {% if submission.preprint.url %} <tr> - <td>Arxiv Link:</td> + <td>Arxiv Link</td> <td> <a href="{{ submission.preprint.url }}" target="_blank">{{ submission.preprint.url }}</a> </td> @@ -23,6 +23,13 @@ <td>{{submission.submission_date}} to {{submission.submitted_to}}</td> </tr> + {% if submission.proceedings %} + <tr> + <td>Proceedings issue</td> + <td>{{ submission.proceedings }}</td> + </tr> + {% endif %} + {% if submission.acceptance_date %} <tr> <td>Accepted</td> @@ -39,10 +46,10 @@ <td> {% if submission.editor_in_charge %} {{ submission.editor_in_charge }} - {% elif perms.scipost.can_assign_submissions %} - <a href="{% url 'submissions:assign_submission' submission.preprint.identifier_w_vn_nr %}">Send a new assignment request</a> - {% else %} + {% elif request.user.contributor.is_MEC %} <strong class="text-danger">You can volunteer to become Editor-in-charge by <a href="{% url 'submissions:volunteer_as_EIC' submission.preprint.identifier_w_vn_nr %}">clicking here</a>.</strong> + {% else %} + <em>No editor assigned yet.</em> {% endif %} </td> </tr> @@ -101,4 +108,6 @@ {% endif %} + {% block extended_info_table %}{% endblock %} + </table> diff --git a/submissions/templates/partials/submissions/pool/submission_info_table_extended.html b/submissions/templates/partials/submissions/pool/submission_info_table_extended.html new file mode 100644 index 000000000..d33b030d0 --- /dev/null +++ b/submissions/templates/partials/submissions/pool/submission_info_table_extended.html @@ -0,0 +1,32 @@ +{% extends 'partials/submissions/pool/submission_info_table.html' %} + +{% block extended_info_table %} + <tr> + <td colspan="2"><br></td> + </tr> + <tr> + <td>As Contributors</td> + <td> + {% for author in submission.authors.all %} + {% if not forloop.first %}<span class="text-blue">·</span> {% endif %}<a href="{% url 'scipost:contributor_info' author.id %}">{{author.user.first_name}} {{author.user.last_name}}</a> + {% empty %} + (none claimed) + {% endfor %} + </td> + </tr> + <tr> + <td>Submitted by</td> + <td> + {{submission.submitted_by.user.first_name}} {{submission.submitted_by.user.last_name}} + </td> + </tr> + <tr> + <td>Domain(s)</td> + <td>{{submission.get_domain_display}}</td> + </tr> + <tr> + <td>Subject area</td> + <td>{{submission.get_subject_area_display}}</td> + </tr> + +{% endblock %} diff --git a/submissions/templates/partials/submissions/pool/submission_li.html b/submissions/templates/partials/submissions/pool/submission_li.html index 708bfc31b..d20e0cafd 100644 --- a/submissions/templates/partials/submissions/pool/submission_li.html +++ b/submissions/templates/partials/submissions/pool/submission_li.html @@ -43,9 +43,9 @@ {% endif %} </div> <div class="col-md-2"> - <small class="text-muted">Original Submission date</small> + <small class="text-muted">Latest activity</small> <br> - {{ submission.original_submission_date }} + {{ submission.latest_activity }} </div> <div class="col-md-5"> <small class="text-muted">Submission Status</small> @@ -56,7 +56,8 @@ {% if submission.cycle.has_required_actions %} <div class="card-text bg-danger text-white mt-1 py-1 px-2"> - This Submission contains required actions, <a href="{% url 'submissions:pool' submission.preprint.identifier_w_vn_nr %}" class="text-white" data-toggle="dynamic" data-target="#container_{{ submission.id }}">click to see details.</a> {% include 'partials/submissions/pool/required_actions_tooltip.html' with submission=submission classes='text-white' %} + <a href="{% url 'submissions:pool' submission.preprint.identifier_w_vn_nr %}" class="text-white mr-1" data-toggle="dynamic" data-target="#container_{{ submission.id }}">This Submission contains required actions, click here to see details.</a> + {% include 'partials/submissions/pool/required_actions_tooltip.html' with submission=submission classes='text-white' %} </div> {% endif %} diff --git a/submissions/templates/partials/submissions/submission_author_information.html b/submissions/templates/partials/submissions/submission_author_information.html index f56b18926..3b47d2a5f 100644 --- a/submissions/templates/partials/submissions/submission_author_information.html +++ b/submissions/templates/partials/submissions/submission_author_information.html @@ -2,14 +2,12 @@ <h3 class="highlight"> Author information - <small><a href="javascript:;" class="text-muted" data-toggle="tooltip" data-title="You see this information because you are a verified author of this Submission.">?</a></small> - <br> - <small><a href="javascript:;" class="mt-2 d-inline-block text-sm" data-toggle="toggle" data-target="#authorinformation">Show/hide author information</a></small> + <a href="javascript:;" class="text-muted" data-toggle="tooltip" data-title="You see this information because you are a verified author of this Submission."><i class="fa fa-question-circle"></i></a> </h3> <div id="authorinformation" class="mt-2"> <div class="mb-4"> - <h3 class="mt-3">Status summary</h3> + <h4 class="mt-3">Status summary</h4> <p> {% if not submission.editor_in_charge %} No Editor-in-charge is assigned yet. The SciPost administration will inform you as soon as one is assigned. @@ -67,7 +65,7 @@ {% if submission.eicrecommendations.active %} <div class="mb-4"> - <h3 class="mb-2">Editorial Recommendation:</h3> + <h4 class="mb-2">Editorial Recommendation:</h4> {% for recommendation in submission.eicrecommendations.active %} {% include 'partials/submissions/recommendation_author_content.html' with recommendation=recommendation %} {% empty %} @@ -77,7 +75,7 @@ {% endif %} <div class="mb-4"> - <h3>Communication</h3> + <h4>Communication</h4> <div id="communications"> {% if submission.editor_in_charge %} <a href="{% url 'submissions:communication' submission.preprint.identifier_w_vn_nr 'AtoE' %}">Write to the Editor-in-charge</a> @@ -87,7 +85,7 @@ </div> <div class="mb-4"> - <h3>Events</h3> + <h4>Events</h4> <div id="eventslist"> {% include 'partials/submissions/submission_events.html' with events=submission.events.for_author %} </div> @@ -95,7 +93,7 @@ <div class="mb-4" id="proofsslist"> - <h3>Proofs</h3> + <h4>Proofs</h4> <ul class="list-group list-group-flush events-list"> {% for proofs in submission.production_stream.proofs.for_authors %} <li> @@ -103,7 +101,7 @@ status: <span class="label label-secondary label-sm">{{ proofs.get_status_display }}</span> {% if proofs.status == 'accepted_sup' or proofs.status == 'sent' %} {% if proofs_decision_form and is_author %} - <h3 class="mb-0 mt-2">Please advise the Production Team on your findings on Proofs version {{ proofs.version }}</h3> + <h4 class="mb-0 mt-2">Please advise the Production Team on your findings on Proofs version {{ proofs.version }}</h4> <form method="post" enctype="multipart/form-data" action="{% url 'production:author_decision' proofs.slug %}" class="my-2"> {% csrf_token %} {{ proofs_decision_form|bootstrap }} diff --git a/submissions/templates/partials/submissions/submission_editorial_information.html b/submissions/templates/partials/submissions/submission_editorial_information.html index 0808c075f..3adca2073 100644 --- a/submissions/templates/partials/submissions/submission_editorial_information.html +++ b/submissions/templates/partials/submissions/submission_editorial_information.html @@ -1,7 +1,7 @@ {% load bootstrap %} -<h3 class="highlight">Editorial information</h3> -<a href="javascript:;" class="btn btn-default mb-2" data-toggle="toggle" data-target="#editorialinformation">Show/hide editorial information</a> +<h3>Editorial information</h3> +<a href="javascript:;" class="d-block mb-2" data-toggle="toggle" data-target="#editorialinformation">Show/hide editorial information</a> <div id="editorialinformation" class="mt-2" style="display:none;"> <div class="mb-4"> diff --git a/submissions/templates/partials/submissions/submission_summary.html b/submissions/templates/partials/submissions/submission_summary.html index b75a9ae12..f69bb8e9f 100644 --- a/submissions/templates/partials/submissions/submission_summary.html +++ b/submissions/templates/partials/submissions/submission_summary.html @@ -52,6 +52,12 @@ <td>Submitted to:</td> <td>{{submission.submitted_to}}</td> </tr> + {% if submission.proceedings %} + <tr> + <td>Proceedings issue:</td> + <td>{{ submission.proceedings }}</td> + </tr> + {% endif %} <tr> <td>Domain(s):</td> <td>{{submission.get_domain_display}}</td> diff --git a/submissions/templates/partials/submissions/submission_topics.html b/submissions/templates/partials/submissions/submission_topics.html index 6f4d50c6d..793304181 100644 --- a/submissions/templates/partials/submissions/submission_topics.html +++ b/submissions/templates/partials/submissions/submission_topics.html @@ -1,4 +1,5 @@ {% load user_groups %} +{% load bootstrap %} {% is_scipost_admin request.user as is_scipost_admin %} {% is_edcol_admin request.user as is_edcol_admin %} @@ -11,7 +12,7 @@ <div> {% for topic in submission.topics.all %} - <span class="label label-secondary"><a href="{% url 'ontology:topic_details' slug=topic.slug %}">{{ topic }}</a>{% if perms.scipost.can_manage_ontology %} <a href="{% url 'submissions:submission_remove_topic' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr slug=topic.slug %}"><i class="fa fa-times-circle text-danger"></i></a>{% endif %}</span> + <span class="label label-secondary mb-2"><a href="{% url 'ontology:topic_details' slug=topic.slug %}">{{ topic }}</a>{% if perms.scipost.can_manage_ontology %} <a href="{% url 'submissions:submission_remove_topic' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr slug=topic.slug %}"><i class="fa fa-times-circle text-danger"></i></a>{% endif %}</span> {% empty %} <div>No Topic has yet been associated to this Submission</div> {% endfor %} @@ -19,17 +20,20 @@ {% if perms.scipost.can_manage_ontology %} <br> - <ul class="list-inline"> - <li class="list-inline-item"> - <form class="form-inline" action="{% url 'submissions:submission_add_topic' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> - <ul class="list-inline"> - <li class="list-inline-item">Add an existing Topic:</li> - <li class="list-inline-item">{% csrf_token %}{{ select_topic_form }}</li> - <li class="list-inline-item"><input class="btn btn-outline-secondary" type="submit" value="Link"></li> - </ul> - </form> - </li> - <li class="list-inline-item p-2">Can't find the Topic you need? <a href="{% url 'ontology:topic_create' %}" target="_blank">Create it</a> (opens in new window)</li> - </ul> + <div class="d-inline-block"> + <form action="{% url 'submissions:submission_add_topic' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> + <div class="d-inline-block">Add an existing Topic:</div> + <div class="d-inline-block p-2"> + {% csrf_token %} + {{ select_topic_form }} + </div> + <div class="d-inline-block"> + <input class="btn btn-outline-secondary" type="submit" value="Link"> + </div> + </form> + </div> + <div class="d-inline-block p-2"> + Can't find the Topic you need? <a href="{% url 'ontology:topic_create' %}" target="_blank">Create it</a> (opens in new window) + </div> {% endif %} {% endif %} diff --git a/submissions/templates/submissions/admin/submission_presassign_editors.html b/submissions/templates/submissions/admin/submission_presassign_editors.html index 27c28a589..8b4724bd2 100644 --- a/submissions/templates/submissions/admin/submission_presassign_editors.html +++ b/submissions/templates/submissions/admin/submission_presassign_editors.html @@ -20,7 +20,7 @@ <h1 class="highlight">Submission editor invitations</h1> -<h3 class="mb-0"><a href="{{ submission.get_absolute_url }}">{{submission.title}}</a></h3> +<h3><a href="{{ submission.get_absolute_url }}">{{submission.title}}</a></h3> <h4>by {{submission.author_list}}</h4> <br> diff --git a/submissions/templates/submissions/admin/submission_prescreening.html b/submissions/templates/submissions/admin/submission_prescreening.html index d41fc2cc6..2ef0e744f 100644 --- a/submissions/templates/submissions/admin/submission_prescreening.html +++ b/submissions/templates/submissions/admin/submission_prescreening.html @@ -14,15 +14,16 @@ {% block content %} <h1 class="highlight">Pre-screening of Submission</h1> - <h3 class="mb-0"><a href="{{ submission.get_absolute_url }}">{{submission.title}}</a></h3> + <h3><a href="{{ submission.get_absolute_url }}">{{submission.title}}</a></h3> <h4>by {{submission.author_list}}</h4> - <br> - <h3>Submission summary</h3> - {% include 'partials/submissions/submission_summary.html' with submission=submission hide_title=1 show_abstract=1 %} + <h3 class="mt-4">Submission summary</h3> + {% include 'partials/submissions/pool/submission_info_table_extended.html' with submission=submission %} + <h3 class="mt-4">Abstract</h3> + <p>{{submission.abstract}}</p> - <h3>Pre-screening steps</h3> + <h3 class="mt-4">Pre-screening steps</h3> <ul class="fa-ul"> <li> <i class="fa-li fa fa-check-square text-success"></i> diff --git a/submissions/templates/submissions/admin/submission_reassign.html b/submissions/templates/submissions/admin/submission_reassign.html index c49cfa1d6..24cd7fcfa 100644 --- a/submissions/templates/submissions/admin/submission_reassign.html +++ b/submissions/templates/submissions/admin/submission_reassign.html @@ -14,7 +14,7 @@ {% block content %} <h1 class="highlight">Reassign Submission</h1> - <h3 class="mb-0"><a href="{{ submission.get_absolute_url }}">{{submission.title}}</a></h3> + <h3><a href="{{ submission.get_absolute_url }}">{{submission.title}}</a></h3> <h4>by {{submission.author_list}}</h4> <br> <p>Current Editor-in-charge: {{ submission.editor_in_charge }}</p> diff --git a/submissions/utils.py b/submissions/utils.py index 8855acd84..813c9e503 100644 --- a/submissions/utils.py +++ b/submissions/utils.py @@ -3,316 +3,21 @@ __license__ = "AGPL v3" import datetime -import json from django.core.mail import EmailMessage, EmailMultiAlternatives from django.template import Context, Template -from django.utils import timezone -from django.utils.html import format_html, format_html_join, html_safe from .constants import ( - NO_REQUIRED_ACTION_STATUSES, STATUS_VETTED, STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_INCOMING, - STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC, STATUS_EIC_ASSIGNED) + STATUS_VETTED, STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC) from scipost.utils import EMAIL_FOOTER from common.utils import BaseMailUtil -@html_safe -class RequiredActionsDict(dict): - """A collection of required actions. - - The required action, meant for the editors-in-charge know how to display itself in - various formats. Dictionary keys are the action-codes, the values are the texts - to present to the user. - """ - - def as_data(self): - return {f: e.as_data() for f, e in self.items()} - - def as_list_text(self): - return [e.__str__() for e in self.values()] - - def get_json_data(self, escape_html=False): - return {f: e.get_json_data(escape_html) for f, e in self.items()} - - def as_json(self, escape_html=False): - return json.dumps(self.get_json_data(escape_html)) - - def as_ul(self): - if not self: - return '<div class="no-actions-msg">No required actions.</div>' - return format_html( - '<ul class="actions-list">{}</ul>', format_html_join('', '<li>{}</li>', self.values())) - - def as_text(self): - return ' '.join([action.as_text() for action in self.values()]) - - def __getitem__(self, action): - return super().__getitem__(action.id) - - def __setitem__(self, action, val): - super().__setitem__(action.id, val) - - def __contains__(self, value): - return value.id in list(self.keys()) - - def __str__(self): - return self.as_ul() - - -class BaseSubmissionCycle: - """ - The submission cycle may take different approaches. All steps within a specific - cycle are handles by the class related to the specific cycle chosen. This class - is meant as an abstract blueprint for the overall submission cycle and its needed - actions. - """ - default_days = 28 - may_add_referees = True - may_reinvite_referees = True - minimum_referees = 3 - name = None - required_actions = [] - submission = None - updated_action = False - - def __init__(self, submission): - self.submission = submission - - def __str__(self): - return self.submission.get_refereeing_cycle_display() - - def _update_actions(self): - """Create the list of actions for the current submission.""" - self.required_actions = [] - if self.submission.status in NO_REQUIRED_ACTION_STATUSES: - # Submission does not appear in the pool, no action required. - return False - - if self.submission.revision_requested: - # Editor-in-charge has requested revision. - return False - - if self.submission.eicrecommendations.active().exists(): - # An Editorial Recommendation has already been submitted. Cycle done. - return False - - 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() - if comments_to_vet > 0: - # There are comments on the submission awaiting vetting. - if comments_to_vet > 1: - text = '%i Comments have' % comments_to_vet - else: - text = 'One Comment has' - text += ' been delivered but is not yet vetted. Please vet it.' - self.required_actions.append(('vet_comments', text,)) - - nr_ref_inv = self.submission.referee_invitations.count() - if nr_ref_inv < self.minimum_referees: - # The submission cycle does not meet the criteria of a minimum of - # `self.minimum_referees` referees yet. - text = 'No' if nr_ref_inv == 0 else 'Only %i' % nr_ref_inv - text += ' Referees have yet been invited.' - text += ' At least %i should be.' % self.minimum_referees - self.required_actions.append(('invite_referees', text,)) - - reports_awaiting_vetting = self.submission.reports.awaiting_vetting().count() - if reports_awaiting_vetting > 0: - # There are reports on the submission awaiting vetting. - if reports_awaiting_vetting > 1: - text = '%i Reports have' % reports_awaiting_vetting - else: - text = 'One Report has' - text += ' been delivered but is not yet vetted. Please vet it.' - self.required_actions.append(('vet_reports', text,)) - return True - - def reinvite_referees(self, referees, request=None): - """ - Reinvite referees if allowed. This method does not check if it really is - a reinvitation or just a new invitation. - """ - if self.may_reinvite_referees: - SubmissionUtils.load({'submission': self.submission}) - for referee in referees: - invitation = referee - invitation.pk = None # Duplicate, do not remove the old invitation - invitation.submission = self.submission - invitation.reset_content() - invitation.date_invited = timezone.now() - invitation.save() - SubmissionUtils.load({'invitation': invitation}, request) - SubmissionUtils.reinvite_referees_email() - - def update_deadline(self, period=None): - """ - Reset the reporting deadline according to current datetime and default cycle length. - New reporting deadline may be explicitly given as datetime instance. - """ - delta_d = period or self.default_days - deadline = timezone.now() + datetime.timedelta(days=delta_d) - - from .models import Submission - Submission.objects.filter(id=self.submission.id).update(reporting_deadline=deadline) - - def get_required_actions(self): - '''Return list of the submission its required actions''' - if not self.updated_action: - self._update_actions() - self.updated_action = True - return self.required_actions - - def has_required_actions(self): - """ - Certain submission statuses will not show the required actions block. - The decision to show this block is taken by this method. - """ - return self.submission.status not in NO_REQUIRED_ACTION_STATUSES - - def update_status(self): - """ - Implement: - Let the submission status be centrally handled by this method. This makes sure - the status cycle is clear and makes sure the cycle isn't broken due to unclear coding - elsewhere. The next status to go to should ideally be determined on all the - available in the submission with only few exceptions to explicilty force a new status code. - """ - raise NotImplementedError - - -class BaseRefereeSubmissionCycle(BaseSubmissionCycle): - """ - This *abstract* submission cycle adds the specific actions needed for submission cycles - that require referees to be invited. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if self.submission.proceedings: - # Check if proceedings has overwritten the `minimum_referees` - if self.submission.proceedings.minimum_referees: - self.minimum_referees = self.submission.proceedings.minimum_referees - - def update_status(self): - if self.submission.status == STATUS_INCOMING and self.submission.is_resubmission: - from .models import Submission - Submission.objects.filter(id=self.submission.id).update(status=STATUS_EIC_ASSIGNED) - - def _update_actions(self): - continue_update = super()._update_actions() - if not continue_update: - return False - - for ref_inv in self.submission.referee_invitations.all(): - if not ref_inv.cancelled: - if ref_inv.accepted is None: - # An invited referee may have not responded yet. - timelapse = timezone.now() - ref_inv.date_invited - if timelapse > datetime.timedelta(days=3): - text = ('Referee %s has not responded for %i days. ' - 'Consider sending a reminder or cancelling the invitation.' - % (ref_inv.referee_str, timelapse.days)) - self.required_actions.append(('referee_no_response', text,)) - elif ref_inv.accepted and not ref_inv.fulfilled: - # A referee has not fulfilled its duty and the deadline is closing in. - timeleft = self.submission.reporting_deadline - timezone.now() - if timeleft < datetime.timedelta(days=7): - text = ('Referee %s has accepted to send a Report, ' - 'but not yet delivered it ' % ref_inv.referee_str) - if timeleft.days < 0: - text += '(%i days overdue). ' % (- timeleft.days) - elif timeleft.days == 1: - text += '(with 1 day left). Consider sending an urgent reminder.' - else: - text += ('(with %i days left). Consider sending a reminder if ' - 'you think it can ensure a timely Report.' % timeleft.days) - self.required_actions.append(('referee_no_delivery', text,)) - - if self.submission.reporting_deadline < timezone.now(): - text = ('The refereeing deadline has passed. Please either extend it, ' - 'or formulate your Editorial Recommendation if at least ' - 'one Report has been received.') - self.required_actions.append(('deadline_passed', text,)) - - return True - - -class GeneralSubmissionCycle(BaseRefereeSubmissionCycle): - """ - The default submission cycle assigned to all 'regular' submissions and resubmissions - which are explicitly assigned to go trough the default cycle by the EIC. - It's a four week cycle with full capabilities i.e. invite referees, vet reports, etc. etc. - """ - pass - - -class ShortSubmissionCycle(BaseRefereeSubmissionCycle): - """ - This cycle is used if the EIC has explicitly chosen to do a short version of the general - submission cycle. The deadline is within two weeks instead of the default four weeks. - - This cycle is only available for resubmitted submissions! - """ - default_days = 14 - may_add_referees = False - minimum_referees = 1 - pass - - -class DirectRecommendationSubmissionCycle(BaseSubmissionCycle): - """ - This cycle is used if the EIC has explicitly chosen to immediately write an - editorial recommendation. - - This cycle is only available for resubmitted submissions! - """ - may_add_referees = False - may_reinvite_referees = False - minimum_referees = 0 - - def update_status(self): - if self.submission.status == STATUS_INCOMING and self.submission.is_resubmission: - from .models import Submission - Submission.objects.filter(id=self.submission.id).update(status=STATUS_EIC_ASSIGNED) - # TODO: Generate draft-EICRecommendation. - - def _update_actions(self): - continue_update = super()._update_actions() - if not continue_update: - return False - - # No EIC Recommendation has been formulated yet - text = 'Formulate an Editorial Recommendation.' - self.required_actions.append(('need_eic_rec', text,)) - - return True - - class SubmissionUtils(BaseMailUtil): mail_sender = 'submissions@scipost.org' mail_sender_title = 'SciPost Editorial Admin' - @classmethod - def reinvite_referees_email(cls): - """ - Email to be sent to referees when they are being reinvited by the EIC. - - Requires context to contain: - - `invitation` - """ - extra_bcc_list = [cls._context['invitation'].submission.editor_in_charge.user.email] - cls._send_mail(cls, 'submission_cycle_reinvite_referee', - [cls._context['invitation'].email_address], - 'Invitation on resubmission', - extra_bcc=extra_bcc_list) - @classmethod def send_authors_submission_ack_email(cls): """ Requires loading 'submission' attribute. """ diff --git a/templates/email/submission_cycle_reinvite_referee.html b/templates/email/submission_cycle_reinvite_referee.html deleted file mode 100644 index 08db16275..000000000 --- a/templates/email/submission_cycle_reinvite_referee.html +++ /dev/null @@ -1,28 +0,0 @@ -<p>Dear {{invitation.get_title_display}} {{invitation.last_name}},</p> - -<p> - The authors of submission -</p> - -<p> - {{invitation.submission.title}} - <br> - by {{invitation.submission.author_list}} - <br> - (<a href="https://scipost.org{{invitation.submission.get_absolute_url}}">see on SciPost.org</a>) -<p> - have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}}, we would like to invite you to quickly review this new version. - Please accept or decline the invitation (login required) as soon as possible (ideally within the next 2 days). -</p> -<p> - If you accept, your report can be submitted by simply clicking on the "Contribute a Report" link on the Submission's Page before the reporting deadline (currently set at {{invitation.submission.reporting_deadline|date:'N j, Y'}}; your report will be automatically recognized as an invited report). -</p> -<p> - You might want to make sure you are familiar with our refereeing code of conduct and with the refereeing procedure. -</p> -<p> - We would be extremely grateful for your contribution, and thank you in advance for your consideration.<br> - The SciPost Team. -</p> - -{% include 'email/_footer.html' %} diff --git a/templates/email/submission_cycle_reinvite_referee.txt b/templates/email/submission_cycle_reinvite_referee.txt deleted file mode 100644 index 34c67aa8d..000000000 --- a/templates/email/submission_cycle_reinvite_referee.txt +++ /dev/null @@ -1,15 +0,0 @@ -Dear {{invitation.get_title_display}} {{invitation.last_name}}, - -The authors of submission - -{{invitation.submission.title}} by {{invitation.submission.author_list}} - -have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}}, we would like to invite you to quickly review this new version. -Please accept or decline the invitation (login required) as soon as possible (ideally within the next 2 days). - -If you accept, your report can be submitted by simply clicking on the "Contribute a Report" link on the Submission's Page before the reporting deadline (currently set at {{invitation.submission.reporting_deadline|date:'N j, Y'}}; your report will be automatically recognized as an invited report). - -You might want to make sure you are familiar with our refereeing code of conduct and with the refereeing procedure. - -We would be extremely grateful for your contribution, and thank you in advance for your consideration. -The SciPost Team. -- GitLab