Repository: carpentries/amy

Issue:
Recording point of contact for organizations
PyData records a `contact_name` and `contact_email` for every sponsor organization.

@pbanaszkiewicz @gvwilson How does SWC/DC contact organizations?



Discussion:
@maneesha question for you?

@maneesha Can you provide me with an answer to the above question?

We don't have contacts with organizations documented in AMY now.  I feel like I remember another github issue about this where @jduckles said he didn't want to prioritize this but I could be making this up :) .  I will look for it shortly. 

I think this is what I was thinking of: https://github.com/swcarpentry/amy/issues/747

I'm ok with us managing limited contact info up to the point that we don't feel like AMY is our own custom reimplementation of a full-on Customer Relationship Manager (CRM).  

We've been using Highrise as our CRM and I think it might be pretty easy to add extended info for contacts to it. So we could capture name, email for host organizations and store any extended attributes in a CRM record that is linked to the AMY record.

Thank you @jduckles.

@gvwilson @pbanaszkiewicz 
To allow us to record that point of contact for an org in each sponsorship agreement (as required for PyData), I wish to add a `ForeignKey` to a Person object from the Sponsorship model. This would make things simple for PyData hosted AMY.

Is this change acceptable? Your opinions please.

:+1: from me.

@narayanaditya95 if it doesn't require admins/whoever to manually add `Person` and then manually link to a `Sponsorship`, then it works for me too.


Resolving patch:
diff --git a/workshops/forms.py b/workshops/forms.py
--- a/workshops/forms.py
+++ b/workshops/forms.py
@@ -659,6 +659,14 @@ class SponsorshipForm(forms.ModelForm):
         widget=selectable.AutoComboboxSelectWidget,
     )
 
+    contact = selectable.AutoCompleteSelectField(
+        lookup_class=lookups.PersonLookup,
+        label='Contact',
+        required=False,
+        help_text=AUTOCOMPLETE_HELP_TEXT,
+        widget=selectable.AutoComboboxSelectWidget,
+    )
+
     class Meta:
         model = Sponsorship
         fields = '__all__'
diff --git a/workshops/migrations/0112_sponsorship_contact.py b/workshops/migrations/0112_sponsorship_contact.py
new file mode 100644
--- /dev/null
+++ b/workshops/migrations/0112_sponsorship_contact.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.3 on 2016-07-18 14:06
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('workshops', '0111_migrate_host_to_org'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='sponsorship',
+            name='contact',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/workshops/models.py b/workshops/models.py
--- a/workshops/models.py
+++ b/workshops/models.py
@@ -178,6 +178,11 @@ class Sponsorship(models.Model):
         verbose_name='Sponsorship amount',
         help_text='e.g. 1992.33'
     )
+    contact = models.ForeignKey(
+        'Person',
+        on_delete=models.SET_NULL,
+        null=True, blank=True,
+    )
 
     def __str__(self):
         return '{}: {}'.format(self.organization, self.amount)


Test patch:
diff --git a/workshops/test/test_sponsorship.py b/workshops/test/test_sponsorship.py
--- a/workshops/test/test_sponsorship.py
+++ b/workshops/test/test_sponsorship.py
@@ -75,8 +75,28 @@ def test_sponsor_visible_on_event_edit_page(self):
         self.assertEqual(rv.status_code, 200)
         self.assertIn(self.sponsorship, rv.context['object'].sponsorship_set.all())
 
+    def test_add_sponsor_minimal(self):
+        '''Check that we can add a sponsor w/ minimal parameters'''
+        payload = {
+            'sponsor-organization': self.org_beta.pk,
+            'sponsor-event': self.event.pk,
+        }
+        response = self.client.post(
+            reverse('event_edit', kwargs={'event_ident': self.event.slug}),
+            payload,
+            follow=True
+        )
+        self.assertRedirects(
+            response,
+            '{}#sponsors'.format(
+                reverse('event_edit', kwargs={'event_ident': self.event.slug}),
+            )
+        )
+        self.assertTrue(response.context['object'].sponsorship_set.all())
+        self.assertEqual(response.context['object'].sponsors.count(), 2)
+
     def test_add_sponsor_with_amount(self):
-        '''Check that we can add a sponsor from `event_edit` view'''
+        '''Check that we can add a sponsor with amount'''
         payload = {
             'sponsor-organization': self.org_beta.pk,
             'sponsor-event': self.event.pk,
@@ -95,12 +115,17 @@ def test_add_sponsor_with_amount(self):
         )
         self.assertTrue(response.context['object'].sponsorship_set.all())
         self.assertEqual(response.context['object'].sponsors.count(), 2)
+        self.assertIn(
+            100.00,
+            response.context['object'].sponsorship_set.values_list('amount', flat=True)
+        )
 
-    def test_add_sponsor_without_amount(self):
-        '''Check that we can add a sponsor w/o amount from `event_edit` view'''
+    def test_add_sponsor_with_contact(self):
+        '''Check that we can add a sponsor with a contact person'''
         payload = {
             'sponsor-organization': self.org_beta.pk,
             'sponsor-event': self.event.pk,
+            'sponsor-contact': self.harry.pk,
         }
         response = self.client.post(
             reverse('event_edit', kwargs={'event_ident': self.event.slug}),
@@ -115,6 +140,10 @@ def test_add_sponsor_without_amount(self):
         )
         self.assertTrue(response.context['object'].sponsorship_set.all())
         self.assertEqual(response.context['object'].sponsors.count(), 2)
+        self.assertIn(
+            self.harry.pk,
+            response.context['object'].sponsorship_set.values_list('contact', flat=True)
+        )
 
     def test_delete_sponsor(self):
         '''Check that we can delete a sponsor from `sponsor_delete` view'''