thread.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. from django.core.urlresolvers import reverse
  2. from django.db import models
  3. from django.dispatch import receiver
  4. from misago.conf import settings
  5. from misago.core.shortcuts import paginate
  6. from misago.core.utils import slugify
  7. __all__ = ['Thread', 'ThreadParticipant']
  8. class Thread(models.Model):
  9. forum = models.ForeignKey('misago_forums.Forum')
  10. label = models.ForeignKey('misago_threads.Label',
  11. null=True, blank=True,
  12. on_delete=models.SET_NULL)
  13. title = models.CharField(max_length=255)
  14. slug = models.CharField(max_length=255)
  15. replies = models.PositiveIntegerField(default=0, db_index=True)
  16. has_reported_posts = models.BooleanField(default=False)
  17. has_moderated_posts = models.BooleanField(default=False)
  18. has_hidden_posts = models.BooleanField(default=False)
  19. has_events = models.BooleanField(default=False)
  20. started_on = models.DateTimeField(db_index=True)
  21. first_post = models.ForeignKey('misago_threads.Post', related_name='+',
  22. null=True, blank=True,
  23. on_delete=models.SET_NULL)
  24. starter = models.ForeignKey(settings.AUTH_USER_MODEL,
  25. null=True, blank=True,
  26. on_delete=models.SET_NULL)
  27. starter_name = models.CharField(max_length=255)
  28. starter_slug = models.CharField(max_length=255)
  29. last_post_on = models.DateTimeField(db_index=True)
  30. last_post = models.ForeignKey('misago_threads.Post', related_name='+',
  31. null=True, blank=True,
  32. on_delete=models.SET_NULL)
  33. last_poster = models.ForeignKey(settings.AUTH_USER_MODEL,
  34. related_name='last_poster_set',
  35. null=True, blank=True,
  36. on_delete=models.SET_NULL)
  37. last_poster_name = models.CharField(max_length=255, null=True, blank=True)
  38. last_poster_slug = models.CharField(max_length=255, null=True, blank=True)
  39. is_pinned = models.BooleanField(default=False, db_index=True)
  40. is_poll = models.BooleanField(default=False)
  41. is_moderated = models.BooleanField(default=False, db_index=True)
  42. is_hidden = models.BooleanField(default=False)
  43. is_closed = models.BooleanField(default=False)
  44. participants = models.ManyToManyField(settings.AUTH_USER_MODEL,
  45. related_name='private_thread_set',
  46. through='ThreadParticipant')
  47. class Meta:
  48. index_together = [
  49. ['forum', 'id'],
  50. ['forum', 'last_post_on'],
  51. ['forum', 'replies'],
  52. ]
  53. def __unicode__(self):
  54. return self.title
  55. def lock(self):
  56. return Thread.objects.select_for_update().get(id=self.id)
  57. def delete(self, *args, **kwargs):
  58. from misago.threads.signals import delete_thread
  59. delete_thread.send(sender=self)
  60. super(Thread, self).delete(*args, **kwargs)
  61. def merge(self, other_thread):
  62. if self.pk == other_thread.pk:
  63. raise ValueError("thread can't be merged with itself")
  64. from misago.threads.signals import merge_thread
  65. merge_thread.send(sender=self, other_thread=other_thread)
  66. def move(self, new_forum):
  67. from misago.threads.signals import move_thread
  68. self.forum = new_forum
  69. move_thread.send(sender=self)
  70. def synchronize(self):
  71. self.replies = self.post_set.filter(is_moderated=False).count()
  72. if self.replies > 0:
  73. self.replies -= 1
  74. reported_post_qs = self.post_set.filter(is_reported=True)
  75. self.has_reported_posts = reported_post_qs.exists()
  76. moderated_post_qs = self.post_set.filter(is_moderated=True)
  77. self.has_moderated_posts = moderated_post_qs.exists()
  78. hidden_post_qs = self.post_set.filter(is_hidden=True)[:1]
  79. self.has_hidden_posts = hidden_post_qs.exists()
  80. self.has_events = self.event_set.exists()
  81. first_post = self.post_set.order_by('id')[:1][0]
  82. self.set_first_post(first_post)
  83. self.is_moderated = first_post.is_moderated
  84. self.is_hidden = first_post.is_hidden
  85. last_post_qs = self.post_set.filter(is_moderated=False).order_by('-id')
  86. last_post = last_post_qs[:1]
  87. if last_post:
  88. self.set_last_post(last_post[0])
  89. else:
  90. self.set_last_post(first_post)
  91. @property
  92. def link_prefix(self):
  93. if self.forum.special_role == 'private_threads':
  94. return 'private_thread'
  95. else:
  96. return 'thread'
  97. def get_url_name(self, suffix=None):
  98. link = 'misago:%s' % self.link_prefix
  99. if suffix:
  100. link = '%s_%s' % (link, suffix)
  101. return link
  102. def get_url(self, suffix=None):
  103. return reverse(self.get_url_name(suffix), kwargs={
  104. 'thread_slug': self.slug,
  105. 'thread_id': self.id
  106. })
  107. def get_absolute_url(self):
  108. return self.get_url()
  109. def get_last_reply_url(self):
  110. return self.get_url('last')
  111. def get_new_reply_url(self):
  112. return self.get_url('new')
  113. def get_moderated_url(self):
  114. return self.get_url('moderated')
  115. def get_reported_url(self):
  116. return self.get_url('reported')
  117. def set_title(self, title):
  118. self.title = title
  119. self.slug = slugify(title)
  120. def set_first_post(self, post):
  121. self.started_on = post.posted_on
  122. self.first_post = post
  123. self.starter = post.poster
  124. self.starter_name = post.poster_name
  125. if post.poster:
  126. self.starter_slug = post.poster.slug
  127. else:
  128. self.starter_slug = slugify(post.poster_name)
  129. self.is_moderated = post.is_moderated
  130. self.is_hidden = post.is_hidden
  131. def set_last_post(self, post):
  132. self.last_post_on = post.posted_on
  133. self.last_post = post
  134. self.last_poster = post.poster
  135. self.last_poster_name = post.poster_name
  136. if post.poster:
  137. self.last_poster_slug = post.poster.slug
  138. else:
  139. self.last_poster_slug = slugify(post.poster_name)
  140. PARTICIPANT_REMOVED = 0
  141. PARTICIPANT_ACTIVE = 1
  142. PARTICIPANT_OWNER = 2
  143. class ThreadParticipant(models.Model):
  144. thread = models.ForeignKey(Thread)
  145. user = models.ForeignKey(settings.AUTH_USER_MODEL)
  146. level = models.PositiveIntegerField(default=PARTICIPANT_ACTIVE)
  147. @property
  148. def is_removed(self):
  149. return self.level == PARTICIPANT_REMOVED
  150. @property
  151. def is_active(self):
  152. return self.level == PARTICIPANT_ACTIVE
  153. @property
  154. def is_owner(self):
  155. return self.level == PARTICIPANT_OWNER