Repository: ietf-tools/datatracker

Issue:
Allow editing of interim meeting subject lines / Edits to RSWG interm meeting announcement subject
### Describe the issue

The subject line for interim meeting announcements is not editable by the Secretariat.

For RSWG (admittedly, not a regular group), the subject line is:

  RFC Series Working Group (rswg) EDWG Virtual Meeting: 2023-09-28

That would probably make more sense as something like:

  RFC Series Working Group (RSWG) Virtual Meeting: 2023-09-28

(From https://datatracker.ietf.org/meeting/interim/announce/interim-2023-rswg-01)

### 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:
And while we're there, we can special case the Subject construction for RSWG

Resolving patch:
diff --git a/ietf/celeryapp.py b/ietf/celeryapp.py
--- a/ietf/celeryapp.py
+++ b/ietf/celeryapp.py
@@ -1,6 +1,9 @@
 import os
+import scout_apm.celery
 
 from celery import Celery
+from scout_apm.api import Config
+
 
 # Set the default Django settings module for the 'celery' program
 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ietf.settings')
@@ -13,6 +16,25 @@
 #   should have a `CELERY_` prefix.
 app.config_from_object('django.conf:settings', namespace='CELERY')
 
+# Turn on Scout APM celery instrumentation if configured in the environment
+scout_key = os.environ.get("SCOUT_KEY", "")
+scout_name = os.environ.get("SCOUT_NAME", "")
+scout_core_agent_socket_path = os.environ.get("SCOUT_CORE_AGENT_SOCKET_PATH", "tcp://scoutapm:6590")
+if scout_key and scout_name:
+    Config.set(
+        key=scout_key,
+        name=scout_name,
+        monitor=True,
+        core_agent_download=False,
+        core_agent_launch=False,
+        core_agent_path=scout_core_agent_socket_path,
+    )
+    # Note: Passing the Celery app to install() method as recommended in the
+    # Scout documentation causes failure at startup, likely because Scout
+    # ingests the config greedily before Django is ready. Have not found a
+    # workaround for this other than explicitly configuring Scout.
+    scout_apm.celery.install() 
+
 # Load task modules from all registered Django apps.
 app.autodiscover_tasks()
 
diff --git a/ietf/liaisons/factories.py b/ietf/liaisons/factories.py
--- a/ietf/liaisons/factories.py
+++ b/ietf/liaisons/factories.py
@@ -1,7 +1,7 @@
 import factory
 
 from ietf.group.factories import GroupFactory
-from ietf.liaisons.models import LiaisonStatement, LiaisonStatementEvent, LiaisonStatementAttachment
+from ietf.liaisons.models import LiaisonStatement, LiaisonStatementEvent, LiaisonStatementAttachment, RelatedLiaisonStatement
 
 class LiaisonStatementFactory(factory.django.DjangoModelFactory):
     class Meta:
@@ -50,3 +50,12 @@ class Meta:
         type_id='liai-att',
         # TODO: Make name more convenient (the default now is to try to generate a draftname)
     )
+
+
+class RelatedLiaisonStatementFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = RelatedLiaisonStatement
+
+    source = factory.SubFactory(LiaisonStatementFactory)
+    target = factory.SubFactory(LiaisonStatementFactory)
+    relationship_id = "refunk"
diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py
--- a/ietf/liaisons/forms.py
+++ b/ietf/liaisons/forms.py
@@ -520,8 +520,7 @@ def __init__(self, *args, **kwargs):
         super(EditLiaisonForm, self).__init__(*args, **kwargs)
         self.edit = True
         self.fields['attachments'].initial = self.instance.liaisonstatementattachment_set.exclude(removed=True)
-        related = [ str(x.pk) for x in self.instance.source_of_set.all() ]
-        self.fields['related_to'].initial = ','.join(related)
+        self.fields['related_to'].initial = [ x.target for x in self.instance.source_of_set.all() ]
         self.fields['submitted_date'].initial = self.instance.submitted
 
     def save(self, *args, **kwargs):


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -45,9 +45,9 @@ jobs:
         echo "Running tests..."
         if [[ "x${{ github.event.inputs.ignoreLowerCoverage }}" == "xtrue" ]]; then
           echo "Lower coverage failures will be ignored."
-          ./ietf/manage.py test --validate-html-harder --settings=settings_test --ignore-lower-coverage
+          ./ietf/manage.py test -v2 --validate-html-harder --settings=settings_test --ignore-lower-coverage
         else
-          ./ietf/manage.py test --validate-html-harder --settings=settings_test
+          ./ietf/manage.py test -v2 --validate-html-harder --settings=settings_test
         fi
         coverage xml
 
diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py
--- a/ietf/liaisons/tests.py
+++ b/ietf/liaisons/tests.py
@@ -24,7 +24,7 @@
 
 from ietf.group.factories import GroupFactory, RoleFactory
 from ietf.liaisons.factories import ( LiaisonStatementFactory, 
-    LiaisonStatementEventFactory, LiaisonStatementAttachmentFactory, )
+    LiaisonStatementEventFactory, LiaisonStatementAttachmentFactory, RelatedLiaisonStatementFactory)
 from ietf.liaisons.models import (LiaisonStatement, LiaisonStatementPurposeName,
     LiaisonStatementAttachment)
 from ietf.person.models import Person
@@ -393,6 +393,8 @@ def test_edit_liaison(self):
         LiaisonStatementEventFactory(statement=liaison,type_id='posted')
         from_group = liaison.from_groups.first()
         to_group = liaison.to_groups.first()
+        rel1 = RelatedLiaisonStatementFactory(source=liaison)
+        rel2 = RelatedLiaisonStatementFactory(source=liaison)
 
         url = urlreverse('ietf.liaisons.views.liaison_edit', kwargs=dict(object_id=liaison.pk))
         login_testing_unauthorized(self, "secretary", url)
@@ -402,6 +404,13 @@ def test_edit_liaison(self):
         self.assertEqual(r.status_code, 200)
         q = PyQuery(r.content)
         self.assertEqual(len(q('form input[name=from_contact]')), 1)
+        json_data = q('form select[name=related_to]').attr('data-pre')
+        try:
+            decoded = json.loads(json_data)
+        except json.JSONDecodeError as e:
+            self.fail('data-pre contained invalid JSON data: %s' % str(e))
+        decoded_ids = [item['id'] for item in decoded]
+        self.assertEqual(decoded_ids, [rel1.target.id, rel2.target.id])
 
         # edit
         attachments_before = liaison.attachments.count()
@@ -1165,4 +1174,4 @@ def test_send_liaison_deadline_reminder(self):
 
         mailbox_before = len(outbox)
         possibly_send_deadline_reminder(liaison)
-        self.assertEqual(len(outbox), mailbox_before)
\ No newline at end of file
+        self.assertEqual(len(outbox), mailbox_before)