From 57033196f904477631c397fab50681f0d1aa7834 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Mon, 7 Jun 2010 00:54:39 -0400 Subject: notification records are correct but response counters are exxagerated. added unit tests for response counters --- forum/const/__init__.py | 2 + .../migrations/0013_add_response_count__to_user.py | 28 +- forum/models/answer.py | 3 + forum/models/base.py | 11 +- forum/models/content.py | 1 + forum/models/question.py | 2 +- forum/tests.py | 496 ++++++++++++++++++++- 7 files changed, 526 insertions(+), 17 deletions(-) diff --git a/forum/const/__init__.py b/forum/const/__init__.py index e3f086aa..0da9ec22 100644 --- a/forum/const/__init__.py +++ b/forum/const/__init__.py @@ -125,6 +125,7 @@ TYPE_ACTIVITY = ( ) +#MENTION activity is added implicitly, unfortunately RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS = ( TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, @@ -136,6 +137,7 @@ RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS = ( #the same as for instant notifications for now +#MENTION activity is added implicitly, unfortunately RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY = ( TYPE_ACTIVITY_ANSWER, TYPE_ACTIVITY_ASK_QUESTION, diff --git a/forum/migrations/0013_add_response_count__to_user.py b/forum/migrations/0013_add_response_count__to_user.py index 5cd906a7..30ca164e 100644 --- a/forum/migrations/0013_add_response_count__to_user.py +++ b/forum/migrations/0013_add_response_count__to_user.py @@ -7,19 +7,27 @@ 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 - ) + """adds integer field User.response_counter + if the field does not yet exist + this case checking is necessary to support syncdb of auth models + a bit hacky but we have to do it as long as we keep patching auth models + within the forum application + """ + try: + db.add_column( + u'auth_user', + 'response_count', + self.gf('django.db.models.fields.IntegerField')(default=0, ), + keep_default=False + ) + except: + print 'probably already have column User.response_count' + pass def backwards(self, orm): - - # Deleting field 'Activity.junk' + """remove field User.respose_count + """ db.delete_column(u'auth_user', 'response_count') diff --git a/forum/models/answer.py b/forum/models/answer.py index 48b1c464..b4e8963e 100644 --- a/forum/models/answer.py +++ b/forum/models/answer.py @@ -172,6 +172,9 @@ class Answer(content.Content, DeletableContent): include_comments = True ) ) + for answer in self.question.answers.all(): + receiving_users.update(answer.get_author_list()) + receiving_users -= set(exclude_list) return list(receiving_users) diff --git a/forum/models/base.py b/forum/models/base.py index 964c6142..d832b71c 100644 --- a/forum/models/base.py +++ b/forum/models/base.py @@ -126,11 +126,12 @@ def save_post(post, **kwargs): #create new mentions for u in newly_mentioned_users: from forum.models.user import Activity - Activity.objects.create_new_mention( - mentioned_whom = u, - mentioned_in = post, - mentioned_by = last_author - ) + if u != last_author: + Activity.objects.create_new_mention( + mentioned_whom = u, + mentioned_in = post, + mentioned_by = last_author + ) #todo: this is handled in signal because models for posts #are too spread out diff --git a/forum/models/content.py b/forum/models/content.py index 441f7133..eb2b423e 100644 --- a/forum/models/content.py +++ b/forum/models/content.py @@ -75,6 +75,7 @@ class Content(models.Model): comment.save() self.comment_count = self.comment_count + 1 self.save() + return comment def get_instant_notification_subscribers( self, diff --git a/forum/models/question.py b/forum/models/question.py index 88f01e4f..c6c16578 100644 --- a/forum/models/question.py +++ b/forum/models/question.py @@ -437,7 +437,7 @@ class Question(content.Content, DeletableContent): def add_revision(self,author=None, text=None, comment=None, revised_at=None): if None in (author, text, comment): - raise Exception('author, text and revised_at are required arguments') + raise Exception('author, text and comment are required arguments') rev_no = self.revisions.all().count() + 1 if comment in (None, ''): if rev_no == 1: diff --git a/forum/tests.py b/forum/tests.py index 2096386b..c2e1765f 100644 --- a/forum/tests.py +++ b/forum/tests.py @@ -1,8 +1,502 @@ +import datetime +import time from django.test import TestCase -from django.contrib.auth.models import User from django.template import defaultfilters from django.core.urlresolvers import reverse +from forum.models import User, Question, Answer, Activity +from forum.models import EmailFeedSetting +from forum import const +def create_user(username = None, email = None): + user = User.objects.create_user(username, email) + for feed_type in EmailFeedSetting.FEED_TYPES: + feed = EmailFeedSetting( + feed_type = feed_type[0], + frequency = 'n', + subscriber = user + ) + feed.save() + return user + +def get_re_notif_after(timestamp): + notifications = Activity.objects.filter( + activity_type__in = const.RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY, + active_at__gte = timestamp + ) + return notifications + +class UpdateNotificationTests(TestCase): + + def reset_response_counts(self): + self.reload_users() + for user in self.users: + user.response_count = 0 + user.save() + + def reload_users(self): + self.u11 = User.objects.get(id=self.u11.id) + self.u12 = User.objects.get(id=self.u12.id) + self.u13 = User.objects.get(id=self.u13.id) + self.u14 = User.objects.get(id=self.u14.id) + self.u21 = User.objects.get(id=self.u21.id) + self.u22 = User.objects.get(id=self.u22.id) + self.u23 = User.objects.get(id=self.u23.id) + self.u24 = User.objects.get(id=self.u24.id) + self.u31 = User.objects.get(id=self.u31.id) + self.u32 = User.objects.get(id=self.u32.id) + self.u33 = User.objects.get(id=self.u33.id) + self.u34 = User.objects.get(id=self.u34.id) + self.users = [ + self.u11, + self.u12, + self.u13, + self.u14, + self.u21, + self.u22, + self.u23, + self.u24, + self.u31, + self.u32, + self.u33, + self.u34, + ] + + def setUp(self): + #users for the question + self.u11 = create_user('user11', 'user11@example.com') + self.u12 = create_user('user12', 'user12@example.com') + self.u13 = create_user('user13', 'user13@example.com') + self.u14 = create_user('user14', 'user14@example.com') + + #users for first answer + self.u21 = create_user('user21', 'user21@example.com')#post answer + self.u22 = create_user('user22', 'user22@example.com')#edit answer + self.u23 = create_user('user23', 'user23@example.com') + self.u24 = create_user('user24', 'user24@example.com') + + #users for second answer + self.u31 = create_user('user31', 'user31@example.com')#post answer + self.u32 = create_user('user32', 'user32@example.com')#edit answer + self.u33 = create_user('user33', 'user33@example.com') + self.u34 = create_user('user34', 'user34@example.com') + + #a hack to initialize .users list + self.reload_users() + + #pre-populate forum with some content + self.question = Question.objects.create_new( + title = 'test question', + author = self.u11, + added_at = datetime.datetime.now(), + wiki = False, + tagnames = 'test', + text = 'hey listen up', + ) + self.comment12 = self.question.add_comment( + user = self.u12, + comment = 'comment12' + ) + self.comment13 = self.question.add_comment( + user = self.u13, + comment = 'comment13' + ) + self.answer1 = Answer.objects.create_new( + question = self.question, + author = self.u21, + added_at = datetime.datetime.now(), + text = 'answer1' + ) + self.comment22 = self.answer1.add_comment( + user = self.u22, + comment = 'comment22' + ) + self.comment23 = self.answer1.add_comment( + user = self.u23, + comment = 'comment23' + ) + self.answer2 = Answer.objects.create_new( + question = self.question, + author = self.u31, + added_at = datetime.datetime.now(), + text = 'answer2' + ) + self.comment32 = self.answer2.add_comment( + user = self.u32, + comment = 'comment32' + ) + self.comment33 = self.answer2.add_comment( + user = self.u33, + comment = 'comment33' + ) + + def test_self_comments(self): + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.add_comment( + user = self.u11, + comment = 'self-comment 1', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u12, self.u13]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 1, 1, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer1.add_comment( + user = self.u21, + comment = 'self-comment 2', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u22, self.u23]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 0, 0, 0, + 0, 1, 1, 0, + 0, 0, 0, 0, + ] + ) + + def test_comments_to_post_authors(self): + self.reset_response_counts() + self.question.apply_edit( + edited_by = self.u14, + text = 'now much better', + comment = 'improved text' + ) + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.add_comment( + user = self.u12, + comment = 'self-comment 1', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u11, self.u13, self.u14]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 0, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + self.answer1.apply_edit( + edited_by = self.u24, + text = 'now much better', + comment = 'improved text' + ) + self.reset_repsonse_counters() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer1.add_comment( + user = self.u22, + comment = 'self-comment 1', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u21, self.u23, self.u24]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 0, 0, 0, + 1, 0, 1, 1, + 0, 0, 0, 0, + ] + ) + + def test_question_edit(self): + """when question is edited + response receivers are question authors, commenters + and answer authors, but not answer commenters + """ + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.apply_edit( + edited_by = self.u14, + text = 'waaay better question!', + comment = 'improved question', + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u11, self.u12, self.u13, self.u21, self.u31]) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 1, 1, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + ] + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.apply_edit( + edited_by = self.u31, + text = 'waaay even better question!', + comment = 'improved question', + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u11, self.u12, self.u13, self.u14, self.u21]) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 1, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + + def test_answer_edit(self): + """ + """ + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer1.apply_edit( + edited_by = self.u24, + text = 'waaay better answer!', + comment = 'improved answer1', + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set( + [ + self.u11, self.u12, self.u13, + self.u21, self.u22, self.u23, + self.u31 + ] + ) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 1, 1, 0, + 1, 1, 1, 0, + 1, 0, 0, 0, + ] + ) + + def test_new_answer(self): + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer3 = Answer.objects.create_new( + question = self.question, + author = self.u11, + added_at = timestamp, + text = 'answer3' + ) + time_end = datetime.datetime.now() + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set( + [ + self.u12, self.u13, + self.u21, self.u31 + ] + ) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 1, 1, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + ] + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer3 = Answer.objects.create_new( + question = self.question, + author = self.u31, + added_at = timestamp, + text = 'answer4' + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set( + [ + self.u11, self.u12, self.u13, + self.u21 + ] + ) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 1, 1, 0, + 1, 0, 0, 0, + 0, 0, 0, 0, + ] + ) class AnonymousVisitorTests(TestCase): -- cgit v1.2.3-1-g7c22