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

+ 37 - 2
misago/readtracker/forums.py

@@ -1,7 +1,12 @@
-from misago.readtracker.dates import is_date_tracked
+from django.db.models import F
+from django.utils import timezone
 
+from misago.threads.permissions import exclude_invisible_threads
 
-__all__ = ['make_forums_read_aware', 'make_forums_read']
+from misago.readtracker.dates import cutoff_date, is_date_tracked
+
+
+__all__ = ['make_forums_read_aware', 'make_forums_read', 'sync_forum_record']
 
 
 def make_forums_read_aware(user, forums):
@@ -24,3 +29,33 @@ def make_forums_read(forums):
     for forum in forums:
         forum.is_read = True
 
+
+def sync_forum_record(user, forum):
+    recorded_threads = forum.thread_set.filter(last_post_on__gt=cutoff_date())
+    recorded_threads = exclude_invisible_threads(user, forum, recorded_threads)
+
+    all_threads_count = recorded_threads.count()
+
+    read_threads = user.threadread_set.filter(
+        forum=forum, last_read_on__gt=cutoff_date())
+    read_threads_count = read_threads.filter(
+        thread__last_post_on__lte=F("last_read_on")).count()
+
+    forum_is_read = read_threads_count == all_threads_count
+
+    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.save(update_fields=['last_updated_on', 'last_cleared_on'])
+    except IndexError:
+        if forum_is_read:
+            cleared_on = timezone.now()
+        else:
+            cleared_on = cutoff_date()
+
+        forum_record = user.forumread_set.create(
+            forum=forum,
+            last_updated_on=timezone.now(),
+            last_cleared_on=cleared_on)

+ 78 - 7
misago/readtracker/threads.py

@@ -1,7 +1,14 @@
-from misago.readtracker.dates import is_date_tracked
+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']
+__all__ = [
+    'make_threads_read_aware',
+    'make_threads_read',
+    'make_thread_read_aware',
+    'make_posts_read_aware',
+    'sync_thread_read',
+]
 
 
 def make_threads_read_aware(user, threads):
@@ -13,9 +20,9 @@ def make_threads_read_aware(user, threads):
     for thread in threads:
         thread.is_read = not is_date_tracked(thread.last_post_on)
         if thread.is_read:
-            thread.unread_posts = 0
+            thread.unread_replies = 0
         else:
-            thread.unread_posts = thread.replies
+            thread.unread_replies = thread.replies
         threads_dict[thread.pk] = thread
 
     for record in user.threadread_set.filter(thread__in=threads_dict.keys()):
@@ -23,13 +30,77 @@ def make_threads_read_aware(user, threads):
             thread = threads_dict[record.thread_id]
             thread.is_read = record.last_read_on >= thread.last_post_on
             if thread.is_read:
-                thread.unread_posts = 0
+                thread.unread_replies = 0
             else:
-                thread.unread_posts = thread.replies - record.read_replies
+                thread.unread_replies = thread.replies - record.read_replies
 
 
 def make_threads_read(threads):
     for thread in threads:
-        thread.unread_posts = 0
+        thread.unread_replies = 0
         thread.is_read = True
 
+
+def make_thread_read_aware(user, thread):
+    thread.is_read = True
+    if user.is_authenticated() and is_date_tracked(thread.last_post_on):
+        try:
+            record = user.threadread_set.filter(thread=thread).all()[0]
+            thread.last_read_on = record.last_read_on
+            thread.is_read = thread.last_post_on <= record.last_read_on
+            thread.read_record = record
+        except IndexError:
+            thread.read_record = None
+            thread.is_read = False
+            thread.last_read_on = cutoff_date()
+
+
+def make_posts_read_aware(thread, posts):
+    try:
+        is_thread_read = thread.is_read
+    except AttributeError:
+        raise ValueError("thread passed make_posts_read_aware should be "
+                         "read aware too via make_thread_read_aware")
+
+    if is_thread_read:
+        for post in posts:
+            post.is_read = True
+    else:
+        for post in posts:
+            if is_date_tracked(post.updated_on):
+                post.is_read = post.updated_on <= thread.last_read_on
+            else:
+                post.is_read = True
+
+
+def count_read_replies(user, thread, last_read_reply):
+    if last_read_reply.updated_on >= thread.last_read_on:
+        return 0
+    else:
+        last_reply_date = last_read_reply.last_read_on
+        queryset = thread.post_set.filter(last_read_on__lte=last_reply_date)
+        queryset = queryset.filter(is_moderated=False)
+        return queryset.count()
+
+
+def sync_thread_read(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)
+
+
+def read_thread(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
+        thread.read_record.last_read_on = last_read_reply.updated_on
+        thread.read_record.save(update_fields=['read_replies', 'last_read_on'])
+    else:
+         user.threadread_set.create(
+            forum=thread.forum,
+            thread=thread,
+            read_replies=read_replies,
+            last_read_on=last_read_reply.updated_on)
+
+    if last_read_reply.updated_on == thread.last_post_on:
+        sync_forum_record(user, thread.forum)

+ 5 - 0
misago/templates/misago/thread/post.html

@@ -29,6 +29,11 @@
         <a href="" class="post-date tooltip-top dynamic time-ago" title="{{ post.posted_on }}" data-timestamp="{{ post.posted_on|date:"c" }}">
           {{ post.posted_on|date }}
         </a>
+
+        {% if not post.is_read %}
+        <span class="text-warning">{% trans "New" %}</span>
+        {% endif %}
+
       </div>
       <div class="panel-body">
         {% if post.is_valid %}

+ 41 - 0
misago/threads/permissions.py

@@ -1,4 +1,5 @@
 from django.core.exceptions import PermissionDenied
+from django.db.models import Q
 from django.http import Http404
 from django.utils.translation import ugettext_lazy as _
 
@@ -275,3 +276,43 @@ def allow_start_thread(user, target):
         raise PermissionDenied(_("You don't have permission to start "
                                  "new threads in this forum."))
 can_start_thread = return_boolean(allow_start_thread)
+
+
+"""
+Queryset helpers
+"""
+def exclude_invisible_threads(user, forum, queryset):
+    if user.is_authenticated():
+        condition_author = Q(starter_id=user.id)
+
+        can_mod = forum.acl['can_review_moderated_content']
+        can_hide = forum.acl['can_hide_threads']
+
+        if not can_mod and not can_hide:
+            condition = Q(is_moderated=False) & Q(is_hidden=False)
+            queryset = queryset.filter(condition_author | condition)
+        elif not can_mod:
+            condition = Q(is_moderated=False)
+            queryset = queryset.filter(condition_author | condition)
+        elif not can_hide:
+            condition = Q(is_hidden=False)
+            queryset = queryset.filter(condition_author | condition)
+    else:
+        if not forum.acl['can_review_moderated_content']:
+            queryset = queryset.filter(is_moderated=False)
+        if not forum.acl['can_hide_threads']:
+            queryset = queryset.filter(is_hidden=False)
+
+    return queryset
+
+
+def exclude_invisible_postss(user, forum, queryset):
+    if user.is_authenticated():
+        if not forum.acl['can_review_moderated_content']:
+            condition_author = Q(starter_id=user.id)
+            condition = Q(is_moderated=False)
+            queryset = queryset.filter(condition_author | condition)
+    elif not forum.acl['can_review_moderated_content']:
+            queryset = queryset.filter(is_moderated=False)
+
+    return queryset

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

@@ -9,6 +9,7 @@ from misago.forums.lists import get_forums_list, get_forum_path
 from misago.threads.posting import (PostingInterrupt, EditorFormset,
                                     START, REPLY, EDIT)
 from misago.threads.models import ANNOUNCEMENT, Thread, Label
+from misago.threads.permissions import exclude_invisible_threads
 from misago.threads.views.generic.threads import OrderThreadsMixin, ThreadsView
 
 
@@ -167,28 +168,7 @@ class ForumView(FilterThreadsMixin, OrderThreadsMixin, ThreadsView):
         return threads, announcements
 
     def filter_all_querysets(self, request, forum, queryset):
-        if request.user.is_authenticated():
-            condition_author = Q(starter_id=request.user.id)
-
-            can_mod = forum.acl['can_review_moderated_content']
-            can_hide = forum.acl['can_hide_threads']
-
-            if not can_mod and not can_hide:
-                condition = Q(is_moderated=False) & Q(is_hidden=False)
-                queryset = queryset.filter(condition_author | condition)
-            elif not can_mod:
-                condition = Q(is_moderated=False)
-                queryset = queryset.filter(condition_author | condition)
-            elif not can_hide:
-                condition = Q(is_hidden=False)
-                queryset = queryset.filter(condition_author | condition)
-        else:
-            if not forum.acl['can_review_moderated_content']:
-                queryset = queryset.filter(is_moderated=False)
-            if not forum.acl['can_hide_threads']:
-                queryset = queryset.filter(is_hidden=False)
-
-        return queryset
+        return exclude_invisible_threads(request.user, forum, queryset)
 
     def filter_threads_queryset(self, request, forum, queryset):
         if forum.acl['can_see_own_threads']:

+ 6 - 0
misago/threads/views/generic/thread.py

@@ -4,6 +4,8 @@ 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.users.online.utils import get_user_state
 
 from misago.threads.views.generic.base import ViewBase
@@ -53,7 +55,11 @@ class ThreadView(ViewBase):
         self.check_forum_permissions(request, forum)
         self.check_thread_permissions(request, thread)
 
+        make_thread_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])
 
         return self.render(request, {
             'forum': forum,