From 9f781a6e26216a26c787c6181895434444a8248e Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Sun, 3 Nov 2024 11:47:26 +0000 Subject: [PATCH 1/4] feat: add Group Leadership list --- ietf/group/tests.py | 23 +++++++++++++++ ietf/group/urls.py | 4 ++- ietf/group/views.py | 34 ++++++++++++++++++++++ ietf/templates/group/group_leadership.html | 34 ++++++++++++++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 ietf/templates/group/group_leadership.html diff --git a/ietf/group/tests.py b/ietf/group/tests.py index 130c68b3fc..6a1425b3d9 100644 --- a/ietf/group/tests.py +++ b/ietf/group/tests.py @@ -65,6 +65,29 @@ def test_stream_edit(self): self.assertTrue(Role.objects.filter(name="delegate", group__acronym=stream_acronym, email__address="ad2@ietf.org")) +class GroupLeadershipTests(TestCase): + def test_leadership_wg(self): + url = urlreverse("ietf.group.views.group_leadership", kwargs={'group_type': 'wg'}) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertContains(r, "Group Leadership") + self.assertContains(r, "Chairman, Sops") + + def test_leadership_wg_csv(self): + url = urlreverse("ietf.group.views.group_leadership_csv", kwargs={'group_type': 'wg'}) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertEqual(r['Content-Type'], 'text/csv') + self.assertContains(r, "Chairman, Sops") + + def test_leadership_rg(self): + url = urlreverse("ietf.group.views.group_leadership", kwargs={'group_type': 'rg'}) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertContains(r, "Group Leadership") + self.assertNotContains(r, "Chairman, Sops") + + class GroupStatsTests(TestCase): def setUp(self): super().setUp() diff --git a/ietf/group/urls.py b/ietf/group/urls.py index b2af8d9e2b..1824564c4d 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -57,7 +57,9 @@ group_urls = [ - url(r'^$', views.active_groups), + url(r'^$', views.active_groups), + url(r'^leadership/(?P(wg|rg))/$', views.group_leadership), + url(r'^leadership/(?P(wg|rg))/csv/$', views.group_leadership_csv), url(r'^groupstats.json', views.group_stats_data, None, 'ietf.group.views.group_stats_data'), url(r'^groupmenu.json', views.group_menu_data, None, 'ietf.group.views.group_menu_data'), url(r'^chartering/$', views.chartering_groups), diff --git a/ietf/group/views.py b/ietf/group/views.py index 71986384e0..d52e859865 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -35,6 +35,7 @@ import copy +import csv import datetime import itertools import math @@ -437,6 +438,39 @@ def prepare_group_documents(request, group, clist): return docs, meta, docs_related, meta_related +def get_leadership(group_type): + people = Person.objects.filter( + role__name__slug='chair', + role__group__type=group_type, + role__group__state__slug__in=('active', 'bof', 'proposed')).distinct() + leaders = [] + for person in people: + parts = person.name_parts() + groups = [r.group.acronym for r in person.role_set.filter( + name__slug='chair', + group__type=group_type, + group__state__slug__in=('active', 'bof', 'proposed'))] + entry = {'name': '%s, %s' % (parts[3], parts[1]), + 'groups': ', '.join(groups)} + leaders.append(entry) + return sorted(leaders, key=lambda a: a['name']) + +def group_leadership(request, group_type=None): + context = {} + context['leaders'] = get_leadership(group_type) + context['group_type'] = group_type + return render(request, 'group/group_leadership.html', context) + +def group_leadership_csv(request, group_type=None): + leaders = get_leadership(group_type) + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = f'attachment; filename="group_leadership_{group_type}.csv"' + writer = csv.writer(response, dialect=csv.excel, delimiter=str(',')) + writer.writerow(["Name", "Groups"]) + for leader in leaders: + writer.writerow([leader['name'], leader['groups']]) + return response + def group_home(request, acronym, group_type=None): group = get_group_or_404(acronym, group_type) kwargs = dict(acronym=group.acronym) diff --git a/ietf/templates/group/group_leadership.html b/ietf/templates/group/group_leadership.html new file mode 100644 index 0000000000..689977e3fb --- /dev/null +++ b/ietf/templates/group/group_leadership.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin static person_filters %} +{% block pagehead %} + +{% endblock %} +{% block title %}Group Leadership{% endblock %} +{% block content %} + {% origin %} +

Group Leadership ({{ group_type }})

+ + + + + + + + + + {% for leader in leaders %} + + + + + {% endfor %} + +
LeaderGroups
{{ leader.name }}{{ leader.groups }}
+{% endblock %} +{% block js %} +{% endblock %} \ No newline at end of file From 0d24f293e29ef64a2b1de9a77a496103f21082f1 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Sun, 3 Nov 2024 12:06:49 +0000 Subject: [PATCH 2/4] fix: only offer export to staff --- ietf/templates/group/group_leadership.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ietf/templates/group/group_leadership.html b/ietf/templates/group/group_leadership.html index 689977e3fb..804c5ede7a 100644 --- a/ietf/templates/group/group_leadership.html +++ b/ietf/templates/group/group_leadership.html @@ -8,11 +8,13 @@ {% block content %} {% origin %}

Group Leadership ({{ group_type }})

- + {% if request.user.is_staff %} + + {% endif %} From ca3690585ffd0346f881e543510c1ee804915cb7 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Sun, 3 Nov 2024 16:28:53 +0000 Subject: [PATCH 3/4] fix: fix export button conditional --- ietf/templates/group/group_leadership.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ietf/templates/group/group_leadership.html b/ietf/templates/group/group_leadership.html index 804c5ede7a..a565e3ab0b 100644 --- a/ietf/templates/group/group_leadership.html +++ b/ietf/templates/group/group_leadership.html @@ -1,14 +1,14 @@ {% extends "base.html" %} {# Copyright The IETF Trust 2015, All Rights Reserved #} -{% load origin static person_filters %} +{% load origin static person_filters ietf_filters %} {% block pagehead %} - + {% endblock %} {% block title %}Group Leadership{% endblock %} {% block content %} {% origin %}

Group Leadership ({{ group_type }})

- {% if request.user.is_staff %} + {% if user|has_role:"Secretariat" %}
Export as CSV From 3ea84eb971af48360088e2d13fd31cfef9f9c516 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Mon, 4 Nov 2024 10:34:06 +0000 Subject: [PATCH 4/4] fix: improve tests. black format --- ietf/group/tests.py | 34 +++++++++++++++--- ietf/group/views.py | 41 +++++++++++++--------- ietf/templates/group/group_leadership.html | 4 +-- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/ietf/group/tests.py b/ietf/group/tests.py index 6a1425b3d9..31f8cc45b5 100644 --- a/ietf/group/tests.py +++ b/ietf/group/tests.py @@ -67,24 +67,48 @@ def test_stream_edit(self): class GroupLeadershipTests(TestCase): def test_leadership_wg(self): - url = urlreverse("ietf.group.views.group_leadership", kwargs={'group_type': 'wg'}) + # setup various group states + bof_role = RoleFactory( + group__type_id="wg", group__state_id="bof", name_id="chair" + ) + proposed_role = RoleFactory( + group__type_id="wg", group__state_id="proposed", name_id="chair" + ) + active_role = RoleFactory( + group__type_id="wg", group__state_id="active", name_id="chair" + ) + conclude_role = RoleFactory( + group__type_id="wg", group__state_id="conclude", name_id="chair" + ) + url = urlreverse( + "ietf.group.views.group_leadership", kwargs={"group_type": "wg"} + ) r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertContains(r, "Group Leadership") - self.assertContains(r, "Chairman, Sops") + self.assertContains(r, bof_role.person.last_name()) + self.assertContains(r, proposed_role.person.last_name()) + self.assertContains(r, active_role.person.last_name()) + self.assertNotContains(r, conclude_role.person.last_name()) def test_leadership_wg_csv(self): - url = urlreverse("ietf.group.views.group_leadership_csv", kwargs={'group_type': 'wg'}) + url = urlreverse( + "ietf.group.views.group_leadership_csv", kwargs={"group_type": "wg"} + ) r = self.client.get(url) self.assertEqual(r.status_code, 200) - self.assertEqual(r['Content-Type'], 'text/csv') + self.assertEqual(r["Content-Type"], "text/csv") self.assertContains(r, "Chairman, Sops") def test_leadership_rg(self): - url = urlreverse("ietf.group.views.group_leadership", kwargs={'group_type': 'rg'}) + role = RoleFactory(group__type_id="rg", name_id="chair") + url = urlreverse( + "ietf.group.views.group_leadership", kwargs={"group_type": "rg"} + ) r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertContains(r, "Group Leadership") + self.assertContains(r, role.person.last_name()) self.assertNotContains(r, "Chairman, Sops") diff --git a/ietf/group/views.py b/ietf/group/views.py index d52e859865..f30569d230 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -438,37 +438,46 @@ def prepare_group_documents(request, group, clist): return docs, meta, docs_related, meta_related + def get_leadership(group_type): people = Person.objects.filter( - role__name__slug='chair', + role__name__slug="chair", role__group__type=group_type, - role__group__state__slug__in=('active', 'bof', 'proposed')).distinct() + role__group__state__slug__in=("active", "bof", "proposed"), + ).distinct() leaders = [] for person in people: parts = person.name_parts() - groups = [r.group.acronym for r in person.role_set.filter( - name__slug='chair', - group__type=group_type, - group__state__slug__in=('active', 'bof', 'proposed'))] - entry = {'name': '%s, %s' % (parts[3], parts[1]), - 'groups': ', '.join(groups)} + groups = [ + r.group.acronym + for r in person.role_set.filter( + name__slug="chair", + group__type=group_type, + group__state__slug__in=("active", "bof", "proposed"), + ) + ] + entry = {"name": "%s, %s" % (parts[3], parts[1]), "groups": ", ".join(groups)} leaders.append(entry) - return sorted(leaders, key=lambda a: a['name']) + return sorted(leaders, key=lambda a: a["name"]) + def group_leadership(request, group_type=None): context = {} - context['leaders'] = get_leadership(group_type) - context['group_type'] = group_type - return render(request, 'group/group_leadership.html', context) + context["leaders"] = get_leadership(group_type) + context["group_type"] = group_type + return render(request, "group/group_leadership.html", context) + def group_leadership_csv(request, group_type=None): leaders = get_leadership(group_type) - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = f'attachment; filename="group_leadership_{group_type}.csv"' - writer = csv.writer(response, dialect=csv.excel, delimiter=str(',')) + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = ( + f'attachment; filename="group_leadership_{group_type}.csv"' + ) + writer = csv.writer(response, dialect=csv.excel, delimiter=str(",")) writer.writerow(["Name", "Groups"]) for leader in leaders: - writer.writerow([leader['name'], leader['groups']]) + writer.writerow([leader["name"], leader["groups"]]) return response def group_home(request, acronym, group_type=None): diff --git a/ietf/templates/group/group_leadership.html b/ietf/templates/group/group_leadership.html index a565e3ab0b..644be3e150 100644 --- a/ietf/templates/group/group_leadership.html +++ b/ietf/templates/group/group_leadership.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{# Copyright The IETF Trust 2015, All Rights Reserved #} +{# Copyright The IETF Trust 2024, All Rights Reserved #} {% load origin static person_filters ietf_filters %} {% block pagehead %} @@ -32,5 +32,3 @@

Group Leadership ({{ group_type }})

{% endblock %} -{% block js %} -{% endblock %} \ No newline at end of file