Repository: carpentries/amy

Issue:
Comments: adding a comment from account without email address triggers form error
When user without email address tries to add a comment, then form validation prevents them.

Expected behaviour: a comment can be added even if user doesn't have email address on file.


Discussion:

Resolving patch:
diff --git a/amy/extcomments/forms.py b/amy/extcomments/forms.py
--- a/amy/extcomments/forms.py
+++ b/amy/extcomments/forms.py
@@ -1,3 +1,4 @@
+from django import forms
 from django.utils.translation import ugettext_lazy as _
 from django_comments.forms import COMMENT_MAX_LENGTH
 from django_comments.forms import CommentForm as DjCF
@@ -5,4 +6,5 @@
 
 
 class CommentForm(DjCF):
+    email = forms.EmailField(required=False)
     comment = MarkdownxFormField(label=_("Comment"), max_length=COMMENT_MAX_LENGTH)
diff --git a/amy/extcomments/utils.py b/amy/extcomments/utils.py
new file mode 100644
--- /dev/null
+++ b/amy/extcomments/utils.py
@@ -0,0 +1,34 @@
+from django.contrib.auth.base_user import AbstractBaseUser
+from django.db.models import Model
+from django_comments import get_form
+from django_comments.models import Comment
+
+
+def add_comment_for_object(
+    object: Model,
+    user: AbstractBaseUser,
+    content: str,
+) -> Comment:
+    """A simple utility to add a comment for given object by given user."""
+
+    # Adding comment is the easiest to achieve using comment form methods.
+    CommentForm = get_form()
+
+    security_data = CommentForm(object).generate_security_data()
+    data = {
+        "honeypot": "",
+        "comment": content,
+        "name": user.get_username(),
+        **security_data,
+    }
+
+    form = CommentForm(object, data=data)
+    comment = form.get_comment_object()
+    comment.user = user
+
+    # Original code in django_comments emits `comment_will_be_posted` signal at this
+    # point and checks if any of the receivers prevents comment from posting. For
+    # simplicity this behavior has not been implemented here.
+
+    comment.save()
+    return comment


Test patch:
diff --git a/amy/extcomments/tests.py b/amy/extcomments/tests.py
--- a/amy/extcomments/tests.py
+++ b/amy/extcomments/tests.py
@@ -1 +1,96 @@
-# Create your tests here.
+from django.test import TestCase
+from django.urls import reverse
+import django_comments
+
+from workshops.models import Organization, Person
+
+from .utils import add_comment_for_object
+
+
+class TestCommentForObjectUtil(TestCase):
+    def test_util(self):
+        # Arrange
+        person = Person.objects.create(
+            personal="Ron",
+            family="Weasley",
+            username="rw",
+            is_active=True,
+            email="",
+            data_privacy_agreement=True,
+        )
+        organization = Organization.objects.create(
+            domain="example.org",
+            fullname="Example Organisation",
+        )
+        content = "Test comment"
+        CommentModel = django_comments.get_model()
+
+        # Act
+        comment = add_comment_for_object(organization, person, content)
+
+        # Assert
+        self.assertEqual(list(CommentModel.objects.for_model(organization)), [comment])
+
+
+class TestEmailFieldRequiredness(TestCase):
+    def test_email_field_requiredness(self):
+        """Regression test for #1944.
+
+        Previously a user without email address would not be able to add a comment."""
+
+        # Arrange
+        organization = Organization.objects.create(
+            domain="example.org",
+            fullname="Example Organisation",
+        )
+        CommentForm = django_comments.get_form()
+
+        data = {
+            "honeypot": "",
+            "comment": "Content",
+            "name": "Ron",  # required outside the request cycle
+            **CommentForm(organization).generate_security_data(),
+        }
+
+        # Act
+        form = CommentForm(organization, data)
+
+        # Assert
+        self.assertTrue(form.is_valid())
+
+    def test_email_field_requiredness_POST(self):
+        """Regression test for #1944.
+
+        Previously a user without email address would not be able to add a comment.
+
+        This test makes a POST request with comment data."""
+
+        # Arrange
+        person = Person.objects.create(
+            personal="Ron",
+            family="Weasley",
+            username="rw",
+            is_active=True,
+            email="",
+            data_privacy_agreement=True,
+        )
+
+        organization = Organization.objects.create(
+            domain="example.org",
+            fullname="Example Organisation",
+        )
+
+        CommentModel = django_comments.get_model()
+        CommentForm = django_comments.get_form()
+        data = {
+            "honeypot": "",
+            "comment": "Content",
+            **CommentForm(organization).generate_security_data(),
+        }
+
+        # Act
+        self.client.force_login(person)
+        self.client.post(reverse("comments-post-comment"), data=data, follow=True)
+
+        # Assert
+        self.assertEqual(CommentModel.objects.for_model(organization).count(), 1)