From 11f2cc0579716d3f830e2a82d33a3f1162108dbf Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Fri, 3 Dec 2010 22:10:01 -0500 Subject: half of the badges are redone --- askbot/auth.py | 3 - askbot/conf/badges.py | 29 ++++- .../management/commands/load_stackexchange.py | 2 +- askbot/models/__init__.py | 15 ++- askbot/models/badges.py | 137 ++++++++++++++++++--- askbot/tests/badge_tests.py | 105 ++++++++++++++++ 6 files changed, 258 insertions(+), 33 deletions(-) diff --git a/askbot/auth.py b/askbot/auth.py index 05403634..7b4fac68 100644 --- a/askbot/auth.py +++ b/askbot/auth.py @@ -121,9 +121,6 @@ def onFlaggedItem(post, user, timestamp=None): @transaction.commit_on_success def onAnswerAccept(answer, user, timestamp=None): - if timestamp is None: - timestamp = datetime.datetime.now() - answer.accepted = True answer.accepted_at = timestamp answer.question.answer_accepted = True diff --git a/askbot/conf/badges.py b/askbot/conf/badges.py index d5f8313a..7019a69f 100644 --- a/askbot/conf/badges.py +++ b/askbot/conf/badges.py @@ -125,7 +125,34 @@ settings.register( IntegerValue( BADGES, 'SELF_LEARNER_BADGE_MIN_UPVOTES', - default=500, + default=1, description=_('Self-Learner: minimum answer upvotes') ) ) + +settings.register( + IntegerValue( + BADGES, + 'CIVIC_DUTY_BADGE_MIN_VOTES', + default=100, + description=_('Civic Duty: minimum votes') + ) +) + +settings.register( + IntegerValue( + BADGES, + 'ENLIGHTENED_BADGE_MIN_UPVOTES', + default=3, + description=_('Enlightened Duty: minimum upvotes') + ) +) + +settings.register( + IntegerValue( + BADGES, + 'GURU_BADGE_MIN_UPVOTES', + default=5, + description=_('Guru: minimum upvotes') + ) +) diff --git a/askbot/importers/stackexchange/management/commands/load_stackexchange.py b/askbot/importers/stackexchange/management/commands/load_stackexchange.py index a39a9cd6..96327323 100644 --- a/askbot/importers/stackexchange/management/commands/load_stackexchange.py +++ b/askbot/importers/stackexchange/management/commands/load_stackexchange.py @@ -59,7 +59,7 @@ class X(object):# vote_actions = { 'UpMod':'upvote', 'DownMod':'downvote', - 'AcceptedByOriginator':'accept_answer', + 'AcceptedByOriginator':'accept_best_answer', 'Offensive':'flag_post', 'Favorite':'toggle_favorite_question', } diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 43982e37..4280e650 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -745,7 +745,13 @@ def user_accept_best_answer(self, answer = None, timestamp = None): for prev_answer in prev_accepted_answers: auth.onAnswerAcceptCanceled(prev_answer, self) - auth.onAnswerAccept(answer, self) + auth.onAnswerAccept(answer, self, timestamp = timestamp) + award_badges_signal.send(None, + event = 'accept_best_answer', + actor = self, + context_object = answer, + timestamp = timestamp + ) @auto_now_timestamp def user_unaccept_best_answer(self, answer = None, timestamp = None): @@ -1414,12 +1420,6 @@ def downvote(self, post, timestamp=None, cancel=False): vote_type=Vote.VOTE_DOWN ) -def accept_answer(self, answer, timestamp=None, cancel=False): - if cancel: - auth.onAnswerAcceptCanceled(answer, self, timestamp=timestamp) - else: - auth.onAnswerAccept(answer, self, timestamp=timestamp) - @auto_now_timestamp def flag_post(user, post, timestamp=None, cancel=False): if cancel:#todo: can't unflag? @@ -1499,7 +1499,6 @@ User.add_to_class('delete_post', user_delete_post) User.add_to_class('visit_question', user_visit_question) User.add_to_class('upvote', upvote) User.add_to_class('downvote', downvote) -User.add_to_class('accept_answer', accept_answer) User.add_to_class('flag_post', flag_post) User.add_to_class('get_flags', user_get_flags) User.add_to_class('get_flag_count_posted_today', user_get_flag_count_posted_today) diff --git a/askbot/models/badges.py b/askbot/models/badges.py index 99a10ef2..46f10e04 100644 --- a/askbot/models/badges.py +++ b/askbot/models/badges.py @@ -28,6 +28,14 @@ from askbot.utils.decorators import auto_now_timestamp class Badge(object): """base class for the badges + + badges must implement method consider_award + which returns a boolean True if award succeds + and False otherwise + + consider_award assumes that the function is called + upon correct event, i.e. it is the responsibility of + the caller to try awarding badges at appropriate times """ def __init__(self, key = '', @@ -217,6 +225,25 @@ class Critic(FirstVote): self.description = _('First downvote') return self +class CivicDuty(Badge): + """awarded once after a certain number of votes""" + def __init__(self): + min_votes = askbot_settings.CIVIC_DUTY_BADGE_MIN_VOTES + super(CivicDuty, self).__init__( + key = 'civic-duty', + name = _('Civic Duty'), + description = _('Voted %(num)s times') % {'num': min_votes}, + level = const.SILVER_BADGE, + multiple = False + ) + + def consider_award(self, actor = None, + context_object = None, timestamp = None): + if context_object.post_type not in ('question', 'answer'): + return False + if actor.votes.count() == askbot_settings.CIVIC_DUTY_BADGE_MIN_VOTES: + return self.award(actor, context_object, timestamp) + class SelfLearner(Badge): def __init__(self): description = _('Answered own question with at least %(num)s up votes') @@ -412,15 +439,79 @@ class FamousQuestion(FrequentedQuestion): % {'views' : self.min_views} return self -ORIGINAL_DATA = """ - (_('Civic duty'), 2, _('civic-duty'), _('Voted 300 times'), False, 0), +class Scholar(Badge): + """scholar badge is awarded to the asker when + he/she accepts an answer for the first time + """ + def __init__(self): + description = _('Asked a question and accepted an answer') + super(Scholar, self).__init__( + key = 'scholar', + name = _('Scholar'), + level = const.BRONZE_BADGE, + multiple = False, + description = description + ) + + def consider_award(self, actor = None, + context_object = None, timestamp = None): + if context_object.post_type != 'answer': + return False + answer = context_object + if answer.question.author != actor: + return False + return self.award(actor, context_object, timestamp) + +class VotedAcceptedAnswer(Badge): + """superclass for Enlightened and Guru badges + not awarded directly + + Subclasses must define __new__ function + """ + def __init__(self): + super(VotedAcceptedAnswer, self).__init__( + key = self.key, + name = self.name, + level = self.level, + multiple = self.multiple, + description = self.description + ) + + def consider_award(self, actor = None, + context_object = None, timestamp = None): + if context_object.post_type != 'answer': + return None + answer = context_object + if answer.score >= self.min_votes and answer.accepted: + return self.award(answer.author, answer, timestamp) - (_('Enlightened'), 2, _('enlightened'), _('First answer was accepted with at least 10 up votes'), False, 0), - (_('Guru'), 2, _('guru'), _('Accepted answer and voted up 40 times'), True, 0), +class Enlightened(VotedAcceptedAnswer): + def __new__(cls): + self = super(Enlightened, cls).__new__(cls) + self.key = 'enlightened' + self.name = _('Enlightened') + self.level = const.SILVER_BADGE + self.multiple = False + self.min_votes = askbot_settings.ENLIGHTENED_BADGE_MIN_UPVOTES + descr = _('First answer was accepted with %(num)s or more votes') + self.description = descr % {'num': self.min_votes} + return self + +class Guru(VotedAcceptedAnswer): + def __new__(cls): + self = super(Guru, cls).__new__(cls) + self.key = 'guru' + self.name = _('Guru') + self.level = const.GOLD_BADGE + self.multiple = True + descr = _('Answer accepted with %(num)s or more votes') + self.min_votes = askbot_settings.GURU_BADGE_MIN_UPVOTES + self.description = descr % {'num': self.min_votes} + return self +ORIGINAL_DATA = """ (_('Necromancer'), 2, _('necromancer'), _('Answered a question more than 60 days later with at least 5 votes'), True, 0), - (_('Scholar'), 3, _('scholar'), _('First accepted answer on your own question'), False, 0), (_('Pundit'), 3, _('pundit'), _('Left 10 comments with score of 10 or more'), False, 0), (_('Citizen patrol'), 3, _('citizen-patrol'), _('First flagged post'), False, 0), @@ -435,7 +526,6 @@ ORIGINAL_DATA = """ (_('Stellar Question'), 1, _('stellar-question'), _('Question favorited by 100 users'), True, 0), (_('Favorite Question'), 2, _('favorite-question'), _('Question favorited by 25 users'), True, 0), - (_('Alpha'), 2, _('alpha'), _('Actively participated in the private alpha'), False, 0), (_('Generalist'), 2, _('generalist'), _('Active in many different tags'), False, 0), (_('Expert'), 2, _('expert'), _('Very active in one tag'), False, 0), @@ -443,40 +533,47 @@ ORIGINAL_DATA = """ (_('Yearling'), 2, _('yearling'), _('Active member for a year'), False, 0), (_('Beta'), 2, _('beta'), _('Actively participated in the private beta'), False, 0), + (_('Alpha'), 2, _('alpha'), _('Actively participated in the private alpha'), False, 0), """ BADGES = { + 'critic': Critic, + 'civic-duty': CivicDuty, 'disciplined': Disciplined, - 'peer-pressure': PeerPressure, - 'teacher': Teacher, - 'student': Student, - 'supporter': Supporter, - 'self-learner': SelfLearner, - 'nice-answer': NiceAnswer, + 'enlightened': Enlightened, + 'famous-question': FamousQuestion, 'good-answer': GoodAnswer, - 'great-answer': GreatAnswer, - 'nice-question': NiceQuestion, 'good-question': GoodQuestion, + 'great-answer': GreatAnswer, 'great-question': GreatQuestion, - 'popular-question': PopularQuestion, + 'guru': Guru, + 'nice-answer': NiceAnswer, + 'nice-question': NiceQuestion, 'notable-question': NotableQuestion, - 'famous-question': FamousQuestion, - 'critic': Critic, + 'peer-pressure': PeerPressure, + 'popular-question': PopularQuestion, + 'scholar': Scholar, + 'student': Student, + 'supporter': Supporter, + 'self-learner': SelfLearner, + 'teacher': Teacher, } #events are sent as a parameter via signal award_badges_signal #from appropriate locations in the code of askbot application #most likely - from manipulator functions that are added to the User objects EVENTS_TO_BADGES = { + 'accept_best_answer': (Scholar, Guru, Enlightened), 'upvote_answer': ( Teacher, NiceAnswer, GoodAnswer, - GreatAnswer, Supporter, SelfLearner + GreatAnswer, Supporter, SelfLearner, CivicDuty, + Guru, Enlightened ), 'upvote_question': ( NiceQuestion, GoodQuestion, - GreatQuestion, Student, Supporter + GreatQuestion, Student, Supporter, CivicDuty ), - 'downvote': (Critic,),#no regard for question or answer for now + 'downvote': (Critic, CivicDuty),#no regard for question or answer for now 'delete_post': (Disciplined, PeerPressure,), 'view_question': (PopularQuestion, NotableQuestion, FamousQuestion,), } diff --git a/askbot/tests/badge_tests.py b/askbot/tests/badge_tests.py index be7e604f..1930430d 100644 --- a/askbot/tests/badge_tests.py +++ b/askbot/tests/badge_tests.py @@ -16,6 +16,31 @@ class BadgeTests(AskbotTestCase): count = models.Award.objects.filter(**filters).count() self.assertEquals(count, expected_count) + def assert_accepted_answer_badge_works(self, + badge_key = None, + min_score = None, + expected_count = 1, + previous_count = 0, + trigger = None + ): + assert(trigger in ('accept_best_answer', 'upvote_answer')) + question = self.post_question(user = self.u1) + answer = self.post_answer(user = self.u2, question = question) + answer.score = min_score - 1 + answer.save() + + recipient = answer.author + + if trigger == 'accept_best_answer': + self.u1.upvote(answer) + self.assert_have_badge(badge_key, recipient, previous_count) + self.u1.accept_best_answer(answer) + else: + self.u1.accept_best_answer(answer) + self.assert_have_badge(badge_key, recipient, previous_count) + self.u1.upvote(answer) + self.assert_have_badge(badge_key, recipient, expected_count) + def assert_upvoted_answer_badge_works(self, badge_key = None, min_score = None, @@ -207,3 +232,83 @@ class BadgeTests(AskbotTestCase): answer.save() self.u2.upvote(answer) self.assert_have_badge('self-learner', recipient = self.u1, expected_count = 2) + + question = self.post_question(user = self.u2) + answer = self.post_answer(user = self.u1, question = question) + answer.score = min_votes - 1 + answer.save() + self.u2.upvote(answer) + #no badge when asker != answerer + self.assert_have_badge( + 'self-learner', + recipient = self.u1, + expected_count = 2 + ) + + def test_civic_duty_badge(self): + settings.update('CIVIC_DUTY_BADGE_MIN_VOTES', 2) + question = self.post_question(user = self.u1) + answer = self.post_answer(user = self.u2, question = question) + answer2 = self.post_answer(user = self.u1, question = question) + self.u3.upvote(question) + self.u3.downvote(answer) + self.assert_have_badge('civic-duty', recipient = self.u3) + self.u3.upvote(answer2) + self.assert_have_badge('civic-duty', recipient = self.u3, expected_count = 1) + self.u3.downvote(answer) + self.assert_have_badge('civic-duty', recipient = self.u3, expected_count = 1) + + def test_scholar_badge(self): + question = self.post_question(user = self.u1) + answer = self.post_answer(user = self.u2, question = question) + self.u1.accept_best_answer(answer) + self.assert_have_badge('scholar', recipient = self.u1) + answer2 = self.post_answer(user = self.u2, question = question) + self.u1.accept_best_answer(answer2) + self.assert_have_badge( + 'scholar', + recipient = self.u1, + expected_count=1 + ) + + def assert_enlightened_badge_works(self, trigger): + self.assert_accepted_answer_badge_works( + 'enlightened', + min_score = settings.ENLIGHTENED_BADGE_MIN_UPVOTES, + expected_count = 1, + trigger = trigger + ) + self.assert_accepted_answer_badge_works( + 'enlightened', + min_score = settings.ENLIGHTENED_BADGE_MIN_UPVOTES, + expected_count = 1, + previous_count = 1, + trigger = trigger + ) + + def assert_guru_badge_works(self, trigger): + self.assert_accepted_answer_badge_works( + 'guru', + min_score = settings.GURU_BADGE_MIN_UPVOTES, + expected_count = 1, + trigger = trigger + ) + self.assert_accepted_answer_badge_works( + 'guru', + min_score = settings.GURU_BADGE_MIN_UPVOTES, + previous_count = 1, + expected_count = 2, + trigger = trigger + ) + + def test_enlightened_badge1(self): + self.assert_enlightened_badge_works('upvote_answer') + + def test_enlightened_badge2(self): + self.assert_enlightened_badge_works('accept_best_answer') + + def test_guru_badge1(self): + self.assert_guru_badge_works('upvote_answer') + + def test_guru_badge1(self): + self.assert_guru_badge_works('accept_best_answer') -- cgit v1.2.3-1-g7c22