summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--forum/const/__init__.py36
-rw-r--r--forum/migrations/0013_add_response_count__to_user.py314
-rw-r--r--forum/models/__init__.py90
-rw-r--r--forum/skins/default/media/images/mail-envelope-empty.pngbin0 -> 547 bytes
-rw-r--r--forum/skins/default/media/images/mail-envelope-full.pngbin0 -> 482 bytes
-rwxr-xr-xforum/skins/default/media/style/style.css7
-rw-r--r--forum/skins/default/templates/header.html17
-rw-r--r--forum/utils/functions.py34
-rw-r--r--forum/views/readers.py89
9 files changed, 508 insertions, 79 deletions
diff --git a/forum/const/__init__.py b/forum/const/__init__.py
index f27229c8..e3f086aa 100644
--- a/forum/const/__init__.py
+++ b/forum/const/__init__.py
@@ -124,35 +124,37 @@ TYPE_ACTIVITY = (
(TYPE_ACTIVITY_MENTION, _('mentioned in the post')),
)
-#response activity has receiving user not empty
-RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY = (
- TYPE_ACTIVITY_ANSWER,
- TYPE_ACTIVITY_ASK_QUESTION,
+
+RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS = (
TYPE_ACTIVITY_COMMENT_QUESTION,
TYPE_ACTIVITY_COMMENT_ANSWER,
TYPE_ACTIVITY_UPDATE_ANSWER,
TYPE_ACTIVITY_UPDATE_QUESTION,
- TYPE_ACTIVITY_UPDATE_ANSWER,
- TYPE_ACTIVITY_PRIZE,
- TYPE_ACTIVITY_MARK_ANSWER,
- TYPE_ACTIVITY_VOTE_UP,
- TYPE_ACTIVITY_VOTE_DOWN,
- TYPE_ACTIVITY_CANCEL_VOTE,
- TYPE_ACTIVITY_DELETE_QUESTION,
- TYPE_ACTIVITY_DELETE_ANSWER,
- TYPE_ACTIVITY_MARK_OFFENSIVE,
- TYPE_ACTIVITY_FAVORITE,
+ TYPE_ACTIVITY_ANSWER,
+ TYPE_ACTIVITY_ASK_QUESTION,
)
-RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS = (
+
+#the same as for instant notifications for now
+RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY = (
+ TYPE_ACTIVITY_ANSWER,
+ TYPE_ACTIVITY_ASK_QUESTION,
TYPE_ACTIVITY_COMMENT_QUESTION,
TYPE_ACTIVITY_COMMENT_ANSWER,
TYPE_ACTIVITY_UPDATE_ANSWER,
TYPE_ACTIVITY_UPDATE_QUESTION,
- TYPE_ACTIVITY_ANSWER,
- TYPE_ACTIVITY_ASK_QUESTION,
+# TYPE_ACTIVITY_PRIZE,
+# TYPE_ACTIVITY_MARK_ANSWER,
+# TYPE_ACTIVITY_VOTE_UP,
+# TYPE_ACTIVITY_VOTE_DOWN,
+# TYPE_ACTIVITY_CANCEL_VOTE,
+# TYPE_ACTIVITY_DELETE_QUESTION,
+# TYPE_ACTIVITY_DELETE_ANSWER,
+# TYPE_ACTIVITY_MARK_OFFENSIVE,
+# TYPE_ACTIVITY_FAVORITE,
)
+
RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES = {
TYPE_ACTIVITY_COMMENT_QUESTION: 'question_comment',
TYPE_ACTIVITY_COMMENT_ANSWER: 'answer_comment',
diff --git a/forum/migrations/0013_add_response_count__to_user.py b/forum/migrations/0013_add_response_count__to_user.py
new file mode 100644
index 00000000..5cd906a7
--- /dev/null
+++ b/forum/migrations/0013_add_response_count__to_user.py
@@ -0,0 +1,314 @@
+# 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 'Activity.junk'
+ db.add_column(
+ u'auth_user',
+ 'response_count',
+ self.gf('django.db.models.fields.IntegerField')(default=0, ),
+ keep_default=False
+ )
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Activity.junk'
+ db.delete_column(u'auth_user', 'response_count')
+
+
+ models = {
+ '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']", 'symmetrical': 'False', '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'}),
+ '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']", 'symmetrical': 'False', '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_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', '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'}),
+ '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'}),
+ 'response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ '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']", 'symmetrical': 'False', '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'})
+ },
+ 'forum.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', [], {}),
+ 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'forum.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['forum.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'})
+ },
+ 'forum.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'})
+ },
+ 'forum.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['forum.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'})
+ },
+ 'forum.answerrevision': {
+ 'Meta': {'object_name': 'AnswerRevision', 'db_table': "u'answer_revision'"},
+ 'answer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['forum.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', [], {})
+ },
+ 'forum.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['forum.Badge']"}),
+ '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']"})
+ },
+ 'forum.badge': {
+ 'Meta': {'unique_together': "(('name', 'type'),)", 'object_name': 'Badge', 'db_table': "u'badge'"},
+ 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "'Award'", 'to': "orm['auth.User']"}),
+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '300'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),
+ 'type': ('django.db.models.fields.SmallIntegerField', [], {})
+ },
+ 'forum.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', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['auth.User']"})
+ },
+ 'forum.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']"})
+ },
+ 'forum.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['forum.Question']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"})
+ },
+ 'forum.flaggeditem': {
+ 'Meta': {'unique_together': "(('content_type', 'object_id', 'user'),)", 'object_name': 'FlaggedItem', 'db_table': "u'flagged_item'"},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'flagged_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ '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': "'flaggeditems'", 'to': "orm['auth.User']"})
+ },
+ 'forum.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['forum.Tag']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"})
+ },
+ 'forum.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'", 'symmetrical': 'False', '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'", 'symmetrical': 'False', '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'", 'symmetrical': 'False', 'to': "orm['forum.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'})
+ },
+ 'forum.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['forum.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'})
+ },
+ 'forum.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['forum.Question']"}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {}),
+ 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"})
+ },
+ 'forum.repute': {
+ 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"},
+ '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['forum.Question']"}),
+ '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']"})
+ },
+ 'forum.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'})
+ },
+ 'forum.validationhash': {
+ 'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'ValidationHash'},
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 6, 6, 20, 9, 43, 480777)'}),
+ 'hash_code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'seed': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'forum.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'})
+ }
+ }
+
+ complete_apps = ['forum']
diff --git a/forum/models/__init__.py b/forum/models/__init__.py
index 946e2a90..5d25da09 100644
--- a/forum/models/__init__.py
+++ b/forum/models/__init__.py
@@ -31,17 +31,9 @@ from forum import auth
User.add_to_class('is_approved', models.BooleanField(default=False))
User.add_to_class('email_isvalid', models.BooleanField(default=False))
User.add_to_class('email_key', models.CharField(max_length=32, null=True))
-
#hardcoded initial reputaion of 1, no setting for this one
User.add_to_class('reputation', models.PositiveIntegerField(default=1))
User.add_to_class('gravatar', models.CharField(max_length=32))
-
-#User.add_to_class('favorite_questions',
-# models.ManyToManyField(Question, through=FavoriteQuestion,
-# related_name='favorited_by'))
-
-#User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
-# related_name='awarded_to'))
User.add_to_class('gold', models.SmallIntegerField(default=0))
User.add_to_class('silver', models.SmallIntegerField(default=0))
User.add_to_class('bronze', models.SmallIntegerField(default=0))
@@ -65,6 +57,7 @@ User.add_to_class('tag_filter_setting',
default='ignored'
)
)
+User.add_to_class('response_count', models.IntegerField(default=0))
def user_is_username_taken(cls,username):
try:
@@ -78,17 +71,16 @@ def user_is_username_taken(cls,username):
def user_get_q_sel_email_feed_frequency(self):
#print 'looking for frequency for user %s' % self
try:
- feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel')
+ feed_setting = EmailFeedSetting.objects.get(
+ subscriber=self,
+ feed_type='q_sel'
+ )
except Exception, e:
#print 'have error %s' % e.message
raise e
#print 'have freq=%s' % feed_setting.frequency
return feed_setting.frequency
-def user_get_absolute_url(self):
- return "/users/%d/%s/" % (self.id, (self.username))
-
-
def get_messages(self):
messages = []
for m in self.message_set.all():
@@ -98,12 +90,21 @@ def get_messages(self):
def delete_messages(self):
self.message_set.all().delete()
+#todo: find where this is used and replace with get_absolute_url
def get_profile_url(self):
"""Returns the URL for this User's profile."""
- return reverse('user_profile', kwargs={'id':self.id, 'slug':slugify(self.username)})
+ return reverse(
+ 'user_profile',
+ kwargs={'id':self.id, 'slug':slugify(self.username)}
+ )
+
+def user_get_absolute_url(self):
+ return self.get_profile_url()
def get_profile_link(self):
- profile_link = u'<a href="%s">%s</a>' % (self.get_profile_url(),self.username)
+ profile_link = u'<a href="%s">%s</a>' \
+ % (self.get_profile_url(),self.username)
+
return mark_safe(profile_link)
#series of methods for user vote-type commands
@@ -139,8 +140,10 @@ def toggle_favorite_question(self, question, timestamp=None, cancel=False):
Question.objects.update_favorite_count(question)
return result
-#"private" wrapper function that applies post upvotes/downvotes and cancelations
def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None):
+ """"private" wrapper function that applies post upvotes/downvotes
+ and cancelations
+ """
post_type = ContentType.objects.get_for_model(post)
#get or create the vote object
#return with noop in some situations
@@ -224,7 +227,10 @@ def flag_post(user, post, timestamp=None, cancel=False):
auth.onFlaggedItem(flag, post, user, timestamp=timestamp)
User.add_to_class('is_username_taken',classmethod(user_is_username_taken))
-User.add_to_class('get_q_sel_email_feed_frequency',user_get_q_sel_email_feed_frequency)
+User.add_to_class(
+ 'get_q_sel_email_feed_frequency',
+ user_get_q_sel_email_feed_frequency
+ )
User.add_to_class('get_absolute_url', user_get_absolute_url)
User.add_to_class('upvote', upvote)
User.add_to_class('downvote', downvote)
@@ -309,7 +315,7 @@ def send_instant_notifications_about_activity_in_post(
template = template,
)
#todo: this could be packaged as an "action" - a bundle
- #of executive function with the corresponding activity log recording
+ #of executive function with the activity log recording
msg = EmailMessage(
subject,
text,
@@ -370,11 +376,19 @@ def record_post_update_activity(
update_activity.receiving_users.add(*receiving_users)
+ assert(updated_by not in receiving_users)
+
+ for user in set(receiving_users) | set(newly_mentioned_users):
+ user.response_count += 1
+ user.save()
+
+ #todo: weird thing is that only comments need the receiving_users
+ #argument to this call
notification_subscribers = post.get_instant_notification_subscribers(
- potential_subscribers = receiving_users,
- mentioned_users = newly_mentioned_users,
- exclude_list = [updated_by, ]
- )
+ potential_subscribers = receiving_users,
+ mentioned_users = newly_mentioned_users,
+ exclude_list = [updated_by, ]
+ )
send_instant_notifications_about_activity_in_post(
update_activity = update_activity,
@@ -385,12 +399,14 @@ def record_post_update_activity(
def record_award_event(instance, created, **kwargs):
"""
- After we awarded a badge to user, we need to record this activity and notify user.
+ After we awarded a badge to user, we need to
+ record this activity and notify user.
We also recaculate awarded_count of this badge and user information.
"""
if created:
+ #todo: change this to community user who gives the award
activity = Activity(
- user=instance.user,#todo: change this to community user who gives the award
+ user=instance.user,
active_at=instance.awarded_at,
content_object=instance,
activity_type=const.TYPE_ACTIVITY_PRIZE
@@ -424,7 +440,8 @@ def notify_award_message(instance, created, **kwargs):
def record_answer_accepted(instance, created, **kwargs):
"""
- when answer is accepted, we record this for question author - who accepted it.
+ when answer is accepted, we record this for question author
+ - who accepted it.
"""
if not created and instance.accepted:
activity = Activity(
@@ -435,8 +452,8 @@ def record_answer_accepted(instance, created, **kwargs):
)
activity.save()
receiving_users = instance.get_author_list(
- exclude_list = [instance.question.author]
- )
+ exclude_list = [instance.question.author]
+ )
activity.receiving_users.add(*receiving_users)
@@ -546,8 +563,8 @@ def record_favorite_question(instance, created, **kwargs):
)
activity.save()
receiving_users = instance.question.get_author_list(
- exclude_list = [instance.user]
- )
+ exclude_list = [instance.user]
+ )
activity.receiving_users.add(*receiving_users)
def record_user_full_updated(instance, **kwargs):
@@ -559,7 +576,14 @@ def record_user_full_updated(instance, **kwargs):
)
activity.save()
-def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs):
+def post_stored_anonymous_content(
+ sender,
+ user,
+ session_key,
+ signal,
+ *args,
+ **kwargs):
+
aq_list = AnonymousQuestion.objects.filter(session_key = session_key)
aa_list = AnonymousAnswer.objects.filter(session_key = session_key)
#from forum.conf import settings as forum_settings
@@ -584,7 +608,10 @@ 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, sender=FavoriteQuestion)
+django_signals.post_save.connect(
+ record_favorite_question,
+ sender=FavoriteQuestion
+ )
django_signals.post_delete.connect(record_cancel_vote, sender=Vote)
#change this to real m2m_changed with Django1.2
@@ -670,7 +697,6 @@ __all__ = [
#from forum.modules import get_modules_script_classes
-#
#for k, v in get_modules_script_classes('models', models.Model).items():
# if not k in __all__:
# __all__.append(k)
diff --git a/forum/skins/default/media/images/mail-envelope-empty.png b/forum/skins/default/media/images/mail-envelope-empty.png
new file mode 100644
index 00000000..0fde87dc
--- /dev/null
+++ b/forum/skins/default/media/images/mail-envelope-empty.png
Binary files differ
diff --git a/forum/skins/default/media/images/mail-envelope-full.png b/forum/skins/default/media/images/mail-envelope-full.png
new file mode 100644
index 00000000..2277e919
--- /dev/null
+++ b/forum/skins/default/media/images/mail-envelope-full.png
Binary files differ
diff --git a/forum/skins/default/media/style/style.css b/forum/skins/default/media/style/style.css
index 3a96f62e..25cb2ada 100755
--- a/forum/skins/default/media/style/style.css
+++ b/forum/skins/default/media/style/style.css
@@ -271,6 +271,13 @@ blockquote {
color: #555555;
}
+#top a.ab-responses-envelope {
+ margin-left: 0;
+}
+#top a img {
+ vertical-align:text-bottom;
+}
+
#logo {
padding: 0px 0px 0px 10px;
height: 90px;
diff --git a/forum/skins/default/templates/header.html b/forum/skins/default/templates/header.html
index f2eaa3ac..014c2990 100644
--- a/forum/skins/default/templates/header.html
+++ b/forum/skins/default/templates/header.html
@@ -6,7 +6,22 @@
<div id="navBar">
<div id="top">
{% if request.user.is_authenticated %}
- <a href="{% url user_profile id=request.user.id,slug=request.user.username|slugify %}">{{ request.user.username }}</a> {% get_score_badge request.user %}
+ <a href="{% url user_profile id=request.user.id,slug=request.user.username|slugify %}">{{ request.user.username }}</a>
+ {% spaceless %}
+ <a class='ab-responses-envelope' href="{{request.user.get_absolute_url}}?sort=responses">
+ <img
+ alt="{%blocktrans with request.user.username as username %}responses for {{username}}{% endblocktrans %}"
+ {% if request.user.response_count > 0 %}
+ src="{% media "/media/images/mail-envelope-full.png" %}"
+ title="{% blocktrans count request.user.response_count as response_count %}you have a new response{% plural %}you nave {{response_count}} new responses{% endblocktrans %}"
+ {% else %}
+ src="{% media "/media/images/mail-envelope-empty.png" %}"
+ title="{% trans "no new responses yet" %}"
+ {% endif %}
+ />
+ </a>
+ {% endspaceless %}
+ <span>(karma:</span> {% get_score_badge request.user %})
<a href="{% url logout %}">{% trans "logout" %}</a>
{% else %}
<a href="{% url user_signin %}">{% trans "login" %}</a>
diff --git a/forum/utils/functions.py b/forum/utils/functions.py
index da6a2cae..7d886a2a 100644
--- a/forum/utils/functions.py
+++ b/forum/utils/functions.py
@@ -1,11 +1,45 @@
+import re
+
def get_from_dict_or_object(source, key):
try:
return source[key]
except:
return getattr(source,key)
+
def is_iterable(thing):
if hasattr(thing, '__iter__'):
return True
else:
return isinstance(thing, basestring)
+
+BOT_REGEX = re.compile(
+ r'bot|http|\.com|crawl|spider|python|curl|yandex'
+)
+BROWSER_REGEX = re.compile(
+ r'^(Mozilla.*(Gecko|KHTML|MSIE|Presto|Trident)|Opera).*$'
+)
+MOBILE_REGEX = re.compile(
+ r'(BlackBerry|HTC|LG|MOT|Nokia|NOKIAN|PLAYSTATION|PSP|SAMSUNG|SonyEricsson)'
+)
+
+
+def not_a_robot_request(request):
+
+ if 'HTTP_ACCEPT_LANGUAGE' not in request.META:
+ return False
+
+ user_agent = request.META.get('HTTP_USER_AGENT', None)
+ if user_agent is None:
+ return False
+
+ if BOT_REGEX.match(user_agent, re.IGNORECASE):
+ return False
+
+ if MOBILE_REGEX.match(user_agent):
+ return True
+
+ if BROWSER_REGEX.search(user_agent):
+ return True
+
+ return False
diff --git a/forum/views/readers.py b/forum/views/readers.py
index 041f44bd..ff825c10 100644
--- a/forum/views/readers.py
+++ b/forum/views/readers.py
@@ -24,6 +24,7 @@ from forum.models import *
from forum import const
from forum import auth
from forum.utils.forms import get_next_url
+from forum.utils.functions import not_a_robot_request
from forum.search.state_manager import SearchState
# used in index page
@@ -304,36 +305,66 @@ 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)
- #todo: merge view counts per user and per session
- #1) view count per session
- update_view_count = False
- if 'question_view_times' not in request.session:
- request.session['question_view_times'] = {}
-
- last_seen = request.session['question_view_times'].get(question.id,None)
- updated_when, updated_who = question.get_last_update_info()
-
- if updated_who != request.user:
- if last_seen:
- if last_seen < updated_when:
- update_view_count = True
- else:
- update_view_count = True
-
- request.session['question_view_times'][question.id] = datetime.datetime.now()
-
- if update_view_count:
- question.view_count += 1
- question.save()
+ if 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
+ update_view_count = False
+ if 'question_view_times' not in request.session:
+ request.session['question_view_times'] = {}
+
+ last_seen = request.session['question_view_times'].get(question.id,None)
+ updated_when, updated_who = question.get_last_update_info()
+
+ if updated_who != request.user:
+ if last_seen:
+ if last_seen < updated_when:
+ update_view_count = True
+ else:
+ update_view_count = True
+
+ request.session['question_view_times'][question.id] = \
+ datetime.datetime.now()
+ if update_view_count:
+ question.view_count += 1
+ question.save()
+
+ #2) question view count per user
+ if request.user.is_authenticated():
+ try:
+ question_view = QuestionView.objects.get(
+ who=request.user,
+ question=question
+ )
+ #another task - remove the response alert if it exists
+ ACTIVITY_TYPES = const.RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY
+ response_activities = Activity.objects.filter(
+ receiving_users = request.user,
+ activity_type__in = ACTIVITY_TYPES,
+ active_at__gt = question_view.when
+ )
+
+ for activity in response_activities:
+ post = activity.content_object
+ if hasattr(post, 'get_origin_post'):
+ if question == post.get_origin_post():
+ activity.receiving_users.remove(request.user)
+ if request.user.response_count > 0:
+ request.user.response_count -= 1
+ request.user.save()
+ else:
+ logging.critical(
+ 'response count wanted to go below zero'
+ )
+ else:
+ logging.critical(
+ 'activity content object has no get_origin_post method'
+ )
- #2) question view count per user
- if request.user.is_authenticated():
- try:
- question_view = QuestionView.objects.get(who=request.user, question=question)
- except QuestionView.DoesNotExist:
- question_view = QuestionView(who=request.user, question=question)
- question_view.when = datetime.datetime.now()
- question_view.save()
+ except QuestionView.DoesNotExist:
+ question_view = QuestionView(who=request.user, question=question)
+ question_view.when = datetime.datetime.now()
+ question_view.save()
return render_to_response('question.html', {
'view_name': 'question',