diff options
Diffstat (limited to 'forum')
-rw-r--r-- | forum/auth.py | 886 | ||||
-rw-r--r-- | forum/feed.py | 86 | ||||
-rw-r--r-- | forum/forms.py | 386 | ||||
-rw-r--r-- | forum/management/commands/once_award_badges.py | 696 | ||||
-rw-r--r-- | forum/managers.py | 518 | ||||
-rw-r--r-- | forum/models.py | 1308 | ||||
-rw-r--r-- | forum/templatetags/extra_filters.py | 164 | ||||
-rw-r--r-- | forum/templatetags/extra_tags.py | 478 | ||||
-rw-r--r-- | forum/user.py | 150 | ||||
-rw-r--r-- | forum/views.py | 2 |
10 files changed, 2337 insertions, 2337 deletions
diff --git a/forum/auth.py b/forum/auth.py index 0608031a..4688a69a 100644 --- a/forum/auth.py +++ b/forum/auth.py @@ -1,443 +1,443 @@ -"""
-Authorisation related functions.
-
-The actions a User is authorised to perform are dependent on their reputation
-and superuser status.
-"""
-import datetime
-from django.contrib.contenttypes.models import ContentType
-from django.db import transaction
-from models import Repute
-from models import Question
-from models import Answer
-from const import TYPE_REPUTATION
-question_type = ContentType.objects.get_for_model(Question)
-answer_type = ContentType.objects.get_for_model(Answer)
-
-VOTE_UP = 15
-FLAG_OFFENSIVE = 15
-POST_IMAGES = 15
-LEAVE_COMMENTS = 50
-UPLOAD_FILES = 60
-VOTE_DOWN = 100
-CLOSE_OWN_QUESTIONS = 250
-RETAG_OTHER_QUESTIONS = 500
-REOPEN_OWN_QUESTIONS = 500
-EDIT_COMMUNITY_WIKI_POSTS = 750
-EDIT_OTHER_POSTS = 2000
-DELETE_COMMENTS = 2000
-VIEW_OFFENSIVE_FLAGS = 2000
-DISABLE_URL_NOFOLLOW = 2000
-CLOSE_OTHER_QUESTIONS = 3000
-LOCK_POSTS = 4000
-
-VOTE_RULES = {
- 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
- 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday
- 'scope_warn_votes_left' : 10, # start when to warn user how many votes left
- 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes.
- 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
- 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags
-}
-
-REPUTATION_RULES = {
- 'initial_score' : 1,
- 'scope_per_day_by_upvotes' : 200,
- 'gain_by_upvoted' : 10,
- 'gain_by_answer_accepted' : 15,
- 'gain_by_accepting_answer' : 2,
- 'gain_by_downvote_canceled' : 2,
- 'gain_by_canceling_downvote' : 1,
- 'lose_by_canceling_accepted_answer' : -2,
- 'lose_by_accepted_answer_cancled' : -15,
- 'lose_by_downvoted' : -2,
- 'lose_by_flagged' : -2,
- 'lose_by_downvoting' : -1,
- 'lose_by_flagged_lastrevision_3_times': -30,
- 'lose_by_flagged_lastrevision_5_times': -100,
- 'lose_by_upvote_canceled' : -10,
-}
-
-def can_vote_up(user):
- """Determines if a User can vote Questions and Answers up."""
- return user.is_authenticated() and (
- user.reputation >= VOTE_UP or
- user.is_superuser)
-
-def can_flag_offensive(user):
- """Determines if a User can flag Questions and Answers as offensive."""
- return user.is_authenticated() and (
- user.reputation >= FLAG_OFFENSIVE or
- user.is_superuser)
-
-def can_add_comments(user):
- """Determines if a User can add comments to Questions and Answers."""
- return user.is_authenticated() and (
- user.reputation >= LEAVE_COMMENTS or
- user.is_superuser)
-
-def can_vote_down(user):
- """Determines if a User can vote Questions and Answers down."""
- return user.is_authenticated() and (
- user.reputation >= VOTE_DOWN or
- user.is_superuser)
-
-def can_retag_questions(user):
- """Determines if a User can retag Questions."""
- return user.is_authenticated() and (
- RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
- user.is_superuser)
-
-def can_edit_post(user, post):
- """Determines if a User can edit the given Question or Answer."""
- return user.is_authenticated() and (
- user.id == post.author_id or
- (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
- user.reputation >= EDIT_OTHER_POSTS or
- user.is_superuser)
-
-def can_delete_comment(user, comment):
- """Determines if a User can delete the given Comment."""
- return user.is_authenticated() and (
- user.id == comment.user_id or
- user.reputation >= DELETE_COMMENTS or
- user.is_superuser)
-
-def can_view_offensive_flags(user):
- """Determines if a User can view offensive flag counts."""
- return user.is_authenticated() and (
- user.reputation >= VIEW_OFFENSIVE_FLAGS or
- user.is_superuser)
-
-def can_close_question(user, question):
- """Determines if a User can close the given Question."""
- return user.is_authenticated() and (
- (user.id == question.author_id and
- user.reputation >= CLOSE_OWN_QUESTIONS) or
- user.reputation >= CLOSE_OTHER_QUESTIONS or
- user.is_superuser)
-
-def can_lock_posts(user):
- """Determines if a User can lock Questions or Answers."""
- return user.is_authenticated() and (
- user.reputation >= LOCK_POSTS or
- user.is_superuser)
-
-def can_follow_url(user):
- """Determines if the URL link can be followed by Google search engine."""
- return user.reputation >= DISABLE_URL_NOFOLLOW
-
-def can_accept_answer(user, question, answer):
- return (user.is_authenticated() and
- question.author != answer.author and
- question.author == user) or user.is_superuser
-
-# now only support to reopen own question except superuser
-def can_reopen_question(user, question):
- return (user.is_authenticated() and
- user.id == question.author_id and
- user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser
-
-def can_delete_post(user, post):
- return (user.is_authenticated() and
- user.id == post.author_id) or user.is_superuser
-
-def can_view_deleted_post(user, post):
- return user.is_superuser
-
-# user preferences view permissions
-def is_user_self(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_votes(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_preferences(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_edit(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_upload_files(request_user):
- return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
- request_user.is_superuser
-
-###########################################
-## actions and reputation changes event
-###########################################
-def calculate_reputation(origin, offset):
- result = int(origin) + int(offset)
- return result if result > 0 else 1
-
-@transaction.commit_on_success
-def onFlaggedItem(item, post, user):
-
- item.save()
- post.offensive_flag_count = post.offensive_flag_count + 1
- post.save()
-
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged']))
- post.author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged']),
- question=question, reputed_at=datetime.datetime.now(),
- reputation_type=-4,
- reputation=post.author.reputation)
- reputation.save()
-
- #todo: These should be updated to work on same revisions.
- if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] :
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
- post.author.save()
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-6,
- reputation=post.author.reputation)
- reputation.save()
-
- elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
- post.author.save()
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-7,
- reputation=post.author.reputation)
- reputation.save()
-
- post.deleted = True
- #post.deleted_at = datetime.datetime.now()
- #post.deleted_by = Admin
- post.save()
-
-
-@transaction.commit_on_success
-def onAnswerAccept(answer, user):
- answer.accepted = True
- answer.accepted_at = datetime.datetime.now()
- answer.question.answer_accepted = True
- answer.save()
- answer.question.save()
-
- answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['gain_by_answer_accepted']))
- answer.author.save()
- reputation = Repute(user=answer.author,
- positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=2,
- reputation=answer.author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_accepting_answer']))
- user.save()
- reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=3,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onAnswerAcceptCanceled(answer, user):
- answer.accepted = False
- answer.accepted_at = None
- answer.question.answer_accepted = False
- answer.save()
- answer.question.save()
-
- answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
- answer.author.save()
- reputation = Repute(user=answer.author,
- negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-2,
- reputation=answer.author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
- user.save()
- reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-1,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onUpVoted(vote, post, user):
- vote.save()
-
- post.vote_up_count = int(post.vote_up_count) + 1
- post.score = int(post.score) + 1
- post.save()
-
- if not post.wiki:
- author = post.author
- if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']):
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_upvoted']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_upvoted']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=1,
- reputation=author.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onUpVotedCanceled(vote, post, user):
- vote.delete()
-
- post.vote_up_count = int(post.vote_up_count) - 1
- if post.vote_up_count < 0:
- post.vote_up_count = 0
- post.score = int(post.score) - 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['lose_by_upvote_canceled']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-8,
- reputation=author.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onDownVoted(vote, post, user):
- vote.save()
-
- post.vote_down_count = int(post.vote_down_count) + 1
- post.score = int(post.score) - 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['lose_by_downvoted']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_downvoted']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-3,
- reputation=author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_downvoting']))
- user.save()
-
- reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_downvoting']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-5,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onDownVotedCanceled(vote, post, user):
- vote.delete()
-
- post.vote_down_count = int(post.vote_down_count) - 1
- if post.vote_down_count < 0:
- post.vote_down_count = 0
- post.score = post.score + 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_downvote_canceled']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=4,
- reputation=author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_canceling_downvote']))
- user.save()
-
- reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=5,
- reputation=user.reputation)
- reputation.save()
-
-def onDeleteCanceled(post, user):
- post.deleted = False
- post.deleted_by = None
- post.deleted_at = None
- post.save()
- for tag in list(post.tags.all()):
- if tag.used_count == 1 and tag.deleted:
- tag.deleted = False
- tag.deleted_by = None
- tag.deleted_at = None
- tag.save()
-
-def onDeleted(post, user):
- post.deleted = True
- post.deleted_by = user
- post.deleted_at = datetime.datetime.now()
- post.save()
-
- for tag in list(post.tags.all()):
- if tag.used_count == 1:
- tag.deleted = True
- tag.deleted_by = user
- tag.deleted_at = datetime.datetime.now()
- tag.save()
+""" +Authorisation related functions. + +The actions a User is authorised to perform are dependent on their reputation +and superuser status. +""" +import datetime +from django.contrib.contenttypes.models import ContentType +from django.db import transaction +from models import Repute +from models import Question +from models import Answer +from const import TYPE_REPUTATION +question_type = ContentType.objects.get_for_model(Question) +answer_type = ContentType.objects.get_for_model(Answer) + +VOTE_UP = 15 +FLAG_OFFENSIVE = 15 +POST_IMAGES = 15 +LEAVE_COMMENTS = 50 +UPLOAD_FILES = 60 +VOTE_DOWN = 100 +CLOSE_OWN_QUESTIONS = 250 +RETAG_OTHER_QUESTIONS = 500 +REOPEN_OWN_QUESTIONS = 500 +EDIT_COMMUNITY_WIKI_POSTS = 750 +EDIT_OTHER_POSTS = 2000 +DELETE_COMMENTS = 2000 +VIEW_OFFENSIVE_FLAGS = 2000 +DISABLE_URL_NOFOLLOW = 2000 +CLOSE_OTHER_QUESTIONS = 3000 +LOCK_POSTS = 4000 + +VOTE_RULES = { + 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday + 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday + 'scope_warn_votes_left' : 10, # start when to warn user how many votes left + 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes. + 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags + 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags +} + +REPUTATION_RULES = { + 'initial_score' : 1, + 'scope_per_day_by_upvotes' : 200, + 'gain_by_upvoted' : 10, + 'gain_by_answer_accepted' : 15, + 'gain_by_accepting_answer' : 2, + 'gain_by_downvote_canceled' : 2, + 'gain_by_canceling_downvote' : 1, + 'lose_by_canceling_accepted_answer' : -2, + 'lose_by_accepted_answer_cancled' : -15, + 'lose_by_downvoted' : -2, + 'lose_by_flagged' : -2, + 'lose_by_downvoting' : -1, + 'lose_by_flagged_lastrevision_3_times': -30, + 'lose_by_flagged_lastrevision_5_times': -100, + 'lose_by_upvote_canceled' : -10, +} + +def can_vote_up(user): + """Determines if a User can vote Questions and Answers up.""" + return user.is_authenticated() and ( + user.reputation >= VOTE_UP or + user.is_superuser) + +def can_flag_offensive(user): + """Determines if a User can flag Questions and Answers as offensive.""" + return user.is_authenticated() and ( + user.reputation >= FLAG_OFFENSIVE or + user.is_superuser) + +def can_add_comments(user): + """Determines if a User can add comments to Questions and Answers.""" + return user.is_authenticated() and ( + user.reputation >= LEAVE_COMMENTS or + user.is_superuser) + +def can_vote_down(user): + """Determines if a User can vote Questions and Answers down.""" + return user.is_authenticated() and ( + user.reputation >= VOTE_DOWN or + user.is_superuser) + +def can_retag_questions(user): + """Determines if a User can retag Questions.""" + return user.is_authenticated() and ( + RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or + user.is_superuser) + +def can_edit_post(user, post): + """Determines if a User can edit the given Question or Answer.""" + return user.is_authenticated() and ( + user.id == post.author_id or + (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or + user.reputation >= EDIT_OTHER_POSTS or + user.is_superuser) + +def can_delete_comment(user, comment): + """Determines if a User can delete the given Comment.""" + return user.is_authenticated() and ( + user.id == comment.user_id or + user.reputation >= DELETE_COMMENTS or + user.is_superuser) + +def can_view_offensive_flags(user): + """Determines if a User can view offensive flag counts.""" + return user.is_authenticated() and ( + user.reputation >= VIEW_OFFENSIVE_FLAGS or + user.is_superuser) + +def can_close_question(user, question): + """Determines if a User can close the given Question.""" + return user.is_authenticated() and ( + (user.id == question.author_id and + user.reputation >= CLOSE_OWN_QUESTIONS) or + user.reputation >= CLOSE_OTHER_QUESTIONS or + user.is_superuser) + +def can_lock_posts(user): + """Determines if a User can lock Questions or Answers.""" + return user.is_authenticated() and ( + user.reputation >= LOCK_POSTS or + user.is_superuser) + +def can_follow_url(user): + """Determines if the URL link can be followed by Google search engine.""" + return user.reputation >= DISABLE_URL_NOFOLLOW + +def can_accept_answer(user, question, answer): + return (user.is_authenticated() and + question.author != answer.author and + question.author == user) or user.is_superuser + +# now only support to reopen own question except superuser +def can_reopen_question(user, question): + return (user.is_authenticated() and + user.id == question.author_id and + user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser + +def can_delete_post(user, post): + return (user.is_authenticated() and + user.id == post.author_id) or user.is_superuser + +def can_view_deleted_post(user, post): + return user.is_superuser + +# user preferences view permissions +def is_user_self(request_user, target_user): + return (request_user.is_authenticated() and request_user == target_user) + +def can_view_user_votes(request_user, target_user): + return (request_user.is_authenticated() and request_user == target_user) + +def can_view_user_preferences(request_user, target_user): + return (request_user.is_authenticated() and request_user == target_user) + +def can_view_user_edit(request_user, target_user): + return (request_user.is_authenticated() and request_user == target_user) + +def can_upload_files(request_user): + return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \ + request_user.is_superuser + +########################################### +## actions and reputation changes event +########################################### +def calculate_reputation(origin, offset): + result = int(origin) + int(offset) + return result if result > 0 else 1 + +@transaction.commit_on_success +def onFlaggedItem(item, post, user): + + item.save() + post.offensive_flag_count = post.offensive_flag_count + 1 + post.save() + + post.author.reputation = calculate_reputation(post.author.reputation, + int(REPUTATION_RULES['lose_by_flagged'])) + post.author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=post.author, + negative=int(REPUTATION_RULES['lose_by_flagged']), + question=question, reputed_at=datetime.datetime.now(), + reputation_type=-4, + reputation=post.author.reputation) + reputation.save() + + #todo: These should be updated to work on same revisions. + if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] : + post.author.reputation = calculate_reputation(post.author.reputation, + int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times'])) + post.author.save() + + reputation = Repute(user=post.author, + negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-6, + reputation=post.author.reputation) + reputation.save() + + elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']: + post.author.reputation = calculate_reputation(post.author.reputation, + int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times'])) + post.author.save() + + reputation = Repute(user=post.author, + negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-7, + reputation=post.author.reputation) + reputation.save() + + post.deleted = True + #post.deleted_at = datetime.datetime.now() + #post.deleted_by = Admin + post.save() + + +@transaction.commit_on_success +def onAnswerAccept(answer, user): + answer.accepted = True + answer.accepted_at = datetime.datetime.now() + answer.question.answer_accepted = True + answer.save() + answer.question.save() + + answer.author.reputation = calculate_reputation(answer.author.reputation, + int(REPUTATION_RULES['gain_by_answer_accepted'])) + answer.author.save() + reputation = Repute(user=answer.author, + positive=int(REPUTATION_RULES['gain_by_answer_accepted']), + question=answer.question, + reputed_at=datetime.datetime.now(), + reputation_type=2, + reputation=answer.author.reputation) + reputation.save() + + user.reputation = calculate_reputation(user.reputation, + int(REPUTATION_RULES['gain_by_accepting_answer'])) + user.save() + reputation = Repute(user=user, + positive=int(REPUTATION_RULES['gain_by_accepting_answer']), + question=answer.question, + reputed_at=datetime.datetime.now(), + reputation_type=3, + reputation=user.reputation) + reputation.save() + +@transaction.commit_on_success +def onAnswerAcceptCanceled(answer, user): + answer.accepted = False + answer.accepted_at = None + answer.question.answer_accepted = False + answer.save() + answer.question.save() + + answer.author.reputation = calculate_reputation(answer.author.reputation, + int(REPUTATION_RULES['lose_by_accepted_answer_cancled'])) + answer.author.save() + reputation = Repute(user=answer.author, + negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']), + question=answer.question, + reputed_at=datetime.datetime.now(), + reputation_type=-2, + reputation=answer.author.reputation) + reputation.save() + + user.reputation = calculate_reputation(user.reputation, + int(REPUTATION_RULES['lose_by_canceling_accepted_answer'])) + user.save() + reputation = Repute(user=user, + negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']), + question=answer.question, + reputed_at=datetime.datetime.now(), + reputation_type=-1, + reputation=user.reputation) + reputation.save() + +@transaction.commit_on_success +def onUpVoted(vote, post, user): + vote.save() + + post.vote_up_count = int(post.vote_up_count) + 1 + post.score = int(post.score) + 1 + post.save() + + if not post.wiki: + author = post.author + if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']): + author.reputation = calculate_reputation(author.reputation, + int(REPUTATION_RULES['gain_by_upvoted'])) + author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=author, + positive=int(REPUTATION_RULES['gain_by_upvoted']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=1, + reputation=author.reputation) + reputation.save() + +@transaction.commit_on_success +def onUpVotedCanceled(vote, post, user): + vote.delete() + + post.vote_up_count = int(post.vote_up_count) - 1 + if post.vote_up_count < 0: + post.vote_up_count = 0 + post.score = int(post.score) - 1 + post.save() + + if not post.wiki: + author = post.author + author.reputation = calculate_reputation(author.reputation, + int(REPUTATION_RULES['lose_by_upvote_canceled'])) + author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=author, + negative=int(REPUTATION_RULES['lose_by_upvote_canceled']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-8, + reputation=author.reputation) + reputation.save() + +@transaction.commit_on_success +def onDownVoted(vote, post, user): + vote.save() + + post.vote_down_count = int(post.vote_down_count) + 1 + post.score = int(post.score) - 1 + post.save() + + if not post.wiki: + author = post.author + author.reputation = calculate_reputation(author.reputation, + int(REPUTATION_RULES['lose_by_downvoted'])) + author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=author, + negative=int(REPUTATION_RULES['lose_by_downvoted']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-3, + reputation=author.reputation) + reputation.save() + + user.reputation = calculate_reputation(user.reputation, + int(REPUTATION_RULES['lose_by_downvoting'])) + user.save() + + reputation = Repute(user=user, + negative=int(REPUTATION_RULES['lose_by_downvoting']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-5, + reputation=user.reputation) + reputation.save() + +@transaction.commit_on_success +def onDownVotedCanceled(vote, post, user): + vote.delete() + + post.vote_down_count = int(post.vote_down_count) - 1 + if post.vote_down_count < 0: + post.vote_down_count = 0 + post.score = post.score + 1 + post.save() + + if not post.wiki: + author = post.author + author.reputation = calculate_reputation(author.reputation, + int(REPUTATION_RULES['gain_by_downvote_canceled'])) + author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=author, + positive=int(REPUTATION_RULES['gain_by_downvote_canceled']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=4, + reputation=author.reputation) + reputation.save() + + user.reputation = calculate_reputation(user.reputation, + int(REPUTATION_RULES['gain_by_canceling_downvote'])) + user.save() + + reputation = Repute(user=user, + positive=int(REPUTATION_RULES['gain_by_canceling_downvote']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=5, + reputation=user.reputation) + reputation.save() + +def onDeleteCanceled(post, user): + post.deleted = False + post.deleted_by = None + post.deleted_at = None + post.save() + for tag in list(post.tags.all()): + if tag.used_count == 1 and tag.deleted: + tag.deleted = False + tag.deleted_by = None + tag.deleted_at = None + tag.save() + +def onDeleted(post, user): + post.deleted = True + post.deleted_by = user + post.deleted_at = datetime.datetime.now() + post.save() + + for tag in list(post.tags.all()): + if tag.used_count == 1: + tag.deleted = True + tag.deleted_by = user + tag.deleted_at = datetime.datetime.now() + tag.save() diff --git a/forum/feed.py b/forum/feed.py index a4218630..6374ba71 100644 --- a/forum/feed.py +++ b/forum/feed.py @@ -1,43 +1,43 @@ -#!/usr/bin/env python
-#encoding:utf-8
-#-------------------------------------------------------------------------------
-# Name: Syndication feed class for subsribtion
-# Purpose:
-#
-# Author: Mike
-#
-# Created: 29/01/2009
-# Copyright: (c) CNPROG.COM 2009
-# Licence: GPL V2
-#-------------------------------------------------------------------------------
-from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
-from django.utils.translation import ugettext as _
-from models import Question
-class RssLastestQuestionsFeed(Feed):
- title = _('site title') + _(' - ') + _('site slogan') + _(' - ')+ _('latest questions')
- #EDIT!!!
- link = 'http://where.com/questions/'
- description = _('meta site content')
- #ttl = 10
- copyright = _('copyright message')
-
- def item_link(self, item):
- return '/questions/%s/' % item.id
-
- def item_author_name(self, item):
- return item.author.username
-
- def item_author_link(self, item):
- return item.author.get_profile_url()
-
- def item_pubdate(self, item):
- return item.added_at
-
- def items(self, item):
- return Question.objects.filter(deleted=False).order_by('-added_at')[:30]
-
-def main():
- pass
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python +#encoding:utf-8 +#------------------------------------------------------------------------------- +# Name: Syndication feed class for subsribtion +# Purpose: +# +# Author: Mike +# +# Created: 29/01/2009 +# Copyright: (c) CNPROG.COM 2009 +# Licence: GPL V2 +#------------------------------------------------------------------------------- +from django.contrib.syndication.feeds import Feed, FeedDoesNotExist +from django.utils.translation import ugettext as _ +from models import Question +class RssLastestQuestionsFeed(Feed): + title = _('site title') + _(' - ') + _('site slogan') + _(' - ')+ _('latest questions') + #EDIT!!! + link = 'http://where.com/questions/' + description = _('meta site content') + #ttl = 10 + copyright = _('copyright message') + + def item_link(self, item): + return '/questions/%s/' % item.id + + def item_author_name(self, item): + return item.author.username + + def item_author_link(self, item): + return item.author.get_profile_url() + + def item_pubdate(self, item): + return item.added_at + + def items(self, item): + return Question.objects.filter(deleted=False).order_by('-added_at')[:30] + +def main(): + pass + +if __name__ == '__main__': + main() diff --git a/forum/forms.py b/forum/forms.py index 1b811ad9..9d866720 100644 --- a/forum/forms.py +++ b/forum/forms.py @@ -1,193 +1,193 @@ -import re
-from datetime import date
-from django import forms
-from models import *
-from const import *
-from django.utils.translation import ugettext as _
-
-class TitleField(forms.CharField):
- def __init__(self, *args, **kwargs):
- super(TitleField, self).__init__(*args, **kwargs)
- self.required = True
- self.widget = forms.TextInput(attrs={'size' : 70, 'autocomplete' : 'off'})
- self.max_length = 255
- self.label = _('title')
- self.help_text = _('please enter a descriptive title for your question')
- self.initial = ''
-
- def clean(self, value):
- if len(value) < 10:
- raise forms.ValidationError(_('title must be > 10 characters'))
-
- return value
-
-class EditorField(forms.CharField):
- def __init__(self, *args, **kwargs):
- super(EditorField, self).__init__(*args, **kwargs)
- self.required = True
- self.widget = forms.Textarea(attrs={'id':'editor'})
- self.label = _('content')
- self.help_text = u''
- self.initial = ''
-
- def clean(self, value):
- if len(value) < 10:
- raise forms.ValidationError(_('question content must be > 10 characters'))
-
- return value
-
-class TagNamesField(forms.CharField):
- def __init__(self, *args, **kwargs):
- super(TagNamesField, self).__init__(*args, **kwargs)
- self.required = True
- self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
- self.max_length = 255
- self.label = _('tags')
- self.help_text = _('please use space to separate tags (this enables autocomplete feature)')
- self.initial = ''
-
- def clean(self, value):
- value = super(TagNamesField, self).clean(value)
- data = value.strip()
- if len(data) < 1:
- raise forms.ValidationError(_('tags are required'))
- list = data.split(' ')
- list_temp = []
- if len(list) > 5:
- raise forms.ValidationError(_('please use 5 tags or less'))
- for tag in list:
- if len(tag) > 20:
- raise forms.ValidationError(_('tags must be shorter than 20 characters'))
- #take tag regex from settings
- tagname_re = re.compile(r'[a-z0-9]+')
- if not tagname_re.match(tag):
- raise forms.ValidationError(_('please use following characters in tags: letters \'a-z\', numbers, and characters \'.-_#\''))
- # only keep one same tag
- if tag not in list_temp and len(tag.strip()) > 0:
- list_temp.append(tag)
- return u' '.join(list_temp)
-
-class WikiField(forms.BooleanField):
- def __init__(self, *args, **kwargs):
- super(WikiField, self).__init__(*args, **kwargs)
- self.required = False
- self.label = _('community wiki')
- self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown')
-
-
-class SummaryField(forms.CharField):
- def __init__(self, *args, **kwargs):
- super(SummaryField, self).__init__(*args, **kwargs)
- self.required = False
- self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
- self.max_length = 300
- self.label = _('update summary:')
- self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
-
-class AskForm(forms.Form):
- title = TitleField()
- text = EditorField()
- tags = TagNamesField()
- wiki = WikiField()
-
- openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
- user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
-
-
-
-class AnswerForm(forms.Form):
- text = EditorField()
- wiki = WikiField()
- openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
- user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- def __init__(self, question, *args, **kwargs):
- super(AnswerForm, self).__init__(*args, **kwargs)
- if question.wiki:
- self.fields['wiki'].initial = True
-
-class CloseForm(forms.Form):
- reason = forms.ChoiceField(choices=CLOSE_REASONS)
-
-class RetagQuestionForm(forms.Form):
- tags = TagNamesField()
- # initialize the default values
- def __init__(self, question, *args, **kwargs):
- super(RetagQuestionForm, self).__init__(*args, **kwargs)
- self.fields['tags'].initial = question.tagnames
-
-class RevisionForm(forms.Form):
- """
- Lists revisions of a Question or Answer
- """
- revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'}))
-
- def __init__(self, post, latest_revision, *args, **kwargs):
- super(RevisionForm, self).__init__(*args, **kwargs)
- revisions = post.revisions.all().values_list(
- 'revision', 'author__username', 'revised_at', 'summary')
- date_format = '%c'
- self.fields['revision'].choices = [
- (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3]))
- for r in revisions]
- self.fields['revision'].initial = latest_revision.revision
-
-class EditQuestionForm(forms.Form):
- title = TitleField()
- text = EditorField()
- tags = TagNamesField()
- summary = SummaryField()
-
- def __init__(self, question, revision, *args, **kwargs):
- super(EditQuestionForm, self).__init__(*args, **kwargs)
- self.fields['title'].initial = revision.title
- self.fields['text'].initial = revision.text
- self.fields['tags'].initial = revision.tagnames
- # Once wiki mode is enabled, it can't be disabled
- if not question.wiki:
- self.fields['wiki'] = WikiField()
-
-class EditAnswerForm(forms.Form):
- text = EditorField()
- summary = SummaryField()
-
- def __init__(self, answer, revision, *args, **kwargs):
- super(EditAnswerForm, self).__init__(*args, **kwargs)
- self.fields['text'].initial = revision.text
-
-class EditUserForm(forms.Form):
- email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- city = forms.CharField(label=_('Location'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- birthday = forms.DateField(label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), required=False, widget=forms.TextInput(attrs={'size' : 35}))
- about = forms.CharField(label=_('Profile'), required=False, widget=forms.Textarea(attrs={'cols' : 60}))
-
- def __init__(self, user, *args, **kwargs):
- super(EditUserForm, self).__init__(*args, **kwargs)
- self.fields['email'].initial = user.email
- self.fields['realname'].initial = user.real_name
- self.fields['website'].initial = user.website
- self.fields['city'].initial = user.location
-
- if user.date_of_birth is not None:
- self.fields['birthday'].initial = user.date_of_birth
- else:
- self.fields['birthday'].initial = '1990-01-01'
- self.fields['about'].initial = user.about
- self.user = user
-
- def clean_email(self):
- """For security reason one unique email in database"""
- if self.user.email != self.cleaned_data['email']:
- if 'email' in self.cleaned_data:
- try:
- user = User.objects.get(email = self.cleaned_data['email'])
- except User.DoesNotExist:
- return self.cleaned_data['email']
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(_('this email has already been registered, please use another one'))
- raise forms.ValidationError(_('this email has already been registered, please use another one'))
- else:
- return self.cleaned_data['email']
+import re +from datetime import date +from django import forms +from models import * +from const import * +from django.utils.translation import ugettext as _ + +class TitleField(forms.CharField): + def __init__(self, *args, **kwargs): + super(TitleField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.TextInput(attrs={'size' : 70, 'autocomplete' : 'off'}) + self.max_length = 255 + self.label = _('title') + self.help_text = _('please enter a descriptive title for your question') + self.initial = '' + + def clean(self, value): + if len(value) < 10: + raise forms.ValidationError(_('title must be > 10 characters')) + + return value + +class EditorField(forms.CharField): + def __init__(self, *args, **kwargs): + super(EditorField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.Textarea(attrs={'id':'editor'}) + self.label = _('content') + self.help_text = u'' + self.initial = '' + + def clean(self, value): + if len(value) < 10: + raise forms.ValidationError(_('question content must be > 10 characters')) + + return value + +class TagNamesField(forms.CharField): + def __init__(self, *args, **kwargs): + super(TagNamesField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'}) + self.max_length = 255 + self.label = _('tags') + self.help_text = _('please use space to separate tags (this enables autocomplete feature)') + self.initial = '' + + def clean(self, value): + value = super(TagNamesField, self).clean(value) + data = value.strip() + if len(data) < 1: + raise forms.ValidationError(_('tags are required')) + list = data.split(' ') + list_temp = [] + if len(list) > 5: + raise forms.ValidationError(_('please use 5 tags or less')) + for tag in list: + if len(tag) > 20: + raise forms.ValidationError(_('tags must be shorter than 20 characters')) + #take tag regex from settings + tagname_re = re.compile(r'[a-z0-9]+') + if not tagname_re.match(tag): + raise forms.ValidationError(_('please use following characters in tags: letters \'a-z\', numbers, and characters \'.-_#\'')) + # only keep one same tag + if tag not in list_temp and len(tag.strip()) > 0: + list_temp.append(tag) + return u' '.join(list_temp) + +class WikiField(forms.BooleanField): + def __init__(self, *args, **kwargs): + super(WikiField, self).__init__(*args, **kwargs) + self.required = False + self.label = _('community wiki') + self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown') + + +class SummaryField(forms.CharField): + def __init__(self, *args, **kwargs): + super(SummaryField, self).__init__(*args, **kwargs) + self.required = False + self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'}) + self.max_length = 300 + self.label = _('update summary:') + self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)') + +class AskForm(forms.Form): + title = TitleField() + text = EditorField() + tags = TagNamesField() + wiki = WikiField() + + openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'})) + user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + + + +class AnswerForm(forms.Form): + text = EditorField() + wiki = WikiField() + openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'})) + user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + def __init__(self, question, *args, **kwargs): + super(AnswerForm, self).__init__(*args, **kwargs) + if question.wiki: + self.fields['wiki'].initial = True + +class CloseForm(forms.Form): + reason = forms.ChoiceField(choices=CLOSE_REASONS) + +class RetagQuestionForm(forms.Form): + tags = TagNamesField() + # initialize the default values + def __init__(self, question, *args, **kwargs): + super(RetagQuestionForm, self).__init__(*args, **kwargs) + self.fields['tags'].initial = question.tagnames + +class RevisionForm(forms.Form): + """ + Lists revisions of a Question or Answer + """ + revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'})) + + def __init__(self, post, latest_revision, *args, **kwargs): + super(RevisionForm, self).__init__(*args, **kwargs) + revisions = post.revisions.all().values_list( + 'revision', 'author__username', 'revised_at', 'summary') + date_format = '%c' + self.fields['revision'].choices = [ + (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3])) + for r in revisions] + self.fields['revision'].initial = latest_revision.revision + +class EditQuestionForm(forms.Form): + title = TitleField() + text = EditorField() + tags = TagNamesField() + summary = SummaryField() + + def __init__(self, question, revision, *args, **kwargs): + super(EditQuestionForm, self).__init__(*args, **kwargs) + self.fields['title'].initial = revision.title + self.fields['text'].initial = revision.text + self.fields['tags'].initial = revision.tagnames + # Once wiki mode is enabled, it can't be disabled + if not question.wiki: + self.fields['wiki'] = WikiField() + +class EditAnswerForm(forms.Form): + text = EditorField() + summary = SummaryField() + + def __init__(self, answer, revision, *args, **kwargs): + super(EditAnswerForm, self).__init__(*args, **kwargs) + self.fields['text'].initial = revision.text + +class EditUserForm(forms.Form): + email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + city = forms.CharField(label=_('Location'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + birthday = forms.DateField(label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), required=False, widget=forms.TextInput(attrs={'size' : 35})) + about = forms.CharField(label=_('Profile'), required=False, widget=forms.Textarea(attrs={'cols' : 60})) + + def __init__(self, user, *args, **kwargs): + super(EditUserForm, self).__init__(*args, **kwargs) + self.fields['email'].initial = user.email + self.fields['realname'].initial = user.real_name + self.fields['website'].initial = user.website + self.fields['city'].initial = user.location + + if user.date_of_birth is not None: + self.fields['birthday'].initial = user.date_of_birth + else: + self.fields['birthday'].initial = '1990-01-01' + self.fields['about'].initial = user.about + self.user = user + + def clean_email(self): + """For security reason one unique email in database""" + if self.user.email != self.cleaned_data['email']: + if 'email' in self.cleaned_data: + try: + user = User.objects.get(email = self.cleaned_data['email']) + except User.DoesNotExist: + return self.cleaned_data['email'] + except User.MultipleObjectsReturned: + raise forms.ValidationError(_('this email has already been registered, please use another one')) + raise forms.ValidationError(_('this email has already been registered, please use another one')) + else: + return self.cleaned_data['email'] diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py index 7074e3db..447e8971 100644 --- a/forum/management/commands/once_award_badges.py +++ b/forum/management/commands/once_award_badges.py @@ -1,348 +1,348 @@ -#!/usr/bin/env python
-#encoding:utf-8
-#-------------------------------------------------------------------------------
-# Name: Award badges command
-# Purpose: This is a command file croning in background process regularly to
-# query database and award badges for user's special acitivities.
-#
-# Author: Mike, Sailing
-#
-# Created: 18/01/2009
-# Copyright: (c) Mike 2009
-# Licence: GPL V2
-#-------------------------------------------------------------------------------
-
-from django.db import connection
-from django.shortcuts import get_object_or_404
-from django.contrib.contenttypes.models import ContentType
-
-from forum.models import *
-from forum.const import *
-from base_command import BaseCommand
-"""
-(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
-(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
-(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
-(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
-(5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
-(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
-(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
-(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
-(9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
-(10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
-(11, '村长', 3, '村长', '第一次重新标签', 0, 0),
-(12, '学者', 3, '学者', '第一次标记答案', 0, 0),
-(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
-(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
-(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
-(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
-(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
-(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
-(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
-(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
-(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
-(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0),
-(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
-(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
-(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
-(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0),
-(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0),
-(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0),
-(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0),
-(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0),
-(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
-(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0),
-(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0),
-(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
-(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
-(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
-
-
-TYPE_ACTIVITY_ASK_QUESTION=1
-TYPE_ACTIVITY_ANSWER=2
-TYPE_ACTIVITY_COMMENT_QUESTION=3
-TYPE_ACTIVITY_COMMENT_ANSWER=4
-TYPE_ACTIVITY_UPDATE_QUESTION=5
-TYPE_ACTIVITY_UPDATE_ANSWER=6
-TYPE_ACTIVITY_PRIZE=7
-TYPE_ACTIVITY_MARK_ANSWER=8
-TYPE_ACTIVITY_VOTE_UP=9
-TYPE_ACTIVITY_VOTE_DOWN=10
-TYPE_ACTIVITY_CANCEL_VOTE=11
-TYPE_ACTIVITY_DELETE_QUESTION=12
-TYPE_ACTIVITY_DELETE_ANSWER=13
-TYPE_ACTIVITY_MARK_OFFENSIVE=14
-TYPE_ACTIVITY_UPDATE_TAGS=15
-TYPE_ACTIVITY_FAVORITE=16
-TYPE_ACTIVITY_USER_FULL_UPDATED = 17
-"""
-
-BADGE_AWARD_TYPE_FIRST = {
- TYPE_ACTIVITY_MARK_OFFENSIVE : 7,
- TYPE_ACTIVITY_CANCEL_VOTE: 8,
- TYPE_ACTIVITY_VOTE_DOWN : 9,
- TYPE_ACTIVITY_UPDATE_QUESTION : 10,
- TYPE_ACTIVITY_UPDATE_ANSWER : 10,
- TYPE_ACTIVITY_UPDATE_TAGS : 11,
- TYPE_ACTIVITY_MARK_ANSWER : 12,
- TYPE_ACTIVITY_VOTE_UP : 14,
- TYPE_ACTIVITY_USER_FULL_UPDATED: 16
-
-}
-
-class Command(BaseCommand):
- def handle_noargs(self, **options):
- try:
- self.alpha_user()
- self.beta_user()
- self.first_type_award()
- self.first_ask_be_voted()
- self.first_answer_be_voted()
- self.first_answer_be_voted_10()
- self.vote_count_300()
- self.edit_count_100()
- self.comment_count_10()
- except Exception, e:
- print e
- finally:
- connection.close()
-
- def alpha_user(self):
- """
- Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user
- will be awarded the "Alpha" badge if he has any activities.
- """
- alpha_end_date = date(2009, 1, 25)
- if date.today() < alpha_end_date:
- badge = get_object_or_404(Badge, id=22)
- for user in User.objects.all():
- award = Award.objects.filter(user=user, badge=badge)
- if award and not badge.multiple:
- continue
- activities = Activity.objects.filter(user=user)
- if len(activities) > 0:
- new_award = Award(user=user, badge=badge)
- new_award.save()
-
- def beta_user(self):
- """
- Before Feb 25, 2009, every registered user
- will be awarded the "Beta" badge if he has any activities.
- """
- beta_end_date = date(2009, 2, 25)
- if date.today() < beta_end_date:
- badge = get_object_or_404(Badge, id=33)
- for user in User.objects.all():
- award = Award.objects.filter(user=user, badge=badge)
- if award and not badge.multiple:
- continue
- activities = Activity.objects.filter(user=user)
- if len(activities) > 0:
- new_award = Award(user=user, badge=badge)
- new_award.save()
-
- def first_type_award(self):
- """
- This will award below badges for users first behaviors:
-
- (7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
- (8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
- (9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
- (10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
- (11, '村长', 3, '村长', '第一次重新标签', 0, 0),
- (12, '学者', 3, '学者', '第一次标记答案', 0, 0),
- (14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
- (16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
- """
- activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys())
- # ORDER BY user_id, activity_type
- query = "SELECT id, user_id, activity_type, content_type_id, object_id FROM activity WHERE is_auditted = 0 AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types
-
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
- # collect activity_id in current process
- activity_ids = []
- last_user_id = 0
- last_activity_type = 0
- for row in rows:
- activity_ids.append(row[0])
- user_id = row[1]
- activity_type = row[2]
- content_type_id = row[3]
- object_id = row[4]
-
- # if the user and activity are same as the last, continue
- if user_id == last_user_id and activity_type == last_activity_type:
- continue;
-
- user = get_object_or_404(User, id=user_id)
- badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type])
- content_type = get_object_or_404(ContentType, id=content_type_id)
-
- count = Award.objects.filter(user=user, badge=badge).count()
- if count and not badge.multiple:
- continue
- else:
- # new award
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
-
- # set the current user_id and activity_type to last
- last_user_id = user_id
- last_activity_type = activity_type
-
- # update processed rows to auditted
- self.update_activities_auditted(cursor, activity_ids)
- finally:
- cursor.close()
-
- def first_ask_be_voted(self):
- """
- For user asked question and got first upvote, we award him following badge:
-
- (13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
- """
- query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM " \
- "activity act, question q WHERE act.activity_type = %s AND " \
- "act.object_id = q.id AND " \
- "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- badge = get_object_or_404(Badge, id=13)
- content_type = ContentType.objects.get_for_model(Question)
- awarded_users = []
- for row in rows:
- user_id = row[0]
- vote_up_count = row[1]
- object_id = row[2]
- if vote_up_count > 0 and user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def first_answer_be_voted(self):
- """
- When user answerd questions and got first upvote, we award him following badge:
-
- (15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
- """
- query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM " \
- "activity act, answer a WHERE act.activity_type = %s AND " \
- "act.object_id = a.id AND " \
- "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=15)
- content_type = ContentType.objects.get_for_model(Answer)
- for row in rows:
- user_id = row[0]
- vote_up_count = row[1]
- object_id = row[2]
- if vote_up_count > 0 and user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def first_answer_be_voted_10(self):
- """
- (32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0)
- """
- query = "SELECT act.user_id, act.object_id FROM " \
- "activity act, answer a WHERE act.object_id = a.id AND " \
- "act.activity_type = %s AND " \
- "a.vote_up_count >= 10 AND " \
- "act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=32)
- content_type = ContentType.objects.get_for_model(Answer)
- for row in rows:
- user_id = row[0]
- if user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- object_id = row[1]
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def vote_count_300(self):
- """
- (26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0)
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26)
-
- self.__award_for_count_num(query, 26)
-
- def edit_count_100(self):
- """
- (27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0)
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27)
-
- self.__award_for_count_num(query, 27)
-
- def comment_count_10(self):
- """
- (5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5)
- self.__award_for_count_num(query, 5)
-
- def __award_for_count_num(self, query, badge):
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=badge)
- for row in rows:
- vote_count = row[0]
- user_id = row[1]
-
- if user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
-def main():
- pass
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python +#encoding:utf-8 +#------------------------------------------------------------------------------- +# Name: Award badges command +# Purpose: This is a command file croning in background process regularly to +# query database and award badges for user's special acitivities. +# +# Author: Mike, Sailing +# +# Created: 18/01/2009 +# Copyright: (c) Mike 2009 +# Licence: GPL V2 +#------------------------------------------------------------------------------- + +from django.db import connection +from django.shortcuts import get_object_or_404 +from django.contrib.contenttypes.models import ContentType + +from forum.models import * +from forum.const import * +from base_command import BaseCommand +""" +(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0), +(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0), +(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0), +(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0), +(5, '评论家', 3, '评论家', '评论10次以上', 0, 0), +(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0), +(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0), +(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0), +(9, '批评家', 3, '批评家', '第一次反对票', 0, 0), +(10, '小编', 3, '小编', '第一次编辑更新', 0, 0), +(11, '村长', 3, '村长', '第一次重新标签', 0, 0), +(12, '学者', 3, '学者', '第一次标记答案', 0, 0), +(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0), +(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0), +(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0), +(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0), +(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0), +(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0), +(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0), +(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0), +(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0), +(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0), +(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0), +(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0), +(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0), +(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0), +(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0), +(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0), +(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0), +(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0), +(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0), +(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0), +(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0), +(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0), +(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0), +(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0); + + +TYPE_ACTIVITY_ASK_QUESTION=1 +TYPE_ACTIVITY_ANSWER=2 +TYPE_ACTIVITY_COMMENT_QUESTION=3 +TYPE_ACTIVITY_COMMENT_ANSWER=4 +TYPE_ACTIVITY_UPDATE_QUESTION=5 +TYPE_ACTIVITY_UPDATE_ANSWER=6 +TYPE_ACTIVITY_PRIZE=7 +TYPE_ACTIVITY_MARK_ANSWER=8 +TYPE_ACTIVITY_VOTE_UP=9 +TYPE_ACTIVITY_VOTE_DOWN=10 +TYPE_ACTIVITY_CANCEL_VOTE=11 +TYPE_ACTIVITY_DELETE_QUESTION=12 +TYPE_ACTIVITY_DELETE_ANSWER=13 +TYPE_ACTIVITY_MARK_OFFENSIVE=14 +TYPE_ACTIVITY_UPDATE_TAGS=15 +TYPE_ACTIVITY_FAVORITE=16 +TYPE_ACTIVITY_USER_FULL_UPDATED = 17 +""" + +BADGE_AWARD_TYPE_FIRST = { + TYPE_ACTIVITY_MARK_OFFENSIVE : 7, + TYPE_ACTIVITY_CANCEL_VOTE: 8, + TYPE_ACTIVITY_VOTE_DOWN : 9, + TYPE_ACTIVITY_UPDATE_QUESTION : 10, + TYPE_ACTIVITY_UPDATE_ANSWER : 10, + TYPE_ACTIVITY_UPDATE_TAGS : 11, + TYPE_ACTIVITY_MARK_ANSWER : 12, + TYPE_ACTIVITY_VOTE_UP : 14, + TYPE_ACTIVITY_USER_FULL_UPDATED: 16 + +} + +class Command(BaseCommand): + def handle_noargs(self, **options): + try: + self.alpha_user() + self.beta_user() + self.first_type_award() + self.first_ask_be_voted() + self.first_answer_be_voted() + self.first_answer_be_voted_10() + self.vote_count_300() + self.edit_count_100() + self.comment_count_10() + except Exception, e: + print e + finally: + connection.close() + + def alpha_user(self): + """ + Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user + will be awarded the "Alpha" badge if he has any activities. + """ + alpha_end_date = date(2009, 1, 25) + if date.today() < alpha_end_date: + badge = get_object_or_404(Badge, id=22) + for user in User.objects.all(): + award = Award.objects.filter(user=user, badge=badge) + if award and not badge.multiple: + continue + activities = Activity.objects.filter(user=user) + if len(activities) > 0: + new_award = Award(user=user, badge=badge) + new_award.save() + + def beta_user(self): + """ + Before Feb 25, 2009, every registered user + will be awarded the "Beta" badge if he has any activities. + """ + beta_end_date = date(2009, 2, 25) + if date.today() < beta_end_date: + badge = get_object_or_404(Badge, id=33) + for user in User.objects.all(): + award = Award.objects.filter(user=user, badge=badge) + if award and not badge.multiple: + continue + activities = Activity.objects.filter(user=user) + if len(activities) > 0: + new_award = Award(user=user, badge=badge) + new_award.save() + + def first_type_award(self): + """ + This will award below badges for users first behaviors: + + (7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0), + (8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0), + (9, '批评家', 3, '批评家', '第一次反对票', 0, 0), + (10, '小编', 3, '小编', '第一次编辑更新', 0, 0), + (11, '村长', 3, '村长', '第一次重新标签', 0, 0), + (12, '学者', 3, '学者', '第一次标记答案', 0, 0), + (14, '支持者', 3, '支持者', '第一次赞成票', 0, 0), + (16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0), + """ + activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys()) + # ORDER BY user_id, activity_type + query = "SELECT id, user_id, activity_type, content_type_id, object_id FROM activity WHERE is_auditted = 0 AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types + + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + # collect activity_id in current process + activity_ids = [] + last_user_id = 0 + last_activity_type = 0 + for row in rows: + activity_ids.append(row[0]) + user_id = row[1] + activity_type = row[2] + content_type_id = row[3] + object_id = row[4] + + # if the user and activity are same as the last, continue + if user_id == last_user_id and activity_type == last_activity_type: + continue; + + user = get_object_or_404(User, id=user_id) + badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type]) + content_type = get_object_or_404(ContentType, id=content_type_id) + + count = Award.objects.filter(user=user, badge=badge).count() + if count and not badge.multiple: + continue + else: + # new award + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + + # set the current user_id and activity_type to last + last_user_id = user_id + last_activity_type = activity_type + + # update processed rows to auditted + self.update_activities_auditted(cursor, activity_ids) + finally: + cursor.close() + + def first_ask_be_voted(self): + """ + For user asked question and got first upvote, we award him following badge: + + (13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0), + """ + query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM " \ + "activity act, question q WHERE act.activity_type = %s AND " \ + "act.object_id = q.id AND " \ + "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + badge = get_object_or_404(Badge, id=13) + content_type = ContentType.objects.get_for_model(Question) + awarded_users = [] + for row in rows: + user_id = row[0] + vote_up_count = row[1] + object_id = row[2] + if vote_up_count > 0 and user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def first_answer_be_voted(self): + """ + When user answerd questions and got first upvote, we award him following badge: + + (15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0), + """ + query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM " \ + "activity act, answer a WHERE act.activity_type = %s AND " \ + "act.object_id = a.id AND " \ + "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=15) + content_type = ContentType.objects.get_for_model(Answer) + for row in rows: + user_id = row[0] + vote_up_count = row[1] + object_id = row[2] + if vote_up_count > 0 and user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def first_answer_be_voted_10(self): + """ + (32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0) + """ + query = "SELECT act.user_id, act.object_id FROM " \ + "activity act, answer a WHERE act.object_id = a.id AND " \ + "act.activity_type = %s AND " \ + "a.vote_up_count >= 10 AND " \ + "act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=32) + content_type = ContentType.objects.get_for_model(Answer) + for row in rows: + user_id = row[0] + if user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + object_id = row[1] + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def vote_count_300(self): + """ + (26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0) + """ + query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING vote_count >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26) + + self.__award_for_count_num(query, 26) + + def edit_count_100(self): + """ + (27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0) + """ + query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING vote_count >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27) + + self.__award_for_count_num(query, 27) + + def comment_count_10(self): + """ + (5, '评论家', 3, '评论家', '评论10次以上', 0, 0), + """ + query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING vote_count >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5) + self.__award_for_count_num(query, 5) + + def __award_for_count_num(self, query, badge): + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=badge) + for row in rows: + vote_count = row[0] + user_id = row[1] + + if user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + +def main(): + pass + +if __name__ == '__main__': + main() diff --git a/forum/managers.py b/forum/managers.py index 0f22c59c..2e3e4186 100644 --- a/forum/managers.py +++ b/forum/managers.py @@ -1,259 +1,259 @@ -import datetime
-import logging
-from django.contrib.auth.models import User, UserManager
-from django.db import connection, models, transaction
-from django.db.models import Q
-from forum.models import *
-from urllib import quote, unquote
-
-class QuestionManager(models.Manager):
- def get_translation_questions(self, orderby, page_size):
- questions = self.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:page_size]
- return questions
-
- def get_questions_by_pagesize(self, orderby, page_size):
- questions = self.filter(deleted=False).order_by(orderby)[:page_size]
- return questions
-
- def get_questions_by_tag(self, tagname, orderby):
- questions = self.filter(deleted=False, tags__name = unquote(tagname)).order_by(orderby)
- return questions
-
- def get_unanswered_questions(self, orderby):
- questions = self.filter(deleted=False, answer_count=0).order_by(orderby)
- return questions
-
- def get_questions(self, orderby):
- questions = self.filter(deleted=False).order_by(orderby)
- return questions
-
- def update_tags(self, question, tagnames, user):
- """
- Updates Tag associations for a question to match the given
- tagname string.
-
- Returns ``True`` if tag usage counts were updated as a result,
- ``False`` otherwise.
- """
- from forum.models import Tag
- current_tags = list(question.tags.all())
- current_tagnames = set(t.name for t in current_tags)
- updated_tagnames = set(t for t in tagnames.split(' ') if t)
- modified_tags = []
-
- removed_tags = [t for t in current_tags
- if t.name not in updated_tagnames]
- if removed_tags:
- modified_tags.extend(removed_tags)
- question.tags.remove(*removed_tags)
-
- added_tagnames = updated_tagnames - current_tagnames
- if added_tagnames:
- added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
- user)
- modified_tags.extend(added_tags)
- question.tags.add(*added_tags)
-
- if modified_tags:
- Tag.objects.update_use_counts(modified_tags)
- return True
-
- return False
-
- def update_answer_count(self, question):
- """
- Executes an UPDATE query to update denormalised data with the
- number of answers the given question has.
- """
-
- # for some reasons, this Answer class failed to be imported,
- # although we have imported all classes from models on top.
- from forum.models import Answer
- self.filter(id=question.id).update(
- answer_count=Answer.objects.get_answers_from_question(question).count())
-
- def update_view_count(self, question):
- """
- update counter+1 when user browse question page
- """
- self.filter(id=question.id).update(view_count = question.view_count + 1)
-
- def update_favorite_count(self, question):
- """
- update favourite_count for given question
- """
- from forum.models import FavoriteQuestion
- self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
-
- def get_similar_questions(self, question):
- """
- Get 10 similar questions for given one.
- This will search the same tag list for give question(by exactly same string) first.
- Questions with the individual tags will be added to list if above questions are not full.
- """
- #print datetime.datetime.now()
- from forum.models import Question
- questions = list(Question.objects.filter(tagnames = question.tagnames).all())
-
- tags_list = question.tags.all()
- for tag in tags_list:
- extend_questions = Question.objects.filter(tags__id = tag.id)[:50]
- for item in extend_questions:
- if item not in questions and len(questions) < 10:
- questions.append(item)
-
- #print datetime.datetime.now()
- return questions
-
-class TagManager(models.Manager):
- UPDATE_USED_COUNTS_QUERY = (
- 'UPDATE tag '
- 'SET used_count = ('
- 'SELECT COUNT(*) FROM question_tags '
- 'WHERE tag_id = tag.id'
- ') '
- 'WHERE id IN (%s)')
-
- def get_valid_tags(self, page_size):
- from forum.models import Tag
- tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
- return tags
-
- def get_or_create_multiple(self, names, user):
- """
- Fetches a list of Tags with the given names, creating any Tags
- which don't exist when necesssary.
- """
- tags = list(self.filter(name__in=names))
- #Set all these tag visible
- for tag in tags:
- if tag.deleted:
- tag.deleted = False
- tag.deleted_by = None
- tag.deleted_at = None
- tag.save()
-
- if len(tags) < len(names):
- existing_names = set(tag.name for tag in tags)
- new_names = [name for name in names if name not in existing_names]
- tags.extend([self.create(name=name, created_by=user)
- for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
-
- return tags
-
- def update_use_counts(self, tags):
- """Updates the given Tags with their current use counts."""
- if not tags:
- return
- cursor = connection.cursor()
- query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
- cursor.execute(query, [tag.id for tag in tags])
- transaction.commit_unless_managed()
-
- def get_tags_by_questions(self, questions):
- question_ids = []
- for question in questions:
- question_ids.append(question.id)
-
- question_ids_str = ','.join([str(id) for id in question_ids])
- related_tags = self.extra(
- tables=['tag', 'question_tags'],
- where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
- ).distinct()
-
- return related_tags
-
-class AnswerManager(models.Manager):
- GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
- def get_answers_from_question(self, question, user=None):
- """
- Retrieves visibile answers for the given question. Delete answers
- are only visibile to the person who deleted them.
- """
-
- if user is None or not user.is_authenticated():
- return self.filter(question=question, deleted=False)
- else:
- return self.filter(Q(question=question),
- Q(deleted=False) | Q(deleted_by=user))
-
- def get_answers_from_questions(self, user_id):
- """
- Retrieves visibile answers for the given question. Which are not included own answers
- """
- cursor = connection.cursor()
- cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
- return cursor.fetchall()
-
-class VoteManager(models.Manager):
- COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1"
- COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1"
- COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = DATE(NOW())"
- def get_up_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_down_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_votes_count_today_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-
-class FlaggedItemManager(models.Manager):
- COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = DATE(NOW())"
- def get_flagged_items_count_today(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-
-class ReputeManager(models.Manager):
- COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = DATE(NOW())"
- def get_reputation_by_upvoted_today(self, user):
- """
- For one user in one day, he can only earn rep till certain score (ep. +200)
- by upvoted(also substracted from upvoted canceled). This is because we need
- to prohibit gaming system by upvoting/cancel again and again.
- """
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-class AwardManager(models.Manager):
- def get_recent_awards(self):
- awards = super(AwardManager, self).extra(
- select={'badge_id': 'badge.id', 'badge_name':'badge.name',
- 'badge_description': 'badge.description', 'badge_type': 'badge.type',
- 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
- },
- tables=['award', 'badge', 'auth_user'],
- order_by=['-awarded_at'],
- where=['auth_user.id=award.user_id AND badge_id=badge.id'],
- ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
- return awards
+import datetime +import logging +from django.contrib.auth.models import User, UserManager +from django.db import connection, models, transaction +from django.db.models import Q +from forum.models import * +from urllib import quote, unquote + +class QuestionManager(models.Manager): + def get_translation_questions(self, orderby, page_size): + questions = self.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:page_size] + return questions + + def get_questions_by_pagesize(self, orderby, page_size): + questions = self.filter(deleted=False).order_by(orderby)[:page_size] + return questions + + def get_questions_by_tag(self, tagname, orderby): + questions = self.filter(deleted=False, tags__name = unquote(tagname)).order_by(orderby) + return questions + + def get_unanswered_questions(self, orderby): + questions = self.filter(deleted=False, answer_count=0).order_by(orderby) + return questions + + def get_questions(self, orderby): + questions = self.filter(deleted=False).order_by(orderby) + return questions + + def update_tags(self, question, tagnames, user): + """ + Updates Tag associations for a question to match the given + tagname string. + + Returns ``True`` if tag usage counts were updated as a result, + ``False`` otherwise. + """ + from forum.models import Tag + current_tags = list(question.tags.all()) + current_tagnames = set(t.name for t in current_tags) + updated_tagnames = set(t for t in tagnames.split(' ') if t) + modified_tags = [] + + removed_tags = [t for t in current_tags + if t.name not in updated_tagnames] + if removed_tags: + modified_tags.extend(removed_tags) + question.tags.remove(*removed_tags) + + added_tagnames = updated_tagnames - current_tagnames + if added_tagnames: + added_tags = Tag.objects.get_or_create_multiple(added_tagnames, + user) + modified_tags.extend(added_tags) + question.tags.add(*added_tags) + + if modified_tags: + Tag.objects.update_use_counts(modified_tags) + return True + + return False + + def update_answer_count(self, question): + """ + Executes an UPDATE query to update denormalised data with the + number of answers the given question has. + """ + + # for some reasons, this Answer class failed to be imported, + # although we have imported all classes from models on top. + from forum.models import Answer + self.filter(id=question.id).update( + answer_count=Answer.objects.get_answers_from_question(question).count()) + + def update_view_count(self, question): + """ + update counter+1 when user browse question page + """ + self.filter(id=question.id).update(view_count = question.view_count + 1) + + def update_favorite_count(self, question): + """ + update favourite_count for given question + """ + from forum.models import FavoriteQuestion + self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count()) + + def get_similar_questions(self, question): + """ + Get 10 similar questions for given one. + This will search the same tag list for give question(by exactly same string) first. + Questions with the individual tags will be added to list if above questions are not full. + """ + #print datetime.datetime.now() + from forum.models import Question + questions = list(Question.objects.filter(tagnames = question.tagnames).all()) + + tags_list = question.tags.all() + for tag in tags_list: + extend_questions = Question.objects.filter(tags__id = tag.id)[:50] + for item in extend_questions: + if item not in questions and len(questions) < 10: + questions.append(item) + + #print datetime.datetime.now() + return questions + +class TagManager(models.Manager): + UPDATE_USED_COUNTS_QUERY = ( + 'UPDATE tag ' + 'SET used_count = (' + 'SELECT COUNT(*) FROM question_tags ' + 'WHERE tag_id = tag.id' + ') ' + 'WHERE id IN (%s)') + + def get_valid_tags(self, page_size): + from forum.models import Tag + tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size] + return tags + + def get_or_create_multiple(self, names, user): + """ + Fetches a list of Tags with the given names, creating any Tags + which don't exist when necesssary. + """ + tags = list(self.filter(name__in=names)) + #Set all these tag visible + for tag in tags: + if tag.deleted: + tag.deleted = False + tag.deleted_by = None + tag.deleted_at = None + tag.save() + + if len(tags) < len(names): + existing_names = set(tag.name for tag in tags) + new_names = [name for name in names if name not in existing_names] + tags.extend([self.create(name=name, created_by=user) + for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0]) + + return tags + + def update_use_counts(self, tags): + """Updates the given Tags with their current use counts.""" + if not tags: + return + cursor = connection.cursor() + query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags)) + cursor.execute(query, [tag.id for tag in tags]) + transaction.commit_unless_managed() + + def get_tags_by_questions(self, questions): + question_ids = [] + for question in questions: + question_ids.append(question.id) + + question_ids_str = ','.join([str(id) for id in question_ids]) + related_tags = self.extra( + tables=['tag', 'question_tags'], + where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"] + ).distinct() + + return related_tags + +class AnswerManager(models.Manager): + GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s' + def get_answers_from_question(self, question, user=None): + """ + Retrieves visibile answers for the given question. Delete answers + are only visibile to the person who deleted them. + """ + + if user is None or not user.is_authenticated(): + return self.filter(question=question, deleted=False) + else: + return self.filter(Q(question=question), + Q(deleted=False) | Q(deleted_by=user)) + + def get_answers_from_questions(self, user_id): + """ + Retrieves visibile answers for the given question. Which are not included own answers + """ + cursor = connection.cursor() + cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id]) + return cursor.fetchall() + +class VoteManager(models.Manager): + COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1" + COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1" + COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = DATE(NOW())" + def get_up_vote_count_from_user(self, user): + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + else: + return 0 + + def get_down_vote_count_from_user(self, user): + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + else: + return 0 + + def get_votes_count_today_from_user(self, user): + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + + else: + return 0 + +class FlaggedItemManager(models.Manager): + COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = DATE(NOW())" + def get_flagged_items_count_today(self, user): + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + + else: + return 0 + +class ReputeManager(models.Manager): + COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = DATE(NOW())" + def get_reputation_by_upvoted_today(self, user): + """ + For one user in one day, he can only earn rep till certain score (ep. +200) + by upvoted(also substracted from upvoted canceled). This is because we need + to prohibit gaming system by upvoting/cancel again and again. + """ + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + + else: + return 0 +class AwardManager(models.Manager): + def get_recent_awards(self): + awards = super(AwardManager, self).extra( + select={'badge_id': 'badge.id', 'badge_name':'badge.name', + 'badge_description': 'badge.description', 'badge_type': 'badge.type', + 'user_id': 'auth_user.id', 'user_name': 'auth_user.username' + }, + tables=['award', 'badge', 'auth_user'], + order_by=['-awarded_at'], + where=['auth_user.id=award.user_id AND badge_id=badge.id'], + ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name') + return awards diff --git a/forum/models.py b/forum/models.py index 570db274..b966ccb0 100644 --- a/forum/models.py +++ b/forum/models.py @@ -1,654 +1,654 @@ -# encoding:utf-8
-import datetime
-import hashlib
-from urllib import quote_plus, urlencode
-from django.db import models
-from django.utils.html import strip_tags
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from django.contrib.contenttypes import generic
-from django.contrib.contenttypes.models import ContentType
-from django.template.defaultfilters import slugify
-from django.db.models.signals import post_delete, post_save, pre_save
-from django.utils.translation import ugettext as _
-import django.dispatch
-
-from forum.managers import *
-from const import *
-
-class Tag(models.Model):
- name = models.CharField(max_length=255, unique=True)
- created_by = models.ForeignKey(User, related_name='created_tags')
- deleted = models.BooleanField(default=False)
- deleted_at = models.DateTimeField(null=True, blank=True)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags')
- # Denormalised data
- used_count = models.PositiveIntegerField(default=0)
-
- objects = TagManager()
-
- class Meta:
- db_table = u'tag'
- ordering = ('-used_count', 'name')
-
- def __unicode__(self):
- return self.name
-
-class Comment(models.Model):
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='comments')
- comment = models.CharField(max_length=300)
- added_at = models.DateTimeField(default=datetime.datetime.now)
-
- class Meta:
- ordering = ('-added_at',)
- db_table = u'comment'
- def __unicode__(self):
- return self.comment
-
-class Vote(models.Model):
- VOTE_UP = +1
- VOTE_DOWN = -1
- VOTE_CHOICES = (
- (VOTE_UP, u'Up'),
- (VOTE_DOWN, u'Down'),
- )
-
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='votes')
- vote = models.SmallIntegerField(choices=VOTE_CHOICES)
- voted_at = models.DateTimeField(default=datetime.datetime.now)
-
- objects = VoteManager()
-
- class Meta:
- unique_together = ('content_type', 'object_id', 'user')
- db_table = u'vote'
- def __unicode__(self):
- return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
-
- def is_upvote(self):
- return self.vote == self.VOTE_UP
-
- def is_downvote(self):
- return self.vote == self.VOTE_DOWN
-
-class FlaggedItem(models.Model):
- """A flag on a Question or Answer indicating offensive content."""
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='flagged_items')
- flagged_at = models.DateTimeField(default=datetime.datetime.now)
-
- objects = FlaggedItemManager()
-
- class Meta:
- unique_together = ('content_type', 'object_id', 'user')
- db_table = u'flagged_item'
- def __unicode__(self):
- return '[%s] flagged at %s' %(self.user, self.flagged_at)
-
-class Question(models.Model):
- title = models.CharField(max_length=300)
- author = models.ForeignKey(User, related_name='questions')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- tags = models.ManyToManyField(Tag, related_name='questions')
- # Status
- wiki = models.BooleanField(default=False)
- wikified_at = models.DateTimeField(null=True, blank=True)
- answer_accepted = models.BooleanField(default=False)
- closed = models.BooleanField(default=False)
- closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
- closed_at = models.DateTimeField(null=True, blank=True)
- close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
- deleted = models.BooleanField(default=False)
- deleted_at = models.DateTimeField(null=True, blank=True)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions')
- locked = models.BooleanField(default=False)
- locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions')
- locked_at = models.DateTimeField(null=True, blank=True)
- # Denormalised data
- score = models.IntegerField(default=0)
- vote_up_count = models.IntegerField(default=0)
- vote_down_count = models.IntegerField(default=0)
- answer_count = models.PositiveIntegerField(default=0)
- comment_count = models.PositiveIntegerField(default=0)
- view_count = models.PositiveIntegerField(default=0)
- offensive_flag_count = models.SmallIntegerField(default=0)
- favourite_count = models.PositiveIntegerField(default=0)
- last_edited_at = models.DateTimeField(null=True, blank=True)
- last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions')
- last_activity_at = models.DateTimeField(default=datetime.datetime.now)
- last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')
- tagnames = models.CharField(max_length=125)
- summary = models.CharField(max_length=180)
- html = models.TextField()
- comments = generic.GenericRelation(Comment)
- votes = generic.GenericRelation(Vote)
- flagged_items = generic.GenericRelation(FlaggedItem)
-
- objects = QuestionManager()
-
- def save(self, **kwargs):
- """
- Overridden to manually manage addition of tags when the object
- is first saved.
-
- This is required as we're using ``tagnames`` as the sole means of
- adding and editing tags.
- """
- initial_addition = (self.id is None)
- super(Question, self).save(**kwargs)
- if initial_addition:
- tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
- self.author)
- self.tags.add(*tags)
- Tag.objects.update_use_counts(tags)
-
- def tagname_list(self):
- """Creates a list of Tag names from the ``tagnames`` attribute."""
- return [name for name in self.tagnames.split(u' ')]
-
- def get_absolute_url(self):
- return '%s%s' % (reverse('question', args=[self.id]), self.title)
-
- def has_favorite_by_user(self, user):
- if not user.is_authenticated():
- return False
- return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
-
- def get_answer_count_by_user(self, user_id):
- query_set = Answer.objects.filter(author__id=user_id)
- return query_set.filter(question=self).count()
-
- def get_question_title(self):
- if self.closed:
- attr = CONST['closed']
- elif self.deleted:
- attr = CONST['deleted']
- else:
- attr = None
- return u'%s %s' % (self.title, attr) if attr is not None else self.title
-
- def get_revision_url(self):
- return reverse('question_revisions', args=[self.id])
-
- def get_latest_revision(self):
- return self.revisions.all()[0]
-
- def __unicode__(self):
- return self.title
-
- class Meta:
- db_table = u'question'
-
-class QuestionRevision(models.Model):
- """A revision of a Question."""
- question = models.ForeignKey(Question, related_name='revisions')
- revision = models.PositiveIntegerField(blank=True)
- title = models.CharField(max_length=300)
- author = models.ForeignKey(User, related_name='question_revisions')
- revised_at = models.DateTimeField()
- tagnames = models.CharField(max_length=125)
- summary = models.CharField(max_length=300, blank=True)
- text = models.TextField()
-
- class Meta:
- db_table = u'question_revision'
- ordering = ('-revision',)
-
- def get_question_title(self):
- return self.question.title
-
- def get_absolute_url(self):
- return '/questions/%s/revisions' % (self.question.id)
-
- def save(self, **kwargs):
- """Looks up the next available revision number."""
- if not self.revision:
- self.revision = QuestionRevision.objects.filter(
- question=self.question).values_list('revision',
- flat=True)[0] + 1
- super(QuestionRevision, self).save(**kwargs)
-
- def __unicode__(self):
- return u'revision %s of %s' % (self.revision, self.title)
-
-class Answer(models.Model):
- question = models.ForeignKey(Question, related_name='answers')
- author = models.ForeignKey(User, related_name='answers')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- # Status
- wiki = models.BooleanField(default=False)
- wikified_at = models.DateTimeField(null=True, blank=True)
- accepted = models.BooleanField(default=False)
- accepted_at = models.DateTimeField(null=True, blank=True)
- deleted = models.BooleanField(default=False)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers')
- locked = models.BooleanField(default=False)
- locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers')
- locked_at = models.DateTimeField(null=True, blank=True)
- # Denormalised data
- score = models.IntegerField(default=0)
- vote_up_count = models.IntegerField(default=0)
- vote_down_count = models.IntegerField(default=0)
- comment_count = models.PositiveIntegerField(default=0)
- offensive_flag_count = models.SmallIntegerField(default=0)
- last_edited_at = models.DateTimeField(null=True, blank=True)
- last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers')
- html = models.TextField()
- comments = generic.GenericRelation(Comment)
- votes = generic.GenericRelation(Vote)
- flagged_items = generic.GenericRelation(FlaggedItem)
-
- objects = AnswerManager()
-
- def get_user_vote(self, user):
- votes = self.votes.filter(user=user)
- if votes.count() > 0:
- return votes[0]
- else:
- return None
-
- def get_latest_revision(self):
- return self.revisions.all()[0]
-
- def get_question_title(self):
- return self.question.title
-
- def get_absolute_url(self):
- return '%s%s#%s' % (reverse('question', args=[self.question.id]), self.question.title, self.id)
-
- class Meta:
- db_table = u'answer'
-
- def __unicode__(self):
- return self.html
-
-class AnswerRevision(models.Model):
- """A revision of an Answer."""
- answer = models.ForeignKey(Answer, related_name='revisions')
- revision = models.PositiveIntegerField()
- author = models.ForeignKey(User, related_name='answer_revisions')
- revised_at = models.DateTimeField()
- summary = models.CharField(max_length=300, blank=True)
- text = models.TextField()
-
- def get_absolute_url(self):
- return '/answers/%s/revisions' % (self.answer.id)
-
- def get_question_title(self):
- return self.answer.question.title
-
- class Meta:
- db_table = u'answer_revision'
- ordering = ('-revision',)
-
- def save(self, **kwargs):
- """Looks up the next available revision number if not set."""
- if not self.revision:
- self.revision = AnswerRevision.objects.filter(
- answer=self.answer).values_list('revision',
- flat=True)[0] + 1
- super(AnswerRevision, self).save(**kwargs)
-
-class FavoriteQuestion(models.Model):
- """A favorite Question of a User."""
- question = models.ForeignKey(Question)
- user = models.ForeignKey(User, related_name='user_favorite_questions')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- class Meta:
- db_table = u'favorite_question'
- def __unicode__(self):
- return '[%s] favorited at %s' %(self.user, self.added_at)
-
-class Badge(models.Model):
- """Awarded for notable actions performed on the site by Users."""
- GOLD = 1
- SILVER = 2
- BRONZE = 3
- TYPE_CHOICES = (
- (GOLD, _('gold')),
- (SILVER, _('silver')),
- (BRONZE, _('bronze')),
- )
-
- name = models.CharField(max_length=50)
- type = models.SmallIntegerField(choices=TYPE_CHOICES)
- slug = models.SlugField(max_length=50, blank=True)
- description = models.CharField(max_length=300)
- multiple = models.BooleanField(default=False)
- # Denormalised data
- awarded_count = models.PositiveIntegerField(default=0)
-
- class Meta:
- db_table = u'badge'
- ordering = ('name',)
- unique_together = ('name', 'type')
-
- def __unicode__(self):
- return u'%s: %s' % (self.get_type_display(), self.name)
-
- def save(self, **kwargs):
- if not self.slug:
- self.slug = self.name#slugify(self.name)
- super(Badge, self).save(**kwargs)
-
- def get_absolute_url(self):
- return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
-
-class Award(models.Model):
- """The awarding of a Badge to a User."""
- user = models.ForeignKey(User, related_name='award_user')
- badge = models.ForeignKey(Badge, related_name='award_badge')
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- awarded_at = models.DateTimeField(default=datetime.datetime.now)
- notified = models.BooleanField(default=False)
- objects = AwardManager()
-
- def __unicode__(self):
- return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
-
- class Meta:
- db_table = u'award'
-
-class Repute(models.Model):
- """The reputation histories for user"""
- user = models.ForeignKey(User)
- positive = models.SmallIntegerField(default=0)
- negative = models.SmallIntegerField(default=0)
- question = models.ForeignKey(Question)
- reputed_at = models.DateTimeField(default=datetime.datetime.now)
- reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
- reputation = models.IntegerField(default=1)
- objects = ReputeManager()
-
- def __unicode__(self):
- return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
-
- class Meta:
- db_table = u'repute'
-
-class Activity(models.Model):
- """
- We keep some history data for user activities
- """
- user = models.ForeignKey(User)
- activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)
- active_at = models.DateTimeField(default=datetime.datetime.now)
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- is_auditted = models.BooleanField(default=False)
-
- def __unicode__(self):
- return u'[%s] was active at %s' % (self.user.username, self.active_at)
-
- class Meta:
- db_table = u'activity'
-
-class Book(models.Model):
- """
- Model for book info
- """
- user = models.ForeignKey(User)
- title = models.CharField(max_length=255)
- short_name = models.CharField(max_length=255)
- author = models.CharField(max_length=255)
- price = models.DecimalField(max_digits=6, decimal_places=2)
- pages = models.SmallIntegerField()
- published_at = models.DateTimeField()
- publication = models.CharField(max_length=255)
- cover_img = models.CharField(max_length=255)
- tagnames = models.CharField(max_length=125)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
- questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
-
- def get_absolute_url(self):
- return '%s' % reverse('book', args=[self.short_name])
-
- def __unicode__(self):
- return self.title
- class Meta:
- db_table = u'book'
-
-class BookAuthorInfo(models.Model):
- """
- Model for book author info
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- blog_url = models.CharField(max_length=255)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
-
- class Meta:
- db_table = u'book_author_info'
-
-class BookAuthorRss(models.Model):
- """
- Model for book author blog rss
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- title = models.CharField(max_length=255)
- url = models.CharField(max_length=255)
- rss_created_at = models.DateTimeField()
- added_at = models.DateTimeField()
-
- class Meta:
- db_table = u'book_author_rss'
-
-# User extend properties
-QUESTIONS_PER_PAGE_CHOICES = (
- (10, u'10'),
- (30, u'30'),
- (50, u'50'),
-)
-
-User.add_to_class('reputation', models.PositiveIntegerField(default=1))
-User.add_to_class('gravatar', models.CharField(max_length=32))
-User.add_to_class('favorite_questions',
- models.ManyToManyField(Question, through=FavoriteQuestion,
- related_name='favorited_by'))
-User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
- related_name='awarded_to'))
-User.add_to_class('gold', models.SmallIntegerField(default=0))
-User.add_to_class('silver', models.SmallIntegerField(default=0))
-User.add_to_class('bronze', models.SmallIntegerField(default=0))
-User.add_to_class('questions_per_page',
- models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
-User.add_to_class('last_seen',
- models.DateTimeField(default=datetime.datetime.now))
-User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
-User.add_to_class('website', models.URLField(max_length=200, blank=True))
-User.add_to_class('location', models.CharField(max_length=100, blank=True))
-User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
-User.add_to_class('about', models.TextField(blank=True))
-
-# custom signal
-tags_updated = django.dispatch.Signal(providing_args=["question"])
-edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"])
-delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"])
-mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"])
-user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
-def get_messages(self):
- messages = []
- for m in self.message_set.all():
- messages.append(m.message)
- return messages
-
-def delete_messages(self):
- self.message_set.all().delete()
-
-def get_profile_url(self):
- """Returns the URL for this User's profile."""
- return '%s%s/' % (reverse('user', args=[self.id]), self.username)
-User.add_to_class('get_profile_url', get_profile_url)
-User.add_to_class('get_messages', get_messages)
-User.add_to_class('delete_messages', delete_messages)
-
-def calculate_gravatar_hash(instance, **kwargs):
- """Calculates a User's gravatar hash from their email address."""
- if kwargs.get('raw', False):
- return
- instance.gravatar = hashlib.md5(instance.email).hexdigest()
-
-def record_ask_event(instance, created, **kwargs):
- if created:
- activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)
- activity.save()
-
-def record_answer_event(instance, created, **kwargs):
- if created:
- activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)
- activity.save()
-
-def record_comment_event(instance, created, **kwargs):
- if created:
- from django.contrib.contenttypes.models import ContentType
- question_type = ContentType.objects.get_for_model(Question)
- question_type_id = question_type.id
- type = TYPE_ACTIVITY_COMMENT_QUESTION if instance.content_type_id == question_type_id else TYPE_ACTIVITY_COMMENT_ANSWER
- activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type)
- activity.save()
-
-def record_revision_question_event(instance, created, **kwargs):
- if created and instance.revision <> 1:
- activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION)
- activity.save()
-
-def record_revision_answer_event(instance, created, **kwargs):
- if created and instance.revision <> 1:
- activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER)
- activity.save()
-
-def record_award_event(instance, created, **kwargs):
- """
- After we awarded a badge to user, we need to record this activity and notify user.
- We also recaculate awarded_count of this badge and user information.
- """
- if created:
- activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,
- activity_type=TYPE_ACTIVITY_PRIZE)
- activity.save()
-
- instance.badge.awarded_count += 1
- instance.badge.save()
-
- if instance.badge.type == Badge.GOLD:
- instance.user.gold += 1
- if instance.badge.type == Badge.SILVER:
- instance.user.silver += 1
- if instance.badge.type == Badge.BRONZE:
- instance.user.bronze += 1
- instance.user.save()
-
-def notify_award_message(instance, created, **kwargs):
- """
- Notify users when they have been awarded badges by using Django message.
- """
- if created:
- user = instance.user
- user.message_set.create(message=u"%s" % instance.badge.name)
-
-def record_answer_accepted(instance, created, **kwargs):
- """
- when answer is accepted, we record this for question author - who accepted it.
- """
- if not created and instance.accepted:
- activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \
- content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER)
- activity.save()
-
-def update_last_seen(instance, created, **kwargs):
- """
- when user has activities, we update 'last_seen' time stamp for him
- """
- user = instance.user
- user.last_seen = datetime.datetime.now()
- user.save()
-
-def record_vote(instance, created, **kwargs):
- """
- when user have voted
- """
- if created:
- if instance.vote == 1:
- vote_type = TYPE_ACTIVITY_VOTE_UP
- else:
- vote_type = TYPE_ACTIVITY_VOTE_DOWN
-
- activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type)
- activity.save()
-
-def record_cancel_vote(instance, **kwargs):
- """
- when user canceled vote, the vote will be deleted.
- """
- activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE)
- activity.save()
-
-def record_delete_question(instance, delete_by, **kwargs):
- """
- when user deleted the question
- """
- if instance.__class__ == "Question":
- activity_type = TYPE_ACTIVITY_DELETE_QUESTION
- else:
- activity_type = TYPE_ACTIVITY_DELETE_ANSWER
-
- activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type)
- activity.save()
-
-def record_mark_offensive(instance, mark_by, **kwargs):
- activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)
- activity.save()
-
-def record_update_tags(question, **kwargs):
- """
- when user updated tags of the question
- """
- activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)
- activity.save()
-
-def record_favorite_question(instance, created, **kwargs):
- """
- when user add the question in him favorite questions list.
- """
- if created:
- activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)
- activity.save()
-
-def record_user_full_updated(instance, **kwargs):
- activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)
- activity.save()
-
-#signal for User modle save changes
-pre_save.connect(calculate_gravatar_hash, sender=User)
-post_save.connect(record_ask_event, sender=Question)
-post_save.connect(record_answer_event, sender=Answer)
-post_save.connect(record_comment_event, sender=Comment)
-post_save.connect(record_revision_question_event, sender=QuestionRevision)
-post_save.connect(record_revision_answer_event, sender=AnswerRevision)
-post_save.connect(record_award_event, sender=Award)
-post_save.connect(notify_award_message, sender=Award)
-post_save.connect(record_answer_accepted, sender=Answer)
-post_save.connect(update_last_seen, sender=Activity)
-post_save.connect(record_vote, sender=Vote)
-post_delete.connect(record_cancel_vote, sender=Vote)
-delete_post_or_answer.connect(record_delete_question, sender=Question)
-delete_post_or_answer.connect(record_delete_question, sender=Answer)
-mark_offensive.connect(record_mark_offensive, sender=Question)
-mark_offensive.connect(record_mark_offensive, sender=Answer)
-tags_updated.connect(record_update_tags, sender=Question)
-post_save.connect(record_favorite_question, sender=FavoriteQuestion)
-user_updated.connect(record_user_full_updated, sender=User)
+# encoding:utf-8 +import datetime +import hashlib +from urllib import quote_plus, urlencode +from django.db import models +from django.utils.html import strip_tags +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from django.contrib.contenttypes import generic +from django.contrib.contenttypes.models import ContentType +from django.template.defaultfilters import slugify +from django.db.models.signals import post_delete, post_save, pre_save +from django.utils.translation import ugettext as _ +import django.dispatch + +from forum.managers import * +from const import * + +class Tag(models.Model): + name = models.CharField(max_length=255, unique=True) + created_by = models.ForeignKey(User, related_name='created_tags') + deleted = models.BooleanField(default=False) + deleted_at = models.DateTimeField(null=True, blank=True) + deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags') + # Denormalised data + used_count = models.PositiveIntegerField(default=0) + + objects = TagManager() + + class Meta: + db_table = u'tag' + ordering = ('-used_count', 'name') + + def __unicode__(self): + return self.name + +class Comment(models.Model): + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + user = models.ForeignKey(User, related_name='comments') + comment = models.CharField(max_length=300) + added_at = models.DateTimeField(default=datetime.datetime.now) + + class Meta: + ordering = ('-added_at',) + db_table = u'comment' + def __unicode__(self): + return self.comment + +class Vote(models.Model): + VOTE_UP = +1 + VOTE_DOWN = -1 + VOTE_CHOICES = ( + (VOTE_UP, u'Up'), + (VOTE_DOWN, u'Down'), + ) + + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + user = models.ForeignKey(User, related_name='votes') + vote = models.SmallIntegerField(choices=VOTE_CHOICES) + voted_at = models.DateTimeField(default=datetime.datetime.now) + + objects = VoteManager() + + class Meta: + unique_together = ('content_type', 'object_id', 'user') + db_table = u'vote' + def __unicode__(self): + return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote) + + def is_upvote(self): + return self.vote == self.VOTE_UP + + def is_downvote(self): + return self.vote == self.VOTE_DOWN + +class FlaggedItem(models.Model): + """A flag on a Question or Answer indicating offensive content.""" + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + user = models.ForeignKey(User, related_name='flagged_items') + flagged_at = models.DateTimeField(default=datetime.datetime.now) + + objects = FlaggedItemManager() + + class Meta: + unique_together = ('content_type', 'object_id', 'user') + db_table = u'flagged_item' + def __unicode__(self): + return '[%s] flagged at %s' %(self.user, self.flagged_at) + +class Question(models.Model): + title = models.CharField(max_length=300) + author = models.ForeignKey(User, related_name='questions') + added_at = models.DateTimeField(default=datetime.datetime.now) + tags = models.ManyToManyField(Tag, related_name='questions') + # Status + wiki = models.BooleanField(default=False) + wikified_at = models.DateTimeField(null=True, blank=True) + answer_accepted = models.BooleanField(default=False) + closed = models.BooleanField(default=False) + closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions') + closed_at = models.DateTimeField(null=True, blank=True) + close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True) + deleted = models.BooleanField(default=False) + deleted_at = models.DateTimeField(null=True, blank=True) + deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions') + locked = models.BooleanField(default=False) + locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions') + locked_at = models.DateTimeField(null=True, blank=True) + # Denormalised data + score = models.IntegerField(default=0) + vote_up_count = models.IntegerField(default=0) + vote_down_count = models.IntegerField(default=0) + answer_count = models.PositiveIntegerField(default=0) + comment_count = models.PositiveIntegerField(default=0) + view_count = models.PositiveIntegerField(default=0) + offensive_flag_count = models.SmallIntegerField(default=0) + favourite_count = models.PositiveIntegerField(default=0) + last_edited_at = models.DateTimeField(null=True, blank=True) + last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions') + last_activity_at = models.DateTimeField(default=datetime.datetime.now) + last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions') + tagnames = models.CharField(max_length=125) + summary = models.CharField(max_length=180) + html = models.TextField() + comments = generic.GenericRelation(Comment) + votes = generic.GenericRelation(Vote) + flagged_items = generic.GenericRelation(FlaggedItem) + + objects = QuestionManager() + + def save(self, **kwargs): + """ + Overridden to manually manage addition of tags when the object + is first saved. + + This is required as we're using ``tagnames`` as the sole means of + adding and editing tags. + """ + initial_addition = (self.id is None) + super(Question, self).save(**kwargs) + if initial_addition: + tags = Tag.objects.get_or_create_multiple(self.tagname_list(), + self.author) + self.tags.add(*tags) + Tag.objects.update_use_counts(tags) + + def tagname_list(self): + """Creates a list of Tag names from the ``tagnames`` attribute.""" + return [name for name in self.tagnames.split(u' ')] + + def get_absolute_url(self): + return '%s%s' % (reverse('question', args=[self.id]), self.title.replace(' ', '-')) + + def has_favorite_by_user(self, user): + if not user.is_authenticated(): + return False + return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0 + + def get_answer_count_by_user(self, user_id): + query_set = Answer.objects.filter(author__id=user_id) + return query_set.filter(question=self).count() + + def get_question_title(self): + if self.closed: + attr = CONST['closed'] + elif self.deleted: + attr = CONST['deleted'] + else: + attr = None + return u'%s %s' % (self.title, attr) if attr is not None else self.title + + def get_revision_url(self): + return reverse('question_revisions', args=[self.id]) + + def get_latest_revision(self): + return self.revisions.all()[0] + + def __unicode__(self): + return self.title + + class Meta: + db_table = u'question' + +class QuestionRevision(models.Model): + """A revision of a Question.""" + question = models.ForeignKey(Question, related_name='revisions') + revision = models.PositiveIntegerField(blank=True) + title = models.CharField(max_length=300) + author = models.ForeignKey(User, related_name='question_revisions') + revised_at = models.DateTimeField() + tagnames = models.CharField(max_length=125) + summary = models.CharField(max_length=300, blank=True) + text = models.TextField() + + class Meta: + db_table = u'question_revision' + ordering = ('-revision',) + + def get_question_title(self): + return self.question.title + + def get_absolute_url(self): + return '/questions/%s/revisions' % (self.question.id) + + def save(self, **kwargs): + """Looks up the next available revision number.""" + if not self.revision: + self.revision = QuestionRevision.objects.filter( + question=self.question).values_list('revision', + flat=True)[0] + 1 + super(QuestionRevision, self).save(**kwargs) + + def __unicode__(self): + return u'revision %s of %s' % (self.revision, self.title) + +class Answer(models.Model): + question = models.ForeignKey(Question, related_name='answers') + author = models.ForeignKey(User, related_name='answers') + added_at = models.DateTimeField(default=datetime.datetime.now) + # Status + wiki = models.BooleanField(default=False) + wikified_at = models.DateTimeField(null=True, blank=True) + accepted = models.BooleanField(default=False) + accepted_at = models.DateTimeField(null=True, blank=True) + deleted = models.BooleanField(default=False) + deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers') + locked = models.BooleanField(default=False) + locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers') + locked_at = models.DateTimeField(null=True, blank=True) + # Denormalised data + score = models.IntegerField(default=0) + vote_up_count = models.IntegerField(default=0) + vote_down_count = models.IntegerField(default=0) + comment_count = models.PositiveIntegerField(default=0) + offensive_flag_count = models.SmallIntegerField(default=0) + last_edited_at = models.DateTimeField(null=True, blank=True) + last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers') + html = models.TextField() + comments = generic.GenericRelation(Comment) + votes = generic.GenericRelation(Vote) + flagged_items = generic.GenericRelation(FlaggedItem) + + objects = AnswerManager() + + def get_user_vote(self, user): + votes = self.votes.filter(user=user) + if votes.count() > 0: + return votes[0] + else: + return None + + def get_latest_revision(self): + return self.revisions.all()[0] + + def get_question_title(self): + return self.question.title + + def get_absolute_url(self): + return '%s%s#%s' % (reverse('question', args=[self.question.id]), self.question.title, self.id) + + class Meta: + db_table = u'answer' + + def __unicode__(self): + return self.html + +class AnswerRevision(models.Model): + """A revision of an Answer.""" + answer = models.ForeignKey(Answer, related_name='revisions') + revision = models.PositiveIntegerField() + author = models.ForeignKey(User, related_name='answer_revisions') + revised_at = models.DateTimeField() + summary = models.CharField(max_length=300, blank=True) + text = models.TextField() + + def get_absolute_url(self): + return '/answers/%s/revisions' % (self.answer.id) + + def get_question_title(self): + return self.answer.question.title + + class Meta: + db_table = u'answer_revision' + ordering = ('-revision',) + + def save(self, **kwargs): + """Looks up the next available revision number if not set.""" + if not self.revision: + self.revision = AnswerRevision.objects.filter( + answer=self.answer).values_list('revision', + flat=True)[0] + 1 + super(AnswerRevision, self).save(**kwargs) + +class FavoriteQuestion(models.Model): + """A favorite Question of a User.""" + question = models.ForeignKey(Question) + user = models.ForeignKey(User, related_name='user_favorite_questions') + added_at = models.DateTimeField(default=datetime.datetime.now) + class Meta: + db_table = u'favorite_question' + def __unicode__(self): + return '[%s] favorited at %s' %(self.user, self.added_at) + +class Badge(models.Model): + """Awarded for notable actions performed on the site by Users.""" + GOLD = 1 + SILVER = 2 + BRONZE = 3 + TYPE_CHOICES = ( + (GOLD, _('gold')), + (SILVER, _('silver')), + (BRONZE, _('bronze')), + ) + + name = models.CharField(max_length=50) + type = models.SmallIntegerField(choices=TYPE_CHOICES) + slug = models.SlugField(max_length=50, blank=True) + description = models.CharField(max_length=300) + multiple = models.BooleanField(default=False) + # Denormalised data + awarded_count = models.PositiveIntegerField(default=0) + + class Meta: + db_table = u'badge' + ordering = ('name',) + unique_together = ('name', 'type') + + def __unicode__(self): + return u'%s: %s' % (self.get_type_display(), self.name) + + def save(self, **kwargs): + if not self.slug: + self.slug = self.name#slugify(self.name) + super(Badge, self).save(**kwargs) + + def get_absolute_url(self): + return '%s%s/' % (reverse('badge', args=[self.id]), self.slug) + +class Award(models.Model): + """The awarding of a Badge to a User.""" + user = models.ForeignKey(User, related_name='award_user') + badge = models.ForeignKey(Badge, related_name='award_badge') + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + awarded_at = models.DateTimeField(default=datetime.datetime.now) + notified = models.BooleanField(default=False) + objects = AwardManager() + + def __unicode__(self): + return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at) + + class Meta: + db_table = u'award' + +class Repute(models.Model): + """The reputation histories for user""" + user = models.ForeignKey(User) + positive = models.SmallIntegerField(default=0) + negative = models.SmallIntegerField(default=0) + question = models.ForeignKey(Question) + reputed_at = models.DateTimeField(default=datetime.datetime.now) + reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION) + reputation = models.IntegerField(default=1) + objects = ReputeManager() + + def __unicode__(self): + return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at) + + class Meta: + db_table = u'repute' + +class Activity(models.Model): + """ + We keep some history data for user activities + """ + user = models.ForeignKey(User) + activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY) + active_at = models.DateTimeField(default=datetime.datetime.now) + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + is_auditted = models.BooleanField(default=False) + + def __unicode__(self): + return u'[%s] was active at %s' % (self.user.username, self.active_at) + + class Meta: + db_table = u'activity' + +class Book(models.Model): + """ + Model for book info + """ + user = models.ForeignKey(User) + title = models.CharField(max_length=255) + short_name = models.CharField(max_length=255) + author = models.CharField(max_length=255) + price = models.DecimalField(max_digits=6, decimal_places=2) + pages = models.SmallIntegerField() + published_at = models.DateTimeField() + publication = models.CharField(max_length=255) + cover_img = models.CharField(max_length=255) + tagnames = models.CharField(max_length=125) + added_at = models.DateTimeField() + last_edited_at = models.DateTimeField() + questions = models.ManyToManyField(Question, related_name='book', db_table='book_question') + + def get_absolute_url(self): + return '%s' % reverse('book', args=[self.short_name]) + + def __unicode__(self): + return self.title + class Meta: + db_table = u'book' + +class BookAuthorInfo(models.Model): + """ + Model for book author info + """ + user = models.ForeignKey(User) + book = models.ForeignKey(Book) + blog_url = models.CharField(max_length=255) + added_at = models.DateTimeField() + last_edited_at = models.DateTimeField() + + class Meta: + db_table = u'book_author_info' + +class BookAuthorRss(models.Model): + """ + Model for book author blog rss + """ + user = models.ForeignKey(User) + book = models.ForeignKey(Book) + title = models.CharField(max_length=255) + url = models.CharField(max_length=255) + rss_created_at = models.DateTimeField() + added_at = models.DateTimeField() + + class Meta: + db_table = u'book_author_rss' + +# User extend properties +QUESTIONS_PER_PAGE_CHOICES = ( + (10, u'10'), + (30, u'30'), + (50, u'50'), +) + +User.add_to_class('reputation', models.PositiveIntegerField(default=1)) +User.add_to_class('gravatar', models.CharField(max_length=32)) +User.add_to_class('favorite_questions', + models.ManyToManyField(Question, through=FavoriteQuestion, + related_name='favorited_by')) +User.add_to_class('badges', models.ManyToManyField(Badge, through=Award, + related_name='awarded_to')) +User.add_to_class('gold', models.SmallIntegerField(default=0)) +User.add_to_class('silver', models.SmallIntegerField(default=0)) +User.add_to_class('bronze', models.SmallIntegerField(default=0)) +User.add_to_class('questions_per_page', + models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10)) +User.add_to_class('last_seen', + models.DateTimeField(default=datetime.datetime.now)) +User.add_to_class('real_name', models.CharField(max_length=100, blank=True)) +User.add_to_class('website', models.URLField(max_length=200, blank=True)) +User.add_to_class('location', models.CharField(max_length=100, blank=True)) +User.add_to_class('date_of_birth', models.DateField(null=True, blank=True)) +User.add_to_class('about', models.TextField(blank=True)) + +# custom signal +tags_updated = django.dispatch.Signal(providing_args=["question"]) +edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"]) +delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"]) +mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"]) +user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"]) +def get_messages(self): + messages = [] + for m in self.message_set.all(): + messages.append(m.message) + return messages + +def delete_messages(self): + self.message_set.all().delete() + +def get_profile_url(self): + """Returns the URL for this User's profile.""" + return '%s%s/' % (reverse('user', args=[self.id]), self.username) +User.add_to_class('get_profile_url', get_profile_url) +User.add_to_class('get_messages', get_messages) +User.add_to_class('delete_messages', delete_messages) + +def calculate_gravatar_hash(instance, **kwargs): + """Calculates a User's gravatar hash from their email address.""" + if kwargs.get('raw', False): + return + instance.gravatar = hashlib.md5(instance.email).hexdigest() + +def record_ask_event(instance, created, **kwargs): + if created: + activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION) + activity.save() + +def record_answer_event(instance, created, **kwargs): + if created: + activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER) + activity.save() + +def record_comment_event(instance, created, **kwargs): + if created: + from django.contrib.contenttypes.models import ContentType + question_type = ContentType.objects.get_for_model(Question) + question_type_id = question_type.id + type = TYPE_ACTIVITY_COMMENT_QUESTION if instance.content_type_id == question_type_id else TYPE_ACTIVITY_COMMENT_ANSWER + activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type) + activity.save() + +def record_revision_question_event(instance, created, **kwargs): + if created and instance.revision <> 1: + activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION) + activity.save() + +def record_revision_answer_event(instance, created, **kwargs): + if created and instance.revision <> 1: + activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER) + activity.save() + +def record_award_event(instance, created, **kwargs): + """ + After we awarded a badge to user, we need to record this activity and notify user. + We also recaculate awarded_count of this badge and user information. + """ + if created: + activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance, + activity_type=TYPE_ACTIVITY_PRIZE) + activity.save() + + instance.badge.awarded_count += 1 + instance.badge.save() + + if instance.badge.type == Badge.GOLD: + instance.user.gold += 1 + if instance.badge.type == Badge.SILVER: + instance.user.silver += 1 + if instance.badge.type == Badge.BRONZE: + instance.user.bronze += 1 + instance.user.save() + +def notify_award_message(instance, created, **kwargs): + """ + Notify users when they have been awarded badges by using Django message. + """ + if created: + user = instance.user + user.message_set.create(message=u"%s" % instance.badge.name) + +def record_answer_accepted(instance, created, **kwargs): + """ + when answer is accepted, we record this for question author - who accepted it. + """ + if not created and instance.accepted: + activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \ + content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER) + activity.save() + +def update_last_seen(instance, created, **kwargs): + """ + when user has activities, we update 'last_seen' time stamp for him + """ + user = instance.user + user.last_seen = datetime.datetime.now() + user.save() + +def record_vote(instance, created, **kwargs): + """ + when user have voted + """ + if created: + if instance.vote == 1: + vote_type = TYPE_ACTIVITY_VOTE_UP + else: + vote_type = TYPE_ACTIVITY_VOTE_DOWN + + activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type) + activity.save() + +def record_cancel_vote(instance, **kwargs): + """ + when user canceled vote, the vote will be deleted. + """ + activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE) + activity.save() + +def record_delete_question(instance, delete_by, **kwargs): + """ + when user deleted the question + """ + if instance.__class__ == "Question": + activity_type = TYPE_ACTIVITY_DELETE_QUESTION + else: + activity_type = TYPE_ACTIVITY_DELETE_ANSWER + + activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type) + activity.save() + +def record_mark_offensive(instance, mark_by, **kwargs): + activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE) + activity.save() + +def record_update_tags(question, **kwargs): + """ + when user updated tags of the question + """ + activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS) + activity.save() + +def record_favorite_question(instance, created, **kwargs): + """ + when user add the question in him favorite questions list. + """ + if created: + activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE) + activity.save() + +def record_user_full_updated(instance, **kwargs): + activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED) + activity.save() + +#signal for User modle save changes +pre_save.connect(calculate_gravatar_hash, sender=User) +post_save.connect(record_ask_event, sender=Question) +post_save.connect(record_answer_event, sender=Answer) +post_save.connect(record_comment_event, sender=Comment) +post_save.connect(record_revision_question_event, sender=QuestionRevision) +post_save.connect(record_revision_answer_event, sender=AnswerRevision) +post_save.connect(record_award_event, sender=Award) +post_save.connect(notify_award_message, sender=Award) +post_save.connect(record_answer_accepted, sender=Answer) +post_save.connect(update_last_seen, sender=Activity) +post_save.connect(record_vote, sender=Vote) +post_delete.connect(record_cancel_vote, sender=Vote) +delete_post_or_answer.connect(record_delete_question, sender=Question) +delete_post_or_answer.connect(record_delete_question, sender=Answer) +mark_offensive.connect(record_mark_offensive, sender=Question) +mark_offensive.connect(record_mark_offensive, sender=Answer) +tags_updated.connect(record_update_tags, sender=Question) +post_save.connect(record_favorite_question, sender=FavoriteQuestion) +user_updated.connect(record_user_full_updated, sender=User) diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py index 744fa762..cec97920 100644 --- a/forum/templatetags/extra_filters.py +++ b/forum/templatetags/extra_filters.py @@ -1,83 +1,83 @@ -from django import template
-from forum import auth
-
-register = template.Library()
-
-@register.filter
-def can_vote_up(user):
- return auth.can_vote_up(user)
-
-@register.filter
-def can_flag_offensive(user):
- return auth.can_flag_offensive(user)
-
-@register.filter
-def can_add_comments(user):
- return auth.can_add_comments(user)
-
-@register.filter
-def can_vote_down(user):
- return auth.can_vote_down(user)
-
-@register.filter
-def can_retag_questions(user):
- return auth.can_retag_questions(user)
-
-@register.filter
-def can_edit_post(user, post):
- return auth.can_edit_post(user, post)
-
-@register.filter
-def can_delete_comment(user, comment):
- return auth.can_delete_comment(user, comment)
-
-@register.filter
-def can_view_offensive_flags(user):
- return auth.can_view_offensive_flags(user)
-
-@register.filter
-def can_close_question(user, question):
- return auth.can_close_question(user, question)
-
-@register.filter
-def can_lock_posts(user):
- return auth.can_lock_posts(user)
-
-@register.filter
-def can_accept_answer(user, question, answer):
- return auth.can_accept_answer(user, question, answer)
-
-@register.filter
-def can_reopen_question(user, question):
- return auth.can_reopen_question(user, question)
-
-@register.filter
-def can_delete_post(user, post):
- return auth.can_delete_post(user, post)
-
-@register.filter
-def can_view_user_edit(request_user, target_user):
- return auth.can_view_user_edit(request_user, target_user)
-
-@register.filter
-def can_view_user_votes(request_user, target_user):
- return auth.can_view_user_votes(request_user, target_user)
-
-@register.filter
-def can_view_user_preferences(request_user, target_user):
- return auth.can_view_user_preferences(request_user, target_user)
-
-@register.filter
-def is_user_self(request_user, target_user):
- return auth.is_user_self(request_user, target_user)
-
-@register.filter
-def cnprog_intword(number):
- try:
- if 1000 <= number < 10000:
- string = str(number)[0:1]
- return "<span class=""thousand"">%sk</span>" % string
- else:
- return number
- except:
+from django import template +from forum import auth + +register = template.Library() + +@register.filter +def can_vote_up(user): + return auth.can_vote_up(user) + +@register.filter +def can_flag_offensive(user): + return auth.can_flag_offensive(user) + +@register.filter +def can_add_comments(user): + return auth.can_add_comments(user) + +@register.filter +def can_vote_down(user): + return auth.can_vote_down(user) + +@register.filter +def can_retag_questions(user): + return auth.can_retag_questions(user) + +@register.filter +def can_edit_post(user, post): + return auth.can_edit_post(user, post) + +@register.filter +def can_delete_comment(user, comment): + return auth.can_delete_comment(user, comment) + +@register.filter +def can_view_offensive_flags(user): + return auth.can_view_offensive_flags(user) + +@register.filter +def can_close_question(user, question): + return auth.can_close_question(user, question) + +@register.filter +def can_lock_posts(user): + return auth.can_lock_posts(user) + +@register.filter +def can_accept_answer(user, question, answer): + return auth.can_accept_answer(user, question, answer) + +@register.filter +def can_reopen_question(user, question): + return auth.can_reopen_question(user, question) + +@register.filter +def can_delete_post(user, post): + return auth.can_delete_post(user, post) + +@register.filter +def can_view_user_edit(request_user, target_user): + return auth.can_view_user_edit(request_user, target_user) + +@register.filter +def can_view_user_votes(request_user, target_user): + return auth.can_view_user_votes(request_user, target_user) + +@register.filter +def can_view_user_preferences(request_user, target_user): + return auth.can_view_user_preferences(request_user, target_user) + +@register.filter +def is_user_self(request_user, target_user): + return auth.is_user_self(request_user, target_user) + +@register.filter +def cnprog_intword(number): + try: + if 1000 <= number < 10000: + string = str(number)[0:1] + return "<span class=""thousand"">%sk</span>" % string + else: + return number + except: return number
\ No newline at end of file diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py index 1a4d3641..6c826771 100644 --- a/forum/templatetags/extra_tags.py +++ b/forum/templatetags/extra_tags.py @@ -1,240 +1,240 @@ -import time
-import datetime
-import math
-import re
-import logging
-from django import template
-from django.utils.encoding import smart_unicode
-from django.utils.safestring import mark_safe
-from django.utils.timesince import timesince
-from forum.const import *
-from django.utils.translation import ugettext as _
-
-register = template.Library()
-
-GRAVATAR_TEMPLATE = ('<img width="%(size)s" height="%(size)s" '
- 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
- '?s=%(size)s&d=identicon&r=PG">')
-
-@register.simple_tag
-def gravatar(user, size):
- """
- Creates an ``<img>`` for a user's Gravatar with a given size.
-
- This tag can accept a User object, or a dict containing the
- appropriate values.
- """
- try:
- gravatar = user['gravatar']
- except (TypeError, AttributeError, KeyError):
- gravatar = user.gravatar
- return mark_safe(GRAVATAR_TEMPLATE % {
- 'size': size,
- 'gravatar_hash': gravatar,
- })
-
-MAX_FONTSIZE = 18
-MIN_FONTSIZE = 12
-@register.simple_tag
-def tag_font_size(max_size, min_size, current_size):
- """
- do a logarithmic mapping calcuation for a proper size for tagging cloud
- Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
- """
- #avoid invalid calculation
- if current_size == 0:
- current_size = 1
- try:
- weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
- except:
- weight = 0
- return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
-
-
-LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
-LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
-NUM_PAGES_OUTSIDE_RANGE = 1
-ADJACENT_PAGES = 2
-@register.inclusion_tag("paginator.html")
-def cnprog_paginator(context):
- """
- custom paginator tag
- Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
- """
- if (context["is_paginated"]):
- " Initialize variables "
- in_leading_range = in_trailing_range = False
- pages_outside_leading_range = pages_outside_trailing_range = range(0)
-
- if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
- in_leading_range = in_trailing_range = True
- page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
- elif (context["page"] <= LEADING_PAGE_RANGE):
- in_leading_range = True
- page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
- pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
- elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
- in_trailing_range = True
- page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
- pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
- else:
- page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
- pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
- pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
-
- extend_url = context.get('extend_url', '')
- return {
- "base_url": context["base_url"],
- "is_paginated": context["is_paginated"],
- "previous": context["previous"],
- "has_previous": context["has_previous"],
- "next": context["next"],
- "has_next": context["has_next"],
- "page": context["page"],
- "pages": context["pages"],
- "page_numbers": page_numbers,
- "in_leading_range" : in_leading_range,
- "in_trailing_range" : in_trailing_range,
- "pages_outside_leading_range": pages_outside_leading_range,
- "pages_outside_trailing_range": pages_outside_trailing_range,
- "extend_url" : extend_url
- }
-
-@register.inclusion_tag("pagesize.html")
-def cnprog_pagesize(context):
- """
- display the pagesize selection boxes for paginator
- """
- if (context["is_paginated"]):
- return {
- "base_url": context["base_url"],
- "pagesize" : context["pagesize"],
- "is_paginated": context["is_paginated"]
- }
-
-@register.simple_tag
-def get_score_badge(user):
- BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>'
- if user.gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
- '<span class="badge1">●</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if user.silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
- '<span class="silver">●</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if user.bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
- '<span class="bronze">●</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : user.reputation,
- 'gold' : user.gold,
- 'silver' : user.silver,
- 'bronze' : user.bronze,
- 'badgesword' : _('badges'),
- 'reputationword' : _('reputation points'),
- })
-
-@register.simple_tag
-def get_score_badge_by_details(rep, gold, silver, bronze):
- BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
- if gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
- '<span class="badge1">●</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
- '<span class="badge2">●</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
- '<span class="badge3">●</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : rep,
- 'gold' : gold,
- 'silver' : silver,
- 'bronze' : bronze,
- 'repword' : _('reputation points'),
- 'badgeword' : _('badges'),
- })
-
-@register.simple_tag
-def get_user_vote_image(dic, key, arrow):
- if dic.has_key(key):
- if int(dic[key]) == int(arrow):
- return '-on'
- return ''
-
-@register.simple_tag
-def get_age(birthday):
- current_time = datetime.datetime(*time.localtime()[0:6])
- year = birthday.year
- month = birthday.month
- day = birthday.day
- diff = current_time - datetime.datetime(year,month,day,0,0,0)
- return diff.days / 365
-
-@register.simple_tag
-def get_total_count(up_count, down_count):
- return up_count + down_count
-
-@register.simple_tag
-def format_number(value):
- strValue = str(value)
- if len(strValue) <= 3:
- return strValue
- result = ''
- first = ''
- pattern = re.compile('(-?\d+)(\d{3})')
- m = re.match(pattern, strValue)
- while m != None:
- first = m.group(1)
- second = m.group(2)
- result = ',' + second + result
- strValue = first + ',' + second
- m = re.match(pattern, strValue)
- return first + result
-
-@register.simple_tag
-def convert2tagname_list(question):
- question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
- return ''
-
-@register.simple_tag
-def diff_date(date, limen=2):
- current_time = datetime.datetime(*time.localtime()[0:6])
- diff = current_time - date
- diff_days = diff.days
- if diff_days > limen:
- return date
- else:
- return timesince(date) + _(' ago')
-
-@register.simple_tag
-def get_latest_changed_timestamp():
- try:
- from time import localtime, strftime
- from os import path
- from django.conf import settings
- root = settings.SITE_SRC_ROOT
- dir = (
- root,
- '%s/forum' % root,
- '%s/templates' % root,
- )
- stamp = (path.getmtime(d) for d in dir)
- latest = max(stamp)
- timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
- except:
- timestr = ''
+import time +import datetime +import math +import re +import logging +from django import template +from django.utils.encoding import smart_unicode +from django.utils.safestring import mark_safe +from django.utils.timesince import timesince +from forum.const import * +from django.utils.translation import ugettext as _ + +register = template.Library() + +GRAVATAR_TEMPLATE = ('<img width="%(size)s" height="%(size)s" ' + 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s' + '?s=%(size)s&d=identicon&r=PG">') + +@register.simple_tag +def gravatar(user, size): + """ + Creates an ``<img>`` for a user's Gravatar with a given size. + + This tag can accept a User object, or a dict containing the + appropriate values. + """ + try: + gravatar = user['gravatar'] + except (TypeError, AttributeError, KeyError): + gravatar = user.gravatar + return mark_safe(GRAVATAR_TEMPLATE % { + 'size': size, + 'gravatar_hash': gravatar, + }) + +MAX_FONTSIZE = 18 +MIN_FONTSIZE = 12 +@register.simple_tag +def tag_font_size(max_size, min_size, current_size): + """ + do a logarithmic mapping calcuation for a proper size for tagging cloud + Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/ + """ + #avoid invalid calculation + if current_size == 0: + current_size = 1 + try: + weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size)) + except: + weight = 0 + return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight) + + +LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5 +LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4 +NUM_PAGES_OUTSIDE_RANGE = 1 +ADJACENT_PAGES = 2 +@register.inclusion_tag("paginator.html") +def cnprog_paginator(context): + """ + custom paginator tag + Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/ + """ + if (context["is_paginated"]): + " Initialize variables " + in_leading_range = in_trailing_range = False + pages_outside_leading_range = pages_outside_trailing_range = range(0) + + if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED): + in_leading_range = in_trailing_range = True + page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]] + elif (context["page"] <= LEADING_PAGE_RANGE): + in_leading_range = True + page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]] + pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)] + elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE): + in_trailing_range = True + page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]] + pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)] + else: + page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]] + pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)] + pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)] + + extend_url = context.get('extend_url', '') + return { + "base_url": context["base_url"], + "is_paginated": context["is_paginated"], + "previous": context["previous"], + "has_previous": context["has_previous"], + "next": context["next"], + "has_next": context["has_next"], + "page": context["page"], + "pages": context["pages"], + "page_numbers": page_numbers, + "in_leading_range" : in_leading_range, + "in_trailing_range" : in_trailing_range, + "pages_outside_leading_range": pages_outside_leading_range, + "pages_outside_trailing_range": pages_outside_trailing_range, + "extend_url" : extend_url + } + +@register.inclusion_tag("pagesize.html") +def cnprog_pagesize(context): + """ + display the pagesize selection boxes for paginator + """ + if (context["is_paginated"]): + return { + "base_url": context["base_url"], + "pagesize" : context["pagesize"], + "is_paginated": context["is_paginated"] + } + +@register.simple_tag +def get_score_badge(user): + BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>' + if user.gold > 0 : + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">' + '<span class="badge1">●</span>' + '<span class="badgecount">%(gold)s</span>' + '</span>') + if user.silver > 0: + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">' + '<span class="silver">●</span>' + '<span class="badgecount">%(silver)s</span>' + '</span>') + if user.bronze > 0: + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">' + '<span class="bronze">●</span>' + '<span class="badgecount">%(bronze)s</span>' + '</span>') + BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict') + return mark_safe(BADGE_TEMPLATE % { + 'reputation' : user.reputation, + 'gold' : user.gold, + 'silver' : user.silver, + 'bronze' : user.bronze, + 'badgesword' : _('badges'), + 'reputationword' : _('reputation points'), + }) + +@register.simple_tag +def get_score_badge_by_details(rep, gold, silver, bronze): + BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>' + if gold > 0 : + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">' + '<span class="badge1">●</span>' + '<span class="badgecount">%(gold)s</span>' + '</span>') + if silver > 0: + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">' + '<span class="badge2">●</span>' + '<span class="badgecount">%(silver)s</span>' + '</span>') + if bronze > 0: + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">' + '<span class="badge3">●</span>' + '<span class="badgecount">%(bronze)s</span>' + '</span>') + BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict') + return mark_safe(BADGE_TEMPLATE % { + 'reputation' : rep, + 'gold' : gold, + 'silver' : silver, + 'bronze' : bronze, + 'repword' : _('reputation points'), + 'badgeword' : _('badges'), + }) + +@register.simple_tag +def get_user_vote_image(dic, key, arrow): + if dic.has_key(key): + if int(dic[key]) == int(arrow): + return '-on' + return '' + +@register.simple_tag +def get_age(birthday): + current_time = datetime.datetime(*time.localtime()[0:6]) + year = birthday.year + month = birthday.month + day = birthday.day + diff = current_time - datetime.datetime(year,month,day,0,0,0) + return diff.days / 365 + +@register.simple_tag +def get_total_count(up_count, down_count): + return up_count + down_count + +@register.simple_tag +def format_number(value): + strValue = str(value) + if len(strValue) <= 3: + return strValue + result = '' + first = '' + pattern = re.compile('(-?\d+)(\d{3})') + m = re.match(pattern, strValue) + while m != None: + first = m.group(1) + second = m.group(2) + result = ',' + second + result + strValue = first + ',' + second + m = re.match(pattern, strValue) + return first + result + +@register.simple_tag +def convert2tagname_list(question): + question['tagnames'] = [name for name in question['tagnames'].split(u' ')] + return '' + +@register.simple_tag +def diff_date(date, limen=2): + current_time = datetime.datetime(*time.localtime()[0:6]) + diff = current_time - date + diff_days = diff.days + if diff_days > limen: + return date + else: + return timesince(date) + _(' ago') + +@register.simple_tag +def get_latest_changed_timestamp(): + try: + from time import localtime, strftime + from os import path + from django.conf import settings + root = settings.SITE_SRC_ROOT + dir = ( + root, + '%s/forum' % root, + '%s/templates' % root, + ) + stamp = (path.getmtime(d) for d in dir) + latest = max(stamp) + timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest)) + except: + timestr = '' return timestr
\ No newline at end of file diff --git a/forum/user.py b/forum/user.py index 233baf0c..ed4494d6 100644 --- a/forum/user.py +++ b/forum/user.py @@ -1,75 +1,75 @@ -# coding=utf-8
-from django.utils.translation import ugettext as _
-class UserView:
- def __init__(self, id, tab_title, tab_description, page_title, view_name, template_file, data_size=0):
- self.id = id
- self.tab_title = tab_title
- self.tab_description = tab_description
- self.page_title = page_title
- self.view_name = view_name
- self.template_file = template_file
- self.data_size = data_size
-
-
-USER_TEMPLATE_VIEWS = (
- UserView(
- id = 'stats',
- tab_title = _('overview'),
- tab_description = _('user profile'),
- page_title = _('user profile overview'),
- view_name = 'user_stats',
- template_file = 'user_stats.html'
- ),
- UserView(
- id = 'recent',
- tab_title = _('recent activity'),
- tab_description = _('recent user activity'),
- page_title = _('profile - recent activity'),
- view_name = 'user_recent',
- template_file = 'user_recent.html',
- data_size = 50
- ),
- UserView(
- id = 'responses',
- tab_title = _('responses'),
- tab_description = _('comments and answers to others questions'),
- page_title = _('profile - responses'),
- view_name = 'user_responses',
- template_file = 'user_responses.html',
- data_size = 50
- ),
- UserView(
- id = 'reputation',
- tab_title = _('reputation'),
- tab_description = _('user reputation in the community'),
- page_title = _('profile - user reputation'),
- view_name = 'user_reputation',
- template_file = 'user_reputation.html'
- ),
- UserView(
- id = 'favorites',
- tab_title = _('favorite questions'),
- tab_description = _('users favorite questions'),
- page_title = _('profile - favorite questions'),
- view_name = 'user_favorites',
- template_file = 'user_favorites.html',
- data_size = 50
- ),
- UserView(
- id = 'votes',
- tab_title = _('casted votes'),
- tab_description = _('user vote record'),
- page_title = _('profile - votes'),
- view_name = 'user_votes',
- template_file = 'user_votes.html',
- data_size = 50
- ),
- UserView(
- id = 'preferences',
- tab_title = _('preferences'),
- tab_description = _('user preference settings'),
- page_title = _('profile - user preferences'),
- view_name = 'user_preferences',
- template_file = 'user_preferences.html'
- )
-)
+# coding=utf-8 +from django.utils.translation import ugettext as _ +class UserView: + def __init__(self, id, tab_title, tab_description, page_title, view_name, template_file, data_size=0): + self.id = id + self.tab_title = tab_title + self.tab_description = tab_description + self.page_title = page_title + self.view_name = view_name + self.template_file = template_file + self.data_size = data_size + + +USER_TEMPLATE_VIEWS = ( + UserView( + id = 'stats', + tab_title = _('overview'), + tab_description = _('user profile'), + page_title = _('user profile overview'), + view_name = 'user_stats', + template_file = 'user_stats.html' + ), + UserView( + id = 'recent', + tab_title = _('recent activity'), + tab_description = _('recent user activity'), + page_title = _('profile - recent activity'), + view_name = 'user_recent', + template_file = 'user_recent.html', + data_size = 50 + ), + UserView( + id = 'responses', + tab_title = _('responses'), + tab_description = _('comments and answers to others questions'), + page_title = _('profile - responses'), + view_name = 'user_responses', + template_file = 'user_responses.html', + data_size = 50 + ), + UserView( + id = 'reputation', + tab_title = _('reputation'), + tab_description = _('user reputation in the community'), + page_title = _('profile - user reputation'), + view_name = 'user_reputation', + template_file = 'user_reputation.html' + ), + UserView( + id = 'favorites', + tab_title = _('favorite questions'), + tab_description = _('users favorite questions'), + page_title = _('profile - favorite questions'), + view_name = 'user_favorites', + template_file = 'user_favorites.html', + data_size = 50 + ), + UserView( + id = 'votes', + tab_title = _('casted votes'), + tab_description = _('user vote record'), + page_title = _('profile - votes'), + view_name = 'user_votes', + template_file = 'user_votes.html', + data_size = 50 + ), + UserView( + id = 'preferences', + tab_title = _('preferences'), + tab_description = _('user preference settings'), + page_title = _('profile - user preferences'), + view_name = 'user_preferences', + template_file = 'user_preferences.html' + ) +) diff --git a/forum/views.py b/forum/views.py index 6ac172df..2c61d41e 100644 --- a/forum/views.py +++ b/forum/views.py @@ -115,7 +115,7 @@ def questions(request, tagname=None, unanswered=False): # Set flag to False by default. If it is equal to True, then need to be saved. pagesize_changed = False # get pagesize from session, if failed then get default value - pagesize = request.session.get("pagesize") + pagesize = request.session.get("pagesize",10) try: page = int(request.GET.get('page', '1')) except ValueError: |