Repository: liqd/a4-opin

Issue:
Improved form validation for invites 
The invites text box gives strange error messages if commas between email addresses are omitted.



Discussion:

Resolving patch:
diff --git a/euth/dashboard/forms.py b/euth/dashboard/forms.py
--- a/euth/dashboard/forms.py
+++ b/euth/dashboard/forms.py
@@ -1,10 +1,10 @@
 import collections
-import email.utils
 import re
 
 from django import forms
 from django.conf import settings
 from django.core.exceptions import ValidationError
+from django.core.validators import RegexValidator
 from django.db.models import loading
 from django.forms import modelformset_factory
 from django.utils.translation import ugettext as _
@@ -69,7 +69,13 @@ class ProjectInviteForm(forms.Form):
         widget=forms.TextInput(attrs={
             'placeholder': 'magdalena@example.com, yves@example.com,'
                            ' nadine@example.com…'}
-        )
+        ),
+        validators=[RegexValidator(
+            # a list of emails, separated by commas with optional space after
+            regex=r'^([^@]+@[^@\s]+\.[^@\s,]+((,\s?)|$))+$',
+            message=_('Please enter correct e-mail addresses, separated by '
+                      'commas.')
+        )]
     )
 
     def __init__(self, project, *args, **kwargs):
@@ -77,21 +83,12 @@ def __init__(self, project, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
     def clean_emails(self):
-        emails_str = self.cleaned_data['emails']
-        emails = email.utils.getaddresses([emails_str])
-
-        invalid_emails = []
-        for name, email_addr in emails:
-            if not re.match(r'^[^@]+@[^@\s]+\.[^@\s]+$', email_addr):
-                invalid_emails.append(email_addr)
-        if invalid_emails:
-            message = '{} invalid email address'
-            raise ValidationError(message.format(', '.join(invalid_emails)))
-
-        addresses = [email[1] for email in emails]
+        emails_str = self.cleaned_data['emails'].strip(' ,')
+        emails = re.split(r',\s?', emails_str)
+
         query = {
             'project': self.project,
-            'email__in': addresses,
+            'email__in': emails,
         }
         existing = member_models.Invite.objects.filter(**query)\
                                                .values_list('email', flat=True)
diff --git a/euth/dashboard/views.py b/euth/dashboard/views.py
--- a/euth/dashboard/views.py
+++ b/euth/dashboard/views.py
@@ -263,8 +263,8 @@ def form_valid(self, form):
         emails = form.cleaned_data['emails']
         user = self.request.user
         project = self.project
-        for (name, address) in emails:
-            member_models.Invite.objects.invite(user, project, address)
+        for email in emails:
+            member_models.Invite.objects.invite(user, project, email)
         return super().form_valid(form)
 
     def get_success_url(self):


Test patch:
diff --git a/tests/dashboard/test_dashboard_views.py b/tests/dashboard/test_dashboard_views.py
--- a/tests/dashboard/test_dashboard_views.py
+++ b/tests/dashboard/test_dashboard_views.py
@@ -235,7 +235,7 @@ def test_dashboard_project_invite(client, project):
     assert response.status_code == 200
 
     response = client.post(url, {
-        'emails': 'Jimmy Hendrix <j@he.ix>, james.dean@gmail.com'
+        'emails': 'j@he.ix, james.dean@gmail.com'
     })
     assert redirect_target(response) == 'dashboard-project-users'
     assert len(project.invite_set.all()) == 2
@@ -265,13 +265,15 @@ def test_dashboard_project_invalid(client, project):
         'emails': 'test@test.de foo@bar.de'
     })
     errors = response.context_data['form']['emails'].errors
-    assert errors == ['@bar.de invalid email address']
+    assert errors == ['Please enter correct e-mail addresses,'
+                      ' separated by commas.']
 
     response = client.post(url, {
         'emails': 'test@foo, @aded'
     })
     errors = response.context_data['form']['emails'].errors
-    assert errors == ['test@foo, @aded invalid email address']
+    assert errors == ['Please enter correct e-mail addresses,'
+                      ' separated by commas.']
 
 
 @pytest.mark.django_db