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

#477: count unread private threads for UI

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

+ 8 - 0
misago/readtracker/signals.py

@@ -38,6 +38,14 @@ def decrease_unread_count(sender, **kwargs):
     user.unread_threads.decrease()
 
 
+@receiver(thread_read)
+def decrease_unread_private_count(sender, **kwargs):
+    user = sender
+    if user.unread_private_threads:
+        user.unread_private_threads -= 1
+        user.save(update_fields=['unread_private_threads'])
+
+
 @receiver(all_read)
 def zero_unread_counters(sender, **kwargs):
     sender.new_threads.set(0)

+ 7 - 3
misago/templates/misago/user_nav.html

@@ -64,7 +64,11 @@
   </li>
   {% if user.acl.moderated_forums %}
   <li>
-    <a href="{% url 'misago:moderated_content' %}" class="tooltip-bottom" title="{% trans "Moderated content" %}">
+    <a href="{% url 'misago:moderated_content' %}" class="tooltip-bottom" {% if user.moderated_content %}
+        title="{% blocktrans with moderated=user.moderated_content count counter=user.moderated_content %}{{ moderated }} item in moderation{% plural %}{{ moderated }} items in moderation{% endblocktrans %}"
+        {% else %}
+        title="{% trans "Moderated content" %}"
+        {% endif %}>
       <span class="fa fa-eye-slash fa-fw"></span>
       <span class="badge fade {{ user.moderated_content|iftrue:"in" }}" data-misago-badge="moderated_content">{{ user.moderated_content }}</span>
     </a>
@@ -72,7 +76,7 @@
   {% endif %}
   <li>
     <a href="{% url 'misago:private_threads' %}" class="tooltip-bottom" {% if user.unread_private_threads %}
-        title="{% blocktrans with unread=user.unread_private_threads count counter=user.unread_private_threads %}{{ unread }} unread private threads{% plural %}{{ unread }} unread private thread{% endblocktrans %}"
+        title="{% blocktrans with unread=user.unread_private_threads count counter=user.unread_private_threads %}{{ unread }} unread private thread{% plural %}{{ unread }} unread private threads{% endblocktrans %}"
         {% else %}
         title="{% trans "Private threads" %}"
         {% endif %}>
@@ -85,7 +89,7 @@
         {% if user.new_notifications %}
         title="{% blocktrans with notifications=user.new_notifications count counter=user.new_notifications %}{{ notifications }} new notification{% plural %}{{ notifications }} new notifications{% endblocktrans %}"
         {% else %}
-        title="{% trans "Your notifications" %}"
+        title="{% trans "Notifications" %}"
         {% endif %}
         data-toggle="dropdown">
       <span class="fa fa-bell-o fa-fw"></span>

+ 31 - 3
misago/threads/counts.py

@@ -1,11 +1,13 @@
 from time import time
 
 from django.conf import settings
+from django.db.models import F
 from django.dispatch import receiver
 
 from misago.threads.views.moderatedcontent import ModeratedContent
 from misago.threads.views.newthreads import NewThreads
 from misago.threads.views.unreadthreads import UnreadThreads
+from misago.threads.views.privatethreads import PrivateThreads
 
 
 class BaseCounter(object):
@@ -30,7 +32,7 @@ class BaseCounter(object):
         count = self.session.get(self.name, None)
 
         if not count or not self.is_cache_valid(count):
-            count = self.get_real_count()
+            count = self.get_current_count_dict()
             self.session[self.name] = count
         return count['threads']
 
@@ -43,13 +45,16 @@ class BaseCounter(object):
     def get_expiration_timestamp(self):
         return time() + settings.MISAGO_CONTENT_COUNTING_FREQUENCY * 60
 
-    def get_real_count(self):
+    def get_current_count_dict(self):
         return {
             'user': self.user.pk,
-            'threads': self.Threads(self.user).get_queryset().count(),
+            'threads': self.count_threads(),
             'expires': self.get_expiration_timestamp()
         }
 
+    def count_threads(self):
+        return self.Threads(self.user).get_queryset().count()
+
     def set(self, count):
         self.count = count
         self.session[self.name] = {
@@ -81,3 +86,26 @@ class NewThreadsCount(BaseCounter):
 class UnreadThreadsCount(BaseCounter):
     Threads = UnreadThreads
     name = 'unread_threads'
+
+
+def sync_user_unread_private_threads_count(user):
+    if not user.sync_unread_private_threads:
+        return
+
+    threads_qs = PrivateThreads(user).get_queryset()
+
+    all_threads_count = threads_qs.count()
+
+    read_qs = threads_qs.filter(threadread__user=user)
+    read_qs = read_qs.filter(threadread__last_read_on__gte=F('last_post_on'))
+    read_threads_count = read_qs.count()
+
+    user.unread_private_threads = all_threads_count - read_threads_count
+    if user.unread_private_threads < 0:
+        # we may end with negative count because of race conditions in counts
+        user.unread_private_threads = 0
+
+    user.sync_unread_private_threads = False
+    user.save(update_fields=[
+        'unread_private_threads', 'sync_unread_private_threads'
+    ])

+ 5 - 1
misago/threads/middleware.py

@@ -3,7 +3,8 @@ from time import time
 from django.conf import settings
 
 from misago.threads.counts import (ModeratedCount, NewThreadsCount,
-                                   UnreadThreadsCount)
+                                   UnreadThreadsCount,
+                                   sync_user_unread_private_threads_count)
 
 
 class UnreadThreadsCountMiddleware(object):
@@ -17,3 +18,6 @@ class UnreadThreadsCountMiddleware(object):
             request.user.unread_threads = UnreadThreadsCount(
                 request.user, request.session)
 
+            if request.user.acl['can_use_private_threads']:
+                # special case: count unread threads
+                sync_user_unread_private_threads_count(request.user)

+ 62 - 6
misago/threads/tests/test_counters.py

@@ -1,12 +1,16 @@
+from datetime import timedelta
 from time import time
 
 from django.core.urlresolvers import reverse
+from django.utils import timezone
 from django.utils.translation import ugettext as _
 
 from misago.forums.models import Forum
+from misago.readtracker.models import ThreadRead
 from misago.users.testutils import AuthenticatedUserTestCase
 
-from misago.threads.counts import NewThreadsCount, UnreadThreadsCount
+from misago.threads.counts import (NewThreadsCount,
+                                   sync_user_unread_private_threads_count)
 from misago.threads import testutils
 
 
@@ -58,20 +62,20 @@ class TestNewThreadsCount(AuthenticatedUserTestCase):
         counter = NewThreadsCount(self.user, {})
         self.assertTrue(counter.get_expiration_timestamp() > time())
 
-    def test_get_real_count(self):
-        """get_real_count returns valid count of new threads"""
+    def test_get_current_count_dict(self):
+        """get_current_count_dict returns valid count of new threads"""
         counter = NewThreadsCount(self.user, {})
         self.assertEqual(counter.count, 0)
-        self.assertEqual(counter.get_real_count()['threads'], 0)
+        self.assertEqual(counter.get_current_count_dict()['threads'], 0)
 
         # create 10 new threads
         threads = [testutils.post_thread(self.forum) for t in xrange(10)]
-        self.assertEqual(counter.get_real_count()['threads'], 10)
+        self.assertEqual(counter.get_current_count_dict()['threads'], 10)
 
         # create new counter
         counter = NewThreadsCount(self.user, {})
         self.assertEqual(counter.count, 10)
-        self.assertEqual(counter.get_real_count()['threads'], 10)
+        self.assertEqual(counter.get_current_count_dict()['threads'], 10)
 
     def test_set(self):
         """set allows for changing count of threads"""
@@ -100,3 +104,55 @@ class TestNewThreadsCount(AuthenticatedUserTestCase):
 
         self.assertEqual(int(counter), 0)
         self.assertEqual(session[counter.name]['threads'], 0)
+
+
+class TestSyncUnreadPrivateThreadsCount(AuthenticatedUserTestCase):
+    def setUp(self):
+        super(TestSyncUnreadPrivateThreadsCount, self).setUp()
+
+        self.forum = Forum.objects.private_threads()
+        self.user.sync_unread_private_threads = True
+
+    def test_user_with_no_threads(self):
+        """user with no private threads has 0 unread threads"""
+        for i in range(5):
+            # post 5 invisible threads
+            testutils.post_thread(
+                self.forum, started_on=timezone.now() - timedelta(days=2))
+
+        sync_user_unread_private_threads_count(self.user)
+        self.assertEqual(self.user.unread_private_threads, 0)
+
+    def test_user_with_new_thread(self):
+        """user has one new private thred"""
+        for i in range(5):
+            # post 5 invisible threads
+            testutils.post_thread(
+                self.forum, started_on=timezone.now() - timedelta(days=2))
+
+        thread = testutils.post_thread(
+            self.forum, started_on=timezone.now() - timedelta(days=2))
+        thread.threadparticipant_set.create(user=self.user)
+
+        sync_user_unread_private_threads_count(self.user)
+        self.assertEqual(self.user.unread_private_threads, 1)
+
+    def test_user_with_new_thread(self):
+        """user has one unread private thred"""
+        for i in range(5):
+            # post 5 invisible threads
+            testutils.post_thread(
+                self.forum, started_on=timezone.now() - timedelta(days=2))
+
+        thread = testutils.post_thread(
+            self.forum, started_on=timezone.now() - timedelta(days=2))
+        thread.threadparticipant_set.create(user=self.user)
+
+        ThreadRead.objects.create(
+            user=self.user,
+            forum=self.forum,
+            thread=thread,
+            last_read_on=timezone.now() - timedelta(days=3))
+
+        sync_user_unread_private_threads_count(self.user)
+        self.assertEqual(self.user.unread_private_threads, 1)