]> git.openstreetmap.org Git - osqa.git/blob - forum/auth.py
Initial commit
[osqa.git] / forum / auth.py
1 """
2 Authorisation related functions.
3
4 The actions a User is authorised to perform are dependent on their reputation
5 and superuser status.
6 """
7 import datetime
8 from django.contrib.contenttypes.models import ContentType
9 from django.utils.translation import ugettext as _
10 from django.db import transaction
11 from models import Repute
12 from models import Question
13 from models import Answer
14 from const import TYPE_REPUTATION
15 import logging
16 question_type = ContentType.objects.get_for_model(Question)
17 answer_type = ContentType.objects.get_for_model(Answer)
18
19 VOTE_UP                   = 15
20 FLAG_OFFENSIVE            = 15
21 POST_IMAGES               = 15
22 LEAVE_COMMENTS            = 50
23 UPLOAD_FILES              = 60
24 VOTE_DOWN                 = 100
25 CLOSE_OWN_QUESTIONS       = 250
26 RETAG_OTHER_QUESTIONS     = 500
27 REOPEN_OWN_QUESTIONS      = 500
28 EDIT_COMMUNITY_WIKI_POSTS = 750
29 EDIT_OTHER_POSTS          = 2000
30 DELETE_COMMENTS           = 2000
31 VIEW_OFFENSIVE_FLAGS      = 2000
32 DISABLE_URL_NOFOLLOW      = 2000
33 CLOSE_OTHER_QUESTIONS     = 3000
34 LOCK_POSTS                = 4000
35
36 VOTE_RULES = {
37     'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
38     'scope_flags_per_user_per_day' : 5,  # how many times user can flag posts everyday
39     'scope_warn_votes_left' : 10,        # start when to warn user how many votes left
40     'scope_deny_unvote_days' : 1,        # if 1 days passed, user can't cancel votes.
41     'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
42     'scope_flags_delete_post' : 5,         # post will be deleted if it has more than 5 offensive flags
43 }
44
45 REPUTATION_RULES = {
46     'initial_score'                       : 1,
47     'scope_per_day_by_upvotes'            : 200,
48     'gain_by_upvoted'                     : 10,
49     'gain_by_answer_accepted'             : 15,
50     'gain_by_accepting_answer'            : 2,
51     'gain_by_downvote_canceled'           : 2,
52     'gain_by_canceling_downvote'          : 1,
53     'lose_by_canceling_accepted_answer'   : -2,
54     'lose_by_accepted_answer_cancled'     : -15,
55     'lose_by_downvoted'                   : -2,
56     'lose_by_flagged'                     : -2,
57     'lose_by_downvoting'                  : -1,
58     'lose_by_flagged_lastrevision_3_times': -30,
59     'lose_by_flagged_lastrevision_5_times': -100,
60     'lose_by_upvote_canceled'             : -10,
61 }
62
63 def can_moderate_users(user):
64     return user.is_superuser
65
66 def can_vote_up(user):
67     """Determines if a User can vote Questions and Answers up."""
68     return user.is_authenticated() and (
69         user.reputation >= VOTE_UP or
70         user.is_superuser)
71
72 def can_flag_offensive(user):
73     """Determines if a User can flag Questions and Answers as offensive."""
74     return user.is_authenticated() and (
75         user.reputation >= FLAG_OFFENSIVE or
76         user.is_superuser)
77
78 def can_add_comments(user,subject):
79     """Determines if a User can add comments to Questions and Answers."""
80     if user.is_authenticated():
81         if user.id == subject.author.id:
82             return True
83         if user.reputation >= LEAVE_COMMENTS:
84             return True
85         if user.is_superuser:
86             return True
87         if isinstance(subject,Answer) and subject.question.author.id == user.id:
88             return True
89     return False
90
91 def can_vote_down(user):
92     """Determines if a User can vote Questions and Answers down."""
93     return user.is_authenticated() and (
94         user.reputation >= VOTE_DOWN or
95         user.is_superuser)
96
97 def can_retag_questions(user):
98     """Determines if a User can retag Questions."""
99     return user.is_authenticated() and (
100         RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
101         user.is_superuser)
102
103 def can_edit_post(user, post):
104     """Determines if a User can edit the given Question or Answer."""
105     return user.is_authenticated() and (
106         user.id == post.author_id or
107         (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
108         user.reputation >= EDIT_OTHER_POSTS or
109         user.is_superuser)
110
111 def can_delete_comment(user, comment):
112     """Determines if a User can delete the given Comment."""
113     return user.is_authenticated() and (
114         user.id == comment.user_id or
115         user.reputation >= DELETE_COMMENTS or
116         user.is_superuser)
117
118 def can_view_offensive_flags(user):
119     """Determines if a User can view offensive flag counts."""
120     return user.is_authenticated() and (
121         user.reputation >= VIEW_OFFENSIVE_FLAGS or
122         user.is_superuser)
123
124 def can_close_question(user, question):
125     """Determines if a User can close the given Question."""
126     return user.is_authenticated() and (
127         (user.id == question.author_id and
128          user.reputation >= CLOSE_OWN_QUESTIONS) or
129         user.reputation >= CLOSE_OTHER_QUESTIONS or
130         user.is_superuser)
131
132 def can_lock_posts(user):
133     """Determines if a User can lock Questions or Answers."""
134     return user.is_authenticated() and (
135         user.reputation >= LOCK_POSTS or
136         user.is_superuser)
137
138 def can_follow_url(user):
139     """Determines if the URL link can be followed by Google search engine."""
140     return user.reputation >= DISABLE_URL_NOFOLLOW
141
142 def can_accept_answer(user, question, answer):
143     return (user.is_authenticated() and
144         question.author != answer.author and
145         question.author == user) or user.is_superuser
146
147 # now only support to reopen own question except superuser
148 def can_reopen_question(user, question):
149     return (user.is_authenticated() and
150         user.id == question.author_id and
151         user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser
152
153 def can_delete_post(user, post):
154     if user.is_superuser:
155         return True
156     elif user.is_authenticated() and user == post.author:
157         if isinstance(post,Answer):
158             return True
159         elif isinstance(post,Question):
160             answers = post.answers.all()
161             for answer in answers:
162                 if user != answer.author and answer.deleted == False:
163                     return False
164             return True
165         else:
166             return False
167     else:
168         return False
169
170 def can_view_deleted_post(user, post):
171     return user.is_superuser
172
173 # user preferences view permissions
174 def is_user_self(request_user, target_user):
175     return (request_user.is_authenticated() and request_user == target_user)
176     
177 def can_view_user_votes(request_user, target_user):
178     return (request_user.is_authenticated() and request_user == target_user)
179
180 def can_view_user_preferences(request_user, target_user):
181     return (request_user.is_authenticated() and request_user == target_user)
182
183 def can_view_user_edit(request_user, target_user):
184     return (request_user.is_authenticated() and request_user == target_user)
185
186 def can_upload_files(request_user):
187     return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
188            request_user.is_superuser
189
190 ###########################################
191 ## actions and reputation changes event
192 ###########################################
193 def calculate_reputation(origin, offset):
194     result = int(origin) + int(offset)
195     if (result > 0):
196         return result
197     else:
198         return 1
199
200 @transaction.commit_on_success
201 def onFlaggedItem(item, post, user):
202
203     item.save()
204     post.offensive_flag_count = post.offensive_flag_count + 1
205     post.save()
206
207     post.author.reputation = calculate_reputation(post.author.reputation,
208                            int(REPUTATION_RULES['lose_by_flagged']))
209     post.author.save()
210
211     question = post
212     if ContentType.objects.get_for_model(post) == answer_type:
213         question = post.question
214
215     reputation = Repute(user=post.author,
216                negative=int(REPUTATION_RULES['lose_by_flagged']),
217                question=question, reputed_at=datetime.datetime.now(),
218                reputation_type=-4,
219                reputation=post.author.reputation)
220     reputation.save()
221
222     #todo: These should be updated to work on same revisions.
223     if post.offensive_flag_count ==  VOTE_RULES['scope_flags_invisible_main_page'] :
224         post.author.reputation = calculate_reputation(post.author.reputation,
225                                int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
226         post.author.save()
227
228         reputation = Repute(user=post.author,
229                    negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
230                    question=question,
231                    reputed_at=datetime.datetime.now(),
232                    reputation_type=-6,
233                    reputation=post.author.reputation)
234         reputation.save()
235
236     elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
237         post.author.reputation = calculate_reputation(post.author.reputation,
238                                int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
239         post.author.save()
240
241         reputation = Repute(user=post.author,
242                    negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
243                    question=question,
244                    reputed_at=datetime.datetime.now(),
245                    reputation_type=-7,
246                    reputation=post.author.reputation)
247         reputation.save()
248
249         post.deleted = True
250         #post.deleted_at = datetime.datetime.now()
251         #post.deleted_by = Admin
252         post.save()
253
254
255 @transaction.commit_on_success
256 def onAnswerAccept(answer, user):
257     answer.accepted = True
258     answer.accepted_at = datetime.datetime.now()
259     answer.question.answer_accepted = True
260     answer.save()
261     answer.question.save()
262
263     answer.author.reputation = calculate_reputation(answer.author.reputation,
264                              int(REPUTATION_RULES['gain_by_answer_accepted']))
265     answer.author.save()
266     reputation = Repute(user=answer.author,
267                positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
268                question=answer.question,
269                reputed_at=datetime.datetime.now(),
270                reputation_type=2,
271                reputation=answer.author.reputation)
272     reputation.save()
273
274     user.reputation = calculate_reputation(user.reputation,
275                     int(REPUTATION_RULES['gain_by_accepting_answer']))
276     user.save()
277     reputation = Repute(user=user,
278                positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
279                question=answer.question,
280                reputed_at=datetime.datetime.now(),
281                reputation_type=3,
282                reputation=user.reputation)
283     reputation.save()
284
285 @transaction.commit_on_success
286 def onAnswerAcceptCanceled(answer, user):
287     answer.accepted = False
288     answer.accepted_at = None
289     answer.question.answer_accepted = False
290     answer.save()
291     answer.question.save()
292
293     answer.author.reputation = calculate_reputation(answer.author.reputation,
294                              int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
295     answer.author.save()
296     reputation = Repute(user=answer.author,
297                negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
298                question=answer.question,
299                reputed_at=datetime.datetime.now(),
300                reputation_type=-2,
301                reputation=answer.author.reputation)
302     reputation.save()
303
304     user.reputation = calculate_reputation(user.reputation,
305                     int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
306     user.save()
307     reputation = Repute(user=user,
308                negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
309                question=answer.question,
310                reputed_at=datetime.datetime.now(),
311                reputation_type=-1,
312                reputation=user.reputation)
313     reputation.save()
314
315 @transaction.commit_on_success
316 def onUpVoted(vote, post, user):
317     vote.save()
318
319     post.vote_up_count = int(post.vote_up_count) + 1
320     post.score = int(post.score) + 1
321     post.save()
322
323     if not post.wiki:
324         author = post.author
325         if Repute.objects.get_reputation_by_upvoted_today(author) <  int(REPUTATION_RULES['scope_per_day_by_upvotes']):
326             author.reputation = calculate_reputation(author.reputation,
327                               int(REPUTATION_RULES['gain_by_upvoted']))
328             author.save()
329
330             question = post
331             if ContentType.objects.get_for_model(post) == answer_type:
332                 question = post.question
333
334             reputation = Repute(user=author,
335                        positive=int(REPUTATION_RULES['gain_by_upvoted']),
336                        question=question,
337                        reputed_at=datetime.datetime.now(),
338                        reputation_type=1,
339                        reputation=author.reputation)
340             reputation.save()
341
342 @transaction.commit_on_success
343 def onUpVotedCanceled(vote, post, user):
344     vote.delete()
345
346     post.vote_up_count = int(post.vote_up_count) - 1
347     if post.vote_up_count < 0:
348         post.vote_up_count  = 0
349     post.score = int(post.score) - 1
350     post.save()
351
352     if not post.wiki:
353         author = post.author
354         author.reputation = calculate_reputation(author.reputation,
355                           int(REPUTATION_RULES['lose_by_upvote_canceled']))
356         author.save()
357
358         question = post
359         if ContentType.objects.get_for_model(post) == answer_type:
360             question = post.question
361
362         reputation = Repute(user=author,
363                    negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
364                    question=question,
365                    reputed_at=datetime.datetime.now(),
366                    reputation_type=-8,
367                    reputation=author.reputation)
368         reputation.save()
369
370 @transaction.commit_on_success
371 def onDownVoted(vote, post, user):
372     vote.save()
373
374     post.vote_down_count = int(post.vote_down_count) + 1
375     post.score = int(post.score) - 1
376     post.save()
377
378     if not post.wiki:
379         author = post.author
380         author.reputation = calculate_reputation(author.reputation,
381                           int(REPUTATION_RULES['lose_by_downvoted']))
382         author.save()
383
384         question = post
385         if ContentType.objects.get_for_model(post) == answer_type:
386             question = post.question
387
388         reputation = Repute(user=author,
389                    negative=int(REPUTATION_RULES['lose_by_downvoted']),
390                    question=question,
391                    reputed_at=datetime.datetime.now(),
392                    reputation_type=-3,
393                    reputation=author.reputation)
394         reputation.save()
395
396         user.reputation = calculate_reputation(user.reputation,
397                         int(REPUTATION_RULES['lose_by_downvoting']))
398         user.save()
399
400         reputation = Repute(user=user,
401                    negative=int(REPUTATION_RULES['lose_by_downvoting']),
402                    question=question,
403                    reputed_at=datetime.datetime.now(),
404                    reputation_type=-5,
405                    reputation=user.reputation)
406         reputation.save()
407
408 @transaction.commit_on_success
409 def onDownVotedCanceled(vote, post, user):
410     vote.delete()
411
412     post.vote_down_count = int(post.vote_down_count) - 1
413     if post.vote_down_count < 0:
414         post.vote_down_count  = 0
415     post.score = post.score + 1
416     post.save()
417
418     if not post.wiki:
419         author = post.author
420         author.reputation = calculate_reputation(author.reputation,
421                           int(REPUTATION_RULES['gain_by_downvote_canceled']))
422         author.save()
423
424         question = post
425         if ContentType.objects.get_for_model(post) == answer_type:
426             question = post.question
427
428         reputation = Repute(user=author,
429                    positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
430                    question=question,
431                    reputed_at=datetime.datetime.now(),
432                    reputation_type=4,
433                    reputation=author.reputation)
434         reputation.save()
435
436         user.reputation = calculate_reputation(user.reputation,
437                         int(REPUTATION_RULES['gain_by_canceling_downvote']))
438         user.save()
439
440         reputation = Repute(user=user,
441                    positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
442                    question=question,
443                    reputed_at=datetime.datetime.now(),
444                    reputation_type=5,
445                    reputation=user.reputation)
446         reputation.save()
447
448 def onDeleteCanceled(post, user):
449     post.deleted = False
450     post.deleted_by = None 
451     post.deleted_at = None 
452     post.save()
453     logging.debug('now restoring something')
454     if isinstance(post,Answer):
455         logging.debug('updated answer count on undelete, have %d' % post.question.answer_count)
456         Question.objects.update_answer_count(post.question)
457     elif isinstance(post,Question):
458         for tag in list(post.tags.all()):
459             if tag.used_count == 1 and tag.deleted:
460                 tag.deleted = False
461                 tag.deleted_by = None
462                 tag.deleted_at = None 
463                 tag.save()
464
465 def onDeleted(post, user):
466     post.deleted = True
467     post.deleted_by = user
468     post.deleted_at = datetime.datetime.now()
469     post.save()
470
471     if isinstance(post, Question):
472         for tag in list(post.tags.all()):
473             if tag.used_count == 1:
474                 tag.deleted = True
475                 tag.deleted_by = user
476                 tag.deleted_at = datetime.datetime.now()
477             else:
478                 tag.used_count = tag.used_count - 1 
479             tag.save()
480
481         answers = post.answers.all()
482         if user == post.author:
483             if len(answers) > 0:
484                 msg = _('Your question and all of it\'s answers have been deleted')
485             else:
486                 msg = _('Your question has been deleted')
487         else:
488             if len(answers) > 0:
489                 msg = _('The question and all of it\'s answers have been deleted')
490             else:
491                 msg = _('The question has been deleted')
492         user.message_set.create(message=msg)
493         logging.debug('posted a message %s' % msg)
494         for answer in answers:
495             onDeleted(answer, user)
496     elif isinstance(post, Answer):
497         Question.objects.update_answer_count(post.question)
498         logging.debug('updated answer count to %d' % post.question.answer_count)