Repository: ietf-tools/datatracker

Issue:
Add "not declined" to nominee filter
### Description

I want to communicate with nominees.  This option would be great.  Either that or a set of checkboxes for each state.

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:

Resolving patch:
diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py
--- a/ietf/nomcom/views.py
+++ b/ietf/nomcom/views.py
@@ -216,10 +216,11 @@ def private_index(request, year):
 
     filters = {}
     questionnaire_state = "questionnaire"
+    not_declined_state = "not-declined"
     selected_state = request.GET.get('state')
     selected_position = request.GET.get('position')
 
-    if selected_state and not selected_state == questionnaire_state:
+    if selected_state and selected_state not in [questionnaire_state, not_declined_state]:
         filters['state__slug'] = selected_state
 
     if selected_position:
@@ -231,13 +232,15 @@ def private_index(request, year):
 
     if selected_state == questionnaire_state:
         nominee_positions = [np for np in nominee_positions if np.questionnaires]
+    elif selected_state == not_declined_state:
+        nominee_positions = nominee_positions.exclude(state__slug='declined')
 
     positions = Position.objects.get_by_nomcom(nomcom=nomcom)
     stats = [ { 'position__name':p.name,
                 'position__id':p.pk,
                 'position': p,
               } for p in positions]
-    states = [{'slug': questionnaire_state, 'name': 'Accepted and sent Questionnaire'}] + list(NomineePositionStateName.objects.values('slug', 'name'))
+    states = [{'slug': questionnaire_state, 'name': 'Accepted and sent Questionnaire'}, {'slug': not_declined_state, 'name': 'Not declined'}] + list(NomineePositionStateName.objects.values('slug', 'name'))
     positions = set([ n.position for n in all_nominee_positions.order_by('position__name') ])
     for s in stats:
         for state in states:


Test patch:
diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py
--- a/ietf/nomcom/tests.py
+++ b/ietf/nomcom/tests.py
@@ -1327,6 +1327,36 @@ def test_cannot_modify_nominees(self):
         q = PyQuery(response.content)
         self.assertIn('not active', q('.alert-warning').text() )
 
+    def test_filter_nominees(self):
+        url = reverse(
+            "ietf.nomcom.views.private_index", kwargs={"year": self.nc.year()}
+        )
+        login_testing_unauthorized(self, self.chair.user.username, url)
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+
+        states = list(NomineePositionStateName.objects.values_list("slug", flat=True))
+        states += ["not-declined", "questionnaire"]
+        for state in states:
+            response = self.client.get(url, {"state": state})
+            self.assertEqual(response.status_code, 200)
+            q = PyQuery(response.content)
+            nps = []
+            if state == "not-declined":
+                nps = NomineePosition.objects.exclude(state__slug="declined")
+            elif state == "questionnaire":
+                nps = [
+                    np
+                    for np in NomineePosition.objects.not_duplicated()
+                    if np.questionnaires
+                ]
+            else:
+                nps = NomineePosition.objects.filter(state__slug=state)
+            # nomination state is in third table column
+            self.assertEqual(
+                len(nps), len(q("#nominee-position-table td:nth-child(3)"))
+            )
+
     def test_email_pasting_closed(self):
         url = reverse('ietf.nomcom.views.private_feedback_email', kwargs={'year':self.nc.year()})
         login_testing_unauthorized(self, self.chair.user.username, url)