From e659be1da0d41d908e34505e69e2277f8bffaf1e Mon Sep 17 00:00:00 2001 From: Andy Byers Date: Tue, 19 May 2026 18:20:55 +0100 Subject: [PATCH] fix #4584: allow editorial decisions after workflow rollback to review --- src/review/tests/test_views.py | 41 +++++++++++++++++++ src/submission/tests/test_models.py | 49 +++++++++++++++++++++++ src/templates/admin/review/in_review.html | 18 ++++----- src/workflow/logic.py | 5 +++ 4 files changed, 103 insertions(+), 10 deletions(-) diff --git a/src/review/tests/test_views.py b/src/review/tests/test_views.py index 6d47cf986a..dd1eb8ee62 100644 --- a/src/review/tests/test_views.py +++ b/src/review/tests/test_views.py @@ -1251,3 +1251,44 @@ def test_notify_reviewer_missing_url_does_not_redirect(self): SERVER_NAME=self.journal_one.domain, ) self.assertEqual(response.status_code, 200) + + +class InReviewActionsTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.press = helpers.create_press() + cls.journal_one, cls.journal_two = helpers.create_journals() + cls.editor = helpers.create_editor(cls.journal_one) + cls.article = helpers.create_article(cls.journal_one) + review_models.ReviewRound.objects.create( + article=cls.article, + round_number=1, + ) + + def test_rolled_back_article_offers_decision_actions(self): + # Acceptance leaves a historical ArticleStageLog entry that + # keeps is_accepted() True even after rollback. + self.article.stage = submission_models.STAGE_ACCEPTED + self.article.date_accepted = timezone.now() + self.article.save() + self.article.stage = submission_models.STAGE_UNDER_REVIEW + self.article.date_accepted = None + self.article.date_declined = None + self.article.save() + self.assertTrue(self.article.is_accepted()) + + self.client.force_login(self.editor) + response = self.client.get( + reverse( + "review_in_review", + kwargs={"article_id": self.article.pk}, + ), + SERVER_NAME=self.journal_one.domain, + ) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "New Review Round") + self.assertContains( + response, + reverse("decision_helper", kwargs={"article_id": self.article.pk}), + ) + self.assertNotContains(response, "Move to Next Stage") diff --git a/src/submission/tests/test_models.py b/src/submission/tests/test_models.py index b764d83923..8a730e5c56 100644 --- a/src/submission/tests/test_models.py +++ b/src/submission/tests/test_models.py @@ -5,9 +5,13 @@ from django.db import IntegrityError from django.test import TestCase +from django.utils import timezone +from core import models as core_models +from core import workflow as core_workflow from submission import models from utils.testing import helpers +from workflow import logic as workflow_logic class FrozenAuthorModelTests(TestCase): @@ -54,3 +58,48 @@ def test_article_authors_and_credits_for_frozen_author(self): roles for _, roles in self.article_one.authors_and_credits().items() ] self.assertEqual(expected_roles[0].first(), role) + + +class WorkflowRollbackTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.press = helpers.create_press() + cls.journal_one, cls.journal_two = helpers.create_journals() + core_workflow.create_default_workflow(cls.journal_one) + cls.review_element = core_models.WorkflowElement.objects.get( + journal=cls.journal_one, + element_name="review", + ) + cls.copyediting_element = core_models.WorkflowElement.objects.get( + journal=cls.journal_one, + element_name="copyediting", + ) + + def test_rollback_to_review_clears_decision_dates(self): + article = helpers.create_article(self.journal_one) + article.stage = models.STAGE_UNDER_REVIEW + article.save() + core_models.WorkflowLog.objects.create( + article=article, + element=self.review_element, + ) + article.stage = models.STAGE_ACCEPTED + article.date_accepted = timezone.now() + article.save() + article.stage = models.STAGE_EDITOR_COPYEDITING + article.save() + core_models.WorkflowLog.objects.create( + article=article, + element=self.copyediting_element, + ) + + workflow_logic.move_to_stage( + article.stage, + models.STAGE_UNASSIGNED, + article, + ) + + article.refresh_from_db() + self.assertEqual(article.stage, models.STAGE_UNDER_REVIEW) + self.assertIsNone(article.date_accepted) + self.assertIsNone(article.date_declined) diff --git a/src/templates/admin/review/in_review.html b/src/templates/admin/review/in_review.html index 88cb629ac4..863a42c2d1 100644 --- a/src/templates/admin/review/in_review.html +++ b/src/templates/admin/review/in_review.html @@ -280,15 +280,13 @@

Actions

 Document Management - {% if article.is_accepted %} - {% if article.in_review_stages %} -
  • - -   - {% trans 'Move to Next Stage' %} - -
  • - {% endif %} + {% if article.stage == 'Accepted' %} +
  • + +   + {% trans 'Move to Next Stage' %} + +
  • {% else %}
  • @@ -326,7 +324,7 @@

    Actions

    {% block js %} {% include "elements/notes/note_script.html" %} - {% if article.is_accepted and article.in_review_stages %} + {% if article.stage == 'Accepted' %} {% include "admin/elements/open_modal.html" with target='move_to_next_modal' %} {% endif %} {% endblock js %} diff --git a/src/workflow/logic.py b/src/workflow/logic.py index f0a639969c..1c9fd65290 100644 --- a/src/workflow/logic.py +++ b/src/workflow/logic.py @@ -99,6 +99,11 @@ def move_to_stage(from_stage, to_stage, article): elif stage == submissions_models.STAGE_EDITOR_COPYEDITING: article.stage = submissions_models.STAGE_EDITOR_COPYEDITING + elif stage == submissions_models.STAGE_UNDER_REVIEW: + article.stage = submissions_models.STAGE_UNDER_REVIEW + article.date_accepted = None + article.date_declined = None + elif stage == submissions_models.STAGE_UNASSIGNED: article.stage = submissions_models.STAGE_UNDER_REVIEW article.date_accepted = None