Bryan's comments #264
[oweals/karmaworld.git] / karmaworld / apps / users / models.py
1 #!/usr/bin/env python
2 # -*- coding:utf8 -*-
3 # Copyright (C) 2013  FinalsClub Foundation
4 import logging
5 import datetime
6 from allauth.account.signals import email_confirmed
7 from django.contrib.auth.models import User
8 from django.db.models import Sum
9 from django.db.models.signals import post_save
10 from django.dispatch import receiver
11 from django.db import models, DatabaseError
12 from django.middleware.transaction import transaction
13 from karmaworld.apps.courses.models import School
14
15 logger = logging.getLogger(__name__)
16
17
18 class UserProfileManager(models.Manager):
19     """ Handle restoring data. """
20     def get_by_natural_key(self, user):
21         return self.get(user=user)
22
23
24 class UserProfile(models.Model):
25     user      = models.OneToOneField(User)
26
27     school    = models.ForeignKey(School, blank=True, null=True)
28
29     def natural_key(self):
30         return (self.user,)
31
32     def get_points(self):
33         sum = 0
34         for cls in ALL_KARMA_EVENT_CLASSES:
35             points = cls.objects.filter(user=self.user).aggregate(Sum('points'))['points__sum']
36             if points:
37                 sum += points
38
39         return sum
40
41     NO_BADGE = 0
42     PROSPECT = 1
43     BEGINNER = 2
44     TRAINEE = 3
45     APPRENTICE = 4
46     SCHOLAR = 5
47
48     BADGES = (
49         PROSPECT,
50         BEGINNER,
51         TRAINEE,
52         APPRENTICE,
53         SCHOLAR
54     )
55
56     BADGE_NAMES = {
57         PROSPECT: 'Prospect',
58         BEGINNER: 'Beginner',
59         TRAINEE: 'Trainee',
60         APPRENTICE: 'Apprentice',
61         SCHOLAR: 'Scholar'
62     }
63
64     BADGE_THRESHOLDS = {
65         PROSPECT: 10,
66         BEGINNER: 100,
67         TRAINEE: 200,
68         APPRENTICE: 500,
69         SCHOLAR: 1000
70     }
71
72     def get_badge(self):
73         points = self.get_points()
74         highest_badge = self.NO_BADGE
75         for badge in self.BADGES:
76             if points >= self.BADGE_THRESHOLDS[badge]:
77                 highest_badge = badge
78
79         if highest_badge is not self.NO_BADGE:
80             return self.BADGE_NAMES[highest_badge]
81         else:
82             return None
83
84     def __unicode__(self):
85         return self.user.__unicode__()
86
87
88 @receiver(email_confirmed, weak=True)
89 def give_email_confirm_karma(sender, **kwargs):
90     GenericKarmaEvent.create_event(kwargs['email_address'].user, kwargs['email_address'].email, GenericKarmaEvent.EMAIL_CONFIRMED)
91
92
93 class BaseKarmaEventManager(models.Manager):
94     """ Handle restoring data. """
95     def get_by_natural_key(self, points, user, timestamp):
96         return self.get(user=user, timestamp=timestamp)
97
98
99 class BaseKarmaEvent(models.Model):
100     points    = models.IntegerField()
101     user      = models.ForeignKey(User)
102     timestamp = models.DateTimeField(default=datetime.datetime.utcnow)
103
104     class Meta:
105         abstract = True
106         unique_together = ('points', 'user', 'timestamp')
107
108     def natural_key(self):
109         return (self.user, self.timestamp)
110
111     def get_message(self):
112         raise NotImplemented()
113
114
115 class GenericKarmaEvent(BaseKarmaEvent):
116     NONE = 'none'
117     NOTE_DELETED       = 'upload'
118     EMAIL_CONFIRMED    = 'thanks'
119
120     EVENT_TYPE_CHOICES = (
121         (NONE,               'This should not happen'),
122         (NOTE_DELETED,       'Your note "{m}" was deleted'),
123         (EMAIL_CONFIRMED,    'You confirmed your email address {m}'),
124     )
125
126     POINTS = {
127         NOTE_DELETED: -5,
128         EMAIL_CONFIRMED: 5,
129     }
130
131     event_type = models.CharField(max_length=15, choices=EVENT_TYPE_CHOICES, default=NONE)
132     message = models.CharField(max_length=255)
133
134     @staticmethod
135     def create_event(user, message, type):
136         event = GenericKarmaEvent.objects.create(user=user,
137                                                  points=GenericKarmaEvent.POINTS[type],
138                                                  event_type=type,
139                                                  message=message)
140         event.save()
141
142     def get_message(self):
143         if self.event_type == self.NONE:
144             return self.message
145         else:
146             return self.get_event_type_display().format(m=self.message)
147
148     def __unicode__(self):
149         return unicode(self.user) + ' -- ' + self.get_message()
150
151
152 class NoteKarmaEvent(BaseKarmaEvent):
153     UPLOAD       = 'upload'
154     THANKS       = 'thanks'
155     NOTE_DELETED = 'deleted'
156     GIVE_FLAG    = 'give_flag'
157     GET_FLAGGED  = 'get_flagged'
158     DOWNLOADED_NOTE = 'downloaded'
159     HAD_NOTE_DOWNLOADED = 'was_downloaded'
160
161     EVENT_TYPE_CHOICES = (
162         (UPLOAD,       "You uploaded a note"),
163         (THANKS,       "You received a thanks for your note"),
164         (NOTE_DELETED, "Your note was deleted"),
165         (GIVE_FLAG,    "You flagged a note"),
166         (GET_FLAGGED,  "Your note was flagged as spam"),
167         (DOWNLOADED_NOTE,  "You downloaded a note"),
168         (HAD_NOTE_DOWNLOADED,  "Your note was downloaded"),
169     )
170     note = models.ForeignKey('notes.Note')
171     event_type = models.CharField(max_length=15, choices=EVENT_TYPE_CHOICES)
172
173     POINTS = {
174         UPLOAD: 5,
175         THANKS: 1,
176         NOTE_DELETED: -5,
177         GIVE_FLAG: -1,
178         GET_FLAGGED: -100,
179         DOWNLOADED_NOTE: -2,
180         HAD_NOTE_DOWNLOADED: 2,
181     }
182
183     def get_message(self):
184         return self.get_event_type_display()
185
186     def __unicode__(self):
187         return unicode(self.user) + ' -- ' + self.get_event_type_display() + ' -- ' + unicode(self.note)
188
189     @staticmethod
190     def create_event(user, note, type):
191         event = NoteKarmaEvent.objects.create(user=user,
192                                       note=note,
193                                       points=NoteKarmaEvent.POINTS[type],
194                                       event_type=type)
195         event.save()
196
197
198 class CourseKarmaEvent(BaseKarmaEvent):
199     GIVE_FLAG    = 'give_flag'
200     EVENT_TYPE_CHOICES = (
201         (GIVE_FLAG,    "You flagged a course"),
202     )
203     course = models.ForeignKey('courses.Course')
204     event_type = models.CharField(max_length=15, choices=EVENT_TYPE_CHOICES)
205
206     POINTS = {
207         GIVE_FLAG: -1,
208     }
209
210     def get_message(self):
211         return self.get_event_type_display()
212
213     def __unicode__(self):
214         return unicode(self.user) + ' -- ' + self.get_event_type_display() + ' -- ' + unicode(self.course)
215
216     @staticmethod
217     def create_event(user, course, type):
218         event = CourseKarmaEvent.objects.create(user=user,
219                                       course=course,
220                                       points=CourseKarmaEvent.POINTS[type],
221                                       event_type=type)
222         event.save()
223
224
225 ALL_KARMA_EVENT_CLASSES = (GenericKarmaEvent, NoteKarmaEvent, CourseKarmaEvent)
226
227
228 def user_display_name(user):
229     """Return the best way to display a user's
230     name to them on the site."""
231     if hasattr(user, 'first_name') and user.first_name and \
232             hasattr(user, 'last_name') and user.last_name:
233         return user.first_name + ' ' + user.last_name
234     elif hasattr(user, 'email') and user.email:
235         return user.email
236     else:
237         return user.username
238
239
240 @receiver(post_save, sender=User, weak=True)
241 def create_user_profile(sender, instance, created, **kwargs):
242     if created:
243         with transaction.commit_on_success():
244             try:
245                 UserProfile.objects.create(user=instance)
246             except DatabaseError:
247                 logger.warn("Could not create UserProfile for user {u}. This is okay if running syncdb.".format(u=instance))
248