From 031818d0b9bce845c9b59b43d55b70b1df35f8a1 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Sat, 4 Dec 2010 01:26:28 -0500 Subject: some more badges --- askbot/conf/badges.py | 27 ++++++++ askbot/models/__init__.py | 23 +++++++ askbot/models/badges.py | 152 +++++++++++++++++++++++++++++++++++++++----- askbot/tests/badge_tests.py | 95 +++++++++++++++++++++++++++ 4 files changed, 280 insertions(+), 17 deletions(-) diff --git a/askbot/conf/badges.py b/askbot/conf/badges.py index 7019a69f..3654da4d 100644 --- a/askbot/conf/badges.py +++ b/askbot/conf/badges.py @@ -156,3 +156,30 @@ settings.register( description=_('Guru: minimum upvotes') ) ) + +settings.register( + IntegerValue( + BADGES, + 'NECROMANCER_BADGE_MIN_UPVOTES', + default=1, + description=_('Necromancer: minimum upvotes') + ) +) + +settings.register( + IntegerValue( + BADGES, + 'NECROMANCER_BADGE_MIN_DELAY', + default=30, + description=_('Necromancer: minimum delay in days') + ) +) + +settings.register( + IntegerValue( + BADGES, + 'ASSOCIATE_EDITOR_BADGE_MIN_EDITS', + default=20, + description=_('Associate Editor: minimum number of edits') + ) +) diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 4280e650..ea20300e 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -964,6 +964,12 @@ def user_edit_question( tags = tags, wiki = wiki, ) + award_badges_signal.send(None, + event = 'edit_question', + actor = self, + context_object = question, + timestamp = timestamp + ) @auto_now_timestamp def user_edit_answer( @@ -982,6 +988,12 @@ def user_edit_answer( comment = revision_comment, wiki = wiki, ) + award_badges_signal.send(None, + event = 'edit_answer', + actor = self, + context_object = answer, + timestamp = timestamp + ) def user_is_following(self, followed_item): if isinstance(followed_item, Question): @@ -1021,6 +1033,11 @@ def user_post_answer( email_notify = follow, wiki = wiki ) + award_badges_signal.send(None, + event = 'post_answer', + actor = self, + context_object = answer + ) return answer def user_visit_question(self, question = None, timestamp = None): @@ -1427,6 +1444,12 @@ def flag_post(user, post, timestamp=None, cancel=False): user.assert_can_flag_offensive(post = post) auth.onFlaggedItem(post, user, timestamp=timestamp) + award_badges_signal.send(None, + event = 'flag_post', + actor = user, + context_object = post, + timestamp = timestamp + ) def user_get_flags(self): """return flag Activity query set diff --git a/askbot/models/badges.py b/askbot/models/badges.py index 46f10e04..363d290a 100644 --- a/askbot/models/badges.py +++ b/askbot/models/badges.py @@ -17,11 +17,13 @@ and make sure that a signal `award_badges_signal` is sent with the corresponding event name, actor (user object), context_object and optionally - timestamp """ +import datetime from django.template.defaultfilters import slugify from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext as _ from django.dispatch import Signal from askbot.models.repute import BadgeData, Award +from askbot.models.user import Activity from askbot import const from askbot.conf import settings as askbot_settings from askbot.utils.decorators import auto_now_timestamp @@ -108,17 +110,16 @@ class Badge(object): award.save()#note: there are signals that listen to saving the Award return True - def consider_award(self, actor = None, - context_object = None, timestamp = None): - """This method should be implemented in subclasses - actor - user who committed some action, context_object - - the object related to the award situation, e.g. an - answer that is being upvoted + def consider_award(self, actor = None, + context_object = None, timestamp = None): + """Normally this method should be reimplemented + in subclass, but some badges are awarded without + checks. Those do no need to override this method - the method should internally check who might be awarded and - whether the situation is appropriate + actor - user who committed some action, context_object - + the object related to the award situation, e.g. answer """ - raise NotImplementedError() + return self.award(actor, context_object, timestamp) class Disciplined(Badge): def __init__(self): @@ -509,16 +510,123 @@ class Guru(VotedAcceptedAnswer): 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), +class Necromancer(Badge): + def __init__(self): + description = _( + 'Answered a question more than %(days)s days ' + 'later with at least %(votes)s votes' + ) + days = askbot_settings.NECROMANCER_BADGE_MIN_DELAY + votes = askbot_settings.NECROMANCER_BADGE_MIN_UPVOTES + super(Necromancer, self).__init__( + key = 'necromancer', + name = _('Necromancer'), + level = const.SILVER_BADGE, + description = description % {'days':days, 'votes':votes}, + multiple = True + ) + + def consider_award(self, actor = None, + context_object = None, timestamp = None): + if context_object.post_type != 'answer': + return False + answer = context_object + question = answer.question + delta = datetime.timedelta(askbot_settings.NECROMANCER_BADGE_MIN_DELAY) + min_score = askbot_settings.NECROMANCER_BADGE_MIN_UPVOTES + if answer.added_at - question.added_at >= delta \ + and answer.score >= min_score: + return self.award(answer.author, answer, timestamp) + return False + +class CitizenPatrol(Badge): + def __init__(self): + super(CitizenPatrol, self).__init__( + key = 'citizen-patrol', + name = _('Citizen Patrol'), + level = const.BRONZE_BADGE, + multiple = False, + description = _('First flagged post') + ) + +class Cleanup(Badge): + """This badge is inactive right now. + to make it live we need to be able to either + detect "undo" actions or rewrite the view + correspondingly + """ + def __init__(self): + super(Cleanup, self).__init__( + key = 'cleanup', + name = _('Cleanup'), + level = const.BRONZE_BADGE, + multiple = False, + description = _('First rollback') + ) + +class Pundit(Badge): + """Inactive until it is possible to vote + for comments. + Pundit is someone who makes good comments. + """ + def __init__(self): + super(Pundit, self).__init__( + key = 'pundit', + name = _('Pundit'), + level = const.SILVER_BADGE, + multiple = False, + description = _('Left 10 comments with score of 10 or more') + ) + +class EditorTypeBadge(Badge): + """subclassing badges are types of editors + must provide usual parameters + min_edits + via __new__ function + """ + def __init__(self): + super(EditorTypeBadge, self).__init__( + key = self.key, + name = self.name, + level = self.level, + multiple = False, + description = self.description + ) - (_('Pundit'), 3, _('pundit'), _('Left 10 comments with score of 10 or more'), False, 0), - (_('Citizen patrol'), 3, _('citizen-patrol'), _('First flagged post'), False, 0), + def consider_award(self, actor = None, + context_object = None, timestamp = None): + + atypes = ( + const.TYPE_ACTIVITY_UPDATE_QUESTION, + const.TYPE_ACTIVITY_UPDATE_ANSWER + ) + filters = {'user': actor, 'activity_type__in': atypes} + if Activity.objects.filter(**filters).count() == self.min_edits: + return self.award(actor, context_object, timestamp) + +class Editor(EditorTypeBadge): + def __new__(cls): + self = super(Editor, cls).__new__(cls) + self.key = 'editor' + self.name = _('Editor') + self.level = const.BRONZE_BADGE + self.multiple = False + self.description = _('First edit') + self.min_edits = 1 + return self + +class AssociateEditor(EditorTypeBadge): + def __new__(cls): + self = super(AssociateEditor, cls).__new__(cls) + self.key = 'strunk-and-white'#legacy copycat name from stackoverflow + self.name = _('Associate Editor') + self.level = const.SILVER_BADGE + self.multiple = False + self.min_edits = askbot_settings.ASSOCIATE_EDITOR_BADGE_MIN_EDITS + self.description = _('Edited %(num)s entries') % {'num': self.min_edits} + return self - (_('Cleanup'), 3, _('cleanup'), _('First rollback'), False, 0), - (_('Editor'), 3, _('editor'), _('First edit'), False, 0), - (_('Strunk & White'), 2, _('strunk-and-white'), _('Edited 100 entries'), False, 0), +ORIGINAL_DATA = """ (_('Organizer'), 3, _('organizer'), _('First retag'), False, 0), (_('Autobiographer'), 3, _('autobiographer'), _('Completed all user profile fields'), False, 0), @@ -537,9 +645,13 @@ ORIGINAL_DATA = """ """ BADGES = { + 'strunk-and-white': AssociateEditor,#legacy slug name 'critic': Critic, + 'citizen-patrol': CitizenPatrol, 'civic-duty': CivicDuty, + 'cleanup': Cleanup, 'disciplined': Disciplined, + 'editor': Editor, 'enlightened': Enlightened, 'famous-question': FamousQuestion, 'good-answer': GoodAnswer, @@ -547,11 +659,13 @@ BADGES = { 'great-answer': GreatAnswer, 'great-question': GreatQuestion, 'guru': Guru, + 'necromancer': Necromancer, 'nice-answer': NiceAnswer, 'nice-question': NiceQuestion, 'notable-question': NotableQuestion, 'peer-pressure': PeerPressure, 'popular-question': PopularQuestion, + 'pundit': Pundit, 'scholar': Scholar, 'student': Student, 'supporter': Supporter, @@ -564,10 +678,13 @@ BADGES = { #most likely - from manipulator functions that are added to the User objects EVENTS_TO_BADGES = { 'accept_best_answer': (Scholar, Guru, Enlightened), + 'edit_answer': (Editor, AssociateEditor), + 'edit_question': (Editor, AssociateEditor), + 'flag_post': (CitizenPatrol,), 'upvote_answer': ( Teacher, NiceAnswer, GoodAnswer, GreatAnswer, Supporter, SelfLearner, CivicDuty, - Guru, Enlightened + Guru, Enlightened, Necromancer ), 'upvote_question': ( NiceQuestion, GoodQuestion, @@ -575,6 +692,7 @@ EVENTS_TO_BADGES = { ), 'downvote': (Critic, CivicDuty),#no regard for question or answer for now 'delete_post': (Disciplined, PeerPressure,), + 'post_answer': (Necromancer,), 'view_question': (PopularQuestion, NotableQuestion, FamousQuestion,), } diff --git a/askbot/tests/badge_tests.py b/askbot/tests/badge_tests.py index 1930430d..516270f8 100644 --- a/askbot/tests/badge_tests.py +++ b/askbot/tests/badge_tests.py @@ -1,3 +1,4 @@ +import datetime from django.test.client import Client from askbot.tests.utils import AskbotTestCase from askbot.conf import settings @@ -312,3 +313,97 @@ class BadgeTests(AskbotTestCase): def test_guru_badge1(self): self.assert_guru_badge_works('accept_best_answer') + + def test_necromancer_badge(self): + question = self.post_question(user = self.u1) + now = datetime.datetime.now() + delta = datetime.timedelta(settings.NECROMANCER_BADGE_MIN_DELAY + 1) + future = now + delta + answer = self.post_answer( + user = self.u2, + question = question, + timestamp = future + ) + answer.score = settings.NECROMANCER_BADGE_MIN_UPVOTES - 1 + answer.save() + self.assert_have_badge('necromancer', self.u2, expected_count = 0) + self.u1.upvote(answer) + self.assert_have_badge('necromancer', self.u2, expected_count = 1) + + def test_citizen_patrol_question(self): + self.u2.set_status('m') + question = self.post_question(user = self.u1) + self.u2.flag_post(question) + self.assert_have_badge('citizen-patrol', self.u2) + question = self.post_question(user = self.u1) + self.u2.flag_post(question) + self.assert_have_badge('citizen-patrol', self.u2, 1) + + def test_citizen_patrol_answer(self): + self.u2.set_status('m') + question = self.post_question(user = self.u1) + answer = self.post_answer(user = self.u1, question = question) + self.u2.flag_post(answer) + self.assert_have_badge('citizen-patrol', self.u2) + question = self.post_question(user = self.u1) + answer = self.post_answer(user = self.u1, question = question) + self.u2.flag_post(answer) + self.assert_have_badge('citizen-patrol', self.u2, 1) + + def test_editor_badge_question(self): + self.u2.set_status('m') + question = self.post_question(user = self.u1) + self.u2.edit_question( + question = question, + title = 'hahaha', + body_text = 'heheeh', + revision_comment = 'ihihih' + ) + self.assert_have_badge('editor', self.u2, 1) + #double check that its not multiple + question = self.post_question(user = self.u1) + self.u2.edit_question( + question = question, + title = 'hahaha', + body_text = 'heheeh', + revision_comment = 'ihihih' + ) + self.assert_have_badge('editor', self.u2, 1) + + def test_editor_badge_answer(self): + self.u2.set_status('m') + question = self.post_question(user = self.u1) + answer = self.post_answer(user = self.u1, question = question) + self.u2.edit_answer(answer = answer, body_text = 'hahaha') + self.assert_have_badge('editor', self.u2, 1) + #double check that its not multiple + question = self.post_question(user = self.u1) + answer = self.post_answer(user = self.u1, question = question) + self.u2.edit_answer(answer = answer, body_text = 'hahaha') + self.assert_have_badge('editor', self.u2, 1) + + def test_associate_editor_badge(self): + self.u2.set_status('m') + question = self.post_question(user = self.u1) + settings.update('ASSOCIATE_EDITOR_BADGE_MIN_EDITS', 2) + self.u2.edit_question( + question = question, + title = 'hahaha', + body_text = 'sdgsdjghsldkfshd', + revision_comment = 'sdgdfgsgfs' + ) + self.assert_have_badge('strunk-and-white', self.u2, 0) + self.u2.edit_question( + question = question, + title = 'hahaha', + body_text = 'sdgsdjghsldkfshd', + revision_comment = 'sdgdfgsgfs' + ) + self.assert_have_badge('strunk-and-white', self.u2, 1) + self.u2.edit_question( + question = question, + title = 'hahaha', + body_text = 'sdgsdjghsldkfshd', + revision_comment = 'sdgdfgsgfs' + ) + self.assert_have_badge('strunk-and-white', self.u2, 1) -- cgit v1.2.3-1-g7c22