Browse Source

fix #432 - rehashed readtracker

Rafał Pitoń 10 years ago
parent
commit
25035c2ded

+ 5 - 5
docs/developers/settings.rst

@@ -223,6 +223,11 @@ MISAGO_DYNAMIC_AVATAR_DRAWER
 Function used to create unique avatar for this user. Allows for customization of algorithm used to generate those.
 Function used to create unique avatar for this user. Allows for customization of algorithm used to generate those.
 
 
 
 
+MISAGO_FRESH_CONTENT_PERIOD
+---------------------------
+Controls amount of data used in building "New threads" and "Threads with unread replies" lists. Active forums can try lowering this value while less active ones may wish to increase this number.
+
+
 MISAGO_MAILER_BATCH_SIZE
 MISAGO_MAILER_BATCH_SIZE
 ------------------------
 ------------------------
 
 
@@ -260,11 +265,6 @@ MISAGO_RANKING_SIZE
 Maximum number of items on ranking page.
 Maximum number of items on ranking page.
 
 
 
 
-MISAGO_READ_RECORD_LENGTH
--------------------------
-Controls amount of data used in resolving read/unread states of threads and forums. Any activity older than number of days specified in this setting is assumed to be read and not tracked anymore. Active forums can try lowering this value while less active ones may wish to increase this number.
-
-
 MISAGO_SENDFILE_HEADER
 MISAGO_SENDFILE_HEADER
 ----------------------
 ----------------------
 
 

+ 6 - 5
misago/conf/defaults.py

@@ -285,11 +285,12 @@ MISAGO_RANKING_LENGTH = 30
 MISAGO_RANKING_SIZE = 30
 MISAGO_RANKING_SIZE = 30
 
 
 
 
-# Controls amount of data used in resolving read/unread states of threads and
-# forums. Any activity older than number of days below is assumed to be read
-# and not tracked anymore. Active forums can try lowering this value while
-# less active ones may wish to increase this number
-MISAGO_READ_RECORD_LENGTH = 28
+# Controls amount of data used for new threads/replies lists
+# Only unread threads younger than number of days specified in this setting
+# will be considered fresh for "new threads" list
+# Only unread threads with last reply younger than number of days specified
+# there will be confidered fresh for "Threads with unread replies" list
+MISAGO_FRESH_CONTENT_PERIOD = 40
 
 
 
 
 # X-Sendfile
 # X-Sendfile

+ 2 - 6
misago/readtracker/dates.py

@@ -4,12 +4,8 @@ from django.conf import settings
 from django.utils import timezone
 from django.utils import timezone
 
 
 
 
-def cutoff_date():
-    return timezone.now() - timedelta(days=settings.MISAGO_READ_RECORD_LENGTH)
-
-
-def is_date_tracked(date):
+def is_date_tracked(user, date):
     if date:
     if date:
-        return date > cutoff_date()
+        return date > user.joined_on
     else:
     else:
         return False
         return False

+ 6 - 6
misago/readtracker/forumstracker.py

@@ -4,7 +4,7 @@ from django.utils import timezone
 from misago.threads.permissions import exclude_invisible_threads
 from misago.threads.permissions import exclude_invisible_threads
 
 
 from misago.readtracker import signals
 from misago.readtracker import signals
-from misago.readtracker.dates import cutoff_date, is_date_tracked
+from misago.readtracker.dates import is_date_tracked
 
 
 
 
 __all__ = ['make_read_aware', 'sync_record']
 __all__ = ['make_read_aware', 'sync_record']
@@ -17,7 +17,7 @@ def make_read_aware(user, forums):
 
 
     forums_dict = {}
     forums_dict = {}
     for forum in forums:
     for forum in forums:
-        forum.is_read = not is_date_tracked(forum.last_post_on)
+        forum.is_read = not is_date_tracked(user, forum.last_post_on)
         forums_dict[forum.pk] = forum
         forums_dict[forum.pk] = forum
 
 
     for record in user.forumread_set.filter(forum__in=forums_dict.keys()):
     for record in user.forumread_set.filter(forum__in=forums_dict.keys()):
@@ -32,13 +32,13 @@ def make_read(forums):
 
 
 
 
 def sync_record(user, forum):
 def sync_record(user, forum):
-    recorded_threads = forum.thread_set.filter(last_post_on__gt=cutoff_date())
+    recorded_threads = forum.thread_set.filter(last_post_on__gt=user.joined_on)
     recorded_threads = exclude_invisible_threads(user, forum, recorded_threads)
     recorded_threads = exclude_invisible_threads(user, forum, recorded_threads)
 
 
     all_threads_count = recorded_threads.count()
     all_threads_count = recorded_threads.count()
 
 
     read_threads = user.threadread_set.filter(
     read_threads = user.threadread_set.filter(
-        forum=forum, last_read_on__gt=cutoff_date())
+        forum=forum, last_read_on__gt=user.joined_on)
     read_threads_count = read_threads.filter(
     read_threads_count = read_threads.filter(
         thread__last_post_on__lte=F("last_read_on")).count()
         thread__last_post_on__lte=F("last_read_on")).count()
 
 
@@ -53,13 +53,13 @@ def sync_record(user, forum):
         if forum_is_read:
         if forum_is_read:
             forum_record.last_cleared_on = forum_record.last_updated_on
             forum_record.last_cleared_on = forum_record.last_updated_on
         else:
         else:
-            forum_record.last_cleared_on = cutoff_date()
+            forum_record.last_cleared_on = user.joined_on
         forum_record.save(update_fields=['last_updated_on', 'last_cleared_on'])
         forum_record.save(update_fields=['last_updated_on', 'last_cleared_on'])
     except IndexError:
     except IndexError:
         if forum_is_read:
         if forum_is_read:
             cleared_on = timezone.now()
             cleared_on = timezone.now()
         else:
         else:
-            cleared_on = cutoff_date()
+            cleared_on = user.joined_on
 
 
         forum_record = user.forumread_set.create(
         forum_record = user.forumread_set.create(
             forum=forum,
             forum=forum,

+ 13 - 9
misago/readtracker/tests/test_dates.py

@@ -3,17 +3,21 @@ from datetime import timedelta
 from django.test import TestCase
 from django.test import TestCase
 from django.utils import timezone
 from django.utils import timezone
 
 
-from misago.readtracker.dates import cutoff_date, is_date_tracked
+from misago.readtracker.dates import is_date_tracked
 
 
 
 
-class ReadTrackerDatesTests(TestCase):
-    def test_cutoff_date(self):
-        """cutoff_date returns cut off date"""
-        cutoff = cutoff_date()
-        self.assertTrue(cutoff < timezone.now())
+class MockUser(object):
+    def __init__(self):
+        self.joined_on = timezone.now()
+
 
 
+class ReadTrackerDatesTests(TestCase):
     def test_is_date_tracked(self):
     def test_is_date_tracked(self):
         """is_date_tracked validates dates"""
         """is_date_tracked validates dates"""
-        self.assertFalse(is_date_tracked(None))
-        self.assertFalse(is_date_tracked(cutoff_date() - timedelta(seconds=1)))
-        self.assertTrue(is_date_tracked(cutoff_date() + timedelta(minutes=1)))
+        self.assertFalse(is_date_tracked(MockUser(), None))
+
+        past_date = timezone.now() - timedelta(minutes=10)
+        self.assertFalse(is_date_tracked(MockUser(), past_date))
+
+        future_date = timezone.now() + timedelta(minutes=10)
+        self.assertTrue(is_date_tracked(MockUser(), future_date))

+ 30 - 21
misago/readtracker/tests/test_readtracker.py

@@ -10,7 +10,6 @@ from misago.threads import testutils
 from misago.users.models import AnonymousUser
 from misago.users.models import AnonymousUser
 
 
 from misago.readtracker import forumstracker, threadstracker
 from misago.readtracker import forumstracker, threadstracker
-from misago.readtracker.dates import cutoff_date
 
 
 
 
 class ReadTrackerTests(TestCase):
 class ReadTrackerTests(TestCase):
@@ -54,14 +53,14 @@ class ForumsTrackerTests(ReadTrackerTests):
 
 
     def test_make_read_aware_sets_read_flag_for_forum_with_old_thread(self):
     def test_make_read_aware_sets_read_flag_for_forum_with_old_thread(self):
         """make_read_aware sets read flag on forum with old thread"""
         """make_read_aware sets read flag on forum with old thread"""
-        self.forum.last_post_on = cutoff_date() - timedelta(days=1)
+        self.forum.last_post_on = self.user.joined_on - timedelta(days=1)
 
 
         forumstracker.make_read_aware(self.user, self.forums)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertTrue(self.forum.is_read)
         self.assertTrue(self.forum.is_read)
 
 
     def test_make_read_aware_sets_unread_flag_for_forum_with_new_thread(self):
     def test_make_read_aware_sets_unread_flag_for_forum_with_new_thread(self):
         """make_read_aware sets unread flag on forum with new thread"""
         """make_read_aware sets unread flag on forum with new thread"""
-        self.forum.last_post_on = cutoff_date() + timedelta(days=1)
+        self.forum.last_post_on = self.user.joined_on + timedelta(days=1)
 
 
         forumstracker.make_read_aware(self.user, self.forums)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
         self.assertFalse(self.forum.is_read)
@@ -80,7 +79,7 @@ class ForumsTrackerTests(ReadTrackerTests):
         sync_record sets read flag on forum with old thread,
         sync_record sets read flag on forum with old thread,
         then changes flag to unread when new reply is posted
         then changes flag to unread when new reply is posted
         """
         """
-        self.post_thread(cutoff_date() - timedelta(days=1))
+        self.post_thread(self.user.joined_on - timedelta(days=1))
 
 
         add_acl(self.user, self.forums)
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
@@ -89,7 +88,7 @@ class ForumsTrackerTests(ReadTrackerTests):
         forumstracker.make_read_aware(self.user, self.forums)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertTrue(self.forum.is_read)
         self.assertTrue(self.forum.is_read)
 
 
-        thread = self.post_thread(cutoff_date() + timedelta(days=1))
+        thread = self.post_thread(self.user.joined_on + timedelta(days=1))
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.make_read_aware(self.user, self.forums)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
         self.assertFalse(self.forum.is_read)
@@ -99,7 +98,7 @@ class ForumsTrackerTests(ReadTrackerTests):
         sync_record sets read flag on forum with old thread,
         sync_record sets read flag on forum with old thread,
         then keeps flag to unread when new reply is posted
         then keeps flag to unread when new reply is posted
         """
         """
-        self.post_thread(cutoff_date() + timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
 
 
         add_acl(self.user, self.forums)
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
@@ -108,16 +107,16 @@ class ForumsTrackerTests(ReadTrackerTests):
         forumstracker.make_read_aware(self.user, self.forums)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
         self.assertFalse(self.forum.is_read)
 
 
-        self.post_thread(cutoff_date() + timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.make_read_aware(self.user, self.forums)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
         self.assertFalse(self.forum.is_read)
 
 
     def test_sync_record_for_forum_with_deleted_threads(self):
     def test_sync_record_for_forum_with_deleted_threads(self):
         """unread forum reverts to read after its emptied"""
         """unread forum reverts to read after its emptied"""
-        self.post_thread(cutoff_date() + timedelta(days=1))
-        self.post_thread(cutoff_date() + timedelta(days=1))
-        self.post_thread(cutoff_date() + timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
 
 
         add_acl(self.user, self.forums)
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
@@ -133,10 +132,10 @@ class ForumsTrackerTests(ReadTrackerTests):
 
 
     def test_sync_record_for_forum_with_many_threads(self):
     def test_sync_record_for_forum_with_many_threads(self):
         """sync_record sets unread flag on forum with many threads"""
         """sync_record sets unread flag on forum with many threads"""
-        self.post_thread(cutoff_date() + timedelta(days=1))
-        self.post_thread(cutoff_date() - timedelta(days=1))
-        self.post_thread(cutoff_date() + timedelta(days=1))
-        self.post_thread(cutoff_date() - timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.joined_on - timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.joined_on - timedelta(days=1))
 
 
         add_acl(self.user, self.forums)
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
@@ -145,7 +144,7 @@ class ForumsTrackerTests(ReadTrackerTests):
         forumstracker.make_read_aware(self.user, self.forums)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
         self.assertFalse(self.forum.is_read)
 
 
-        self.post_thread(cutoff_date() + timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.make_read_aware(self.user, self.forums)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
         self.assertFalse(self.forum.is_read)
@@ -156,13 +155,13 @@ class ThreadsTrackerTests(ReadTrackerTests):
         super(ThreadsTrackerTests, self).setUp()
         super(ThreadsTrackerTests, self).setUp()
 
 
         self.thread = self.post_thread(timezone.now() - timedelta(days=10))
         self.thread = self.post_thread(timezone.now() - timedelta(days=10))
-        self.reply_thread()
 
 
     def reply_thread(self, is_hidden=False, is_moderated=False):
     def reply_thread(self, is_hidden=False, is_moderated=False):
         self.post = testutils.reply_thread(
         self.post = testutils.reply_thread(
             thread=self.thread,
             thread=self.thread,
             is_hidden=is_hidden,
             is_hidden=is_hidden,
-            is_moderated=is_moderated)
+            is_moderated=is_moderated,
+            posted_on=timezone.now())
         return self.post
         return self.post
 
 
     def test_thread_read_for_guest(self):
     def test_thread_read_for_guest(self):
@@ -170,16 +169,26 @@ class ThreadsTrackerTests(ReadTrackerTests):
         threadstracker.make_read_aware(self.anon, self.thread)
         threadstracker.make_read_aware(self.anon, self.thread)
         self.assertTrue(self.thread.is_read)
         self.assertTrue(self.thread.is_read)
 
 
+        self.reply_thread()
         threadstracker.make_read_aware(self.anon, [self.thread])
         threadstracker.make_read_aware(self.anon, [self.thread])
         self.assertTrue(self.thread.is_read)
         self.assertTrue(self.thread.is_read)
 
 
-    def test_thread_unread_for_user(self):
-        """thread is unread for user"""
+    def test_thread_read_for_user(self):
+        """thread is read for user"""
+        threadstracker.make_read_aware(self.user, self.thread)
+        self.assertTrue(self.thread.is_read)
+
+    def test_thread_replied_unread_for_user(self):
+        """replied thread is unread for user"""
+        self.reply_thread(self.thread)
+
         threadstracker.make_read_aware(self.user, self.thread)
         threadstracker.make_read_aware(self.user, self.thread)
         self.assertFalse(self.thread.is_read)
         self.assertFalse(self.thread.is_read)
 
 
-    def test_thread_read(self):
+    def _test_thread_read(self):
         """thread read flag is set for user, then its set as unread by reply"""
         """thread read flag is set for user, then its set as unread by reply"""
+        self.reply_thread(self.thread)
+
         add_acl(self.user, self.forums)
         add_acl(self.user, self.forums)
         threadstracker.make_read_aware(self.user, self.thread)
         threadstracker.make_read_aware(self.user, self.thread)
         self.assertFalse(self.thread.is_read)
         self.assertFalse(self.thread.is_read)
@@ -202,7 +211,7 @@ class ThreadsTrackerTests(ReadTrackerTests):
         self.assertFalse(self.forum.is_read)
         self.assertFalse(self.forum.is_read)
 
 
         posts = [post for post in self.thread.post_set.order_by('id')]
         posts = [post for post in self.thread.post_set.order_by('id')]
-        threadstracker.make_posts_read_aware(self.thread, posts)
+        threadstracker.make_posts_read_aware(self.user, self.thread, posts)
 
 
         for post in posts[:-1]:
         for post in posts[:-1]:
             self.assertTrue(post.is_read)
             self.assertTrue(post.is_read)

+ 16 - 16
misago/readtracker/threadstracker.py

@@ -1,5 +1,5 @@
 from misago.readtracker import forumstracker, signals
 from misago.readtracker import forumstracker, signals
-from misago.readtracker.dates import cutoff_date, is_date_tracked
+from misago.readtracker.dates import is_date_tracked
 
 
 
 
 __all__ = ['make_read_aware', 'read_thread']
 __all__ = ['make_read_aware', 'read_thread']
@@ -19,7 +19,7 @@ def make_threads_read_aware(user, threads):
 
 
     threads_dict = {}
     threads_dict = {}
     for thread in threads:
     for thread in threads:
-        thread.is_read = not is_date_tracked(thread.last_post_on)
+        thread.is_read = not is_date_tracked(user, thread.last_post_on)
         if thread.is_read:
         if thread.is_read:
             thread.unread_replies = 0
             thread.unread_replies = 0
         else:
         else:
@@ -44,7 +44,7 @@ def make_read(threads):
 
 
 def make_thread_read_aware(user, thread):
 def make_thread_read_aware(user, thread):
     thread.is_read = True
     thread.is_read = True
-    if user.is_authenticated() and is_date_tracked(thread.last_post_on):
+    if user.is_authenticated() and is_date_tracked(user, thread.last_post_on):
         try:
         try:
             record = user.threadread_set.filter(thread=thread).all()[0]
             record = user.threadread_set.filter(thread=thread).all()[0]
             thread.last_read_on = record.last_read_on
             thread.last_read_on = record.last_read_on
@@ -53,10 +53,10 @@ def make_thread_read_aware(user, thread):
         except IndexError:
         except IndexError:
             thread.read_record = None
             thread.read_record = None
             thread.is_read = False
             thread.is_read = False
-            thread.last_read_on = cutoff_date()
+            thread.last_read_on = user.joined_on
 
 
 
 
-def make_posts_read_aware(thread, posts):
+def make_posts_read_aware(user, thread, posts):
     try:
     try:
         is_thread_read = thread.is_read
         is_thread_read = thread.is_read
     except AttributeError:
     except AttributeError:
@@ -68,7 +68,7 @@ def make_posts_read_aware(thread, posts):
             post.is_read = True
             post.is_read = True
     else:
     else:
         for post in posts:
         for post in posts:
-            if is_date_tracked(post.updated_on):
+            if is_date_tracked(user, post.updated_on):
                 post.is_read = post.updated_on <= thread.last_read_on
                 post.is_read = post.updated_on <= thread.last_read_on
             else:
             else:
                 post.is_read = True
                 post.is_read = True
@@ -80,16 +80,6 @@ def read_thread(user, thread, last_read_reply):
             sync_record(user, thread, last_read_reply)
             sync_record(user, thread, last_read_reply)
 
 
 
 
-def count_read_replies(user, thread, last_read_reply):
-    if last_read_reply.updated_on >= thread.last_read_on:
-        return 0
-    else:
-        last_reply_date = last_read_reply.last_read_on
-        queryset = thread.post_set.filter(last_read_on__lte=last_reply_date)
-        queryset = queryset.filter(is_moderated=False)
-        return queryset.count()
-
-
 def sync_record(user, thread, last_read_reply):
 def sync_record(user, thread, last_read_reply):
     read_replies = count_read_replies(user, thread, last_read_reply)
     read_replies = count_read_replies(user, thread, last_read_reply)
     if thread.read_record:
     if thread.read_record:
@@ -106,3 +96,13 @@ def sync_record(user, thread, last_read_reply):
     if last_read_reply.updated_on == thread.last_post_on:
     if last_read_reply.updated_on == thread.last_post_on:
         signals.thread_read.send(sender=user, thread=thread)
         signals.thread_read.send(sender=user, thread=thread)
         forumstracker.sync_record(user, thread.forum)
         forumstracker.sync_record(user, thread.forum)
+
+
+def count_read_replies(user, thread, last_read_reply):
+    if last_read_reply.updated_on >= thread.last_read_on:
+        return 0
+    else:
+        last_reply_date = last_read_reply.last_read_on
+        queryset = thread.post_set.filter(last_read_on__lte=last_reply_date)
+        queryset = queryset.filter(is_moderated=False)
+        return queryset.count()

+ 7 - 0
misago/static/misago/css/misago/threadslists.less

@@ -42,8 +42,15 @@
         //==
         //==
         .item-title {
         .item-title {
           font-size: @font-size-large;
           font-size: @font-size-large;
+
+          .opacity(0.7);
         }
         }
 
 
+        &.new {
+          .item-title {
+            .opacity(1);
+          }
+        }
 
 
         // Thread last reply
         // Thread last reply
         //
         //

+ 1 - 1
misago/threads/views/generic/thread.py

@@ -64,7 +64,7 @@ class ThreadView(ViewBase):
         threadstracker.make_read_aware(request.user, thread)
         threadstracker.make_read_aware(request.user, thread)
 
 
         page, posts = self.get_posts(request.user, forum, thread, kwargs)
         page, posts = self.get_posts(request.user, forum, thread, kwargs)
-        threadstracker.make_posts_read_aware(thread, posts)
+        threadstracker.make_posts_read_aware(request.user, thread, posts)
         threadstracker.read_thread(request.user, thread, posts[-1])
         threadstracker.read_thread(request.user, thread, posts[-1])
 
 
         return self.render(request, {
         return self.render(request, {