threadstracker.py 5.5 KB

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