Rafał Pitoń 10 years ago
parent
commit
578eccd563

+ 14 - 2
misago/readtracker/signals.py

@@ -6,6 +6,7 @@ from misago.threads.signals import move_thread
 
 
 all_read = Signal()
 all_read = Signal()
 forum_read = Signal(providing_args=["forum"])
 forum_read = Signal(providing_args=["forum"])
+thread_tracked = Signal(providing_args=["thread"])
 thread_read = Signal(providing_args=["thread"])
 thread_read = Signal(providing_args=["thread"])
 
 
 
 
@@ -23,10 +24,21 @@ def delete_thread_tracker(sender, **kwargs):
     sender.threadread_set.all().delete()
     sender.threadread_set.all().delete()
 
 
 
 
+@receiver(thread_tracked)
+def decrease_new_threads_count(sender, **kwargs):
+    user = sender
+    thread = kwargs['thread']
+    user.new_threads.decrease()
+
+
 @receiver(thread_read)
 @receiver(thread_read)
 def decrease_unread_count(sender, **kwargs):
 def decrease_unread_count(sender, **kwargs):
     user = sender
     user = sender
     thread = kwargs['thread']
     thread = kwargs['thread']
+    user.unread_threads.decrease()
+
 
 
-    if thread.is_new:
-        user.new_threads.decrease()
+@receiver(all_read)
+def zero_unread_counters(sender, **kwargs):
+    sender.new_threads.set(0)
+    sender.unread_threads.set(0)

+ 2 - 0
misago/readtracker/threadstracker.py

@@ -99,6 +99,8 @@ def sync_record(user, thread, last_read_reply):
             read_replies=read_replies,
             read_replies=read_replies,
             last_read_on=last_read_reply.updated_on)
             last_read_on=last_read_reply.updated_on)
 
 
+         signals.thread_tracked.send(sender=user, thread=thread)
+
     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)

+ 8 - 0
misago/templates/misago/threads/new.html

@@ -22,6 +22,14 @@
   {% include "misago/threads/paginator.html" %}
   {% include "misago/threads/paginator.html" %}
 
 
   {% include "misago/threads/sort.html" %}
   {% include "misago/threads/sort.html" %}
+
+  <form action="{% url 'misago:clear_new_threads' %}" method="POST" class="pull-right">
+    {% csrf_token %}
+    <button type="submit" class="btn btn-default">
+      <span class="fa fa-circle-o fa-fw"></span>
+      {% trans "Clear list" %}
+    </button>
+  </form>
 </div>
 </div>
 
 
 {{ block.super }}
 {{ block.super }}

+ 8 - 0
misago/templates/misago/threads/unread.html

@@ -22,6 +22,14 @@
   {% include "misago/threads/paginator.html" %}
   {% include "misago/threads/paginator.html" %}
 
 
   {% include "misago/threads/sort.html" %}
   {% include "misago/threads/sort.html" %}
+
+  <form action="{% url 'misago:clear_unread_threads' %}" method="POST" class="pull-right">
+    {% csrf_token %}
+    <button type="submit" class="btn btn-default">
+      <span class="fa fa-circle-o fa-fw"></span>
+      {% trans "Clear list" %}
+    </button>
+  </form>
 </div>
 </div>
 
 
 {{ block.super }}
 {{ block.super }}

+ 0 - 7
misago/threads/counts.py

@@ -73,10 +73,3 @@ class NewThreadsCount(BaseCounter):
 class UnreadThreadsCount(BaseCounter):
 class UnreadThreadsCount(BaseCounter):
     Threads = UnreadThreads
     Threads = UnreadThreads
     name = 'unread_threads'
     name = 'unread_threads'
-
-
-from misago.readtracker.signals import all_read
-@receiver(all_read)
-def zero_unread_counters(sender, **kwargs):
-    sender.new_threads.set(0)
-    sender.unread_threads.set(0)

+ 9 - 0
misago/threads/tests/test_newthreads_views.py

@@ -35,6 +35,15 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
         for thread in threads[:5]:
         for thread in threads[:5]:
             self.assertIn(thread.get_absolute_url(), response.content)
             self.assertIn(thread.get_absolute_url(), response.content)
 
 
+        # clear list
+        response = self.client.post(reverse('misago:clear_new_threads'))
+        self.assertEqual(response.status_code, 302)
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("There are no threads from last", response.content)
+
+
     def test_multipage_threads_list(self):
     def test_multipage_threads_list(self):
         """multipage threads list is rendered"""
         """multipage threads list is rendered"""
         forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
         forum = Forum.objects.all_forums().filter(role="forum")[:1][0]

+ 10 - 1
misago/threads/tests/test_unreadthreads_view.py

@@ -1,4 +1,5 @@
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
+from django.utils import timezone
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
 from misago.forums.models import Forum
 from misago.forums.models import Forum
@@ -27,7 +28,7 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
         # we'll read and reply to first five threads
         # we'll read and reply to first five threads
         for thread in threads[5:]:
         for thread in threads[5:]:
             response = self.client.get(thread.get_absolute_url())
             response = self.client.get(thread.get_absolute_url())
-            testutils.reply_thread(thread)
+            testutils.reply_thread(thread, posted_on=timezone.now())
 
 
         # assert that replied threads show on list
         # assert that replied threads show on list
         response = self.client.get(reverse('misago:unread_threads'))
         response = self.client.get(reverse('misago:unread_threads'))
@@ -37,6 +38,14 @@ class AuthenticatedTests(AuthenticatedUserTestCase):
         for thread in threads[:5]:
         for thread in threads[:5]:
             self.assertNotIn(thread.get_absolute_url(), response.content)
             self.assertNotIn(thread.get_absolute_url(), response.content)
 
 
+        # clear list
+        response = self.client.post(reverse('misago:clear_unread_threads'))
+        self.assertEqual(response.status_code, 302)
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("There are no threads with unread", response.content)
+
 
 
 class AnonymousTests(UserTestCase):
 class AnonymousTests(UserTestCase):
     def test_anon_access_to_view(self):
     def test_anon_access_to_view(self):

+ 7 - 4
misago/threads/urls.py

@@ -1,7 +1,7 @@
 from django.conf.urls import patterns, include, url
 from django.conf.urls import patterns, include, url
 
 
-from misago.threads.views.threads import (ForumView, ThreadView, StartThreadView,
-                                          ReplyView, EditView)
+from misago.threads.views.threads import (ForumView, ThreadView,
+                                          StartThreadView, ReplyView, EditView)
 
 
 
 
 urlpatterns = patterns('',
 urlpatterns = patterns('',
@@ -24,20 +24,23 @@ urlpatterns += patterns('',
 
 
 
 
 # new threads lists
 # new threads lists
-from misago.threads.views.newthreads import NewThreadsView
+from misago.threads.views.newthreads import NewThreadsView, clear_new_threads
 urlpatterns += patterns('',
 urlpatterns += patterns('',
     url(r'^new-threads/$', NewThreadsView.as_view(), name='new_threads'),
     url(r'^new-threads/$', NewThreadsView.as_view(), name='new_threads'),
     url(r'^new-threads/(?P<page>\d+)/$', NewThreadsView.as_view(), name='new_threads'),
     url(r'^new-threads/(?P<page>\d+)/$', NewThreadsView.as_view(), name='new_threads'),
     url(r'^new-threads/sort-(?P<sort>[\w-]+)$', NewThreadsView.as_view(), name='new_threads'),
     url(r'^new-threads/sort-(?P<sort>[\w-]+)$', NewThreadsView.as_view(), name='new_threads'),
     url(r'^new-threads/sort-(?P<sort>[\w-]+)(?P<page>\d+)/$', NewThreadsView.as_view(), name='new_threads'),
     url(r'^new-threads/sort-(?P<sort>[\w-]+)(?P<page>\d+)/$', NewThreadsView.as_view(), name='new_threads'),
+    url(r'^new-threads/clear/$', clear_new_threads, name='clear_new_threads'),
 )
 )
 
 
 
 
 # unread threads lists
 # unread threads lists
-from misago.threads.views.unreadthreads import UnreadThreadsView
+from misago.threads.views.unreadthreads import (UnreadThreadsView,
+                                                clear_unread_threads)
 urlpatterns += patterns('',
 urlpatterns += patterns('',
     url(r'^unread-threads/$', UnreadThreadsView.as_view(), name='unread_threads'),
     url(r'^unread-threads/$', UnreadThreadsView.as_view(), name='unread_threads'),
     url(r'^unread-threads/(?P<page>\d+)/$', UnreadThreadsView.as_view(), name='unread_threads'),
     url(r'^unread-threads/(?P<page>\d+)/$', UnreadThreadsView.as_view(), name='unread_threads'),
     url(r'^unread-threads/sort-(?P<sort>[\w-]+)$', UnreadThreadsView.as_view(), name='unread_threads'),
     url(r'^unread-threads/sort-(?P<sort>[\w-]+)$', UnreadThreadsView.as_view(), name='unread_threads'),
     url(r'^unread-threads/sort-(?P<sort>[\w-]+)(?P<page>\d+)/$', UnreadThreadsView.as_view(), name='unread_threads'),
     url(r'^unread-threads/sort-(?P<sort>[\w-]+)(?P<page>\d+)/$', UnreadThreadsView.as_view(), name='unread_threads'),
+    url(r'^unread-threads/clear/$', clear_unread_threads, name='clear_unread_threads'),
 )
 )

+ 26 - 2
misago/threads/views/newthreads.py

@@ -1,11 +1,18 @@
 from datetime import timedelta
 from datetime import timedelta
 
 
 from django.conf import settings
 from django.conf import settings
+from django.contrib import messages
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
-from misago.core.uiviews import uiview
-from misago.users.decorators import deny_guests
+from django.db.transaction import atomic
+from django.shortcuts import redirect
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
+from django.views.decorators.cache import never_cache
+from django.views.decorators.csrf import csrf_protect
+
+from misago.core.decorators import require_POST
+from misago.core.uiviews import uiview
+from misago.users.decorators import deny_guests
 
 
 from misago.threads.models import Thread
 from misago.threads.models import Thread
 from misago.threads.permissions import exclude_invisible_threads
 from misago.threads.permissions import exclude_invisible_threads
@@ -18,6 +25,8 @@ class NewThreads(Threads):
         cutoff_date = timezone.now() - timedelta(days=cutoff_days)
         cutoff_date = timezone.now() - timedelta(days=cutoff_days)
         if cutoff_date < self.user.reads_cutoff:
         if cutoff_date < self.user.reads_cutoff:
             cutoff_date = self.user.reads_cutoff
             cutoff_date = self.user.reads_cutoff
+        if cutoff_date < self.user.new_threads_cutoff:
+            cutoff_date = self.user.new_threads_cutoff
 
 
         queryset = Thread.objects.filter(started_on__gte=cutoff_date)
         queryset = Thread.objects.filter(started_on__gte=cutoff_date)
         queryset = queryset.select_related('forum')
         queryset = queryset.select_related('forum')
@@ -51,6 +60,21 @@ class NewThreadsView(ThreadsView):
                 request, *args, **kwargs)
                 request, *args, **kwargs)
 
 
 
 
+@deny_guests
+@require_POST
+@csrf_protect
+@never_cache
+@atomic
+def clear_new_threads(request):
+    request.user.new_threads_cutoff = timezone.now()
+    request.user.save(update_fields=['new_threads_cutoff'])
+
+    request.user.new_threads.set(0)
+
+    messages.success(request, _("New threads list has been cleared."))
+    return redirect('misago:new_threads')
+
+
 @uiview("new_threads")
 @uiview("new_threads")
 @deny_guests
 @deny_guests
 def event_sender(request, resolver_match):
 def event_sender(request, resolver_match):

+ 26 - 2
misago/threads/views/unreadthreads.py

@@ -1,12 +1,19 @@
 from datetime import timedelta
 from datetime import timedelta
 
 
 from django.conf import settings
 from django.conf import settings
+from django.contrib import messages
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
-from misago.core.uiviews import uiview
-from misago.users.decorators import deny_guests
 from django.db.models import F
 from django.db.models import F
+from django.db.transaction import atomic
+from django.shortcuts import redirect
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
+from django.views.decorators.cache import never_cache
+from django.views.decorators.csrf import csrf_protect
+
+from misago.core.decorators import require_POST
+from misago.core.uiviews import uiview
+from misago.users.decorators import deny_guests
 
 
 from misago.threads.models import Thread
 from misago.threads.models import Thread
 from misago.threads.permissions import exclude_invisible_threads
 from misago.threads.permissions import exclude_invisible_threads
@@ -19,6 +26,8 @@ class UnreadThreads(Threads):
         cutoff_date = timezone.now() - timedelta(days=cutoff_days)
         cutoff_date = timezone.now() - timedelta(days=cutoff_days)
         if cutoff_date < self.user.reads_cutoff:
         if cutoff_date < self.user.reads_cutoff:
             cutoff_date = self.user.reads_cutoff
             cutoff_date = self.user.reads_cutoff
+        if cutoff_date < self.user.unread_threads_cutoff:
+            cutoff_date = self.user.unread_threads_cutoff
 
 
         queryset = Thread.objects.filter(last_post_on__gte=cutoff_date)
         queryset = Thread.objects.filter(last_post_on__gte=cutoff_date)
         queryset = queryset.select_related('forum')
         queryset = queryset.select_related('forum')
@@ -53,6 +62,21 @@ class UnreadThreadsView(ThreadsView):
                 request, *args, **kwargs)
                 request, *args, **kwargs)
 
 
 
 
+@deny_guests
+@require_POST
+@csrf_protect
+@never_cache
+@atomic
+def clear_unread_threads(request):
+    request.user.unread_threads_cutoff = timezone.now()
+    request.user.save(update_fields=['unread_threads_cutoff'])
+
+    request.user.unread_threads.set(0)
+
+    messages.success(request, _("Unread threads list has been cleared."))
+    return redirect('misago:unread_threads')
+
+
 @uiview("unread_threads")
 @uiview("unread_threads")
 @deny_guests
 @deny_guests
 def event_sender(request, resolver_match):
 def event_sender(request, resolver_match):

+ 3 - 1
misago/users/migrations/0001_initial.py

@@ -64,7 +64,9 @@ class Migration(migrations.Migration):
                 ('posts', models.PositiveIntegerField(default=0, db_index=True)),
                 ('posts', models.PositiveIntegerField(default=0, db_index=True)),
                 ('last_post', models.DateTimeField(null=True, blank=True)),
                 ('last_post', models.DateTimeField(null=True, blank=True)),
                 ('last_search', models.DateTimeField(null=True, blank=True)),
                 ('last_search', models.DateTimeField(null=True, blank=True)),
-                ('reads_cutoff', models.DateTimeField(default=django.utils.timezone.now))
+                ('reads_cutoff', models.DateTimeField(default=django.utils.timezone.now)),
+                ('new_threads_cutoff', models.DateTimeField(default=django.utils.timezone.now)),
+                ('unread_threads_cutoff', models.DateTimeField(default=django.utils.timezone.now))
             ],
             ],
             options={
             options={
                 'abstract': False,
                 'abstract': False,

+ 2 - 0
misago/users/models/user.py

@@ -221,6 +221,8 @@ class User(AbstractBaseUser, PermissionsMixin):
     last_search = models.DateTimeField(null=True, blank=True)
     last_search = models.DateTimeField(null=True, blank=True)
 
 
     reads_cutoff = models.DateTimeField(default=dj_timezone.now)
     reads_cutoff = models.DateTimeField(default=dj_timezone.now)
+    new_threads_cutoff = models.DateTimeField(default=dj_timezone.now)
+    unread_threads_cutoff = models.DateTimeField(default=dj_timezone.now)
 
 
     is_active = True  # Django's is_active means "is not deleted"
     is_active = True  # Django's is_active means "is not deleted"