From a5cf08b3f6f43f0d8c9c38a233380e03b1f253c6 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 5 Jan 2011 16:17:45 -0500 Subject: made the enthusiast badge work and added version display to the footer template --- askbot/conf/badges.py | 9 + askbot/context.py | 10 +- askbot/middleware/view_log.py | 37 ++- ..._consecutive_days_visit_count__to__auth_user.py | 309 +++++++++++++++++++++ askbot/models/__init__.py | 26 +- askbot/models/badges.py | 24 +- askbot/models/signals.py | 3 + askbot/skins/default/templates/blocks/footer.html | 2 +- askbot/startup_procedures.py | 52 +++- askbot/tests/badge_tests.py | 12 + askbot/views/readers.py | 2 - 11 files changed, 451 insertions(+), 35 deletions(-) create mode 100644 askbot/migrations/0033_add__consecutive_days_visit_count__to__auth_user.py diff --git a/askbot/conf/badges.py b/askbot/conf/badges.py index 3db0cca0..b113af2e 100644 --- a/askbot/conf/badges.py +++ b/askbot/conf/badges.py @@ -219,3 +219,12 @@ settings.register( description = _('Taxonomist: minimum tag use count') ) ) + +settings.register( + IntegerValue( + BADGES, + 'ENTHUSIAST_BADGE_MIN_DAYS', + default = 30, + description = _('Enthusiast: minimum days') + ) +) diff --git a/askbot/context.py b/askbot/context.py index 47930aef..94257f63 100644 --- a/askbot/context.py +++ b/askbot/context.py @@ -1,13 +1,19 @@ +"""Askbot template context processor that makes some parameters +from the django settings, all parameters from the askbot livesettings +and the application available for the templates +""" from django.conf import settings -from askbot.conf import settings as askbot_settings +import askbot from askbot import api -import datetime +from askbot.conf import settings as askbot_settings def application_settings(request): + """The context processor function""" my_settings = askbot_settings.as_dict() my_settings['LANGUAGE_CODE'] = settings.LANGUAGE_CODE my_settings['ASKBOT_URL'] = settings.ASKBOT_URL my_settings['DEBUG'] = settings.DEBUG + my_settings['ASKBOT_VERSION'] = askbot.get_version() return { 'settings': my_settings, 'moderation_items': api.get_info_on_moderation_items(request.user) diff --git a/askbot/middleware/view_log.py b/askbot/middleware/view_log.py index 3dbbd4a8..f6077e12 100644 --- a/askbot/middleware/view_log.py +++ b/askbot/middleware/view_log.py @@ -1,8 +1,15 @@ +"""This module records the site visits by the authenticaded +users and heps maintain the state of the search (for all visitors). + +Included here is the ViewLogMiddleware and the helper class ViewLog. +""" import logging +import datetime from django.conf import settings +from askbot.models import signals +from django.views.static import serve from askbot.views.readers import questions as questions_view from askbot.views.commands import vote -from django.views.static import serve from askbot.views.writers import delete_comment, post_comments, retag_question from askbot.views.readers import revisions @@ -12,24 +19,31 @@ IGNORED_VIEWS = (serve, vote, delete_comment, post_comments, retag_question, revisions) class ViewLog(object): - """must be modified only in this middlware - however, can be read anywhere else + """The ViewLog helper obejcts store the trail of the page visits for a + given user. The trail is recorded only up to a certain depth. + + The purpose to record this info is to reset the search state + when the user walks "too far away" from the search page. + + These objects must be modified only in this middlware. """ def __init__(self): self.views = [] self.depth = 3 #todo maybe move this to const.py def get_previous(self, num): + """get a previous record from a certain depth""" if num > self.depth - 1: raise Exception("view log depth exceeded") elif num < 0: - raise Exception("num must be positive"); + raise Exception("num must be positive") elif num <= len(self.views) - 1: return self.views[num] else: return None def set_current(self, view_name): + """insert a new record""" self.views.insert(0, view_name) if len(self.views) > self.depth: self.views.pop() @@ -38,7 +52,18 @@ class ViewLog(object): return str(self.views) + ' depth=%d' % self.depth class ViewLogMiddleware(object): + """ViewLogMiddleware does two things: tracks visits of pages for the + stateful site search and sends the site_visited signal + """ def process_view(self, request, view_func, view_args, view_kwargs): + #send the site_visited signal for the authenticated users + if request.user.is_authenticated(): + signals.site_visited.send(None, #this signal has no sender + user = request.user, + timestamp = datetime.datetime.now() + ) + + #remaining stuff is for the search state if view_func == questions_view: view_str = 'questions' elif view_func in IGNORED_VIEWS: @@ -57,10 +82,6 @@ class ViewLogMiddleware(object): except ImportError: pass - if request.user.is_authenticated(): - user_name = request.user.username - else: - user_name = request.META['REMOTE_ADDR'] logging.debug('user %s, view %s' % (request.user.username, view_str)) logging.debug('next url is %s' % request.REQUEST.get('next','nothing')) diff --git a/askbot/migrations/0033_add__consecutive_days_visit_count__to__auth_user.py b/askbot/migrations/0033_add__consecutive_days_visit_count__to__auth_user.py new file mode 100644 index 00000000..580c2f25 --- /dev/null +++ b/askbot/migrations/0033_add__consecutive_days_visit_count__to__auth_user.py @@ -0,0 +1,309 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'User.consecutive_days_visit_count' + try: + db.add_column( + u'auth_user', + 'consecutive_days_visit_count', + self.gf('django.db.models.fields.IntegerField')(default = 0, max_length = 2), + keep_default=False + ) + except: + pass + + + def backwards(self, orm): + + # Deleting field 'User.consecutive_days_visit_count' + db.delete_column(u'auth_user', 'consecutive_days_visit_count') + + + models = { + 'askbot.activity': { + 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"}, + 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Question']", 'null': 'True'}), + 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'to': "orm['auth.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'incoming_activity'", 'through': "'ActivityAuditStatus'", 'to': "orm['auth.User']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.activityauditstatus': { + 'Meta': {'unique_together': "(('user', 'activity'),)", 'object_name': 'ActivityAuditStatus'}, + 'activity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Activity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.anonymousanswer': { + 'Meta': {'object_name': 'AnonymousAnswer'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_answers'", 'to': "orm['askbot.Question']"}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}) + }, + 'askbot.anonymousquestion': { + 'Meta': {'object_name': 'AnonymousQuestion'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}) + }, + 'askbot.answer': { + 'Meta': {'object_name': 'Answer', 'db_table': "u'answer'"}, + 'accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answers'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answers'", 'to': "orm['askbot.Question']"}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'askbot.answerrevision': { + 'Meta': {'object_name': 'AnswerRevision', 'db_table': "u'answer_revision'"}, + 'answer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['askbot.Answer']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answerrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'askbot.award': { + 'Meta': {'object_name': 'Award', 'db_table': "u'award'"}, + 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"}) + }, + 'askbot.badgedata': { + 'Meta': {'object_name': 'BadgeData'}, + 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'through': "'Award'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}) + }, + 'askbot.comment': { + 'Meta': {'object_name': 'Comment', 'db_table': "u'comment'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'html': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2048'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['auth.User']"}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'object_name': 'EmailFeedSetting'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"}) + }, + 'askbot.favoritequestion': { + 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Question']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + 'askbot.markedtag': { + 'Meta': {'object_name': 'MarkedTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"}) + }, + 'askbot.question': { + 'Meta': {'object_name': 'Question', 'db_table': "u'question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questions'", 'to': "orm['auth.User']"}), + 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'closed_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'favorite_questions'", 'through': "'FavoriteQuestion'", 'to': "orm['auth.User']"}), + 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_questions'", 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_active_in_questions'", 'to': "orm['auth.User']"}), + 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'questions'", 'to': "orm['askbot.Tag']"}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'askbot.questionrevision': { + 'Meta': {'object_name': 'QuestionRevision', 'db_table': "u'question_revision'"}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questionrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['askbot.Question']"}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}) + }, + 'askbot.questionview': { + 'Meta': {'object_name': 'QuestionView'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Question']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"}) + }, + 'askbot.repute': { + 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Question']", 'null': 'True', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.tag': { + 'Meta': {'object_name': 'Tag', 'db_table': "u'tag'"}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('content_type', 'object_id', 'user'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}), + 'vote': ('django.db.models.fields.SmallIntegerField', [], {}), + 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}), + 'hide_ignored_questions': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'tag_filter_setting': ('django.db.models.fields.CharField', [], {'default': "'ignored'", 'max_length': '16'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index fc2f8602..7282f238 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -80,6 +80,7 @@ User.add_to_class('tag_filter_setting', ) User.add_to_class('new_response_count', models.IntegerField(default=0)) User.add_to_class('seen_response_count', models.IntegerField(default=0)) +User.add_to_class('consecutive_days_visit_count', models.IntegerField(default = 0)) def user_get_old_vote_for_post(self, post): @@ -1875,18 +1876,21 @@ def record_answer_accepted(instance, created, **kwargs): ) activity.add_recipients(recipients) - -def update_last_seen(instance, created, **kwargs): +def record_user_visit(user, timestamp, **kwargs): """ - when user has activities, we update 'last_seen' time stamp for him + when user visits any pages, we update the last_seen and + consecutive_days_visit_count """ - #todo: in reality author of this activity must not be the receiving user - #but for now just have this plug, so that last seen timestamp is not - #perturbed by the email update sender - if instance.activity_type == const.TYPE_ACTIVITY_EMAIL_UPDATE_SENT: - return - user = instance.user - user.last_seen = instance.active_at + prev_last_seen = user.last_seen + user.last_seen = timestamp + if (user.last_seen - prev_last_seen).days == 1: + user.consecutive_days_visit_count += 1 + award_badges_signal.send(None, + event = 'site_visit', + actor = user, + context_object = user, + timestamp = timestamp + ) user.save() @@ -2049,7 +2053,6 @@ django_signals.pre_save.connect(calculate_gravatar_hash, sender=User) django_signals.post_save.connect(record_award_event, sender=Award) django_signals.post_save.connect(notify_award_message, sender=Award) django_signals.post_save.connect(record_answer_accepted, sender=Answer) -django_signals.post_save.connect(update_last_seen, sender=Activity) django_signals.post_save.connect(record_vote, sender=Vote) django_signals.post_save.connect( record_favorite_question, @@ -2077,6 +2080,7 @@ signals.post_updated.connect( record_post_update_activity, sender=Question ) +signals.site_visited.connect(record_user_visit) #post_syncdb.connect(create_fulltext_indexes) #todo: wtf??? what is x=x about? diff --git a/askbot/models/badges.py b/askbot/models/badges.py index 3e50d094..77d4e0ab 100644 --- a/askbot/models/badges.py +++ b/askbot/models/badges.py @@ -701,16 +701,27 @@ class FavoriteQuestion(FavoriteTypeBadge): return self class Enthusiast(Badge): - """Unimplemented stub badge""" + """Awarded to a user who visits the site + for a certain number of days in a row + """ def __init__(self): super(Enthusiast, self).__init__( key = 'enthusiast', name = _('Enthusiast'), level = const.SILVER_BADGE, multiple = False, - description = _('Visited site every day for 30 days in a row') + description = _( + 'Visited site every day for %(num)s days in a row' + ) % {'num': askbot_settings.ENTHUSIAST_BADGE_MIN_DAYS} ) + def consider_award(self, actor = None, + context_object = None, timestamp = None): + min_days = askbot_settings.ENTHUSIAST_BADGE_MIN_DAYS + if actor.consecutive_days_visit_count == min_days: + return self.award(actor, context_object, timestamp) + return False + class Commentator(Badge): """Commentator is a bronze badge that is awarded once when user posts a certain number of @@ -730,7 +741,8 @@ class Commentator(Badge): context_object = None, timestamp = None): num_comments = Comment.objects.filter(user = actor).count() if num_comments >= askbot_settings.COMMENTATOR_BADGE_MIN_COMMENTS: - self.award(actor, context_object, timestamp) + return self.award(actor, context_object, timestamp) + return False class Taxonomist(Badge): """Stub badge""" @@ -753,7 +765,8 @@ class Taxonomist(Badge): #the "-1" is used because tag counts are updated in a bulk query #that does not update the value in the python object if tag.used_count == taxonomist_threshold - 1: - self.award(tag.created_by, tag, timestamp) + return self.award(tag.created_by, tag, timestamp) + return False class Expert(Badge): """Stub badge""" @@ -833,8 +846,9 @@ EVENTS_TO_BADGES = { 'post_answer': (Necromancer,), 'post_comment': (Commentator,), 'retag_question': (Organizer,), - 'update_tag': (Taxonomist,), 'select_favorite_question': (FavoriteQuestion, StellarQuestion,), + 'site_visit': (Enthusiast,), + 'update_tag': (Taxonomist,), 'update_user_profile': (Autobiographer,), 'upvote_answer': ( Teacher, NiceAnswer, GoodAnswer, diff --git a/askbot/models/signals.py b/askbot/models/signals.py index cfd75294..83ea6eda 100644 --- a/askbot/models/signals.py +++ b/askbot/models/signals.py @@ -1,3 +1,5 @@ +"""Custom django signals defined for the askbot forum application. +""" import django.dispatch tags_updated = django.dispatch.Signal( @@ -23,3 +25,4 @@ post_updated = django.dispatch.Signal( 'newly_mentioned_users' ] ) +site_visited = django.dispatch.Signal(providing_args=['user', 'timestamp']) diff --git a/askbot/skins/default/templates/blocks/footer.html b/askbot/skins/default/templates/blocks/footer.html index 03f27460..582e6d33 100644 --- a/askbot/skins/default/templates/blocks/footer.html +++ b/askbot/skins/default/templates/blocks/footer.html @@ -19,7 +19,7 @@

- powered by ASKBOT + powered by ASKBOT version {{settings.ASKBOT_VERSION}}
{{settings.APP_COPYRIGHT}}

diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py index 0857da38..f2309dde 100644 --- a/askbot/startup_procedures.py +++ b/askbot/startup_procedures.py @@ -13,13 +13,11 @@ from askbot.models import badges #todo: # # *validate emails in settings.py - -def run_startup_tests(): - """function that runs - all startup tests, mainly checking settings config so far +def test_askbot_url(): + """Tests the ASKBOT_URL setting for the + well-formedness and raises the ImproperlyConfigured + exception, if the setting is not good. """ - - #todo: refactor this when another test arrives url = settings.ASKBOT_URL if url != '': @@ -48,6 +46,48 @@ def run_startup_tests(): msg = 'if ASKBOT_URL setting is not empty, ' + \ 'it must not start with /' +def test_middleware(): + """Checks that all required middleware classes are + installed in the django settings.py file. If that is not the + case - raises an ImproperlyConfigured exception. + """ + required_middleware = ( + 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware', + 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware', + 'askbot.middleware.cancel.CancelActionMiddleware', + 'askbot.deps.recaptcha_django.middleware.ReCaptchaMiddleware', + 'django.middleware.transaction.TransactionMiddleware', + 'askbot.middleware.view_log.ViewLogMiddleware', + ) + #'debug_toolbar.middleware.DebugToolbarMiddleware', + missing_middleware = list() + for middleware in required_middleware: + if middleware not in settings.MIDDLEWARE_CLASSES: + missing_middleware.append(middleware) + + debug_toolbar_middleware = 'debug_toolbar.middleware.DebugToolbarMiddleware' + if 'debug_toolbar' in settings.INSTALLED_APPS: + if debug_toolbar_middleware not in settings.MIDDLEWARE_CLASSES: + missing_middleware.append(debug_toolbar_middleware) + + if missing_middleware: + error_message = """Please add the following middleware (listed +after this message) to the MIDDLEWARE_CLASSES variable in your site settings.py file. +The order the middleware records may be important, please take a look at the example in +https://github.com/ASKBOT/askbot-devel/blob/master/askbot/setup_templates/settings.py\n\n%s""" \ + % ', '.join(missing_middleware) + raise ImproperlyConfigured(error_message) + + +def run_startup_tests(): + """function that runs + all startup tests, mainly checking settings config so far + """ + + #todo: refactor this when another test arrives + test_askbot_url() + test_middleware() + @transaction.commit_manually def run(): """runs all the startup procedures""" diff --git a/askbot/tests/badge_tests.py b/askbot/tests/badge_tests.py index 3d79aa3a..fdb9d626 100644 --- a/askbot/tests/badge_tests.py +++ b/askbot/tests/badge_tests.py @@ -490,3 +490,15 @@ class BadgeTests(AskbotTestCase): self.assert_have_badge('taxonomist', self.u1, 0) self.post_question(user = self.u2, tags = 'test') self.assert_have_badge('taxonomist', self.u1, 1) + + def test_enthusiast_badge(self): + yesterday = datetime.datetime.now() - datetime.timedelta(1) + self.u1.last_seen = yesterday + prev_visit_count = settings.ENTHUSIAST_BADGE_MIN_DAYS - 1 + self.u1.consecutive_days_visit_count = prev_visit_count + self.u1.save() + self.assert_have_badge('enthusiast', self.u1, 0) + self.client.login(method = 'force', user_id = self.u1.id) + self.client.get('/') + self.assert_have_badge('enthusiast', self.u1, 1) + diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 6fb2fd43..8de18004 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -410,8 +410,6 @@ def question(request, id):#refactor - long subroutine. display question body, an #in the case if the permalinked items or their parents are gone - redirect #redirect also happens if id of the object's origin post != requested id show_post = None #used for permalinks - #import pdb - #pdb.set_trace() if show_comment is not None: #comments try: -- cgit v1.2.3-1-g7c22