postmodel.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import copy
  2. from django.db import models
  3. from django.db.models import F
  4. from django.db.models.signals import pre_save, pre_delete
  5. from django.utils import timezone
  6. from django.utils.translation import ugettext_lazy as _
  7. from misago.markdown import clear_markdown
  8. from misago.signals import (delete_user_content, merge_post, merge_thread,
  9. move_forum_content, move_post, move_thread,
  10. rename_user, sync_user_profile)
  11. from misago.utils.translation import ugettext_lazy
  12. import base64
  13. try:
  14. import cPickle as pickle
  15. except ImportError:
  16. import pickle
  17. class PostManager(models.Manager):
  18. def filter_stats(self, start, end):
  19. return self.filter(date__gte=start).filter(date__lte=end)
  20. class Post(models.Model):
  21. forum = models.ForeignKey('Forum')
  22. thread = models.ForeignKey('Thread')
  23. user = models.ForeignKey('User', null=True, blank=True, on_delete=models.SET_NULL)
  24. user_name = models.CharField(max_length=255)
  25. ip = models.GenericIPAddressField()
  26. agent = models.CharField(max_length=255)
  27. post = models.TextField()
  28. post_preparsed = models.TextField()
  29. upvotes = models.PositiveIntegerField(default=0)
  30. downvotes = models.PositiveIntegerField(default=0)
  31. mentions = models.ManyToManyField('User', related_name="mention_set")
  32. date = models.DateTimeField()
  33. current_date = models.DateTimeField(db_index=True)
  34. edits = models.PositiveIntegerField(default=0)
  35. edit_reason = models.CharField(max_length=255, null=True, blank=True)
  36. edit_user = models.ForeignKey('User', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  37. edit_user_name = models.CharField(max_length=255, null=True, blank=True)
  38. edit_user_slug = models.SlugField(max_length=255, null=True, blank=True)
  39. delete_date = models.DateTimeField(null=True, blank=True)
  40. reported = models.BooleanField(default=False, db_index=True)
  41. reports = models.CharField(max_length=255, null=True, blank=True)
  42. moderated = models.BooleanField(default=False)
  43. deleted = models.BooleanField(default=False)
  44. protected = models.BooleanField(default=False)
  45. has_attachments = models.BooleanField(default=False)
  46. _attachments = models.TextField(db_column='attachments', null=True, blank=True)
  47. objects = PostManager()
  48. statistics_name = _('New Posts')
  49. class Meta:
  50. app_label = 'misago'
  51. @property
  52. def attachments(self):
  53. if not self.has_attachments:
  54. return []
  55. try:
  56. return self._attachments_cache
  57. except AttributeError:
  58. pass
  59. try:
  60. self._attachments_cache = pickle.loads(base64.decodestring(self._attachments))
  61. except Exception:
  62. self._attachments_cache = []
  63. return self._attachments_cache
  64. @attachments.setter
  65. def attachments(self, new_attachments):
  66. if new_attachments:
  67. self._update_attachments_store(new_attachments)
  68. else:
  69. self._empty_attachments_store()
  70. def _empty_attachments_store(self):
  71. self.has_attachments = False
  72. self._attachments = None
  73. def _update_attachments_store(self, new_attachments):
  74. self.has_attachments = True
  75. clean_attachments = []
  76. for attachment in new_attachments:
  77. attachment = copy.copy(attachment)
  78. attachment_user_pk = attachment.user_id
  79. attachment.user = None
  80. attachment.user_id = attachment_user_pk
  81. attachment.forum = None
  82. attachment.thread = None
  83. attachment.post = None
  84. clean_attachments.append(attachment)
  85. self._attachments = base64.encodestring(pickle.dumps(clean_attachments, pickle.HIGHEST_PROTOCOL))
  86. def sync_attachments(self):
  87. if self.attachment_set.exists():
  88. self.attachments = self.attachment_set.iterator()
  89. else:
  90. self._empty_attachments_store()
  91. @property
  92. def timeline_date(self):
  93. return self.date
  94. def save(self, *args, **kwargs):
  95. self.current_date = timezone.now()
  96. return super(Post, self).save(*args, **kwargs)
  97. def delete(self, *args, **kwargs):
  98. """
  99. FUGLY HAX for weird stuff that happens with
  100. relations on model deletion in MySQL
  101. """
  102. if self.reported:
  103. self.report_set.update(report_for=None)
  104. return super(Post, self).delete(*args, **kwargs)
  105. def get_date(self):
  106. return self.date
  107. def quote(self):
  108. quote = []
  109. quote.append('@%s' % self.user_name)
  110. for line in self.post.splitlines():
  111. quote.append('> %s' % line)
  112. quote.append('\r\n')
  113. return '\r\n'.join(quote)
  114. @property
  115. def post_clean(self):
  116. return clear_markdown(self.post_preparsed)
  117. def move_to(self, thread):
  118. move_post.send(sender=self, move_to=thread)
  119. self.thread = thread
  120. self.forum = thread.forum
  121. def merge_with(self, post):
  122. post.post = '%s\n- - -\n%s' % (post.post, self.post)
  123. merge_post.send(sender=self, new_post=post)
  124. def notify_mentioned(self, request, thread_type, users):
  125. from misago.acl.exceptions import ACLError403, ACLError404
  126. mentioned = self.mentions.all()
  127. for slug, user in users.items():
  128. if user.pk != request.user.pk and user not in mentioned:
  129. self.mentions.add(user)
  130. try:
  131. user_acl = user.acl()
  132. user_acl.forums.allow_forum_view(self.forum)
  133. user_acl.threads.allow_thread_view(user, self.thread)
  134. user_acl.threads.allow_post_view(user, self.thread, self)
  135. if not user.is_ignoring(request.user):
  136. alert = user.alert(ugettext_lazy("%(username)s has mentioned you in his reply in thread %(thread)s").message)
  137. alert.profile('username', request.user)
  138. alert.post('thread', thread_type, self.thread, self)
  139. alert.save_all()
  140. except (ACLError403, ACLError404):
  141. pass
  142. def is_reported(self):
  143. self.reported = self.report_set.filter(weight=2).count() > 0
  144. def live_report(self):
  145. try:
  146. return self.report_set.filter(weight=2)[0]
  147. except IndexError:
  148. return None
  149. def add_reporter(self, user):
  150. if not self.reports:
  151. self.reports = ','
  152. self.reports += '%s,' % user.pk
  153. def reported_by(self, user):
  154. if not self.reports:
  155. return False
  156. try:
  157. return ',%s,' % user.pk in self.reports
  158. except AttributeError:
  159. return ',%s,' % user in self.reports
  160. def rename_user_handler(sender, **kwargs):
  161. Post.objects.filter(user=sender).update(
  162. user_name=sender.username,
  163. current_date=timezone.now(),
  164. )
  165. Post.objects.filter(edit_user=sender).update(
  166. edit_user_name=sender.username,
  167. edit_user_slug=sender.username_slug,
  168. )
  169. rename_user.connect(rename_user_handler, dispatch_uid="rename_user_posts")
  170. def delete_user_content_handler(sender, **kwargs):
  171. from misago.models import Thread
  172. threads = []
  173. for post in sender.post_set.distinct().values('thread_id').iterator():
  174. if not post['thread_id'] in threads:
  175. threads.append(post['thread_id'])
  176. sender.post_set.all().delete()
  177. for thread in Thread.objects.filter(id__in=threads).iterator():
  178. thread.sync()
  179. thread.save(force_update=True)
  180. delete_user_content.connect(delete_user_content_handler, dispatch_uid="delete_user_posts")
  181. def move_forum_content_handler(sender, **kwargs):
  182. Post.objects.filter(forum=sender).update(forum=kwargs['move_to'])
  183. move_forum_content.connect(move_forum_content_handler, dispatch_uid="move_forum_posts")
  184. def move_thread_handler(sender, **kwargs):
  185. Post.objects.filter(thread=sender).update(forum=kwargs['move_to'])
  186. move_thread.connect(move_thread_handler, dispatch_uid="move_thread_posts")
  187. def merge_thread_handler(sender, **kwargs):
  188. Post.objects.filter(thread=sender).update(thread=kwargs['new_thread'])
  189. merge_thread.connect(merge_thread_handler, dispatch_uid="merge_threads_posts")
  190. def sync_user_handler(sender, **kwargs):
  191. sender.posts = sender.post_set.count()
  192. sync_user_profile.connect(sync_user_handler, dispatch_uid="sync_user_posts")