merging old commit with current master
[oweals/karmaworld.git] / karmaworld / apps / courses / models.py
index 9a36d98e20baba8040d9b87edd69ff39185ba443..6ef836c2d566068a155695a12303b1a3ae1d1e1f 100644 (file)
@@ -9,10 +9,17 @@
     Courses have a manytoone relation to schools.
 """
 import datetime
+import reversion
 
 from django.db import models
 from django.utils.text import slugify
+from django.core.urlresolvers import reverse
 from karmaworld.settings.manual_unique_together import auto_add_check_unique_together
+from ajax_select import LookupChannel
+from ajax_select_cascade import DependentLookupChannel
+from ajax_select_cascade import register_channel_name
+
+from karmaworld.utils.ajax_selects import register_channel_name
 
 
 class SchoolManager(models.Manager):
@@ -59,7 +66,7 @@ class School(models.Model):
     def save(self, *args, **kwargs):
         """ Save school and generate a slug if one doesn't exist """
         if not self.slug:
-            self.slug = slugify(self.name)
+            self.slug = slugify(unicode(self.name))
         super(School, self).save(*args, **kwargs)
 
     @staticmethod
@@ -74,6 +81,24 @@ class School(models.Model):
         self.save()
 
 
+@register_channel_name('school')
+class SchoolLookup(LookupChannel):
+    """
+    Handles AJAX lookups against the school model's name and value fields.
+    """
+    model = School
+
+    def get_query(self, q, request):
+        """ Search against both name and alias. """
+        query = models.Q(name__icontains=q) | models.Q(alias__icontains=q)
+        return School.objects.filter(query)
+
+    def check_auth(self, request):
+        """ Allow anonymous access. """
+        # By default, Lookups require request.is_staff. Don't require anything!
+        pass
+
+
 class DepartmentManager(models.Manager):
     """ Handle restoring data. """
     def get_by_natural_key(self, name, school):
@@ -113,10 +138,32 @@ class Department(models.Model):
     def save(self, *args, **kwargs):
         """ Save department and generate a slug if one doesn't exist """
         if not self.slug:
-            self.slug = slugify(self.name)
+            self.slug = slugify(unicode(self.name))
         super(Department, self).save(*args, **kwargs)
 
 
+@register_channel_name('dept_given_school')
+class DeptGivenSchoolLookup(DependentLookupChannel):
+    """
+    Handles AJAX lookups against the department model's name field given a
+    school.
+    """
+    model = Department
+
+    def get_dependent_query(self, q, request, dependency):
+        """ Search against department name given a school. """
+        if dependency:
+            return Department.objects.filter(name__icontains=q,
+                                             school__id=dependency)
+        else:
+            return []
+
+    def check_auth(self, request):
+        """ Allow anonymous access. """
+        # By default, Lookups require request.is_staff. Don't require anything!
+        pass
+
+
 class ProfessorManager(models.Manager):
     """ Handle restoring data. """
     def get_by_natural_key(self, name, email):
@@ -204,17 +251,18 @@ class Course(models.Model):
     objects     = CourseManager()
 
     # Core metadata
-    name        = models.CharField(max_length=255)
+    name        = models.CharField(max_length=255, verbose_name="Course:")
     slug        = models.SlugField(max_length=150, null=True)
     # department should remove nullable when school gets yoinked
     department  = models.ForeignKey(Department, blank=True, null=True)
     # school is an appendix: the kind that gets swollen and should be removed
     # (vistigial)
-    school      = models.ForeignKey(School
+    school      = models.ForeignKey(School, null=True, blank=True)
     file_count  = models.IntegerField(default=0)
 
     desc        = models.TextField(max_length=511, blank=True, null=True)
-    url         = models.URLField(max_length=511, blank=True, null=True)
+    url         = models.URLField(max_length=511, blank=True, null=True,
+                                  verbose_name="Course URL:")
 
     # instructor_* is vestigial, replaced by Professor+ProfessorTaught models.
     instructor_name     = models.CharField(max_length=255, blank=True, null=True)
@@ -230,6 +278,7 @@ class Course(models.Model):
     class Meta:
         ordering = ['-file_count', 'school', 'name']
         unique_together = ('name', 'department')
+        unique_together = ('name', 'school')
         verbose_name = 'course'
         verbose_name_plural = 'courses'
 
@@ -245,20 +294,23 @@ class Course(models.Model):
     natural_key.dependencies = ['courses.department']
 
     def get_absolute_url(self):
-        """ return url based on school slug and self slug """
-        return u"/{0}/{1}".format(self.school.slug, self.slug)
+        """ return url based on urls.py definition. """
+        return reverse('course_detail', kwargs={'slug':self.slug})
 
     def save(self, *args, **kwargs):
         """ Save school and generate a slug if one doesn't exist """
         super(Course, self).save(*args, **kwargs) # generate a self.id
         if not self.slug:
-            self.slug = slugify("%s %s" % (self.name, self.id))
-            self.save() # Save the slug
+            self.set_slug()
 
     def get_updated_at_string(self):
         """ return the formatted style for datetime strings """
         return self.updated_at.strftime("%I%p // %a %b %d %Y")
 
+    def set_slug(self):
+        self.slug = slugify(u"%s %s" % (self.name, self.id))
+        self.save() # Save the slug
+
     @staticmethod
     def autocomplete_search_fields():
         return ("name__icontains",)
@@ -268,6 +320,16 @@ class Course(models.Model):
         self.file_count = self.note_set.count()
         self.save()
 
+    def get_popularity(self):
+        """ Aggregate popularity of notes contained within. """
+        # Run an efficient GROUP BY aggregation within the database.
+        # It returns {'fieldname': #}, where fieldname is set in the left hand
+        # side of the aggregate kwarg. Call the field x and retrieve the dict
+        # value using that key.
+        # The value might be None, return zero in that case with shortcut logic.
+        return self.note_set.aggregate(x=models.Sum('thanks'))['x'] or 0
+
+reversion.register(Course)
 
 class ProfessorTaughtManager(models.Manager):
     """ Handle restoring data. """