3 # Copyright (C) 2012 FinalsClub Foundation
6 Models for the courses django app.
7 Handles courses, and their related models
8 Courses are the first class object, they contain notes.
9 Courses have a manytoone relation to schools.
14 from django.db import models
15 from django.utils.text import slugify
16 from karmaworld.settings.manual_unique_together import auto_add_check_unique_together
19 class SchoolManager(models.Manager):
20 """ Handle restoring data. """
21 def get_by_natural_key(self, usde_id):
23 Return a School defined by USDE number.
25 return self.get(usde_id=usde_id)
28 class School(models.Model):
29 """ A grouping that contains many courses """
30 objects = SchoolManager()
32 name = models.CharField(max_length=255)
33 slug = models.SlugField(max_length=150, null=True)
34 location = models.CharField(max_length=255, blank=True, null=True)
35 url = models.URLField(max_length=511, blank=True)
36 # Facebook keeps a unique identifier for all schools
37 facebook_id = models.BigIntegerField(blank=True, null=True)
38 # United States Department of Education institution_id
39 usde_id = models.BigIntegerField(blank=True, null=True, unique=True)
40 file_count = models.IntegerField(default=0)
41 priority = models.BooleanField(default=0)
42 alias = models.CharField(max_length=255, null=True, blank=True)
43 hashtag = models.CharField(max_length=16, null=True, blank=True, unique=True, help_text='School abbreviation without #')
46 """ Sort School by file_count descending, name abc=> """
47 ordering = ['-file_count','-priority', 'name']
49 def natural_key(self):
51 A School is uniquely defined by USDE number.
53 Name should be unique, but there are some dupes in the DB.
55 return (self.usde_id,)
57 def __unicode__(self):
58 return u'School {0}: {1}'.format(self.usde_id, self.name)
60 def save(self, *args, **kwargs):
61 """ Save school and generate a slug if one doesn't exist """
63 self.slug = slugify(unicode(self.name))
64 super(School, self).save(*args, **kwargs)
67 def autocomplete_search_fields():
68 return ("name__icontains",)
70 def update_note_count(self):
71 """ Update the School.file_count by summing the
72 contained course.file_count
74 self.file_count = sum([course.file_count for course in self.course_set.all()])
78 class DepartmentManager(models.Manager):
79 """ Handle restoring data. """
80 def get_by_natural_key(self, name, school):
82 Return a Department defined by its name and school.
84 return self.get(name=name, school=school)
87 class Department(models.Model):
88 """ Department within a School. """
89 objects = DepartmentManager()
91 name = models.CharField(max_length=255)
92 school = models.ForeignKey(School) # Should this be optional ever?
93 slug = models.SlugField(max_length=150, null=True)
94 url = models.URLField(max_length=511, blank=True, null=True)
98 The same department name might exist across schools, but only once
101 unique_together = ('name', 'school',)
103 def __unicode__(self):
104 return u'Department: {0} at {1}'.format(self.name, unicode(self.school))
106 def natural_key(self):
108 A Department is uniquely defined by its school and name.
110 return (self.name, self.school.natural_key())
111 # Requires School to be dumped first
112 natural_key.dependencies = ['courses.school']
114 def save(self, *args, **kwargs):
115 """ Save department and generate a slug if one doesn't exist """
117 self.slug = slugify(unicode(self.name))
118 super(Department, self).save(*args, **kwargs)
121 class ProfessorManager(models.Manager):
122 """ Handle restoring data. """
123 def get_by_natural_key(self, name, email):
125 Return a Professor defined by name and email address.
127 return self.get(name=name,email=email)
130 class Professor(models.Model):
132 Track professors for courses.
134 objects = ProfessorManager()
136 name = models.CharField(max_length=255)
137 email = models.EmailField(blank=True, null=True)
141 email should be unique, but some professors have no email address
142 in the database. For those cases, the name must be appended for
145 unique_together = ('name', 'email',)
147 def __unicode__(self):
148 return u'Professor: {0} ({1})'.format(self.name, self.email)
150 def natural_key(self):
152 A Professor is uniquely defined by his/her name and email.
154 return (self.name,self.email)
157 class ProfessorAffiliationManager(models.Manager):
158 """ Handle restoring data. """
159 def get_by_natural_key(self, prof, dept):
161 Return a ProfessorAffiliation defined by prof and department.
163 return self.get(professor=prof,department=dept)
166 class ProfessorAffiliation(models.Model):
168 Track professors for departments. (many-to-many)
170 objects = ProfessorAffiliationManager()
172 professor = models.ForeignKey(Professor)
173 department = models.ForeignKey(Department)
175 def __unicode__(self):
176 return u'Professor {0} working for {1}'.format(unicode(self.professor), unicode(self.department))
180 Many-to-many across both professor and department.
181 However, (prof, dept) as a tuple should only appear once.
183 unique_together = ('professor', 'department',)
185 def natural_key(self):
187 A ProfessorAffiliation is uniquely defined by the prof and department
189 return (self.professor.natural_key(), self.department.natural_key())
190 # Requires dependencies to be dumped first
191 natural_key.dependencies = ['courses.professor','courses.department']
194 class CourseManager(models.Manager):
195 """ Handle restoring data. """
196 def get_by_natural_key(self, name, dept):
198 Return a Course defined by name and department.
200 return self.get(name=name,department=dept)
203 class Course(models.Model):
204 """ First class object that contains many notes.Note objects """
205 objects = CourseManager()
208 name = models.CharField(max_length=255)
209 slug = models.SlugField(max_length=150, null=True)
210 # department should remove nullable when school gets yoinked
211 department = models.ForeignKey(Department, blank=True, null=True)
212 # school is an appendix: the kind that gets swollen and should be removed
214 school = models.ForeignKey(School, null=True, blank=True)
215 file_count = models.IntegerField(default=0)
217 desc = models.TextField(max_length=511, blank=True, null=True)
218 url = models.URLField(max_length=511, blank=True, null=True)
220 # instructor_* is vestigial, replaced by Professor+ProfessorTaught models.
221 instructor_name = models.CharField(max_length=255, blank=True, null=True)
222 instructor_email = models.EmailField(blank=True, null=True)
224 updated_at = models.DateTimeField(default=datetime.datetime.utcnow)
226 created_at = models.DateTimeField(auto_now_add=True)
228 # Number of times this course has been flagged as abusive/spam.
229 flags = models.IntegerField(default=0,null=False)
232 ordering = ['-file_count', 'school', 'name']
233 unique_together = ('name', 'department')
234 unique_together = ('name', 'school')
235 verbose_name = 'course'
236 verbose_name_plural = 'courses'
238 def __unicode__(self):
239 return u"Course {0} in {1}".format(self.name, unicode(self.department))
241 def natural_key(self):
243 A Course is uniquely defined by its name and the department it is in.
245 return (self.name, self.department.natural_key())
246 # Requires dependencies to be dumped first
247 natural_key.dependencies = ['courses.department']
249 def get_absolute_url(self):
250 """ return url based on school slug and self slug """
251 return u"/{0}/{1}".format(self.school.slug, self.slug)
253 def save(self, *args, **kwargs):
254 """ Save school and generate a slug if one doesn't exist """
255 super(Course, self).save(*args, **kwargs) # generate a self.id
259 def get_updated_at_string(self):
260 """ return the formatted style for datetime strings """
261 return self.updated_at.strftime("%I%p // %a %b %d %Y")
264 self.slug = slugify(u"%s %s" % (self.name, self.id))
265 self.save() # Save the slug
268 def autocomplete_search_fields():
269 return ("name__icontains",)
271 def update_note_count(self):
272 """ Update self.file_count by summing the note_set """
273 self.file_count = self.note_set.count()
276 reversion.register(Course)
278 class ProfessorTaughtManager(models.Manager):
279 """ Handle restoring data. """
280 def get_by_natural_key(self, prof, course):
282 Return a ProfessorTaught defined by professor and course.
284 return self.get(professor=prof, course=course)
287 class ProfessorTaught(models.Model):
289 Track professors teaching courses. (many-to-many)
291 objects = ProfessorTaughtManager()
293 professor = models.ForeignKey(Professor)
294 course = models.ForeignKey(Course)
296 def __unicode__(self):
297 return u'Professor {0} taught {1}'.format(unicode(self.professor), unicode(self.course))
300 # many-to-many across both fields,
301 # but (prof, course) as a tuple should only appear once.
302 unique_together = ('professor', 'course',)
304 def natural_key(self):
306 A ProfessorTaught is uniquely defined by the prof and course.
308 return (self.professor.natural_key(), self.course.natural_key())
309 # Requires dependencies to be dumped first
310 natural_key.dependencies = ['courses.professor','courses.course']
313 # Enforce unique constraints even when we're using a database like
314 # SQLite that doesn't understand them
315 auto_add_check_unique_together(Course)
316 auto_add_check_unique_together(Department)
317 auto_add_check_unique_together(Professor)
318 auto_add_check_unique_together(ProfessorAffiliation)
319 auto_add_check_unique_together(ProfessorTaught)