summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2011-01-05 16:17:45 -0500
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2011-01-05 16:17:45 -0500
commita5cf08b3f6f43f0d8c9c38a233380e03b1f253c6 (patch)
tree9b3a798715088bcb954fc8275960e2d96c0c8c43
parent717debc97eb97199c343a5699b04fd2af8e3c858 (diff)
downloadaskbot-a5cf08b3f6f43f0d8c9c38a233380e03b1f253c6.tar.gz
askbot-a5cf08b3f6f43f0d8c9c38a233380e03b1f253c6.tar.bz2
askbot-a5cf08b3f6f43f0d8c9c38a233380e03b1f253c6.zip
made the enthusiast badge work and added version display to the footer template
-rw-r--r--askbot/conf/badges.py9
-rw-r--r--askbot/context.py10
-rw-r--r--askbot/middleware/view_log.py37
-rw-r--r--askbot/migrations/0033_add__consecutive_days_visit_count__to__auth_user.py309
-rw-r--r--askbot/models/__init__.py26
-rw-r--r--askbot/models/badges.py24
-rw-r--r--askbot/models/signals.py3
-rw-r--r--askbot/skins/default/templates/blocks/footer.html2
-rw-r--r--askbot/startup_procedures.py52
-rw-r--r--askbot/tests/badge_tests.py12
-rw-r--r--askbot/views/readers.py2
11 files changed, 451 insertions, 35 deletions
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 @@
</div>
<p>
<a href="http://askbot.org" target="_blank">
- powered by ASKBOT
+ powered by ASKBOT version {{settings.ASKBOT_VERSION}}
</a><br/>{{settings.APP_COPYRIGHT}}
</p>
</div>
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: