Rafał Pitoń 10 years ago
parent
commit
abbe37fbfe

+ 1 - 1
misago/readtracker/dates.py

@@ -6,6 +6,6 @@ from django.utils import timezone
 
 def is_date_tracked(user, date):
     if date:
-        return date > user.joined_on
+        return date > user.reads_cutoff
     else:
         return False

+ 9 - 10
misago/readtracker/forumstracker.py

@@ -23,7 +23,7 @@ def make_read_aware(user, forums):
     for record in user.forumread_set.filter(forum__in=forums_dict.keys()):
         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
+            forum.is_read = record.last_read_on >= forum.last_post_on
 
 
 def make_read(forums):
@@ -32,7 +32,8 @@ def make_read(forums):
 
 
 def sync_record(user, forum):
-    recorded_threads = forum.thread_set.filter(last_post_on__gt=user.joined_on)
+    recorded_threads = forum.thread_set.filter(
+        last_post_on__gt=user.reads_cutoff)
     recorded_threads = exclude_invisible_threads(recorded_threads, user, forum)
 
     all_threads_count = recorded_threads.count()
@@ -49,19 +50,17 @@ def sync_record(user, forum):
 
     try:
         forum_record = user.forumread_set.filter(forum=forum).all()[0]
-        forum_record.last_updated_on = timezone.now()
         if forum_is_read:
-            forum_record.last_cleared_on = forum_record.last_updated_on
+            forum_record.last_read_on = forum_record.last_read_on
         else:
-            forum_record.last_cleared_on = user.joined_on
-        forum_record.save(update_fields=['last_updated_on', 'last_cleared_on'])
+            forum_record.last_read_on = user.reads_cutoff
+        forum_record.save(update_fields=['last_read_on'])
     except IndexError:
         if forum_is_read:
-            cleared_on = timezone.now()
+            last_read_on = timezone.now()
         else:
-            cleared_on = user.joined_on
+            last_read_on = user.joined_on
 
         forum_record = user.forumread_set.create(
             forum=forum,
-            last_updated_on=timezone.now(),
-            last_cleared_on=cleared_on)
+            last_read_on=last_read_on)

+ 1 - 2
misago/readtracker/migrations/0001_initial.py

@@ -17,8 +17,7 @@ class Migration(migrations.Migration):
             name='ForumRead',
             fields=[
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
-                ('last_updated_on', models.DateTimeField()),
-                ('last_cleared_on', models.DateTimeField()),
+                ('last_read_on', models.DateTimeField()),
                 ('forum', models.ForeignKey(to='misago_forums.Forum')),
                 ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
             ],

+ 1 - 2
misago/readtracker/models.py

@@ -8,8 +8,7 @@ from django.utils import timezone
 class ForumRead(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL)
     forum = models.ForeignKey('misago_forums.Forum')
-    last_updated_on = models.DateTimeField()
-    last_cleared_on = models.DateTimeField()
+    last_read_on = models.DateTimeField()
 
 
 class ThreadRead(models.Model):

+ 1 - 0
misago/readtracker/signals.py

@@ -4,6 +4,7 @@ from misago.forums.signals import move_forum_content
 from misago.threads.signals import move_thread
 
 
+all_read = Signal()
 forum_read = Signal(providing_args=["forum"])
 thread_read = Signal(providing_args=["thread"])
 

+ 1 - 1
misago/readtracker/tests/test_dates.py

@@ -8,7 +8,7 @@ from misago.readtracker.dates import is_date_tracked
 
 class MockUser(object):
     def __init__(self):
-        self.joined_on = timezone.now()
+        self.reads_cutoff = timezone.now()
 
 
 class ReadTrackerDatesTests(TestCase):

+ 14 - 14
misago/readtracker/tests/test_readtracker.py

@@ -53,14 +53,14 @@ class ForumsTrackerTests(ReadTrackerTests):
 
     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 = self.user.joined_on - timedelta(days=1)
+        self.forum.last_post_on = self.user.reads_cutoff - 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 = self.user.joined_on + timedelta(days=1)
+        self.forum.last_post_on = self.user.reads_cutoff + timedelta(days=1)
 
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
@@ -79,7 +79,7 @@ class ForumsTrackerTests(ReadTrackerTests):
         sync_record sets read flag on forum with old thread,
         then changes flag to unread when new reply is posted
         """
-        self.post_thread(self.user.joined_on - timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff - timedelta(days=1))
 
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
@@ -88,7 +88,7 @@ class ForumsTrackerTests(ReadTrackerTests):
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertTrue(self.forum.is_read)
 
-        thread = self.post_thread(self.user.joined_on + timedelta(days=1))
+        thread = self.post_thread(self.user.reads_cutoff + timedelta(days=1))
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
@@ -98,7 +98,7 @@ class ForumsTrackerTests(ReadTrackerTests):
         sync_record sets read flag on forum with old thread,
         then keeps flag to unread when new reply is posted
         """
-        self.post_thread(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff + timedelta(days=1))
 
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
@@ -107,16 +107,16 @@ class ForumsTrackerTests(ReadTrackerTests):
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
 
-        self.post_thread(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff + 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(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.reads_cutoff + timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff + timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff + timedelta(days=1))
 
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
@@ -132,10 +132,10 @@ class ForumsTrackerTests(ReadTrackerTests):
 
     def test_sync_record_for_forum_with_many_threads(self):
         """sync_record sets unread flag on forum with many threads"""
-        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))
+        self.post_thread(self.user.reads_cutoff + timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff - timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff + timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff - timedelta(days=1))
 
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
@@ -144,7 +144,7 @@ class ForumsTrackerTests(ReadTrackerTests):
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
 
-        self.post_thread(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff + timedelta(days=1))
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)

+ 28 - 0
misago/readtracker/tests/test_views.py

@@ -0,0 +1,28 @@
+from django.contrib.auth import get_user_model
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
+
+from misago.forums.models import Forum
+from misago.users.testutils import AuthenticatedUserTestCase
+from misago.threads import testutils
+
+from misago.readtracker.forumstracker import make_read_aware
+
+
+class AuthenticatedTests(AuthenticatedUserTestCase):
+    def test_read_all_threads(self):
+        """read_all view updates reads cutoff on user model"""
+        forum = Forum.objects.all_forums().filter(role="forum")[:1][0]
+        threads = [testutils.post_thread(forum) for t in xrange(10)]
+
+        forum = Forum.objects.get(id=forum.id)
+        make_read_aware(self.user, [forum])
+        self.assertFalse(forum.is_read)
+
+        self.client.post(reverse('misago:read_all'))
+
+        forum = Forum.objects.get(id=forum.id)
+        user = get_user_model().objects.get(id=self.user.id)
+
+        make_read_aware(user, [forum])
+        self.assertTrue(forum.is_read)

+ 6 - 0
misago/readtracker/urls.py

@@ -0,0 +1,6 @@
+from django.conf.urls import include, patterns, url
+
+
+urlpatterns = patterns('misago.readtracker.views',
+    url(r'^read-all/$', 'read_all', name='read_all'),
+)

+ 27 - 0
misago/readtracker/views.py

@@ -0,0 +1,27 @@
+from django.contrib import messages
+from django.db.transaction import atomic
+from django.shortcuts import redirect
+from django.utils import timezone
+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.users.decorators import deny_guests
+
+from misago.readtracker.signals import all_read
+
+
+@deny_guests
+@require_POST
+@csrf_protect
+@never_cache
+@atomic
+def read_all(request):
+    request.user.reads_cutoff = timezone.now()
+    request.user.save(update_fields=['reads_cutoff'])
+
+    all_read.send(sender=request.user)
+
+    messages.info(request, _("All forums and threads were marked as read."))
+    return redirect('misago:index')

+ 14 - 0
misago/templates/misago/index.html

@@ -36,6 +36,10 @@
       {% include "misago/ranks_online.html" %}
       {% endif %}
 
+      {% if user.is_authenticated %}
+      {% include "misago/read_all.html" %}
+      {% endif %}
+
     </div>
   </div>
 </div>
@@ -45,4 +49,14 @@
 {% block javascripts %}
 {{ block.super }}
 {% include "misago/forums/js.html" %}
+{% if user.is_authenticated %}
+<script lang="JavaScript">
+  $(function() {
+    $('.read-all-form').submit(function() {
+      var decision = confirm("{% trans "Are you sure you want to mark all threads as read?" %}");
+      return decision;
+    });
+  });
+</script>
+{% endif %}
 {% endblock javascripts %}

+ 8 - 0
misago/templates/misago/read_all.html

@@ -0,0 +1,8 @@
+{% load i18n %}
+<form class="read-all-form" action="{% url 'misago:read_all' %}" method="POST">
+  {% csrf_token %}
+  <button type="submit" class="btn btn-default btn-block btn-lg">
+    <span class="fa fa-circle-o fa-fw"></span>
+    {% trans "Mark all threads as read" %}
+  </button>
+</form>

+ 8 - 0
misago/threads/counts.py

@@ -1,6 +1,7 @@
 from time import time
 
 from django.conf import settings
+from django.dispatch import receiver
 
 from misago.threads.views.newthreads import NewThreads
 from misago.threads.views.unreadthreads import UnreadThreads
@@ -72,3 +73,10 @@ class NewThreadsCount(BaseCounter):
 class UnreadThreadsCount(BaseCounter):
     Threads = UnreadThreads
     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)

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

@@ -16,8 +16,8 @@ class NewThreads(Threads):
     def get_queryset(self):
         cutoff_days = settings.MISAGO_FRESH_CONTENT_PERIOD
         cutoff_date = timezone.now() - timedelta(days=cutoff_days)
-        if cutoff_date < self.user.joined_on:
-            cutoff_date = self.user.joined_on
+        if cutoff_date < self.user.reads_cutoff:
+            cutoff_date = self.user.reads_cutoff
 
         queryset = Thread.objects.filter(started_on__gte=cutoff_date)
         queryset = queryset.select_related('forum')

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

@@ -17,8 +17,8 @@ class UnreadThreads(Threads):
     def get_queryset(self):
         cutoff_days = settings.MISAGO_FRESH_CONTENT_PERIOD
         cutoff_date = timezone.now() - timedelta(days=cutoff_days)
-        if cutoff_date < self.user.joined_on:
-            cutoff_date = self.user.joined_on
+        if cutoff_date < self.user.reads_cutoff:
+            cutoff_date = self.user.reads_cutoff
 
         queryset = Thread.objects.filter(last_post_on__gte=cutoff_date)
         queryset = queryset.select_related('forum')

+ 1 - 0
misago/urls.py

@@ -17,6 +17,7 @@ urlpatterns += patterns('',
     url(r'^', include('misago.notifications.urls')),
     url(r'^', include('misago.forums.urls')),
     url(r'^', include('misago.threads.urls')),
+    url(r'^', include('misago.readtracker.urls')),
     # UI Server view that handles realtime updates of Misago UI
     url(r'^ui-server/$', 'misago.core.uiviews.uiserver', name="ui_server"),
 )

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

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

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

@@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse
 from django.db import models, transaction
 from django.dispatch import receiver
 from django.utils import timezone
+dj_timezone = timezone
 from django.utils.translation import ugettext_lazy as _
 
 from misago.acl import get_user_acl
@@ -219,6 +220,8 @@ class User(AbstractBaseUser, PermissionsMixin):
     last_post = models.DateTimeField(null=True, blank=True)
     last_search = models.DateTimeField(null=True, blank=True)
 
+    reads_cutoff = models.DateTimeField(default=dj_timezone.now)
+
     is_active = True  # Django's is_active means "is not deleted"
 
     USERNAME_FIELD = 'slug'