threadstracker.py 6.4 KB

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