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):
 def is_date_tracked(user, date):
     if date:
     if date:
-        return date > user.joined_on
+        return date > user.reads_cutoff
     else:
     else:
         return False
         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()):
     for record in user.forumread_set.filter(forum__in=forums_dict.keys()):
         if not forum.is_read and 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 = 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):
 def make_read(forums):
@@ -32,7 +32,8 @@ def make_read(forums):
 
 
 
 
 def sync_record(user, forum):
 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)
     recorded_threads = exclude_invisible_threads(recorded_threads, user, forum)
 
 
     all_threads_count = recorded_threads.count()
     all_threads_count = recorded_threads.count()
@@ -49,19 +50,17 @@ def sync_record(user, forum):
 
 
     try:
     try:
         forum_record = user.forumread_set.filter(forum=forum).all()[0]
         forum_record = user.forumread_set.filter(forum=forum).all()[0]
-        forum_record.last_updated_on = timezone.now()
         if forum_is_read:
         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:
         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:
     except IndexError:
         if forum_is_read:
         if forum_is_read:
-            cleared_on = timezone.now()
+            last_read_on = timezone.now()
         else:
         else:
-            cleared_on = user.joined_on
+            last_read_on = user.joined_on
 
 
         forum_record = user.forumread_set.create(
         forum_record = user.forumread_set.create(
             forum=forum,
             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',
             name='ForumRead',
             fields=[
             fields=[
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                 ('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')),
                 ('forum', models.ForeignKey(to='misago_forums.Forum')),
                 ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
                 ('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):
 class ForumRead(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL)
     user = models.ForeignKey(settings.AUTH_USER_MODEL)
     forum = models.ForeignKey('misago_forums.Forum')
     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):
 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
 from misago.threads.signals import move_thread
 
 
 
 
+all_read = Signal()
 forum_read = Signal(providing_args=["forum"])
 forum_read = Signal(providing_args=["forum"])
 thread_read = Signal(providing_args=["thread"])
 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):
 class MockUser(object):
     def __init__(self):
     def __init__(self):
-        self.joined_on = timezone.now()
+        self.reads_cutoff = timezone.now()
 
 
 
 
 class ReadTrackerDatesTests(TestCase):
 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):
     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 = 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)
         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 = 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)
         forumstracker.make_read_aware(self.user, self.forums)
         self.assertFalse(self.forum.is_read)
         self.assertFalse(self.forum.is_read)
@@ -79,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(self.user.joined_on - timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff - 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)
@@ -88,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(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.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)
@@ -98,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(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.reads_cutoff + 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)
@@ -107,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(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.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(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)
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
@@ -132,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(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)
         add_acl(self.user, self.forums)
         forumstracker.sync_record(self.user, self.forum)
         forumstracker.sync_record(self.user, self.forum)
@@ -144,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(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.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)

+ 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" %}
       {% include "misago/ranks_online.html" %}
       {% endif %}
       {% endif %}
 
 
+      {% if user.is_authenticated %}
+      {% include "misago/read_all.html" %}
+      {% endif %}
+
     </div>
     </div>
   </div>
   </div>
 </div>
 </div>
@@ -45,4 +49,14 @@
 {% block javascripts %}
 {% block javascripts %}
 {{ block.super }}
 {{ block.super }}
 {% include "misago/forums/js.html" %}
 {% 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 %}
 {% 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 time import time
 
 
 from django.conf import settings
 from django.conf import settings
+from django.dispatch import receiver
 
 
 from misago.threads.views.newthreads import NewThreads
 from misago.threads.views.newthreads import NewThreads
 from misago.threads.views.unreadthreads import UnreadThreads
 from misago.threads.views.unreadthreads import UnreadThreads
@@ -72,3 +73,10 @@ 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)

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

@@ -16,8 +16,8 @@ class NewThreads(Threads):
     def get_queryset(self):
     def get_queryset(self):
         cutoff_days = settings.MISAGO_FRESH_CONTENT_PERIOD
         cutoff_days = settings.MISAGO_FRESH_CONTENT_PERIOD
         cutoff_date = timezone.now() - timedelta(days=cutoff_days)
         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 = Thread.objects.filter(started_on__gte=cutoff_date)
         queryset = queryset.select_related('forum')
         queryset = queryset.select_related('forum')

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

@@ -17,8 +17,8 @@ class UnreadThreads(Threads):
     def get_queryset(self):
     def get_queryset(self):
         cutoff_days = settings.MISAGO_FRESH_CONTENT_PERIOD
         cutoff_days = settings.MISAGO_FRESH_CONTENT_PERIOD
         cutoff_date = timezone.now() - timedelta(days=cutoff_days)
         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 = Thread.objects.filter(last_post_on__gte=cutoff_date)
         queryset = queryset.select_related('forum')
         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.notifications.urls')),
     url(r'^', include('misago.forums.urls')),
     url(r'^', include('misago.forums.urls')),
     url(r'^', include('misago.threads.urls')),
     url(r'^', include('misago.threads.urls')),
+    url(r'^', include('misago.readtracker.urls')),
     # UI Server view that handles realtime updates of Misago UI
     # UI Server view that handles realtime updates of Misago UI
     url(r'^ui-server/$', 'misago.core.uiviews.uiserver', name="ui_server"),
     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)),
                 ('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))
             ],
             ],
             options={
             options={
                 'abstract': False,
                 '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.db import models, transaction
 from django.dispatch import receiver
 from django.dispatch import receiver
 from django.utils import timezone
 from django.utils import timezone
+dj_timezone = timezone
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 
 
 from misago.acl import get_user_acl
 from misago.acl import get_user_acl
@@ -219,6 +220,8 @@ class User(AbstractBaseUser, PermissionsMixin):
     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=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"
 
 
     USERNAME_FIELD = 'slug'
     USERNAME_FIELD = 'slug'