indexden is now optional
[oweals/karmaworld.git] / karmaworld / apps / users / models.py
index 53106dc31556bcfd7f710c2ceeae47c932a561c1..c34917582d5074a4cf0fee51884ac3e5fd18d377 100644 (file)
 #!/usr/bin/env python
 # -*- coding:utf8 -*-
 # Copyright (C) 2013  FinalsClub Foundation
-import random
-from allauth.account.signals import user_logged_in
+import logging
+import datetime
+from allauth.account.signals import email_confirmed
 from django.contrib.auth.models import User
-from django.core.exceptions import ObjectDoesNotExist
-from django.db.models.signals import post_save, pre_save
+from django.db.models import Sum
+from django.db.models.signals import post_save
 from django.dispatch import receiver
-from django.db import models
+from django.db import models, DatabaseError
+from django.middleware.transaction import transaction
 from karmaworld.apps.courses.models import School
 
+logger = logging.getLogger(__name__)
+
+
+class UserProfileManager(models.Manager):
+    """ Handle restoring data. """
+    def get_by_natural_key(self, user):
+        return self.get(user=user)
+
 
 class UserProfile(models.Model):
-    user      = models.OneToOneField(User)
+    user = models.OneToOneField(User)
+    thanked_notes = models.ManyToManyField('notes.Note', related_name='users_thanked', blank=True, null=True)
+    flagged_notes = models.ManyToManyField('notes.Note', related_name='users_flagged', blank=True, null=True)
+    flagged_courses = models.ManyToManyField('courses.Course', related_name='users_flagged', blank=True, null=True)
+    school = models.ForeignKey(School, blank=True, null=True)
+
+    def natural_key(self):
+        return (self.user,)
+
+    def get_points(self):
+        sum = 0
+        for cls in ALL_KARMA_EVENT_CLASSES:
+            points = cls.objects.filter(user=self.user).aggregate(Sum('points'))['points__sum']
+            if points:
+                sum += points
+
+        return sum
+
+    def get_id(self):
+        return self.user.id
 
-    school    = models.ForeignKey(School, blank=True, null=True)
+    def has_staff_status(self):
+        return self.user.is_staff
 
-    karma     = models.IntegerField(default=0)
+    NO_BADGE = 0
+    PROSPECT = 1
+    BEGINNER = 2
+    TRAINEE = 3
+    APPRENTICE = 4
+    SCHOLAR = 5
+
+    BADGES = (
+        PROSPECT,
+        BEGINNER,
+        TRAINEE,
+        APPRENTICE,
+        SCHOLAR
+    )
+
+    BADGE_NAMES = {
+        PROSPECT: 'Prospect',
+        BEGINNER: 'Beginner',
+        TRAINEE: 'Trainee',
+        APPRENTICE: 'Apprentice',
+        SCHOLAR: 'Scholar'
+    }
+
+    BADGE_THRESHOLDS = {
+        PROSPECT: 10,
+        BEGINNER: 100,
+        TRAINEE: 200,
+        APPRENTICE: 500,
+        SCHOLAR: 1000
+    }
+
+    def get_badge(self):
+        points = self.get_points()
+        highest_badge = self.NO_BADGE
+        for badge in self.BADGES:
+            if points >= self.BADGE_THRESHOLDS[badge]:
+                highest_badge = badge
+
+        if highest_badge is not self.NO_BADGE:
+            return self.BADGE_NAMES[highest_badge]
+        else:
+            return None
 
     def __unicode__(self):
         return self.user.__unicode__()
 
+
+@receiver(email_confirmed, weak=True)
+def give_email_confirm_karma(sender, **kwargs):
+    GenericKarmaEvent.create_event(kwargs['email_address'].user, kwargs['email_address'].email, GenericKarmaEvent.EMAIL_CONFIRMED)
+
+
+class BaseKarmaEventManager(models.Manager):
+    """ Handle restoring data. """
+    def get_by_natural_key(self, points, user, timestamp):
+        return self.get(user=user, timestamp=timestamp)
+
+
+class BaseKarmaEvent(models.Model):
+    points    = models.IntegerField()
+    user      = models.ForeignKey(User)
+    timestamp = models.DateTimeField(default=datetime.datetime.utcnow)
+
+    class Meta:
+        abstract = True
+        unique_together = ('points', 'user', 'timestamp')
+
+    def natural_key(self):
+        return (self.user, self.timestamp)
+
+    def get_message(self):
+        raise NotImplemented()
+
+
+class GenericKarmaEvent(BaseKarmaEvent):
+    NONE = 'none'
+    NOTE_DELETED       = 'upload'
+    EMAIL_CONFIRMED    = 'thanks'
+
+    EVENT_TYPE_CHOICES = (
+        (NONE,               'This should not happen'),
+        (NOTE_DELETED,       'Your note "{m}" was deleted'),
+        (EMAIL_CONFIRMED,    'You confirmed your email address {m}'),
+    )
+
+    POINTS = {
+        NOTE_DELETED: -5,
+        EMAIL_CONFIRMED: 5,
+    }
+
+    event_type = models.CharField(max_length=15, choices=EVENT_TYPE_CHOICES, default=NONE)
+    message = models.CharField(max_length=255)
+
+    @staticmethod
+    def create_event(user, message, type):
+        event = GenericKarmaEvent.objects.create(user=user,
+                                                 points=GenericKarmaEvent.POINTS[type],
+                                                 event_type=type,
+                                                 message=message)
+        event.save()
+
+    def get_message(self):
+        if self.event_type == self.NONE:
+            return self.message
+        else:
+            return self.get_event_type_display().format(m=self.message)
+
+    def __unicode__(self):
+        return unicode(self.user) + ' -- ' + self.get_message()
+
+
+class NoteKarmaEvent(BaseKarmaEvent):
+    UPLOAD       = 'upload'
+    THANKS       = 'thanks'
+    NOTE_DELETED = 'deleted'
+    GIVE_FLAG    = 'give_flag'
+    GET_FLAGGED  = 'get_flagged'
+    DOWNLOADED_NOTE = 'downloaded'
+    HAD_NOTE_DOWNLOADED = 'was_downloaded'
+    CREATED_KEYWORD = 'created_keyword'
+
+    EVENT_TYPE_CHOICES = (
+        (UPLOAD,       "You uploaded a note"),
+        (THANKS,       "You received a thanks for your note"),
+        (NOTE_DELETED, "Your note was deleted"),
+        (GIVE_FLAG,    "You flagged a note"),
+        (GET_FLAGGED,  "Your note was flagged as spam"),
+        (DOWNLOADED_NOTE,  "You downloaded a note"),
+        (HAD_NOTE_DOWNLOADED,  "Your note was downloaded"),
+        (CREATED_KEYWORD,  "You created a keyword"),
+    )
+    note = models.ForeignKey('notes.Note')
+    event_type = models.CharField(max_length=15, choices=EVENT_TYPE_CHOICES)
+
+    POINTS = {
+        UPLOAD: 5,
+        THANKS: 1,
+        NOTE_DELETED: -5,
+        GIVE_FLAG: -1,
+        GET_FLAGGED: -100,
+        DOWNLOADED_NOTE: -2,
+        HAD_NOTE_DOWNLOADED: 2,
+        CREATED_KEYWORD: 1,
+    }
+
+    def get_message(self):
+        return self.get_event_type_display()
+
+    def __unicode__(self):
+        return unicode(self.user) + ' -- ' + self.get_event_type_display() + ' -- ' + unicode(self.note)
+
+    @staticmethod
+    def create_event(user, note, type):
+        event = NoteKarmaEvent.objects.create(user=user,
+                                      note=note,
+                                      points=NoteKarmaEvent.POINTS[type],
+                                      event_type=type)
+        event.save()
+
+
+class CourseKarmaEvent(BaseKarmaEvent):
+    GIVE_FLAG    = 'give_flag'
+    EVENT_TYPE_CHOICES = (
+        (GIVE_FLAG,    "You flagged a course"),
+    )
+    course = models.ForeignKey('courses.Course')
+    event_type = models.CharField(max_length=15, choices=EVENT_TYPE_CHOICES)
+
+    POINTS = {
+        GIVE_FLAG: -1,
+    }
+
+    def get_message(self):
+        return self.get_event_type_display()
+
+    def __unicode__(self):
+        return unicode(self.user) + ' -- ' + self.get_event_type_display() + ' -- ' + unicode(self.course)
+
+    @staticmethod
+    def create_event(user, course, type):
+        event = CourseKarmaEvent.objects.create(user=user,
+                                      course=course,
+                                      points=CourseKarmaEvent.POINTS[type],
+                                      event_type=type)
+        event.save()
+
+
+ALL_KARMA_EVENT_CLASSES = (GenericKarmaEvent, NoteKarmaEvent, CourseKarmaEvent)
+
+
 def user_display_name(user):
     """Return the best way to display a user's
     name to them on the site."""
-    if user.first_name or user.last_name:
+    if hasattr(user, 'first_name') and user.first_name and \
+            hasattr(user, 'last_name') and user.last_name:
         return user.first_name + ' ' + user.last_name
+    elif hasattr(user, 'email') and user.email:
+        return user.email
     else:
         return user.username
 
 
-@receiver(pre_save, sender=User, weak=True)
-def assign_username(sender, instance, **kwargs):
-    # If a user does not have a username, they need
-    # one before we save to the database
-    if not instance.username:
-        if instance.email:
-            try:
-                # See if any other users have this email address
-                others = User.objects.get(email=instance.email)
-            except ObjectDoesNotExist:
-                instance.username = instance.email
-            else:
-                instance.username = 'user' + str(random.randint(10000, 100000))
-        else:
-            instance.username = 'user' + str(random.randint(10000, 100000))
-
-
 @receiver(post_save, sender=User, weak=True)
 def create_user_profile(sender, instance, created, **kwargs):
     if created:
-        UserProfile.objects.create(user=instance)
+        with transaction.commit_on_success():
+            try:
+                UserProfile.objects.create(user=instance)
+            except DatabaseError:
+                logger.warn("Could not create UserProfile for user {u}. This is okay if running syncdb.".format(u=instance))