Repository: ietf-tools/datatracker

Issue:
Add warning when character limit is exceeded on Cancel Interim Meeting Request page
### Describe the issue

The comments field on the page to cancel an interim meeting (e.g. https://datatracker.ietf.org/meeting/interim/request/interim-2024-nfsv4-02/cancel) apparently has 255-character limit, and exceeding that makes the form return a 500 internal server error. 

It would be helpful if the form could warn that the character limit was being exceeded, or if the error message that is returned was more descriptive than just a generic "internal server error."

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:
form could be told to stop taking input after 255 characters.

But - is 255 enough for what's needed in the field? Do we need to make it larger?
I ran into issues because I was trying to paste in a message from the chairs verbatim, which came in at around ~400 characters. I was able to pare it down, but maybe expanding this to 500 or so would be useful.
Right - we should expand the field to 512, make the control aware of the limit, and harden the POST against attempted abuse so this path to a 500 is removed.

Resolving patch:
diff --git a/ietf/meeting/forms.py b/ietf/meeting/forms.py
--- a/ietf/meeting/forms.py
+++ b/ietf/meeting/forms.py
@@ -380,7 +380,8 @@ def save(self, *args, **kwargs):
 class InterimCancelForm(forms.Form):
     group = forms.CharField(max_length=255, required=False)
     date = forms.DateField(required=False)
-    comments = forms.CharField(required=False, widget=forms.Textarea(attrs={'placeholder': 'enter optional comments here'}), strip=False)
+    # max_length must match Session.agenda_note
+    comments = forms.CharField(max_length=512, required=False, widget=forms.Textarea(attrs={'placeholder': 'enter optional comments here'}), strip=False)
 
     def __init__(self, *args, **kwargs):
         super(InterimCancelForm, self).__init__(*args, **kwargs)
diff --git a/ietf/meeting/migrations/0005_alter_session_agenda_note.py b/ietf/meeting/migrations/0005_alter_session_agenda_note.py
new file mode 100644
--- /dev/null
+++ b/ietf/meeting/migrations/0005_alter_session_agenda_note.py
@@ -0,0 +1,18 @@
+# Copyright The IETF Trust 2024, All Rights Reserved
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("meeting", "0004_session_chat_room"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="session",
+            name="agenda_note",
+            field=models.CharField(blank=True, max_length=512),
+        ),
+    ]
diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py
--- a/ietf/meeting/models.py
+++ b/ietf/meeting/models.py
@@ -1017,7 +1017,7 @@ class Session(models.Model):
     group = ForeignKey(Group)    # The group type historically determined the session type.  BOFs also need to be added as a group. Note that not all meeting requests have a natural group to associate with.
     joint_with_groups = models.ManyToManyField(Group, related_name='sessions_joint_in',blank=True)
     attendees = models.IntegerField(null=True, blank=True)
-    agenda_note = models.CharField(blank=True, max_length=255)
+    agenda_note = models.CharField(blank=True, max_length=512)
     requested_duration = models.DurationField(default=datetime.timedelta(0))
     comments = models.TextField(blank=True)
     scheduled = models.DateTimeField(null=True, blank=True)


Test patch:
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -5595,8 +5595,17 @@ def test_interim_request_cancel(self, mock):
         self.assertEqual(r.status_code, 403)
         self.assertFalse(mock.called, 'Should not cancel sessions if request rejected')
 
-        # test cancelling before announcement
+        # test with overly-long comments
+        comments += '0123456789abcdef'*32
         self.client.login(username="marschairman", password="marschairman+password")
+        r = self.client.post(url, {'comments': comments})
+        self.assertEqual(r.status_code, 200)
+        q = PyQuery(r.content)
+        self.assertTrue(q('form .is-invalid'))
+        # truncate to max_length
+        comments = comments[:512]
+
+        # test cancelling before announcement
         length_before = len(outbox)
         r = self.client.post(url, {'comments': comments})
         self.assertRedirects(r, urlreverse('ietf.meeting.views.upcoming'))
@@ -5618,6 +5627,7 @@ def test_interim_request_cancel(self, mock):
             self.assertEqual(session.agenda_note, comments)
         self.assertEqual(len(outbox), length_before + 1)
         self.assertIn('Interim Meeting Cancelled', outbox[-1]['Subject'])
+        self.assertIn(comments, get_payload_text(outbox[-1]))
         self.assertTrue(mock.called, 'Should cancel sessions if request handled')
         self.assertCountEqual(mock.call_args[0][1], meeting.session_set.all())