Repository: carpentries/amy

Issue:
Additional Contacts in Submission
Hi Maneeshsa & Piotr,

Can we update the way 'Additional contacts:' show up in the submission email and on the submission page in AMY. The current format makes it difficult to cut and paste the contacts into the CC field of an email.

**Current Format:**
['email@address.com'
'email@address.com'
'email@address.com']
Here is an example: https://amy.carpentries.org/requests/selforganised_submission/367/

**Suggested format:** 
email@address.com,email@address.com,email@address.com
or email@address.com;email@address.com;email@address.com;
--






Discussion:
Related to #1573.
Turns out there's a bug in Select2TagWidget preventing proper value saving or retrieving.

Resolving patch:
diff --git a/amy/extrequests/migrations/0018_auto_20191230_1113.py b/amy/extrequests/migrations/0018_auto_20191230_1113.py
new file mode 100644
--- /dev/null
+++ b/amy/extrequests/migrations/0018_auto_20191230_1113.py
@@ -0,0 +1,47 @@
+# Generated by Django 2.2.5 on 2019-12-30 11:13
+
+from django.db import migrations
+
+
+def mutation(original):
+    # It only works because no-one used apostrophe (') in any existing data.
+    return original.replace("['", "").replace("']", "").replace("', '", "")
+
+
+def update_additional_contact(apps, schema_editor):
+    """Apply `mutation` for all workshop requests, inquiries or self-organised
+    submissions (only to additional_contact field, though)."""
+    WorkshopRequest = apps.get_model('workshops', 'WorkshopRequest')
+    WorkshopInquiryRequest = apps.get_model('extrequests',
+                                            'WorkshopInquiryRequest')
+    SelfOrganisedSubmission = apps.get_model('extrequests',
+                                             'SelfOrganisedSubmission')
+
+    for object in WorkshopRequest.objects.exclude(additional_contact=''):
+        old = object.additional_contact
+        object.additional_contact = mutation(object.additional_contact)
+        if old != object.additional_contact:
+            object.save()
+
+    for object in WorkshopInquiryRequest.objects.exclude(additional_contact=''):
+        old = object.additional_contact
+        object.additional_contact = mutation(object.additional_contact)
+        if old != object.additional_contact:
+            object.save()
+
+    for object in SelfOrganisedSubmission.objects.exclude(additional_contact=''):
+        old = object.additional_contact
+        object.additional_contact = mutation(object.additional_contact)
+        if old != object.additional_contact:
+            object.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('extrequests', '0017_auto_20190913_0835'),
+    ]
+
+    operations = [
+        migrations.RunPython(update_additional_contact),
+    ]
diff --git a/amy/workshops/fields.py b/amy/workshops/fields.py
--- a/amy/workshops/fields.py
+++ b/amy/workshops/fields.py
@@ -158,6 +158,17 @@ class ModelSelect2MultipleWidget(Select2BootstrapMixin,
 
 
 class Select2TagWidget(Select2BootstrapMixin, DS2_Select2TagWidget):
+    def build_attrs(self, base_attrs, extra_attrs=None):
+        """Select2's tag attributes. By default other token separators are
+        used, but we want to use "," and ";"."""
+        default_attrs = {
+            'data-minimum-input-length': 1,
+            'data-tags': 'true',
+            'data-token-separators': '[",", ";"]'
+        }
+        default_attrs.update(base_attrs)
+        return super().build_attrs(default_attrs, extra_attrs=extra_attrs)
+
     def value_from_datadict(self, data, files, name):
         # sometimes data is held as an immutable QueryDict
         # in those cases, we need to make a copy of it to "disable"
@@ -168,4 +179,16 @@ def value_from_datadict(self, data, files, name):
             data_mutable = data
 
         data_mutable.setdefault(name, '')
-        return super().value_from_datadict(data_mutable, files, name)
+        values = super().value_from_datadict(data_mutable, files, name)
+        return ";".join(values)
+
+    def optgroups(self, name, value, attrs=None):
+        """Example from
+        https://django-select2.readthedocs.io/en/latest/django_select2.html#django_select2.forms.Select2TagWidget"""
+        values = value[0].split(';') if value[0] else []
+        selected = set(values)
+        subgroup = [
+            self.create_option(name, v, v, selected, i)
+            for i, v in enumerate(values)
+        ]
+        return [(None, subgroup, 0)]
diff --git a/amy/workshops/management/commands/fake_database.py b/amy/workshops/management/commands/fake_database.py
--- a/amy/workshops/management/commands/fake_database.py
+++ b/amy/workshops/management/commands/fake_database.py
@@ -573,7 +573,7 @@ def fake_workshop_requests(self, count=10):
                 public_event=public_event,
                 public_event_other=public_event_other,
                 additional_contact=(
-                    'Test Person <email@email.com>,'
+                    'Test Person <email@email.com>;'  # use ";" as separator
                     'Another Person <person@example.com>'
                 ),
 
@@ -666,7 +666,7 @@ def fake_workshop_inquiries(self, count=10):
                 public_event=public_event,
                 public_event_other=public_event_other,
                 additional_contact=(
-                    'Test Person <email@email.com>,'
+                    'Test Person <email@email.com>;'  # use ";" as separator
                     'Another Person <person@example.com>'
                 ),
 
@@ -767,7 +767,7 @@ def fake_selforganised_submissions(self, count=10):
                 public_event=public_event,
                 public_event_other=public_event_other,
                 additional_contact=(
-                    'Test Person <email@email.com>,'
+                    'Test Person <email@email.com>;'  # use ";" as separator
                     'Another Person <person@example.com>'
                 ),
 


Test patch: