diff options
-rw-r--r-- | askbot/const/__init__.py | 16 | ||||
-rw-r--r-- | askbot/importers/stackexchange/management/commands/load_stackexchange.py | 38 | ||||
-rw-r--r-- | askbot/models/__init__.py | 13 | ||||
-rw-r--r-- | askbot/models/badges.py | 80 | ||||
-rw-r--r-- | askbot/models/repute.py | 34 | ||||
-rw-r--r-- | askbot/views/meta.py | 8 | ||||
-rw-r--r-- | askbot/views/readers.py | 47 | ||||
-rw-r--r-- | askbot/views/users.py | 2 |
8 files changed, 158 insertions, 80 deletions
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py index eb1ba23d..e5214a05 100644 --- a/askbot/const/__init__.py +++ b/askbot/const/__init__.py @@ -233,5 +233,21 @@ DEPENDENCY_URLS = { PASSWORD_MIN_LENGTH = 8 +GOLD_BADGE = 1 +SILVER_BADGE = 2 +BRONZE_BADGE = 3 +BADGE_TYPE_CHOICES = ( + (GOLD_BADGE, _('gold')), + (SILVER_BADGE, _('silver')), + (BRONZE_BADGE, _('bronze')), +) +BADGE_CSS_CLASSES = { + GOLD_BADGE: 'badge1', + SILVER_BADGE: 'badge2', + BRONZE_BADGE: 'badge3', +} +BADGE_DISPLAY_SYMBOL = '●' + + #an exception import * because that file has only strings from askbot.const.message_keys import * diff --git a/askbot/importers/stackexchange/management/commands/load_stackexchange.py b/askbot/importers/stackexchange/management/commands/load_stackexchange.py index fb3b520f..2aef092f 100644 --- a/askbot/importers/stackexchange/management/commands/load_stackexchange.py +++ b/askbot/importers/stackexchange/management/commands/load_stackexchange.py @@ -265,7 +265,7 @@ class X(object):# @classmethod def get_badge_name(cls, name): - return cls.badge_exceptions.get(name, name) + return slugify(cls.badge_exceptions.get(name, name).lower()) class Command(BaseCommand): help = 'Loads StackExchange data from unzipped directory of XML files into the ASKBOT database' @@ -618,23 +618,20 @@ class Command(BaseCommand): user = USER[se_c.user.id] ) - def _install_missing_badges(self): + def _collect_missing_badges(self): self._missing_badges = {} for se_b in se.Badge.objects.all(): name = X.get_badge_name(se_b.name) try: - askbot.Badge.objects.get(name=name) - except: + #todo: query badge from askbot.models.badges + #using case-insensitive name matching + askbot.badges.get_badge(name=name) + except KeyError: + #todo: if absent - print error message + #and drop it self._missing_badges[name] = 0 if len(se_b.description) > 300: print 'Warning truncated description for badge %d' % se_b.id - askbot.Badge.objects.create( - name = name, - slug = slugify(name), - description = se_b.description, - multiple = (not se_b.single), - type = se_b.class_type - ) def _award_badges(self): #note: SE does not keep information on @@ -644,7 +641,7 @@ class Command(BaseCommand): continue #skip community user u = USER[se_a.user.id] badge_name = X.get_badge_name(se_a.badge.name) - b = askbot.Badge.objects.get(name=badge_name) + b = askbot.badges.get_badge(name=badge_name) if b.multiple == False: if b.award_badge.count() > 0: continue @@ -652,29 +649,28 @@ class Command(BaseCommand): #todo: but askbot requires related content object askbot.Award.objects.create( user=u, - badge=b, + badge=b.get_stored_data(), awarded_at=se_a.date, content_object=u, ) if b.name in self._missing_badges: self._missing_badges[b.name] += 1 - def _cleanup_badges(self): + def _report_missing_badges(self): d = self._missing_badges unused = [name for name in d.keys() if d[name] == 0] - askbot.Badge.objects.filter(name__in=unused).delete() - installed = [name for name in d.keys() if d[name] > 0] - print 'Warning - following unsupported badges were installed:' - print ', '.join(installed) + dropped = [name for name in d.keys() if d[name] > 0] + print 'Warning - following unsupported badges were dropped:' + print ', '.join(dropped) def transfer_badges(self): #note: badge level is neglected #1) install missing badges - self._install_missing_badges() + self._collect_missing_badges() #2) award badges self._award_badges() - #3) delete unused newly installed badges - self._cleanup_badges() + #3) report missing badges + self._report_missing_badges() pass def transfer_question_view_counts(self): diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index d280bba7..426ff582 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -27,7 +27,8 @@ from askbot.models.meta import Vote, Comment from askbot.models.user import EmailFeedSetting, ActivityAuditStatus, Activity from askbot.models import signals #from user import AuthKeyUserAssociation -from askbot.models.repute import Badge, Award, Repute +from askbot.models.repute import BadgeData, Award, Repute +from askbot.models import badges from askbot import auth from askbot.utils.decorators import auto_now_timestamp from askbot.utils.slug import slugify @@ -1755,11 +1756,11 @@ def record_award_event(instance, created, **kwargs): instance.badge.awarded_count += 1 instance.badge.save() - if instance.badge.type == Badge.GOLD: + if instance.badge.type == badges.GOLD: instance.user.gold += 1 - if instance.badge.type == Badge.SILVER: + if instance.badge.type == badges.SILVER: instance.user.silver += 1 - if instance.badge.type == Badge.BRONZE: + if instance.badge.type == badges.BRONZE: instance.user.bronze += 1 instance.user.save() @@ -2011,7 +2012,7 @@ Comment = Comment Vote = Vote MarkedTag = MarkedTag -Badge = Badge +BadgeData = BadgeData Award = Award Repute = Repute @@ -2038,7 +2039,7 @@ __all__ = [ 'Vote', 'MarkedTag', - 'Badge', + 'BadgeData', 'Award', 'Repute', diff --git a/askbot/models/badges.py b/askbot/models/badges.py new file mode 100644 index 00000000..ecf05710 --- /dev/null +++ b/askbot/models/badges.py @@ -0,0 +1,80 @@ +"""badges data that is not stored in the database. +This data is static, so there is no point storing it in the db. + +However, the database does have model BadgeData, that contains +additional mutable data pertaining to the badges - denormalized award counts +and lists of recipients +""" +from django.template.defaultfilters import slugify +from django.utils.translation import gettext as _ +from askbot.models import BadgeData +from askbot import const + +class Badge(object): + """base class for the badges + """ + def __init__(self, + name = '', + level = None, + description = None, + multiple = False): + + self._name = name + self.level = level + self._description = description + self.multiple = multiple + + def get_stored_data(self): + data, created = BadgeData.objects.get_or_create(name = self.name) + return data + + @property + def awarded_count(self): + return self.get_stored_data().awarded_count + + @property + def awarded_to(self): + return self.get_stored_data().awarded_to + + def award(self, recipient): + """do award, the recipient was proven to deserve""" + pass + + def consider_award(self, actor = None, context_object = 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 + + the method should internally check who might be awarded and + whether the situation is appropriate + """ + raise NotImplementedError() + +class Disciplined(Badge): + def __init__(self): + super(Disciplined, self).__init__( + key = 'disciplined', + name = _('Disciplined'), + description = _('Deleted own post with score of 3 or higher'), + level = const.BRONZE_BADGE, + multiple = True + ) + + def consider_award(self, actor = None, context_object = None): + if context_object.author != actor: + return + if context_object.score > 2: + self.award(actor) + + +BADGES = { + 'disciplined': Disciplined +} + +def get_badge(name = None): + """Get badge object by name, if none mathes the name + raise KeyError + """ + key = slugify(name) + return BADGES[key]() diff --git a/askbot/models/repute.py b/askbot/models/repute.py index d8712636..a933801a 100644 --- a/askbot/models/repute.py +++ b/askbot/models/repute.py @@ -1,37 +1,19 @@ +import datetime from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic from django.contrib.auth.models import User from django.db import models from django.utils.translation import ugettext as _ -import datetime -from askbot import const from django.core.urlresolvers import reverse +from askbot import const +from askbot models import badges -class Badge(models.Model): +class BadgeData(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')), - ) - CSS_CLASSES = { - GOLD: 'badge1', - SILVER: 'badge2', - BRONZE: 'badge3', - } - DISPLAY_SYMBOL = '●' - - 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) + + name = models.CharField(max_length=50, unique=True) # Denormalised data awarded_count = models.PositiveIntegerField(default=0) - awarded_to = models.ManyToManyField(User, through='Award', related_name='badges') class Meta: @@ -46,7 +28,7 @@ class Badge(models.Model): def save(self, **kwargs): if not self.slug: self.slug = self.name#slugify(self.name) - super(Badge, self).save(**kwargs) + super(BadgeData, self).save(**kwargs) def get_absolute_url(self): return '%s%s/' % (reverse('badge', args=[self.id]), self.slug) @@ -67,7 +49,7 @@ class AwardManager(models.Manager): 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') + badge = models.ForeignKey(BadgeData, related_name='award_badge') content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') diff --git a/askbot/views/meta.py b/askbot/views/meta.py index 0884d3e8..9da75f91 100644 --- a/askbot/views/meta.py +++ b/askbot/views/meta.py @@ -13,7 +13,7 @@ from django.views import static from askbot.forms import FeedbackForm from askbot.utils.forms import get_next_url from askbot.utils.mail import mail_moderators -from askbot.models import Badge, Award +from askbot.models import BadgeData, Award from askbot.skins.loaders import ENV from askbot import skins import askbot @@ -90,7 +90,8 @@ def logout(request):#refactor/change behavior? return HttpResponse(template.render(context)) def badges(request):#user status/reputation system - badges = Badge.objects.all().order_by('name') + #todo: supplement database data with the stuff from badges.py + badges = BadgeData.objects.all().order_by('name') my_badges = [] if request.user.is_authenticated(): my_badges = Award.objects.filter(user=request.user).values('badge_id') @@ -108,7 +109,8 @@ def badges(request):#user status/reputation system return HttpResponse(template.render(context)) def badge(request, id): - badge = get_object_or_404(Badge, id=id) + #todo: supplement database data with the stuff from badges.py + badge = get_object_or_404(BadgeData, id=id) awards = Award.objects.extra( select={'id': 'auth_user.id', 'name': 'auth_user.username', diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 42ddb63a..1f670c2c 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -31,12 +31,13 @@ from askbot.utils.html import sanitize_html #from lxml.html.diff import htmldiff from askbot.utils.diff import textDiff as htmldiff from askbot.forms import AdvancedSearchForm, AnswerForm -from askbot.models import * +from askbot import models +from askbot.models import badges from askbot import const from askbot import auth from askbot.utils import markup from askbot.utils.forms import get_next_url -from askbot.utils.functions import not_a_robot_request +from askbot.utils import functions from askbot.utils.decorators import anonymous_forbidden, ajax_only, get_only from askbot.search.state_manager import SearchState from askbot.templatetags import extra_tags @@ -61,7 +62,7 @@ def _get_tags_cache_json():#service routine used by views requiring tag list in """returns list of all tags in json format no caching yet, actually """ - tags = Tag.objects.filter(deleted=False).all() + tags = models.Tag.objects.filter(deleted=False).all() tags_list = [] for tag in tags: dic = {'n': tag.name, 'c': tag.used_count} @@ -124,7 +125,7 @@ def questions(request): #request.session.modified = True #todo: have this call implemented for sphinx, mysql and pgsql - (qs, meta_data, related_tags) = Question.objects.run_advanced_search( + (qs, meta_data, related_tags) = models.Question.objects.run_advanced_search( request_user = request.user, search_state = search_state, ) @@ -136,7 +137,7 @@ def questions(request): page = paginator.page(search_state.page) - contributors = Question.objects.get_question_and_answer_contributors(page.object_list) + contributors = models.Question.objects.get_question_and_answer_contributors(page.object_list) paginator_context = { 'is_paginated' : (paginator.count > search_state.page_size), @@ -181,7 +182,7 @@ def questions(request): 'faces': list() } - badge_levels = dict(Badge.TYPE_CHOICES) + badge_levels = dict(badges.TYPE_CHOICES) def pluralize_badge_count(count, level): return ungettext( '%(badge_count)d %(badge_level)s badge', @@ -192,9 +193,9 @@ def questions(request): 'badge_level': badge_levels[level] } - gold_badge_css_class = Badge.CSS_CLASSES[Badge.GOLD], - silver_badge_css_class = Badge.CSS_CLASSES[Badge.SILVER], - bronze_badge_css_class = Badge.CSS_CLASSES[Badge.BRONZE], + gold_badge_css_class = badges.CSS_CLASSES[badges.GOLD], + silver_badge_css_class = badges.CSS_CLASSES[badges.SILVER], + bronze_badge_css_class = badges.CSS_CLASSES[badges.BRONZE], for tag in related_tags: tag_data = { @@ -242,30 +243,30 @@ def questions(request): 'views_class': views_class, 'views_word': ungettext('view', 'views', question.view_count), 'timestamp': unicode(timestamp), - 'timesince': extra_tags.diff_date(timestamp), + 'timesince': functions.diff_date(timestamp), 'u_id': author.id, 'u_name': author.username, 'u_rep': author.reputation, 'u_gold': author.gold, 'u_gold_title': pluralize_badge_count( author.gold, - Badge.GOLD + badges.GOLD ), - 'u_gold_badge_symbol': Badge.DISPLAY_SYMBOL, + 'u_gold_badge_symbol': badges.DISPLAY_SYMBOL, 'u_gold_css_class': gold_badge_css_class, 'u_silver': author.silver, 'u_silver_title': pluralize_badge_count( author.silver, - Badge.SILVER + badges.SILVER ), - 'u_silver_badge_symbol': Badge.DISPLAY_SYMBOL, + 'u_silver_badge_symbol': badges.DISPLAY_SYMBOL, 'u_silver_css_class': silver_badge_css_class, 'u_bronze': author.bronze, 'u_bronze_title': pluralize_badge_count( author.bronze, - Badge.BRONZE + badges.BRONZE ), - 'u_bronze_badge_symbol': Badge.DISPLAY_SYMBOL, + 'u_bronze_badge_symbol': badges.DISPLAY_SYMBOL, 'u_bronze_css_class': bronze_badge_css_class, } ajax_data['questions'].append(question_data) @@ -352,12 +353,12 @@ def tags(request):#view showing a listing of available tags - plain list if request.method == "GET": stag = request.GET.get("q", "").strip() if stag != '': - objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE) + objects_list = Paginator(models.Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE) else: if sortby == "name": - objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE) + objects_list = Paginator(models.Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE) else: - objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE) + objects_list = Paginator(models.Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE) try: tags = objects_list.page(page) @@ -417,7 +418,7 @@ def question(request, id):#refactor - long subroutine. display question body, an logging.debug('answer_sort_method=' + unicode(answer_sort_method)) - question = get_object_or_404(Question, id=id) + question = get_object_or_404(models.Question, id=id) try: assert(request.path == question.get_absolute_url()) except AssertionError: @@ -473,7 +474,7 @@ def question(request, id):#refactor - long subroutine. display question body, an objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE) page_objects = objects_list.page(page) - if not_a_robot_request(request): + if functions.not_a_robot_request(request): #todo: split this out into a subroutine #todo: merge view counts per user and per session #1) view count per session @@ -539,7 +540,7 @@ def question(request, id):#refactor - long subroutine. display question body, an def revisions(request, id, object_name=None): assert(object_name in ('Question', 'Answer')) - post = get_object_or_404(get_model(object_name), id=id) + post = get_object_or_404(models.get_model(object_name), id=id) revisions = list(post.revisions.all()) revisions.reverse() for i, revision in enumerate(revisions): @@ -568,6 +569,6 @@ def get_comment(request): and request must be ajax """ id = int(request.GET['id']) - comment = Comment.objects.get(id = id) + comment = models.Comment.objects.get(id = id) request.user.assert_can_edit_comment(comment) return {'text': comment.comment} diff --git a/askbot/views/users.py b/askbot/views/users.py index 466b2697..51103f91 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -445,7 +445,7 @@ def user_recent(request, user): self.time = time self.type = get_type_name(type) self.type_id = type - self.badge = get_object_or_404(models.Badge, id=id) + self.badge = get_object_or_404(models.BadgeData, id=id) activities = [] # ask questions |