Repository: ietf-tools/datatracker

Issue:
Allow reassignment of withdrawn review assignment
From a review team secretary:

>     I assigned a review to someone
>     That individual did not response in a timely manner so I withdrew the assignment
>     I assigned to another reviewer (new reviewer)
>     The original reviewer then indicated they could do the review
>     I withdrew the review from the new reviewer
>     I attempted to assign to the original review, but cannot

I worked around this by making an adjustment to the withdrawn assignment using the admin.
The team secretary should be able to make the assignment again (just move the state from withdrawn to assigned).


Discussion:
Also note that when the reassignment is made, the ReviewRequest itself should transition to the `assigned` state.

Resolving patch:
diff --git a/ietf/review/utils.py b/ietf/review/utils.py
--- a/ietf/review/utils.py
+++ b/ietf/review/utils.py
@@ -1,4 +1,4 @@
-# Copyright The IETF Trust 2016-2020, All Rights Reserved
+# Copyright The IETF Trust 2016-2023, All Rights Reserved
 # -*- coding: utf-8 -*-
 
 
@@ -392,7 +392,9 @@ def assign_review_request_to_reviewer(request, review_req, reviewer, add_skip=Fa
     # cannot reference reviewassignment_set relation until pk exists
     if review_req.pk is not None:
         reviewassignment_set = review_req.reviewassignment_set.filter(reviewer=reviewer)
-        if reviewassignment_set.exists() and not reviewassignment_set.filter(state_id='rejected').exists():
+        if (reviewassignment_set.exists() and not
+            (reviewassignment_set.filter(state_id='rejected').exists() or
+             reviewassignment_set.filter(state_id='withdrawn').exists())):
             return
 
     # Note that assigning a review no longer unassigns other reviews


Test patch:
diff --git a/ietf/doc/tests_review.py b/ietf/doc/tests_review.py
--- a/ietf/doc/tests_review.py
+++ b/ietf/doc/tests_review.py
@@ -380,6 +380,25 @@ def test_assign_reviewer_after_reject(self):
         reviewer_label = q("option[value=\"{}\"]".format(reviewer_email.address)).text().lower()
         self.assertIn("rejected review of document before", reviewer_label)
 
+    def test_assign_reviewer_after_withdraw(self):
+        doc = WgDraftFactory()
+        review_team = ReviewTeamFactory()
+        rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
+        RoleFactory(group=review_team,person__user__username='reviewsecretary',name_id='secr')
+        review_req = ReviewRequestFactory(team=review_team,doc=doc)
+        reviewer = rev_role.person.email_set.first()
+        ReviewAssignmentFactory(review_request=review_req, state_id='withdrawn', reviewer=reviewer)
+        req_url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
+        assign_url = urlreverse('ietf.doc.views_review.assign_reviewer', kwargs={ "name": doc.name, "request_id": review_req.pk })
+
+        login_testing_unauthorized(self, "reviewsecretary", assign_url)
+        r = self.client.post(assign_url, { "action": "assign", "reviewer": reviewer.pk })
+        self.assertRedirects(r, req_url)
+        review_req = reload_db_objects(review_req)
+        assignment = review_req.reviewassignment_set.last()
+        self.assertEqual(assignment.state, ReviewAssignmentStateName.objects.get(slug='assigned'))
+        self.assertEqual(review_req.state, ReviewRequestStateName.objects.get(slug='assigned'))
+
     def test_previously_reviewed_replaced_doc(self):
         review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
         rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',person__name='Some Reviewer',name_id='reviewer')