threadstracker.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from django.db.transaction import atomic
  2. from django.utils import timezone
  3. from misago.readtracker import forumstracker, signals
  4. from misago.readtracker.dates import is_date_tracked
  5. from misago.readtracker.models import ForumRead, ThreadRead
  6. __all__ = ['make_read_aware', 'read_thread']
  7. def make_read_aware(user, target):
  8. if hasattr(target, '__iter__'):
  9. make_threads_read_aware(user, target)
  10. else:
  11. make_thread_read_aware(user, target)
  12. def make_threads_read_aware(user, threads, forum=None):
  13. if not threads:
  14. return None
  15. if user.is_anonymous():
  16. make_read(threads)
  17. return None
  18. if forum:
  19. make_forum_threads_read_aware(user, forum, threads)
  20. else:
  21. make_forums_threads_read_aware(user, threads)
  22. def make_read(threads):
  23. for thread in threads:
  24. thread.unread_replies = 0
  25. thread.is_read = True
  26. thread.is_new = False
  27. def make_forum_threads_read_aware(user, forum, threads):
  28. if forum.is_read:
  29. make_read(threads)
  30. else:
  31. threads_dict = {}
  32. for thread in threads:
  33. thread.is_read = not is_date_tracked(
  34. thread.last_post_on, user, forum.last_read_on)
  35. thread.is_new = True
  36. if thread.is_read:
  37. thread.unread_replies = 0
  38. else:
  39. thread.unread_replies = thread.replies
  40. threads_dict[thread.pk] = thread
  41. if threads_dict:
  42. make_threads_dict_read_aware(user, threads_dict)
  43. def make_forums_threads_read_aware(user, threads):
  44. forums_cutoffs = fetch_forums_cutoffs_for_threads(user, threads)
  45. threads_dict = {}
  46. for thread in threads:
  47. thread.is_read = not is_date_tracked(
  48. thread.last_post_on, user, forums_cutoffs.get(thread.forum_id))
  49. thread.is_new = True
  50. if thread.is_read:
  51. thread.unread_replies = 0
  52. else:
  53. thread.unread_replies = thread.replies
  54. threads_dict[thread.pk] = thread
  55. if threads_dict:
  56. make_threads_dict_read_aware(user, threads_dict)
  57. def fetch_forums_cutoffs_for_threads(user, threads):
  58. forums = []
  59. for thread in threads:
  60. if thread.forum_id not in forums:
  61. forums.append(thread.forum_id)
  62. forums_dict = {}
  63. for record in user.forumread_set.filter(forum__in=forums):
  64. forums_dict[record.forum_id] = record.last_read_on
  65. return forums_dict
  66. def make_threads_dict_read_aware(user, threads_dict):
  67. for record in user.threadread_set.filter(thread__in=threads_dict.keys()):
  68. if record.thread_id in threads_dict:
  69. thread = threads_dict[record.thread_id]
  70. thread.is_new = False
  71. thread.is_read = record.last_read_on >= thread.last_post_on
  72. if thread.is_read:
  73. thread.unread_replies = 0
  74. else:
  75. thread.unread_replies = thread.replies - record.read_replies
  76. if thread.unread_replies < 1:
  77. thread.unread_replies = 1
  78. def make_thread_read_aware(user, thread):
  79. thread.is_read = True
  80. thread.is_new = False
  81. thread.read_record = None
  82. if user.is_anonymous():
  83. thread.last_read_on = timezone.now()
  84. else:
  85. thread.last_read_on = user.reads_cutoff
  86. if user.is_authenticated() and is_date_tracked(thread.last_post_on, user):
  87. thread.is_read = False
  88. thread.is_new = True
  89. try:
  90. forum_record = user.forumread_set.get(forum_id=thread.forum_id)
  91. if thread.last_post_on > forum_record.last_read_on:
  92. try:
  93. thread_record = user.threadread_set.get(thread=thread)
  94. thread.last_read_on = thread_record.last_read_on
  95. thread.is_new = False
  96. if thread.last_post_on <= thread_record.last_read_on:
  97. thread.is_read = True
  98. thread.read_record = thread_record
  99. except ThreadRead.DoesNotExist:
  100. pass
  101. else:
  102. thread.is_read = True
  103. thread.is_new = False
  104. except ForumRead.DoesNotExist:
  105. forumstracker.start_record(user, thread.forum)
  106. def make_posts_read_aware(user, thread, posts):
  107. try:
  108. is_thread_read = thread.is_read
  109. except AttributeError:
  110. raise ValueError("thread passed make_posts_read_aware should be "
  111. "read aware too via make_thread_read_aware")
  112. if is_thread_read:
  113. for post in posts:
  114. post.is_read = True
  115. else:
  116. for post in posts:
  117. if is_date_tracked(post.posted_on, user):
  118. post.is_read = post.posted_on <= thread.last_read_on
  119. else:
  120. post.is_read = True
  121. def read_thread(user, thread, last_read_reply):
  122. if not thread.is_read:
  123. if thread.last_read_on < last_read_reply.posted_on:
  124. sync_record(user, thread, last_read_reply)
  125. @atomic
  126. def sync_record(user, thread, last_read_reply):
  127. read_replies = count_read_replies(user, thread, last_read_reply)
  128. if thread.read_record:
  129. thread.read_record.read_replies = read_replies
  130. thread.read_record.last_read_on = last_read_reply.posted_on
  131. thread.read_record.save(update_fields=['read_replies', 'last_read_on'])
  132. else:
  133. user.threadread_set.create(
  134. forum=thread.forum,
  135. thread=thread,
  136. read_replies=read_replies,
  137. last_read_on=last_read_reply.posted_on)
  138. signals.thread_tracked.send(sender=user, thread=thread)
  139. if last_read_reply.posted_on == thread.last_post_on:
  140. signals.thread_read.send(sender=user, thread=thread)
  141. forumstracker.sync_record(user, thread.forum)
  142. def count_read_replies(user, thread, last_read_reply):
  143. last_reply_date = last_read_reply.posted_on
  144. queryset = thread.post_set.filter(posted_on__lte=last_reply_date)
  145. queryset = queryset.filter(is_moderated=False)
  146. return queryset.count() - 1 # - starters post