threadmodel.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. from datetime import timedelta
  2. from django.conf import settings
  3. from django.db import models
  4. from django.utils import timezone
  5. from django.utils.translation import ugettext_lazy as _
  6. from misago.signals import (delete_user_content, merge_thread, move_forum_content,
  7. move_thread, rename_user)
  8. from misago.utils.strings import slugify
  9. class ThreadManager(models.Manager):
  10. def filter_stats(self, start, end):
  11. return self.filter(start__gte=start).filter(start__lte=end)
  12. def with_reads(self, queryset, user):
  13. from misago.models import ForumRead, ThreadRead
  14. threads = []
  15. threads_dict = {}
  16. forum_reads = {}
  17. cutoff = timezone.now() - timedelta(days=settings.READS_TRACKER_LENGTH)
  18. if user.is_authenticated() and user.join_date > cutoff:
  19. cutoff = user.join_date
  20. for row in ForumRead.objects.filter(user=user).values('forum_id', 'cleared'):
  21. forum_reads[row['forum_id']] = row['cleared']
  22. for thread in queryset:
  23. thread.is_read = True
  24. if user.is_authenticated() and thread.last > cutoff:
  25. try:
  26. thread.is_read = thread.last <= forum_reads[thread.forum_id]
  27. except KeyError:
  28. pass
  29. threads.append(thread)
  30. threads_dict[thread.pk] = thread
  31. if user.is_authenticated():
  32. for read in ThreadRead.objects.filter(user=user).filter(thread__in=threads_dict.keys()):
  33. try:
  34. threads_dict[read.thread_id].is_read = (threads_dict[read.thread_id].last <= cutoff or
  35. threads_dict[read.thread_id].last <= read.updated or
  36. threads_dict[read.thread_id].last <= forum_reads[read.forum_id])
  37. except KeyError:
  38. pass
  39. return threads
  40. class Thread(models.Model):
  41. forum = models.ForeignKey('Forum')
  42. weight = models.PositiveIntegerField(default=0)
  43. name = models.CharField(max_length=255)
  44. slug = models.SlugField(max_length=255)
  45. replies = models.PositiveIntegerField(default=0)
  46. replies_reported = models.PositiveIntegerField(default=0)
  47. replies_moderated = models.PositiveIntegerField(default=0)
  48. replies_deleted = models.PositiveIntegerField(default=0)
  49. merges = models.PositiveIntegerField(default=0)
  50. score = models.PositiveIntegerField(default=30)
  51. upvotes = models.PositiveIntegerField(default=0)
  52. downvotes = models.PositiveIntegerField(default=0)
  53. start = models.DateTimeField()
  54. start_post = models.ForeignKey('Post', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  55. start_poster = models.ForeignKey('User', null=True, blank=True, on_delete=models.SET_NULL)
  56. start_poster_name = models.CharField(max_length=255)
  57. start_poster_slug = models.SlugField(max_length=255)
  58. start_poster_style = models.CharField(max_length=255, null=True, blank=True)
  59. last = models.DateTimeField()
  60. last_post = models.ForeignKey('Post', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  61. last_poster = models.ForeignKey('User', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  62. last_poster_name = models.CharField(max_length=255, null=True, blank=True)
  63. last_poster_slug = models.SlugField(max_length=255, null=True, blank=True)
  64. last_poster_style = models.CharField(max_length=255, null=True, blank=True)
  65. moderated = models.BooleanField(default=False)
  66. deleted = models.BooleanField(default=False)
  67. closed = models.BooleanField(default=False)
  68. objects = ThreadManager()
  69. statistics_name = _('New Threads')
  70. class Meta:
  71. app_label = 'misago'
  72. def get_date(self):
  73. return self.start
  74. def move_to(self, move_to):
  75. move_thread.send(sender=self, move_to=move_to)
  76. self.forum = move_to
  77. def merge_with(self, thread, merge):
  78. merge_thread.send(sender=self, new_thread=thread, merge=merge)
  79. def sync(self):
  80. # Counters
  81. self.replies = self.post_set.filter(moderated=False).count() - 1
  82. if self.replies < 0:
  83. self.replies = 0
  84. self.replies_reported = self.post_set.filter(reported=True).count()
  85. self.replies_moderated = self.post_set.filter(moderated=True).count()
  86. self.replies_deleted = self.post_set.filter(deleted=True).count()
  87. # First post
  88. start_post = self.post_set.order_by('merge', 'id')[0:][0]
  89. self.start = start_post.date
  90. self.start_post = start_post
  91. self.start_poster = start_post.user
  92. self.start_poster_name = start_post.user_name
  93. self.start_poster_slug = slugify(start_post.user_name)
  94. self.start_poster_style = start_post.user.rank.style if start_post.user else ''
  95. self.upvotes = start_post.upvotes
  96. self.downvotes = start_post.downvotes
  97. # Last visible post
  98. if self.replies > 0:
  99. last_post = self.post_set.order_by('-merge', '-id').filter(moderated=False)[0:][0]
  100. else:
  101. last_post = start_post
  102. self.last = last_post.date
  103. self.last_post = last_post
  104. self.last_poster = last_post.user
  105. self.last_poster_name = last_post.user_name
  106. self.last_poster_slug = slugify(last_post.user_name)
  107. self.last_poster_style = last_post.user.rank.style if last_post.user else ''
  108. # Flags
  109. self.moderated = start_post.moderated
  110. self.deleted = start_post.deleted
  111. self.merges = last_post.merge
  112. def email_watchers(self, request, post):
  113. from misago.acl.builder import acl
  114. from misago.acl.utils import ACLError403, ACLError404
  115. from misago.models import ThreadRead
  116. for watch in ThreadWatch.objects.filter(thread=self).filter(email=True).filter(last_read__gte=self.previous_last):
  117. user = watch.user
  118. if user.pk != request.user.pk:
  119. try:
  120. acl = acl(request, user)
  121. acl.forums.allow_forum_view(self.forum)
  122. acl.threads.allow_thread_view(user, self)
  123. acl.threads.allow_post_view(user, self, post)
  124. if not user.is_ignoring(request.user):
  125. user.email_user(
  126. request,
  127. 'post_notification',
  128. _('New reply in thread "%(thread)s"') % {'thread': self.name},
  129. {'author': request.user, 'post': post, 'thread': self}
  130. )
  131. except (ACLError403, ACLError404):
  132. pass
  133. def rename_user_handler(sender, **kwargs):
  134. Thread.objects.filter(start_poster=sender).update(
  135. start_poster_name=sender.username,
  136. start_poster_slug=sender.username_slug,
  137. )
  138. Thread.objects.filter(last_poster=sender).update(
  139. last_poster_name=sender.username,
  140. last_poster_slug=sender.username_slug,
  141. )
  142. rename_user.connect(rename_user_handler, dispatch_uid="rename_user_threads")
  143. def delete_user_content_handler(sender, **kwargs):
  144. for thread in Thread.objects.filter(start_poster=sender):
  145. thread.delete()
  146. delete_user_content.connect(delete_user_content_handler, dispatch_uid="delete_user_threads")
  147. def move_forum_content_handler(sender, **kwargs):
  148. Thread.objects.filter(forum=sender).update(forum=kwargs['move_to'])
  149. move_forum_content.connect(move_forum_content_handler, dispatch_uid="move_forum_threads")