diff options
-rw-r--r-- | forum/const/__init__.py | 36 | ||||
-rw-r--r-- | forum/migrations/0013_add_response_count__to_user.py | 314 | ||||
-rw-r--r-- | forum/models/__init__.py | 90 | ||||
-rw-r--r-- | forum/skins/default/media/images/mail-envelope-empty.png | bin | 0 -> 547 bytes | |||
-rw-r--r-- | forum/skins/default/media/images/mail-envelope-full.png | bin | 0 -> 482 bytes | |||
-rwxr-xr-x | forum/skins/default/media/style/style.css | 7 | ||||
-rw-r--r-- | forum/skins/default/templates/header.html | 17 | ||||
-rw-r--r-- | forum/utils/functions.py | 34 | ||||
-rw-r--r-- | forum/views/readers.py | 89 |
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 Binary files differnew file mode 100644 index 00000000..0fde87dc --- /dev/null +++ b/forum/skins/default/media/images/mail-envelope-empty.png diff --git a/forum/skins/default/media/images/mail-envelope-full.png b/forum/skins/default/media/images/mail-envelope-full.png Binary files differnew file mode 100644 index 00000000..2277e919 --- /dev/null +++ b/forum/skins/default/media/images/mail-envelope-full.png 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', |