Repository: carpentries/amy

Issue:
Lack of unique constraints on Sponsorship model
With no unique constraint on the Sponsorship model, there can exist duplicates on repeated imports.
I propose adding a `unique_together` constraint.

``` py
class Meta:
    unique_together = ('organization', 'event', 'amount')
```



Discussion:
Proposal accepted :-)


Resolving patch:
diff --git a/workshops/migrations/0115_auto_20160812_2056.py b/workshops/migrations/0115_auto_20160812_2056.py
new file mode 100644
--- /dev/null
+++ b/workshops/migrations/0115_auto_20160812_2056.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.9 on 2016-08-13 01:56
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('workshops', '0114_instructor_training_workflow'),
+    ]
+
+    operations = [
+        migrations.AlterUniqueTogether(
+            name='sponsorship',
+            unique_together=set([('organization', 'event', 'amount')]),
+        ),
+    ]
diff --git a/workshops/models.py b/workshops/models.py
--- a/workshops/models.py
+++ b/workshops/models.py
@@ -185,6 +185,9 @@ class Sponsorship(models.Model):
         null=True, blank=True,
     )
 
+    class Meta:
+        unique_together = ('organization', 'event', 'amount')
+
     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
@@ -1,5 +1,6 @@
-from django.core.urlresolvers import reverse
 from django.core.exceptions import ValidationError, ObjectDoesNotExist
+from django.core.urlresolvers import reverse
+from django.db import IntegrityError
 
 from ..models import Event, Organization, Sponsorship
 from .base import TestBase
@@ -45,6 +46,20 @@ def test_sponsorship_can_be_deleted_and_related_objects_are_intact(self):
         self.event.refresh_from_db()
         self.org_beta.refresh_from_db()
 
+    def test_unique_together_constraint(self):
+        '''Check that no two sponsorships with same values can exist'''
+        sponsorship = Sponsorship.objects.create(
+            organization=self.org_beta,
+            event=self.event,
+            amount=500,
+        )
+        with self.assertRaises(IntegrityError):
+            sponsorship = Sponsorship.objects.create(
+                organization=self.org_beta,
+                event=self.event,
+                amount=500,
+            )
+
 
 class TestSponsorshipViews(TestBase):
 
@@ -57,6 +72,7 @@ def setUp(self):
         self.sponsorship = Sponsorship.objects.create(
             organization=self.org_alpha,
             event=self.event,
+            amount=100.00,
         )
         self._setUpUsersAndLogin()
 
@@ -145,6 +161,25 @@ def test_add_sponsor_with_contact(self):
             response.context['object'].sponsorship_set.values_list('contact', flat=True)
         )
 
+    def test_add_duplicate_sponsorship_instance(self):
+        '''Check that we cannot successfully submit duplicates'''
+        payload = {
+            'sponsor-organization': self.sponsorship.organization.pk,
+            'sponsor-event': self.sponsorship.event.pk,
+            'sponsor-amount': self.sponsorship.amount,
+        }
+        response = self.client.post(
+            reverse('event_edit', kwargs={'slug': self.event.slug}),
+            payload,
+            follow=True,
+        )
+        self.assertEqual(response.status_code, 200)
+        self.assertFormError(response,
+            form='sponsor_form',
+            field=None,
+            errors='Sponsorship with this Organization, Event and Sponsorship amount already exists.',
+        )
+
     def test_delete_sponsor(self):
         '''Check that we can delete a sponsor from `sponsor_delete` view'''
         response = self.client.post(