Enforce course uniqueness
authorCharles Connell <charles@connells.org>
Tue, 17 Dec 2013 19:36:29 +0000 (14:36 -0500)
committerCharles Connell <charles@connells.org>
Wed, 18 Dec 2013 03:49:03 +0000 (22:49 -0500)
karmaworld/apps/courses/models.py
karmaworld/apps/courses/test/test.py
karmaworld/settings/manual_unique_together.py [new file with mode: 0644]
karmaworld/templates/partial/add_course.html

index 273dc69fc936620b473b715b6caa8a7866c8a1af..7c82802696f17a4302721d8ae3993e17f2333a92 100644 (file)
@@ -12,6 +12,8 @@ import datetime
 
 from django.db import models
 from django.template import defaultfilters
+from karmaworld.settings.manual_unique_together import auto_add_check_unique_together
+
 
 class School(models.Model):
     """ A grouping that contains many courses """
@@ -84,6 +86,9 @@ class Course(models.Model):
 
     class Meta:
         ordering = ['-file_count', 'school', 'name']
+        unique_together = ('school', 'name', 'instructor_name')
+        verbose_name = 'course'
+        verbose_name_plural = 'courses'
 
     def __unicode__(self):
         return u"{0}: {1}".format(self.name, self.school)
@@ -111,3 +116,8 @@ class Course(models.Model):
         """ Update self.file_count by summing the note_set """
         self.file_count = self.note_set.count()
         self.save()
+
+# Enforce unique constraints even when we're using a database like
+# SQLite that doesn't understand them
+auto_add_check_unique_together(Course)
+
index cd577a0f08d849b383375fd506b6f26101a4b1f3..115715242cbbd51a62a723f9a8eaa35b5e39723e 100644 (file)
@@ -1,3 +1,4 @@
+from django.db import IntegrityError
 from django.test import TestCase
 from karmaworld.apps.courses.models import *
 from django.test.client import Client
@@ -15,8 +16,9 @@ class CoursesTests(TestCase):
     def testCourseUniqueness(self):
         """Make sure we can't create multiple courses with the same
         school + course name + instructor name combination."""
-        with self.assertRaises(Exception):
-            Course.objects.create(name="Underwater Basketweaving", instructor_name="Alice Janney", school=self.harvard)
+        with self.assertRaises(IntegrityError):
+            Course.objects.create(name=self.course1.name, instructor_name=self.course1.instructor_name, school=self.course1.school)
+        self.assertEqual(Course.objects.count(), 1)
 
     def testSchoolSlug(self):
         self.assertEqual(self.harvard.slug, defaultfilters.slugify(self.harvard.name))
diff --git a/karmaworld/settings/manual_unique_together.py b/karmaworld/settings/manual_unique_together.py
new file mode 100644 (file)
index 0000000..294d15b
--- /dev/null
@@ -0,0 +1,53 @@
+from django.conf import settings
+from django.db.models import signals, FieldDoesNotExist
+from django.utils.text import get_text_list
+from django.db import IntegrityError
+from django.utils.translation import ugettext as _
+
+# This little gem is borrowed from
+# https://djangosnippets.org/snippets/1628/
+
+def check_unique_together(sender, **kwargs):
+    """
+    Check models unique_together manually. Django enforced unique together only the database level, but
+    some databases (e.g. SQLite) doesn't support this.
+
+    usage:
+        from django.db.models import signals
+        signals.pre_save.connect(check_unique_together, sender=MyModelClass)
+        
+    or use auto_add_check_unique_together(), see below
+    """
+    instance = kwargs["instance"]
+    for field_names in sender._meta.unique_together:
+        model_kwargs = {}
+        for field_name in field_names:
+            try:
+                data = getattr(instance, field_name)
+            except FieldDoesNotExist:
+                # e.g.: a missing field, which is however necessary.
+                # The real exception on model creation should be raised. 
+                continue
+            model_kwargs[field_name] = data
+
+        query_set = sender.objects.filter(**model_kwargs)
+        if instance.pk != None:
+            # Exclude the instance if it was saved in the past
+            query_set = query_set.exclude(pk=instance.pk)
+
+        count = query_set.count()
+        if count > 0:
+            field_names = get_text_list(field_names, _('and'))
+            msg = _(u"%(model_name)s with this %(field_names)s already exists.") % {
+                'model_name': unicode(instance.__class__.__name__),
+                'field_names': unicode(field_names)
+            }
+            raise IntegrityError(msg)
+
+def auto_add_check_unique_together(model_class):
+    """
+    Add only the signal handler check_unique_together, if a database without UNIQUE support is used.
+    """
+    if 'sqlite' in settings.DATABASES['default']['ENGINE']:
+        signals.pre_save.connect(check_unique_together, sender=model_class)
+
index 0ecd82a135921e727955404fad17009c7aa0f757..590f35682f77077d15d1bd01d123ad55afd46ee6 100644 (file)
@@ -13,7 +13,7 @@
 
     <div class="row">
       <div class="small-12 columns">
-        {{ course_form.non_field.errors }}
+        {{ course_form.non_field_errors }}
       </div>
     </div>