Repository: carpentries/amy

Issue:
Revise unique constraints on Task model
Two presentations by the same speaker in a PyData conference would cause an IntegrityError as they have the same `event`, `person` and `role`.
I propose we add `title` to the `unique_together` constraint.

@pbanaszkiewicz Suggestion.



Discussion:
@pbanaszkiewicz Ping

@pbanaszkiewicz This is a bug preventing us from saving multiple tasks belonging to the same speaker at a conference.

@narayanaditya95 sorry for not taking care of this earlier!

I definitely agree with your suggestion.


Resolving patch:
diff --git a/workshops/management/commands/fake_database.py b/workshops/management/commands/fake_database.py
--- a/workshops/management/commands/fake_database.py
+++ b/workshops/management/commands/fake_database.py
@@ -234,7 +234,13 @@ def fake_tasks(self, faker, count=50):
         all_possible = itertools.product(events, persons, roles)
 
         for event, person, role in random.sample(list(all_possible), count):
-            Task.objects.create(event=event, person=person, role=role)
+            Task.objects.create(
+                event=event,
+                person=person,
+                role=role,
+                title=faker.sentence(nb_words=4, variable_nb_words=True),
+                url=faker.url(),
+            )
 
     def fake_trainees(self, faker):
         host = Organization.objects.all()[0]
diff --git a/workshops/migrations/0116_auto_20160902_1413.py b/workshops/migrations/0116_auto_20160902_1413.py
new file mode 100644
--- /dev/null
+++ b/workshops/migrations/0116_auto_20160902_1413.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.9 on 2016-09-02 19:13
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('workshops', '0115_auto_20160812_2056'),
+    ]
+
+    operations = [
+        migrations.AlterUniqueTogether(
+            name='task',
+            unique_together=set([('event', 'person', 'role', 'url')]),
+        ),
+    ]
diff --git a/workshops/models.py b/workshops/models.py
--- a/workshops/models.py
+++ b/workshops/models.py
@@ -1508,19 +1508,18 @@ class Task(models.Model):
     event      = models.ForeignKey(Event)
     person     = models.ForeignKey(Person)
     role       = models.ForeignKey(Role)
-    title      = models.CharField(
-        max_length=STR_LONG,
-        blank=True,
-    )
+    title      = models.CharField(max_length=STR_LONG, blank=True)
     url        = models.URLField(blank=True)
 
     objects = TaskManager()
 
     class Meta:
-        unique_together = ("event", "person", "role")
+        unique_together = ('event', 'person', 'role', 'url')
         ordering = ("role__name", "event")
 
     def __str__(self):
+        if self.title:
+            return self.title
         return '{0}/{1}={2}'.format(self.event, self.person, self.role)
 
     def get_absolute_url(self):


Test patch:
diff --git a/workshops/test/test_tasks.py b/workshops/test/test_tasks.py
--- a/workshops/test/test_tasks.py
+++ b/workshops/test/test_tasks.py
@@ -2,6 +2,7 @@
 from datetime import datetime
 
 from django.core.urlresolvers import reverse
+from django.db import IntegrityError
 
 from .base import TestBase
 from ..models import Task, Event, Role, Person, Organization
@@ -111,6 +112,29 @@ def test_add_task_with_incorrect_url(self):
         task.refresh_from_db()
         self.assertEqual(task.url, '')
 
+    def test_add_duplicate_task(self):
+        '''Ensure that duplicate tasks with empty url field cannot exist'''
+        task_1 = self.fixtures['test_task_1']
+        with self.assertRaises(IntegrityError):
+            Task.objects.create(
+                event=task_1.event,
+                person=task_1.person,
+                role=task_1.role,
+            )
+
+    def test_add_duplicate_task_with_url(self):
+        '''Ensure that duplicate tasks cannot exist'''
+        task_1 = self.fixtures['test_task_1']
+        task_1.url = 'http://example.org'
+        task_1.save()
+        with self.assertRaises(IntegrityError):
+            Task.objects.create(
+                event=task_1.event,
+                person=task_1.person,
+                role=task_1.role,
+                url=task_1.url,
+            )
+
     def test_task_edit_view_reachable_from_event_person_and_role_of_task(self):
         correct_task = self.fixtures['test_task_1']
         url_kwargs = {'task_id': correct_task.id}