Просмотр исходного кода

Refactored readtracker, tests for forumreads

Rafał Pitoń 10 лет назад
Родитель
Сommit
a148aee604

+ 2 - 2
misago/forums/lists.py

@@ -1,5 +1,5 @@
 from misago.acl import add_acl
-from misago.readtracker import make_forums_read_aware
+from misago.readtracker import forumstracker
 
 from misago.forums.models import Forum
 
@@ -33,7 +33,7 @@ def get_forums_list(user, parent=None):
             forums_dict[forum.parent_id].subforums.append(forum)
 
     add_acl(user, forums_list)
-    make_forums_read_aware(user, forums_list)
+    forumstracker.make_read_aware(user, forums_list)
 
     for forum in reversed(visible_forums):
         if forum.acl['can_browse']:

+ 3 - 4
misago/forums/models.py

@@ -102,8 +102,7 @@ class Forum(MPTTModel):
         return super(Forum, self).delete(*args, **kwargs)
 
     def synchronize(self):
-        counted_criteria = {'is_hidden': False, 'is_moderated': False}
-        self.threads = self.thread_set.filter(**counted_criteria).count()
+        self.threads = self.thread_set.filter(is_moderated=False).count()
 
         if self.threads:
             replies_sum = self.thread_set.aggregate(models.Sum('replies'))
@@ -112,8 +111,8 @@ class Forum(MPTTModel):
             self.posts = 0
 
         if self.threads:
-            last_thread_qs = self.thread_set.filter(**counted_criteria)
-            last_thread = last_thread_qs.order_by('-last_post_id')[:1][0]
+            last_thread_qs = self.thread_set.filter(is_moderated=False)
+            last_thread = last_thread_qs.order_by('-last_post_on')[:1][0]
             self.set_last_thread(last_thread)
         else:
             self.empty_last_thread()

+ 3 - 3
misago/forums/tests/test_forum_model.py

@@ -125,9 +125,9 @@ class ForumModelTests(TestCase):
         hidden.save()
 
         self.forum.synchronize()
-        self.assertEqual(self.forum.threads, 1)
-        self.assertEqual(self.forum.posts, 1)
-        self.assertEqual(self.forum.last_thread, thread)
+        self.assertEqual(self.forum.threads, 2)
+        self.assertEqual(self.forum.posts, 2)
+        self.assertEqual(self.forum.last_thread, hidden)
 
         moderated.is_moderated = False
         moderated.post_set.update(is_moderated=False)

+ 0 - 4
misago/readtracker/__init__.py

@@ -1,6 +1,2 @@
 # flake8: noqa
-from misago.readtracker.forums import *
-from misago.readtracker.threads import *
-
-
 default_app_config = 'misago.readtracker.apps.MisagoReadTrackerConfig'

+ 8 - 6
misago/readtracker/forums.py → misago/readtracker/forumstracker.py

@@ -6,12 +6,12 @@ from misago.threads.permissions import exclude_invisible_threads
 from misago.readtracker.dates import cutoff_date, is_date_tracked
 
 
-__all__ = ['make_forums_read_aware', 'make_forums_read', 'sync_forum_record']
+__all__ = ['make_read_aware', 'sync_record']
 
 
-def make_forums_read_aware(user, forums):
+def make_read_aware(user, forums):
     if user.is_anonymous():
-        make_forums_read(forums)
+        make_read(forums)
         return None
 
     forums_dict = {}
@@ -20,17 +20,17 @@ def make_forums_read_aware(user, forums):
         forums_dict[forum.pk] = forum
 
     for record in user.forumread_set.filter(forum__in=forums_dict.keys()):
-        if record.forum_id in forums_dict:
+        if not forum.is_read and record.forum_id in forums_dict:
             forum = forums_dict[record.forum_id]
             forum.is_read = record.last_cleared_on >= forum.last_post_on
 
 
-def make_forums_read(forums):
+def make_read(forums):
     for forum in forums:
         forum.is_read = True
 
 
-def sync_forum_record(user, forum):
+def sync_record(user, forum):
     recorded_threads = forum.thread_set.filter(last_post_on__gt=cutoff_date())
     recorded_threads = exclude_invisible_threads(user, forum, recorded_threads)
 
@@ -48,6 +48,8 @@ def sync_forum_record(user, forum):
         forum_record.last_updated_on = timezone.now()
         if forum_is_read:
             forum_record.last_cleared_on = forum_record.last_updated_on
+        else:
+            forum_record.last_cleared_on = cutoff_date()
         forum_record.save(update_fields=['last_updated_on', 'last_cleared_on'])
     except IndexError:
         if forum_is_read:

+ 2 - 0
misago/readtracker/signals.py

@@ -1,3 +1,5 @@
+from django.dispatch import receiver
+
 from misago.forums.signals import move_forum_content
 from misago.threads.signals import move_thread
 

+ 0 - 0
misago/readtracker/tests/__init__.py


+ 19 - 0
misago/readtracker/tests/test_dates.py

@@ -0,0 +1,19 @@
+from datetime import timedelta
+
+from django.test import TestCase
+from django.utils import timezone
+
+from misago.readtracker.dates import cutoff_date, 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())
+
+    def test_is_date_tracked(self):
+        """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)))

+ 162 - 0
misago/readtracker/tests/test_readtracker.py

@@ -0,0 +1,162 @@
+from datetime import timedelta
+
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from django.utils import timezone
+
+from misago.acl import add_acl
+from misago.forums.models import Forum
+from misago.threads.models import Thread, Post
+from misago.users.models import AnonymousUser
+
+from misago.readtracker import forumstracker, threadstracker
+from misago.readtracker.dates import cutoff_date
+
+
+class ReadTrackerTests(TestCase):
+    def setUp(self):
+        self.forums = [f for f in Forum.objects.filter(role="forum")[:1]]
+        self.forum = self.forums[0]
+
+        User = get_user_model()
+        self.user = User.objects.create_user("Bob", "bob@test.com", "Pass.123")
+        self.anon = AnonymousUser()
+
+    def post_thread(self, datetime):
+        thread = Thread.objects.create(
+            forum=self.forum,
+            weight=0,
+            started_on=datetime,
+            starter_name='Tester',
+            starter_slug='tester',
+            last_post_on=datetime,
+            last_poster_name='Tester',
+            last_poster_slug='tester')
+        self.forum.synchronize()
+        self.forum.save()
+        return thread
+
+
+class ForumsTrackerTests(ReadTrackerTests):
+    def test_anon_empty_forum_read(self):
+        """anon users content is always read"""
+        forumstracker.make_read_aware(self.anon, self.forums)
+        self.assertIsNone(self.forum.last_post_on)
+        self.assertTrue(self.forum.is_read)
+
+    def test_anon_forum_with_recent_reply_read(self):
+        """anon users content is always read"""
+        forumstracker.make_read_aware(self.anon, self.forums)
+        self.forum.last_post_on = timezone.now()
+        self.assertTrue(self.forum.is_read)
+
+    def test_empty_forum_is_read(self):
+        """empty forum is read for signed in user"""
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertTrue(self.forum.is_read)
+
+    def test_make_read_aware_sets_read_flag_for_empty_forum(self):
+        """make_read_aware sets read flag on empty forum"""
+        forumstracker.make_read_aware(self.anon, self.forums)
+        self.assertTrue(self.forum.is_read)
+
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertTrue(self.forum.is_read)
+
+    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"""
+        self.forum.last_post_on = cutoff_date() - timedelta(days=1)
+
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertTrue(self.forum.is_read)
+
+    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"""
+        self.forum.last_post_on = cutoff_date() + timedelta(days=1)
+
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertFalse(self.forum.is_read)
+
+    def test_sync_record_for_empty_forum(self):
+        """sync_record sets read flag on empty forum"""
+        add_acl(self.user, self.forums)
+        forumstracker.sync_record(self.user, self.forum)
+        self.user.forumread_set.get(forum=self.forum)
+
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertTrue(self.forum.is_read)
+
+    def test_sync_record_for_forum_with_old_thread_and_reply(self):
+        """
+        sync_record sets read flag on forum with old thread,
+        then changes flag to unread when new reply is posted
+        """
+        self.post_thread(cutoff_date() - timedelta(days=1))
+
+        add_acl(self.user, self.forums)
+        forumstracker.sync_record(self.user, self.forum)
+        self.user.forumread_set.get(forum=self.forum)
+
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertTrue(self.forum.is_read)
+
+        thread = self.post_thread(cutoff_date() + timedelta(days=1))
+        forumstracker.sync_record(self.user, self.forum)
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertFalse(self.forum.is_read)
+
+    def test_sync_record_for_forum_with_new_thread(self):
+        """
+        sync_record sets read flag on forum with old thread,
+        then keeps flag to unread when new reply is posted
+        """
+        self.post_thread(cutoff_date() + timedelta(days=1))
+
+        add_acl(self.user, self.forums)
+        forumstracker.sync_record(self.user, self.forum)
+        self.user.forumread_set.get(forum=self.forum)
+
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertFalse(self.forum.is_read)
+
+        self.post_thread(cutoff_date() + timedelta(days=1))
+        forumstracker.sync_record(self.user, self.forum)
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertFalse(self.forum.is_read)
+
+    def test_sync_record_for_forum_with_deleted_threads(self):
+        """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))
+
+        add_acl(self.user, self.forums)
+        forumstracker.sync_record(self.user, self.forum)
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertFalse(self.forum.is_read)
+
+        self.forum.thread_set.all().delete()
+        self.forum.synchronize()
+        self.forum.save()
+
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertTrue(self.forum.is_read)
+
+    def test_sync_record_for_forum_with_many_threads(self):
+        """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))
+
+        add_acl(self.user, self.forums)
+        forumstracker.sync_record(self.user, self.forum)
+        self.user.forumread_set.get(forum=self.forum)
+
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertFalse(self.forum.is_read)
+
+        self.post_thread(cutoff_date() + timedelta(days=1))
+        forumstracker.sync_record(self.user, self.forum)
+        forumstracker.make_read_aware(self.user, self.forums)
+        self.assertFalse(self.forum.is_read)

+ 15 - 14
misago/readtracker/threads.py → misago/readtracker/threadstracker.py

@@ -1,19 +1,20 @@
+from misago.readtracker import forumstracker
 from misago.readtracker.dates import cutoff_date, is_date_tracked
-from misago.readtracker.forums import sync_forum_record
 
 
-__all__ = [
-    'make_threads_read_aware',
-    'make_threads_read',
-    'make_thread_read_aware',
-    'make_posts_read_aware',
-    'sync_thread_read',
-]
+__all__ = ['make_read_aware', 'read_thread']
+
+
+def make_read_aware(user, target):
+    if hasattr(target, '__iter__'):
+        make_threads_read_aware(user, target)
+    else:
+        make_thread_read_aware(user, target)
 
 
 def make_threads_read_aware(user, threads):
     if user.is_anonymous():
-        make_threads_read(threads)
+        make_read(threads)
         return None
 
     threads_dict = {}
@@ -35,7 +36,7 @@ def make_threads_read_aware(user, threads):
                 thread.unread_replies = thread.replies - record.read_replies
 
 
-def make_threads_read(threads):
+def make_read(threads):
     for thread in threads:
         thread.unread_replies = 0
         thread.is_read = True
@@ -83,13 +84,13 @@ def count_read_replies(user, thread, last_read_reply):
         return queryset.count()
 
 
-def sync_thread_read(user, thread, last_read_reply):
+def read_thread(user, thread, last_read_reply):
     if not thread.is_read:
         if thread.last_read_on < last_read_reply.updated_on:
-            read_thread(user, thread, last_read_reply)
+            sync_record(user, thread, last_read_reply)
 
 
-def read_thread(user, thread, last_read_reply):
+def sync_record(user, thread, last_read_reply):
     read_replies = count_read_replies(user, thread, last_read_reply)
     if thread.read_record:
         thread.read_record.read_replies = read_replies
@@ -103,4 +104,4 @@ def read_thread(user, thread, last_read_reply):
             last_read_on=last_read_reply.updated_on)
 
     if last_read_reply.updated_on == thread.last_post_on:
-        sync_forum_record(user, thread.forum)
+        forumstracker.sync_record(user, thread.forum)

+ 0 - 0
misago/readtracker/urls.py


+ 0 - 0
misago/readtracker/views.py


+ 4 - 5
misago/threads/views/generic/thread.py

@@ -4,8 +4,7 @@ from django.shortcuts import redirect
 from misago.acl import add_acl
 from misago.core.shortcuts import paginate
 from misago.forums.lists import get_forum_path
-from misago.readtracker import (make_thread_read_aware, make_posts_read_aware,
-                                sync_thread_read)
+from misago.readtracker import threadstracker
 from misago.users.online.utils import get_user_state
 
 from misago.threads.views.generic.base import ViewBase
@@ -55,11 +54,11 @@ class ThreadView(ViewBase):
         self.check_forum_permissions(request, forum)
         self.check_thread_permissions(request, thread)
 
-        make_thread_read_aware(request.user, thread)
+        threadstracker.make_read_aware(request.user, thread)
 
         page, posts = self.get_posts(request.user, forum, thread, kwargs)
-        make_posts_read_aware(thread, posts)
-        sync_thread_read(request.user, thread, posts[-1])
+        threadstracker.make_posts_read_aware(thread, posts)
+        threadstracker.read_thread(request.user, thread, posts[-1])
 
         return self.render(request, {
             'forum': forum,

+ 2 - 2
misago/threads/views/generic/threads.py

@@ -4,7 +4,7 @@ from django.shortcuts import redirect
 from django.utils.translation import ugettext_lazy, ugettext as _
 
 from misago.core.shortcuts import paginate
-from misago.readtracker import make_threads_read_aware
+from misago.readtracker import threadstracker
 
 from misago.threads.views.generic.base import ViewBase
 
@@ -69,4 +69,4 @@ class ThreadsView(ViewBase):
         return forum.thread_set.all().order_by('-last_post_id')
 
     def make_threads_read_aware(self, user, threads):
-        make_threads_read_aware(user, threads)
+        threadstracker.make_read_aware(user, threads)