Skip to content

Commit

Permalink
feat: API to list role-holder addresses (#7291)
Browse files Browse the repository at this point in the history
* feat: API to list role-holder addresses

* test: Test new API endpoint

* fix: role_holder_addresses gets own API token

* refactor: Move role_holder_addresses to ietf.api.views

* test: test for group.utils.role_holder_emails

* test: Clean up test_role_holder_addresses

* fix: Missed a change in urls.py

* refactor: Remove old view

* chore: Remove unused import

* chore: Remove unused import
  • Loading branch information
jennifer-richards committed Apr 5, 2024
1 parent 8d7e8d6 commit 6b4a806
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 3 deletions.
22 changes: 22 additions & 0 deletions ietf/api/tests.py
Expand Up @@ -991,6 +991,28 @@ def test_active_email_list(self):
self.assertCountEqual(result.keys(), ["addresses"])
self.assertCountEqual(result["addresses"], Email.objects.filter(active=True).values_list("address", flat=True))

@override_settings(APP_API_TOKENS={"ietf.api.views.role_holder_addresses": ["valid-token"]})
def test_role_holder_addresses(self):
url = urlreverse("ietf.api.views.role_holder_addresses")
r = self.client.get(url, headers={})
self.assertEqual(r.status_code, 403, "No api token, no access")
r = self.client.get(url, headers={"X-Api-Key": "not-valid-token"})
self.assertEqual(r.status_code, 403, "Bad api token, no access")
r = self.client.post(url, headers={"X-Api-Key": "valid-token"})
self.assertEqual(r.status_code, 405, "Bad method, no access")

emails = EmailFactory.create_batch(5)
email_queryset = Email.objects.filter(pk__in=[e.pk for e in emails])
with mock.patch("ietf.api.views.role_holder_emails", return_value=email_queryset):
r = self.client.get(url, headers={"X-Api-Key": "valid-token"})
self.assertEqual(r.status_code, 200, "Good api token and method, access")
content_dict = json.loads(r.content)
self.assertCountEqual(content_dict.keys(), ["addresses"])
self.assertEqual(
content_dict["addresses"],
sorted(e.address for e in emails),
)


class DirectAuthApiTests(TestCase):

Expand Down
2 changes: 2 additions & 0 deletions ietf/api/urls.py
Expand Up @@ -28,6 +28,8 @@
url(r'^export/personal-information/$', api_views.PersonalInformationExportView.as_view()),
# Email alias information for groups
url(r'^group/group-aliases/$', api_views.group_aliases),
# Email addresses belonging to role holders
url(r'^group/role-holder-addresses/$', api_views.role_holder_addresses),
# Let IESG members set positions programmatically
url(r'^iesg/position', views_ballot.api_set_position),
# Let Meetecho set session video URLs
Expand Down
17 changes: 16 additions & 1 deletion ietf/api/views.py
Expand Up @@ -29,7 +29,7 @@
from ietf.api.ietf_utils import is_valid_token, requires_api_token
from ietf.api.serializer import JsonExportMixin
from ietf.doc.utils import DraftAliasGenerator, fuzzy_find_documents
from ietf.group.utils import GroupAliasGenerator
from ietf.group.utils import GroupAliasGenerator, role_holder_emails
from ietf.ietfauth.utils import role_required
from ietf.ietfauth.views import send_account_creation_email
from ietf.meeting.models import Meeting
Expand Down Expand Up @@ -500,3 +500,18 @@ def active_email_list(request):
}
)
return HttpResponse(status=405)


@requires_api_token
def role_holder_addresses(request):
if request.method == "GET":
return JsonResponse(
{
"addresses": list(
role_holder_emails()
.order_by("address")
.values_list("address", flat=True)
)
}
)
return HttpResponse(status=405)
48 changes: 46 additions & 2 deletions ietf/group/tests.py
Expand Up @@ -18,10 +18,16 @@
from ietf.doc.factories import DocumentFactory, WgDraftFactory, EditorialDraftFactory
from ietf.doc.models import DocEvent, RelatedDocument, Document
from ietf.group.models import Role, Group
from ietf.group.utils import get_group_role_emails, get_child_group_role_emails, get_group_ad_emails, GroupAliasGenerator
from ietf.group.utils import (
get_group_role_emails,
get_child_group_role_emails,
get_group_ad_emails,
GroupAliasGenerator,
role_holder_emails,
)
from ietf.group.factories import GroupFactory, RoleFactory
from ietf.person.factories import PersonFactory, EmailFactory
from ietf.person.models import Person
from ietf.person.models import Email, Person
from ietf.utils.test_utils import login_testing_unauthorized, TestCase

class StreamTests(TestCase):
Expand Down Expand Up @@ -240,3 +246,41 @@ def test_group_ad_emails(self):
self.assertGreater(len(emails), 0)
for item in emails:
self.assertIn('@', item)

def test_role_holder_emails(self):
# The test fixtures create a bunch of addresses that pollute this test's results - disable them
Email.objects.update(active=False)

role_holders = [
RoleFactory(name_id="member", group__type_id=gt).person
for gt in [
"ag",
"area",
"dir",
"iab",
"ietf",
"irtf",
"nomcom",
"rg",
"team",
"wg",
"rag",
]
]
# Expect an additional active email to be included
EmailFactory(
person=role_holders[0],
active=True,
)
# Do not expect an inactive email to be included
EmailFactory(
person=role_holders[1],
active=False,
)
# Do not expect address on a role-holder for a different group type
RoleFactory(name_id="member", group__type_id="adhoc") # arbitrary type not in the of-interest list

self.assertCountEqual(
role_holder_emails(),
Email.objects.filter(active=True, person__in=role_holders),
)
25 changes: 25 additions & 0 deletions ietf/group/utils.py
Expand Up @@ -425,3 +425,28 @@ def __iter__(self):
chair_emails = get_group_role_emails(group, ["chair", "delegate"])
if chair_emails:
yield group.acronym + "-chairs", ["ietf"], list(chair_emails)


def role_holder_emails():
"""Get queryset of active Emails for group role holders"""
group_types_of_interest = [
"ag",
"area",
"dir",
"iab",
"ietf",
"irtf",
"nomcom",
"rg",
"team",
"wg",
"rag",
]
roles = Role.objects.filter(
group__state__slug="active",
group__type__in=group_types_of_interest,
)
emails = Email.objects.filter(active=True).exclude(
address__startswith="unknown-email-"
)
return emails.filter(person__role__in=roles).distinct()

0 comments on commit 6b4a806

Please sign in to comment.