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