Repository: ietf-tools/datatracker

Issue:
nomcom volunteer list as CSV
### Description

It would be fantastic to get the list of volunteers for NomCom as a CSV file.  



### 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/urls.py b/ietf/nomcom/urls.py
--- a/ietf/nomcom/urls.py
+++ b/ietf/nomcom/urls.py
@@ -40,6 +40,7 @@
     url(r'^(?P<year>\d{4})/private/chair/topic/(?P<topic_id>\d+)/remove/$', views.remove_topic),
     url(r'^(?P<year>\d{4})/private/chair/eligible/$', views.private_eligible),
     url(r'^(?P<year>\d{4})/private/chair/volunteers/$', views.private_volunteers),
+    url(r'^(?P<year>\d{4})/private/chair/volunteers/csv/$', views.private_volunteers_csv),
 
     url(r'^(?P<year>\d{4})/$', views.year_index),
     url(r'^(?P<year>\d{4})/requirements/$', views.requirements),
diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py
--- a/ietf/nomcom/utils.py
+++ b/ietf/nomcom/utils.py
@@ -639,3 +639,15 @@ def suggest_affiliation(person):
             affiliation = recent_draft_revision.doc.documentauthor_set.filter(person=person).first().affiliation
     return affiliation
 
+def extract_volunteers(year):
+    nomcom = get_nomcom_by_year(year)
+    # pull list of volunteers
+    # get queryset of all eligible (from utils)
+    # decorate members of the list with eligibility
+    volunteers = nomcom.volunteer_set.all()
+    eligible = list_eligible(nomcom)
+    for v in volunteers:
+        v.eligible = v.person in eligible
+    decorate_volunteers_with_qualifications(volunteers,nomcom=nomcom)
+    volunteers = sorted(volunteers,key=lambda v:(not v.eligible,v.person.last_name()))
+    return nomcom, volunteers
diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py
--- a/ietf/nomcom/views.py
+++ b/ietf/nomcom/views.py
@@ -5,6 +5,7 @@
 import datetime
 import re
 from collections import OrderedDict, Counter
+import csv
 
 from django.conf import settings
 from django.contrib import messages
@@ -12,7 +13,7 @@
 from django.contrib.auth.models import AnonymousUser
 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
 from django.forms.models import modelformset_factory, inlineformset_factory 
-from django.http import Http404, HttpResponseRedirect
+from django.http import Http404, HttpResponseRedirect, HttpResponse
 from django.shortcuts import render, get_object_or_404, redirect
 from django.template.loader import render_to_string
 from django.urls import reverse
@@ -37,7 +38,7 @@
                                 FeedbackLastSeen, Topic, TopicFeedbackLastSeen, )
 from ietf.nomcom.utils import (get_nomcom_by_year, store_nomcom_private_key, suggest_affiliation,
                                get_hash_nominee_position, send_reminder_to_nominees, list_eligible,
-                               decorate_volunteers_with_qualifications,
+                               extract_volunteers,
                                HOME_TEMPLATE, NOMINEE_ACCEPT_REMINDER_TEMPLATE,NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE, )
 
 from ietf.ietfauth.utils import role_required
@@ -1312,16 +1313,20 @@ def public_volunteers(request, year):
 def private_volunteers(request, year):
     return volunteers(request=request, year=year, public=False)
 
+
 @role_required("Nomcom Chair", "Nomcom Advisor", "Secretariat")
 def volunteers(request, year, public=False):
-    nomcom = get_nomcom_by_year(year)
-    # pull list of volunteers
-    # get queryset of all eligible (from utils)
-    # decorate members of the list with eligibility
-    volunteers = nomcom.volunteer_set.all()
-    eligible = list_eligible(nomcom)
-    for v in volunteers:
-        v.eligible = v.person in eligible
-    decorate_volunteers_with_qualifications(volunteers,nomcom=nomcom)
-    volunteers = sorted(volunteers,key=lambda v:(not v.eligible,v.person.last_name()))
+    nomcom, volunteers = extract_volunteers(year)
     return render(request, 'nomcom/volunteers.html', dict(year=year, nomcom=nomcom, volunteers=volunteers, public=public))
+
+@role_required("Nomcom Chair", "Nomcom Advisor", "Secretariat")
+def private_volunteers_csv(request, year, public=False):
+    _, volunteers = extract_volunteers(year)
+    response = HttpResponse(content_type='text/csv')
+    response['Content-Disposition'] =  f'attachment; filename="nomcom{year}_volunteers.csv"'
+    writer = csv.writer(response, dialect=csv.excel, delimiter=str(','))
+    writer.writerow(["Last Name","First Name","Plain Name","Affiliation","Primary Email","Qualifications","Eligible"])
+    for v in volunteers:
+        writer.writerow([v.person.last_name(), v.person.first_name(), v.person.ascii_name(), v.affiliation, v.person.email(), v.qualifications, v.eligible])
+    return response
+


Test patch:
diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py
--- a/ietf/nomcom/tests.py
+++ b/ietf/nomcom/tests.py
@@ -1935,6 +1935,12 @@ def first_meeting_of_year(year):
             self.client.login(username='plain',password='plain+password')
             response = self.client.get(url)
             self.assertEqual(response.status_code, 302)
+        self.client.logout()
+        url = reverse('ietf.nomcom.views.private_volunteers_csv',kwargs={'year':year})
+        login_testing_unauthorized(self,self.chair.user.username,url)
+        response = self.client.get(url)
+        self.assertContains(response,people[-1].email(),status_code=200)
+
 
 
 
@@ -2668,4 +2674,4 @@ def test_decorate_volunteers_with_qualifications(self):
             if v.person == office_person:
                 self.assertEqual(v.qualifications,'path_2')
             if v.person == author_person:
-                self.assertEqual(v.qualifications,'path_3')
\ No newline at end of file
+                self.assertEqual(v.qualifications,'path_3')