diff --git a/scipost_django/edadmin/templates/edadmin/_submission_maintenance_card_general.html b/scipost_django/edadmin/templates/edadmin/_submission_maintenance_card_general.html index d4cf1c43b4b5f077082d55705749225ef5b29979..374e091cff114eb1056086f2ec8efaf269654d6a 100644 --- a/scipost_django/edadmin/templates/edadmin/_submission_maintenance_card_general.html +++ b/scipost_django/edadmin/templates/edadmin/_submission_maintenance_card_general.html @@ -20,25 +20,37 @@ hx-prompt="What is the reason for putting the submission on hold? (will be added as a submission internal note)" {% endif %} hx-target="#submission-{{ submission.id }}-details"> - {% if submission.on_hold %} - Take off hold - {% else %} - Put on hold - {% endif %} + {% if submission.on_hold %} + Take off hold + {% else %} + Put on hold + {% endif %} </button> </li> + {% if submission.assignment_deadline %} + <li> + <div> + Extend assignment deadline by: + </div> + <ul role="menu" class="d-flex gap-3 list-unstyled"> + <li role="menuitem"><a href="{% url 'submissions:extend_assignment_deadline' submission.preprint.identifier_w_vn_nr 7 %}">7 days</a></li> + <li role="menuitem"><a href="{% url 'submissions:extend_assignment_deadline' submission.preprint.identifier_w_vn_nr 14 %}">14 days</a></li> + <li role="menuitem"><a href="{% url 'submissions:extend_assignment_deadline' submission.preprint.identifier_w_vn_nr 28 %}">28 days</a></li> + </ul> + </li> + {% endif %} {% if 'Proceedings' in submission.submitted_to.name %} - <li id="submission-{{ submission.id }}-update-target-proceedings"> - {% include "submissions/admin/_submission_update_target_proceedings.html" with submission=submission %} - </li> + <li id="submission-{{ submission.id }}-update-target-proceedings"> + {% include "submissions/admin/_submission_update_target_proceedings.html" with submission=submission %} + </li> {% endif %} {% if submission.preprint.has_file %} - <li id="submission-{{ submission.id }}-update-preprint-file"> - {% include "submissions/admin/_submission_update_preprint_file.html" with submission=submission %} - </li> + <li id="submission-{{ submission.id }}-update-preprint-file"> + {% include "submissions/admin/_submission_update_preprint_file.html" with submission=submission %} + </li> {% endif %} {% if submission.editor_in_charge and perms.scipost.can_reassign_submissions %} - <li><a href="{% url 'submissions:reassign_submission' submission.preprint.identifier_w_vn_nr %}">Reassign Editor-in-charge</a></li> + <li><a href="{% url 'submissions:reassign_submission' submission.preprint.identifier_w_vn_nr %}">Reassign Editor-in-charge</a></li> {% endif %} {% if submission.editor_in_charge %} <li class="pb-2"><a href="{% url 'submissions:communication' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr comtype='StoE' %}">Send a communication to the Editor-in-charge</a></li> diff --git a/scipost_django/edadmin/views/incoming.py b/scipost_django/edadmin/views/incoming.py index 64fd3fff99cce6c3cf254d02c3d3b98b8d7b43f9..f2395a4fef588c97682f3c96b39f1843b944ea0b 100644 --- a/scipost_django/edadmin/views/incoming.py +++ b/scipost_django/edadmin/views/incoming.py @@ -236,7 +236,8 @@ def _hx_submission_admission(request, identifier_w_vn_nr): if form.is_valid(): if form.cleaned_data["choice"] == "pass": Submission.objects.filter(pk=submission.id).update( - status=Submission.PREASSIGNMENT + status=Submission.PREASSIGNMENT, + checks_cleared_date=timezone.now(), ) # send authors admission passed email # mail_util = DirectMailUtil( diff --git a/scipost_django/edadmin/views/preassignment.py b/scipost_django/edadmin/views/preassignment.py index c6123917b2ff801e754582634c920c680d1724f6..6cfa6058cd41b638ec1bdfbd198cb8160260bdb1 100644 --- a/scipost_django/edadmin/views/preassignment.py +++ b/scipost_django/edadmin/views/preassignment.py @@ -6,6 +6,7 @@ from django.contrib.auth.decorators import login_required, user_passes_test from django.http import HttpResponse from django.shortcuts import get_object_or_404, render, redirect from django.urls import reverse +from django.utils import timezone from colleges.permissions import is_edadmin from mails.utils import DirectMailUtil @@ -159,9 +160,11 @@ def _hx_submission_preassignment_decision(request, identifier_w_vn_nr): form = SubmissionPreassignmentDecisionForm(request.POST or None) if form.is_valid(): if form.cleaned_data["choice"] == "pass": - Submission.objects.filter(pk=submission.id).update( - status=Submission.SEEKING_ASSIGNMENT + submission.status = Submission.SEEKING_ASSIGNMENT + submission.assignment_deadline = ( + timezone.now() + submission.submitted_to.assignment_period ) + submission.save() # send authors admission passed email mail_util = DirectMailUtil( "authors/preassignment_completed", @@ -174,9 +177,8 @@ def _hx_submission_preassignment_decision(request, identifier_w_vn_nr): submission.fellows.set(submission.get_default_fellowship()) else: # inadmissible, inform authors and set status to PREASSIGNMENT_FAILED - Submission.objects.filter(pk=submission.id).update( - status=Submission.PREASSIGNMENT_FAILED - ) + submission.status = Submission.PREASSIGNMENT_FAILED + submission.save() # send authors admission failed email mail_util = DirectMailUtil( "authors/preassignment_failed", @@ -184,7 +186,6 @@ def _hx_submission_preassignment_decision(request, identifier_w_vn_nr): comments_for_authors=form.cleaned_data["comments_for_authors"], ) mail_util.send_mail() - submission.refresh_from_db() response = HttpResponse() # trigger refresh of pool listing response["HX-Trigger-After-Settle"] = "search-conditions-updated" diff --git a/scipost_django/journals/migrations/0138_journal_assignment_period.py b/scipost_django/journals/migrations/0138_journal_assignment_period.py new file mode 100644 index 0000000000000000000000000000000000000000..1d63d8e4181bdc3edc1c2b37f32dba649d5985f6 --- /dev/null +++ b/scipost_django/journals/migrations/0138_journal_assignment_period.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-09-27 09:21 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("journals", "0137_alter_journal_alternative_journals"), + ] + + operations = [ + migrations.AddField( + model_name="journal", + name="assignment_period", + field=models.DurationField(default=datetime.timedelta(days=28)), + ), + ] diff --git a/scipost_django/journals/models/journal.py b/scipost_django/journals/models/journal.py index 092f29930ec51bdf5c54aaf6aadcaa474c7d0edb..1ec06dd0da1c0ddcb8749af2049c0c21462eda57 100644 --- a/scipost_django/journals/models/journal.py +++ b/scipost_django/journals/models/journal.py @@ -84,6 +84,7 @@ class Journal(models.Model): ) refereeing_period = models.DurationField(default=datetime.timedelta(days=28)) + assignment_period = models.DurationField(default=datetime.timedelta(days=28)) style = models.TextField( blank=True, diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html index 40351b2ffb6d9baef24a0ff84ce74987a07b1493..a0ac30f219f572060c823d85a5ed705fbcdf2573 100644 --- a/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html +++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html @@ -45,6 +45,9 @@ <li><a class="btn btn-primary my-1 px-1 py-0" href="{% url 'submissions:accept_puboffer' sub.preprint.identifier_w_vn_nr %}">Accept offer for publication in {{ sub.editorial_decision.for_journal }} (one-click action)</a></li> {% endif %} {% endif %} + {% if sub.editor_in_charge is None and sub.assignment_deadline is not None %} + <li><a href="{% url 'submissions:extend_assignment_deadline' sub.preprint.identifier_w_vn_nr %}">Extend assignment deadline date (by {{ sub.submitted_to.assignment_period.days }} days)</a></li> + {% endif %} <li><a href="{% url 'submissions:withdraw_manuscript' sub.preprint.identifier_w_vn_nr %}"><span class="text-danger">Withdraw</span> (leads to confirmation page)</a></li> {% endif %} </ul> diff --git a/scipost_django/submissions/admin.py b/scipost_django/submissions/admin.py index a282ef7a3481395019c116e8fcb3c5c2c6dbefb0..0d0103bb316e96c9c1c0ad8ce3db6b8e8319a5ca 100644 --- a/scipost_django/submissions/admin.py +++ b/scipost_django/submissions/admin.py @@ -275,6 +275,7 @@ class SubmissionAdmin(GuardedModelAdmin): ("visible_public", "visible_pool"), "refereeing_cycle", ("open_for_commenting", "open_for_reporting"), + "assignment_deadline", "reporting_deadline", "acceptance_date", "referees_flagged", diff --git a/scipost_django/submissions/forms/__init__.py b/scipost_django/submissions/forms/__init__.py index 8bc682eb921f03a3c48fa1828178c9b48ec2d36d..4cb537f28b1d8db8d8ae155848ac79b2fc230bdf 100644 --- a/scipost_django/submissions/forms/__init__.py +++ b/scipost_django/submissions/forms/__init__.py @@ -226,6 +226,7 @@ class SubmissionPoolSearchForm(forms.Form): orderby = forms.ChoiceField( label="Order by", choices=( + ("assignment_deadline", "Assignment deadline"), ("submission_date", "Submission date"), ("latest_activity", "Latest activity"), ), @@ -2324,6 +2325,7 @@ class WithdrawSubmissionForm(forms.Form): open_for_commenting=False, open_for_reporting=False, status=Submission.WITHDRAWN, + assignment_deadline=None, latest_activity=timezone.now(), ) self.submission.get_other_versions().update(visible_public=False) @@ -2423,6 +2425,7 @@ class EditorialAssignmentForm(forms.ModelForm): status=Submission.IN_REFEREEING, editor_in_charge=self.request.user.contributor, reporting_deadline=None, + assignment_deadline=None, open_for_reporting=True, open_for_commenting=True, visible_public=True, @@ -2442,6 +2445,7 @@ class EditorialAssignmentForm(forms.ModelForm): status=Submission.REFEREEING_CLOSED, editor_in_charge=self.request.user.contributor, reporting_deadline=timezone.now(), + assignment_deadline=None, open_for_reporting=False, open_for_commenting=True, visible_public=visible_public, diff --git a/scipost_django/submissions/migrations/0163_submission_assignment_deadline.py b/scipost_django/submissions/migrations/0163_submission_assignment_deadline.py new file mode 100644 index 0000000000000000000000000000000000000000..8f717616067d40626bcf7a3bc1058d00c88e42af --- /dev/null +++ b/scipost_django/submissions/migrations/0163_submission_assignment_deadline.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.15 on 2024-09-27 09:21 + +from django.db import migrations, models +from django.utils import timezone + + +def add_assignment_deadline(apps, schema_editor): + Submission = apps.get_model("submissions", "Submission") + for submission in Submission.objects.filter( + status="seeking_assignment", + assignment_deadline__isnull=True, + ): + submission.assignment_deadline = ( + timezone.now() + submission.submitted_to.assignment_period + if submission.submitted_to + else None + ) + submission.checks_cleared_date = timezone.now() + submission.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("submissions", "0162_alter_refereeinvitation_referee"), + ] + + operations = [ + migrations.AddField( + model_name="submission", + name="assignment_deadline", + field=models.DateField( + blank=True, null=True, verbose_name="assignment deadline" + ), + ), + migrations.RunPython( + add_assignment_deadline, reverse_code=migrations.RunPython.noop + ), + ] diff --git a/scipost_django/submissions/models/submission.py b/scipost_django/submissions/models/submission.py index dc7c6dbae019b6b5628898d9a9db286a69fef24a..a97748121f3504adaccc44ce78fad5ae267b24d3 100644 --- a/scipost_django/submissions/models/submission.py +++ b/scipost_django/submissions/models/submission.py @@ -430,6 +430,9 @@ class Submission(models.Model): completion_date = models.DateField( verbose_name="completion date", null=True, blank=True ) + assignment_deadline = models.DateField( + verbose_name="assignment deadline", null=True, blank=True + ) latest_activity = models.DateTimeField(auto_now=True) update_search_index = models.BooleanField(default=True) @@ -740,6 +743,16 @@ class Submission(models.Model): """Check if Submission has fixed EICRecommendation asking for revision.""" return self.status == self.AWAITING_RESUBMISSION + @property + def has_extended_assignment_deadline(self): + """ + Check if Submission has had its assignment deadline extended, by looking for the corresponding event. + """ + return self.events.filter( + text__icontains="assignment deadline", + text__contains="extended", + ).exists() + @property def reporting_deadline_has_passed(self): """Check if Submission has passed its reporting deadline.""" diff --git a/scipost_django/submissions/templates/submissions/pool/_submission_details_summary_contents.html b/scipost_django/submissions/templates/submissions/pool/_submission_details_summary_contents.html index 440612606d2bc551d5399d61f3a6da6cd03f55f6..7f9b76622cdb7dfe987ce1e9ad23f87bf30abcb0 100644 --- a/scipost_django/submissions/templates/submissions/pool/_submission_details_summary_contents.html +++ b/scipost_django/submissions/templates/submissions/pool/_submission_details_summary_contents.html @@ -60,6 +60,14 @@ <br /> {{ submission.submission_date|date:'Y-m-d' }} </div> + + {% if submission.assignment_deadline is not None %} + <div class="col"> + <small class="text-muted">Assignment deadline</small> + <br /> + in {{ submission.assignment_deadline|timeuntil }} + </div> + {% endif %} {% endif %} <div class="col"> diff --git a/scipost_django/submissions/urls/__init__.py b/scipost_django/submissions/urls/__init__.py index 45a0fec759b09318153c07f707f43ffe7407c1b9..61f01e2f5075891aeefb7916dd583872669c0eba 100644 --- a/scipost_django/submissions/urls/__init__.py +++ b/scipost_django/submissions/urls/__init__.py @@ -358,6 +358,16 @@ urlpatterns = [ views.withdraw_manuscript, name="withdraw_manuscript", ), + path( + "<identifier:identifier_w_vn_nr>/extend_assignment_deadline/<int:days>", + views.extend_assignment_deadline, + name="extend_assignment_deadline", + ), + path( + "<identifier:identifier_w_vn_nr>/extend_assignment_deadline", + views.extend_assignment_deadline, + name="extend_assignment_deadline", + ), path( "update_authors_assignment/<identifier:identifier_w_vn_nr>/<int:nrweeks>", views.update_authors_assignment, diff --git a/scipost_django/submissions/views/__init__.py b/scipost_django/submissions/views/__init__.py index 66e116ac0bb3eea3e7ade3554e33ce8a4ba1d832..ed8a1ea46ae9ef6d961b13afdc5d1a327503d9cb 100644 --- a/scipost_django/submissions/views/__init__.py +++ b/scipost_django/submissions/views/__init__.py @@ -610,6 +610,58 @@ def withdraw_manuscript(request, identifier_w_vn_nr): return render(request, "submissions/withdraw_manuscript.html", context) +@login_required +def extend_assignment_deadline( + request, identifier_w_vn_nr: str, days: int | None = None +): + """ + Extend the assignment deadline date of a Submission. + + Accessible to EdAdmin (always) or authors of the Submission (for first extension). + """ + submission = get_object_or_404( + Submission, preprint__identifier_w_vn_nr=identifier_w_vn_nr + ) + + if submission.assignment_deadline is None: + raise PermissionDenied("This Submission has no assignment deadline.") + + is_submission_author = request.user.contributor in submission.authors.all() + if not (is_edadmin(request.user) or is_submission_author): + raise PermissionDenied("You are not allowed to extend the assignment deadline.") + + # Compute the new assignment deadline if days is given, otherwise use the default + # For authors, ignore the days even if given + extension_date = ( + submission.assignment_deadline + timedelta(days=days) + if days is not None and is_edadmin(request.user) + else submission.assignment_deadline + submission.submitted_to.assignment_period + ) + + if extension_date < submission.assignment_deadline: + raise PermissionDenied("You cannot set an earlier date.") + + if is_submission_author and submission.has_extended_assignment_deadline: + raise PermissionDenied( + "You are not allowed to extend the assignment deadline again." + ) + + submission.assignment_deadline = extension_date + submission.save() + + submission.add_general_event( + f"The assignment deadline has been extended to {extension_date}." + ) + messages.success(request, "The assignment deadline has been extended.") + + return redirect( + reverse( + "submissions:submission", + kwargs={"identifier_w_vn_nr": identifier_w_vn_nr}, + ) + ) + + # Marked for deprecation class SubmissionListView(PaginationMixin, ListView): """List all publicly available Submissions.""" @@ -882,9 +934,7 @@ def report_attachment(request, identifier_w_vn_nr, report_nr): if not report.is_vetted: # Only Admins and EICs are allowed to see non-vetted Report attachments. if ( - not Submission.objects.in_pool_filter_for_eic( - request.user, historical=True, latest=False - ) + not Submission.objects.in_pool_filter_for_eic(request.user) .filter(preprint__identifier_w_vn_nr=identifier_w_vn_nr) .exists() ):