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