]> git.openstreetmap.org Git - osqa.git/blob - forum/models/user.py
f027ffa350296f90f78f0644e0b0ebcacb53f26c
[osqa.git] / forum / models / user.py
1 from base import *
2 from django.contrib.contenttypes.models import ContentType
3 from django.contrib.auth.models import User as DjangoUser, AnonymousUser as DjangoAnonymousUser
4 from django.db.models import Q
5 try:
6     from hashlib import md5
7 except:
8     from md5 import new as md5
9
10 import string
11 from random import Random
12
13 from django.utils.translation import ugettext as _
14 import django.dispatch
15
16
17 QUESTIONS_PER_PAGE_CHOICES = (
18    (10, u'10'),
19    (30, u'30'),
20    (50, u'50'),
21 )
22
23 class UserManager(CachedManager):
24     def get_site_owner(self):
25         return self.all().order_by('date_joined')[0]
26
27 class AnonymousUser(DjangoAnonymousUser):
28     def get_visible_answers(self, question):
29         return question.answers.filter_state(deleted=False)
30
31     def can_view_deleted_post(self, post):
32         return False
33
34     def can_vote_up(self):
35         return False
36
37     def can_vote_down(self):
38         return False
39
40     def can_flag_offensive(self, post=None):
41         return False
42
43     def can_view_offensive_flags(self, post=None):
44         return False
45
46     def can_comment(self, post):
47         return False
48
49     def can_like_comment(self, comment):
50         return False
51
52     def can_edit_comment(self, comment):
53         return False
54
55     def can_delete_comment(self, comment):
56         return False
57
58     def can_convert_to_comment(self, answer):
59         return False
60
61     def can_accept_answer(self, answer):
62         return False
63
64     def can_create_tags(self):
65         return False
66
67     def can_edit_post(self, post):
68         return False
69
70     def can_wikify(self, post):
71         return False
72
73     def can_cancel_wiki(self, post):
74         return False
75
76     def can_retag_questions(self):
77         return False
78
79     def can_close_question(self, question):
80         return False
81
82     def can_reopen_question(self, question):
83         return False
84
85     def can_delete_post(self, post):
86         return False
87
88     def can_upload_files(self):
89         return False
90
91 def true_if_is_super_or_staff(fn):
92     def decorated(self, *args, **kwargs):
93         return self.is_superuser or self.is_staff or fn(self, *args, **kwargs)
94     return decorated
95
96 class User(BaseModel, DjangoUser):
97     is_approved = models.BooleanField(default=False)
98     email_isvalid = models.BooleanField(default=False)
99
100     reputation = models.PositiveIntegerField(default=0)
101     gold = models.PositiveIntegerField(default=0)
102     silver = models.PositiveIntegerField(default=0)
103     bronze = models.PositiveIntegerField(default=0)
104     
105     last_seen = models.DateTimeField(default=datetime.datetime.now)
106     real_name = models.CharField(max_length=100, blank=True)
107     website = models.URLField(max_length=200, blank=True)
108     location = models.CharField(max_length=100, blank=True)
109     date_of_birth = models.DateField(null=True, blank=True)
110     about = models.TextField(blank=True)
111
112     subscriptions = models.ManyToManyField('Node', related_name='subscribers', through='QuestionSubscription')
113
114     vote_up_count = DenormalizedField("actions", canceled=False, action_type="voteup")
115     vote_down_count = DenormalizedField("actions", canceled=False, action_type="votedown")
116    
117     objects = UserManager()
118
119     def __unicode__(self):
120         return self.username
121
122     @property
123     def gravatar(self):
124         return md5(self.email).hexdigest()
125
126     def save(self, *args, **kwargs):
127         if self.reputation < 0:
128             self.reputation = 0
129
130         new = not bool(self.id)
131
132         super(User, self).save(*args, **kwargs)
133
134         if new:
135             sub_settings = SubscriptionSettings(user=self)
136             sub_settings.save()
137
138     def get_absolute_url(self):
139         return self.get_profile_url()
140
141     def get_messages(self):
142         messages = []
143         for m in self.message_set.all():
144             messages.append(m.message)
145         return messages
146
147     def delete_messages(self):
148         self.message_set.all().delete()
149
150     @models.permalink
151     def get_profile_url(self):
152         return ('user_profile', (), {'id': self.id, 'slug': slugify(self.username)})
153
154     def get_absolute_url(self):
155         return self.get_profile_url()
156
157     def get_profile_link(self):
158         profile_link = u'<a href="%s">%s</a>' % (self.get_profile_url(),self.username)
159         return mark_safe(profile_link)
160
161     def get_visible_answers(self, question):
162         return question.answers.filter_state(deleted=False)
163
164     def get_vote_count_today(self):
165         today = datetime.date.today()
166         return self.actions.filter(canceled=False, action_type__in=("voteup", "votedown"),
167                 action_date__gte=(today - datetime.timedelta(days=1))).count()
168
169     def get_reputation_by_upvoted_today(self):
170         today = datetime.datetime.now()
171         sum = self.reputes.filter(reputed_at__range=(today - datetime.timedelta(days=1), today)).aggregate(models.Sum('value'))
172         #todo: redo this, maybe transform in the daily cap
173         #if sum.get('value__sum', None) is not None: return sum['value__sum']
174         return 0
175
176     def get_flagged_items_count_today(self):
177         today = datetime.date.today()
178         return self.actions.filter(canceled=False, action_type="flag",
179                 action_date__gte=(today - datetime.timedelta(days=1))).count()
180
181     @true_if_is_super_or_staff
182     def can_view_deleted_post(self, post):
183         return post.author == self
184
185     @true_if_is_super_or_staff
186     def can_vote_up(self):
187         return self.reputation >= int(settings.REP_TO_VOTE_UP)
188
189     @true_if_is_super_or_staff
190     def can_vote_down(self):
191         return self.reputation >= int(settings.REP_TO_VOTE_DOWN)
192
193     def can_flag_offensive(self, post=None):
194         if post is not None and post.author == self:
195             return False
196         return self.is_superuser or self.is_staff or self.reputation >= int(settings.REP_TO_FLAG)
197
198     @true_if_is_super_or_staff
199     def can_view_offensive_flags(self, post=None):
200         if post is not None and post.author == self:
201             return True
202         return self.reputation >= int(settings.REP_TO_VIEW_FLAGS)
203
204     @true_if_is_super_or_staff
205     def can_comment(self, post):
206         return self == post.author or self.reputation >= int(settings.REP_TO_COMMENT
207         ) or (post.__class__.__name__ == "Answer" and self == post.question.author)
208
209     @true_if_is_super_or_staff
210     def can_like_comment(self, comment):
211         return self != comment.author and (self.reputation >= int(settings.REP_TO_LIKE_COMMENT))
212
213     @true_if_is_super_or_staff
214     def can_edit_comment(self, comment):
215         return (comment.author == self and comment.added_at >= datetime.datetime.now() - datetime.timedelta(minutes=60)
216         ) or self.is_superuser
217
218     @true_if_is_super_or_staff
219     def can_delete_comment(self, comment):
220         return self == comment.author or self.reputation >= int(settings.REP_TO_DELETE_COMMENTS)
221
222     def can_convert_to_comment(self, answer):
223         return (not answer.marked) and (self.is_superuser or self.is_staff or answer.author == self or self.reputation >= int(settings.REP_TO_CONVERT_TO_COMMENT))
224
225     @true_if_is_super_or_staff
226     def can_accept_answer(self, answer):
227         return self == answer.question.author
228
229     @true_if_is_super_or_staff
230     def can_create_tags(self):
231         return self.reputation >= int(settings.REP_TO_CREATE_TAGS)
232
233     @true_if_is_super_or_staff
234     def can_edit_post(self, post):
235         return self == post.author or self.reputation >= int(settings.REP_TO_EDIT_OTHERS
236         ) or (post.nis.wiki and self.reputation >= int(settings.REP_TO_EDIT_WIKI))
237
238     @true_if_is_super_or_staff
239     def can_wikify(self, post):
240         return self == post.author or self.reputation >= int(settings.REP_TO_WIKIFY)
241
242     @true_if_is_super_or_staff
243     def can_cancel_wiki(self, post):
244         return self == post.author
245
246     @true_if_is_super_or_staff
247     def can_retag_questions(self):
248         return self.reputation >= int(settings.REP_TO_RETAG)
249
250     @true_if_is_super_or_staff
251     def can_close_question(self, question):
252         return (self == question.author and self.reputation >= int(settings.REP_TO_CLOSE_OWN)
253         ) or self.reputation >= int(settings.REP_TO_CLOSE_OTHERS)
254
255     @true_if_is_super_or_staff
256     def can_reopen_question(self, question):
257         return self == question.author and self.reputation >= settings.REP_TO_REOPEN_OWN
258
259     @true_if_is_super_or_staff
260     def can_delete_post(self, post):
261         if post.node_type == "comment":
262             return self.can_delete_comment(post)
263             
264         return (self == post.author and (post.__class__.__name__ == "Answer" or
265             not post.answers.exclude(author=self).count()))
266
267     @true_if_is_super_or_staff
268     def can_upload_files(self):
269         return self.reputation >= int(settings.REP_TO_UPLOAD)
270
271     def check_password(self, old_passwd):
272         self.__dict__.update(self.__class__.objects.filter(id=self.id).values('password')[0])
273         return DjangoUser.check_password(self, old_passwd)
274
275
276     class Meta:
277         app_label = 'forum'
278
279 class SubscriptionSettings(models.Model):
280     user = models.OneToOneField(User, related_name='subscription_settings')
281
282     enable_notifications = models.BooleanField(default=True)
283
284     #notify if
285     member_joins = models.CharField(max_length=1, default='n')
286     new_question = models.CharField(max_length=1, default='d')
287     new_question_watched_tags = models.CharField(max_length=1, default='i')
288     subscribed_questions = models.CharField(max_length=1, default='i')
289     
290     #auto_subscribe_to
291     all_questions = models.BooleanField(default=False)
292     all_questions_watched_tags = models.BooleanField(default=False)
293     questions_asked = models.BooleanField(default=True)
294     questions_answered = models.BooleanField(default=True)
295     questions_commented = models.BooleanField(default=False)
296     questions_viewed = models.BooleanField(default=False)
297
298     #notify activity on subscribed
299     notify_answers = models.BooleanField(default=True)
300     notify_reply_to_comments = models.BooleanField(default=True)
301     notify_comments_own_post = models.BooleanField(default=True)
302     notify_comments = models.BooleanField(default=False)
303     notify_accepted = models.BooleanField(default=False)
304
305     class Meta:
306         app_label = 'forum'
307
308 from forum.utils.time import one_day_from_now
309
310 class ValidationHashManager(models.Manager):
311     def _generate_md5_hash(self, user, type, hash_data, seed):
312         return md5("%s%s%s%s" % (seed, "".join(map(str, hash_data)), user.id, type)).hexdigest()
313
314     def create_new(self, user, type, hash_data=[], expiration=None):
315         seed = ''.join(Random().sample(string.letters+string.digits, 12))
316         hash = self._generate_md5_hash(user, type, hash_data, seed)
317
318         obj = ValidationHash(hash_code=hash, seed=seed, user=user, type=type)
319
320         if expiration is not None:
321             obj.expiration = expiration
322
323         try:
324             obj.save()
325         except:
326             return None
327             
328         return obj
329
330     def validate(self, hash, user, type, hash_data=[]):
331         try:
332             obj = self.get(hash_code=hash)
333         except:
334             return False
335
336         if obj.type != type:
337             return False
338
339         if obj.user != user:
340             return False
341
342         valid = (obj.hash_code == self._generate_md5_hash(obj.user, type, hash_data, obj.seed))
343
344         if valid:
345             if obj.expiration < datetime.datetime.now():
346                 obj.delete()
347                 return False
348             else:
349                 obj.delete()
350                 return True
351
352         return False
353
354 class ValidationHash(models.Model):
355     hash_code = models.CharField(max_length=255,unique=True)
356     seed = models.CharField(max_length=12)
357     expiration = models.DateTimeField(default=one_day_from_now)
358     type = models.CharField(max_length=12)
359     user = models.ForeignKey(User)
360
361     objects = ValidationHashManager()
362
363     class Meta:
364         unique_together = ('user', 'type')
365         app_label = 'forum'
366
367     def __str__(self):
368         return self.hash_code
369
370 class AuthKeyUserAssociation(models.Model):
371     key = models.CharField(max_length=255,null=False,unique=True)
372     provider = models.CharField(max_length=64)
373     user = models.ForeignKey(User, related_name="auth_keys")
374     added_at = models.DateTimeField(default=datetime.datetime.now)
375
376     class Meta:
377         app_label = 'forum'