Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 26 additions & 27 deletions dash/orgs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,15 +356,14 @@ class InviteForm(forms.ModelForm):
)

def clean_emails(self):
emails = self.cleaned_data["emails"].lower().strip()
if emails:
email_list = emails.split(",")
for email in email_list:
try:
validate_email(email)
except ValidationError:
raise forms.ValidationError(_("One of the emails you entered is invalid."))
return emails
emails = self.cleaned_data["emails"]
email_list = list(dict.fromkeys(email.strip().lower() for email in emails.split(",") if email.strip()))
for email in email_list:
try:
validate_email(email)
except ValidationError:
raise forms.ValidationError(_("One of the emails you entered is invalid."))
return ",".join(email_list)

class Meta:
model = Invitation
Expand Down Expand Up @@ -422,28 +421,28 @@ def post_save(self, obj):

user_group = cleaned_data["user_group"]

emails = cleaned_data["emails"].lower().strip()
email_list = emails.split(",")
# emails is already normalized by clean_emails: comma-joined, stripped, lowercased and de-duplicated
emails = cleaned_data["emails"]
email_list = emails.split(",") if emails else []

if emails:
for email in email_list:
# if they already have an invite, update it
invites = Invitation.objects.filter(email=email, org=org).order_by("-pk")
invitation = invites.first()
for email in email_list:
# if they already have an invite, update it
invites = Invitation.objects.filter(email=email, org=org).order_by("-pk")
invitation = invites.first()

if invitation:
# remove any old invites
invites.exclude(pk=invitation.pk).delete()
if invitation:
# remove any old invites
invites.exclude(pk=invitation.pk).delete()

invitation.user_group = user_group
invitation.is_active = True
invitation.save()
else:
invitation = Invitation.objects.create(
email=email, org=org, user_group=user_group, created_by=user, modified_by=user
)
invitation.user_group = user_group
invitation.is_active = True
invitation.save()
else:
invitation = Invitation.objects.create(
email=email, org=org, user_group=user_group, created_by=user, modified_by=user
)

invitation.send_invitation()
invitation.send_invitation()

# remove all the org users
for user in self.get_object().get_org_admins():
Expand Down
40 changes: 37 additions & 3 deletions test_runner/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import zoneinfo
from dash.tags.models import Tag
from unittest.mock import Mock, patch, call
from unittest.mock import Mock, call, patch

import valkey
from smartmin.tests import SmartminTest
Expand All @@ -24,9 +23,11 @@
from dash.orgs.models import Invitation, Org, OrgBackend, OrgBackground, TaskState
from dash.orgs.tasks import org_task
from dash.orgs.templatetags.dashorgs import display_time, national_phone
from dash.orgs.views import OrgCRUDL
from dash.stories.models import Story, StoryImage
from dash.utils import random_string
from dash.tags.models import Tag
from dash.test import MockResponse
from dash.utils import random_string


class UserTest(SmartminTest):
Expand Down Expand Up @@ -1012,6 +1013,39 @@ def test_manage_accounts(self):
self.assertEqual(3, Invitation.objects.all().count())
self.assertEqual(4, len(mail.outbox))

# emails with surrounding whitespace and a trailing comma are tolerated
post_data["emails"] = "spaced1@nyaruka.com, spaced2@nyaruka.com ,"
post_data["user_group"] = "E"
response = self.client.post(manage_accounts_url, post_data, SERVER_NAME="uganda.ureport.io")
self.assertEqual(302, response.status_code)

# 2 new invitations are created with clean addresses
self.assertEqual(5, Invitation.objects.all().count())
self.assertEqual(6, len(mail.outbox))
self.assertTrue(Invitation.objects.filter(email="spaced1@nyaruka.com", user_group="E").exists())
self.assertTrue(Invitation.objects.filter(email="spaced2@nyaruka.com", user_group="E").exists())

# duplicate addresses are collapsed to a single invitation and email
post_data["emails"] = "dup@nyaruka.com, dup@nyaruka.com"
post_data["user_group"] = "E"
response = self.client.post(manage_accounts_url, post_data, SERVER_NAME="uganda.ureport.io")
self.assertEqual(302, response.status_code)
self.assertEqual(6, Invitation.objects.all().count())
self.assertEqual(7, len(mail.outbox))
self.assertEqual(1, Invitation.objects.filter(email="dup@nyaruka.com").count())

# separator-only input is treated as no emails
post_data["emails"] = " , , "
response = self.client.post(manage_accounts_url, post_data, SERVER_NAME="uganda.ureport.io")
self.assertEqual(302, response.status_code)
self.assertEqual(6, Invitation.objects.all().count())
self.assertEqual(7, len(mail.outbox))

def test_invite_form_clean_emails(self):
form = OrgCRUDL.ManageAccounts.InviteForm(data={"emails": " A@x.com , b@y.com ,", "user_group": "E"})
self.assertTrue(form.is_valid(), form.errors)
self.assertEqual("a@x.com,b@y.com", form.cleaned_data["emails"])

def test_join(self):
editor_invitation = Invitation.objects.create(
org=self.org, user_group="E", email="norkans7@gmail.com", created_by=self.admin, modified_by=self.admin
Expand Down