models.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. from django.db import models
  2. from django.db.models import F
  3. from django.utils import timezone
  4. from django.utils.translation import ugettext_lazy as _
  5. from misago.forums.signals import move_forum_content
  6. from misago.threads.signals import move_thread, merge_thread, move_post, merge_post
  7. from misago.users.signals import delete_user_content, rename_user
  8. from misago.utils import slugify, ugettext_lazy
  9. from misago.watcher.models import ThreadWatch
  10. class ThreadManager(models.Manager):
  11. def filter_stats(self, start, end):
  12. return self.filter(start__gte=start).filter(start__lte=end)
  13. class Thread(models.Model):
  14. forum = models.ForeignKey('forums.Forum')
  15. weight = models.PositiveIntegerField(default=0)
  16. type = models.PositiveIntegerField(default=0)
  17. name = models.CharField(max_length=255)
  18. slug = models.SlugField(max_length=255)
  19. replies = models.PositiveIntegerField(default=0)
  20. replies_reported = models.PositiveIntegerField(default=0)
  21. replies_moderated = models.PositiveIntegerField(default=0)
  22. replies_deleted = models.PositiveIntegerField(default=0)
  23. merges = models.PositiveIntegerField(default=0)
  24. score = models.PositiveIntegerField(default=30)
  25. upvotes = models.PositiveIntegerField(default=0)
  26. downvotes = models.PositiveIntegerField(default=0)
  27. start = models.DateTimeField()
  28. start_post = models.ForeignKey('Post', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  29. start_poster = models.ForeignKey('users.User', null=True, blank=True, on_delete=models.SET_NULL)
  30. start_poster_name = models.CharField(max_length=255)
  31. start_poster_slug = models.SlugField(max_length=255)
  32. start_poster_style = models.CharField(max_length=255, null=True, blank=True)
  33. last = models.DateTimeField()
  34. last_post = models.ForeignKey('Post', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  35. last_poster = models.ForeignKey('users.User', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  36. last_poster_name = models.CharField(max_length=255, null=True, blank=True)
  37. last_poster_slug = models.SlugField(max_length=255, null=True, blank=True)
  38. last_poster_style = models.CharField(max_length=255, null=True, blank=True)
  39. moderated = models.BooleanField(default=False)
  40. deleted = models.BooleanField(default=False)
  41. closed = models.BooleanField(default=False)
  42. objects = ThreadManager()
  43. statistics_name = _('New Threads')
  44. def get_date(self):
  45. return self.start
  46. def move_to(self, move_to):
  47. move_thread.send(sender=self, move_to=move_to)
  48. self.forum = move_to
  49. def merge_with(self, thread, merge):
  50. merge_thread.send(sender=self, new_thread=thread, merge=merge)
  51. def sync(self):
  52. # Counters
  53. self.replies = self.post_set.filter(moderated=False).count() - 1
  54. if self.replies < 0:
  55. self.replies = 0
  56. self.replies_reported = self.post_set.filter(reported=True).count()
  57. self.replies_moderated = self.post_set.filter(moderated=True).count()
  58. self.replies_deleted = self.post_set.filter(deleted=True).count()
  59. # First post
  60. start_post = self.post_set.order_by('merge', 'id')[0:][0]
  61. self.start = start_post.date
  62. self.start_post = start_post
  63. self.start_poster = start_post.user
  64. self.start_poster_name = start_post.user_name
  65. self.start_poster_slug = slugify(start_post.user_name)
  66. self.start_poster_style = start_post.user.rank.style if start_post.user else ''
  67. self.upvotes = start_post.upvotes
  68. self.downvotes = start_post.downvotes
  69. # Last visible post
  70. if self.replies > 0:
  71. last_post = self.post_set.order_by('-merge', '-id').filter(moderated=False)[0:][0]
  72. else:
  73. last_post = start_post
  74. self.last = last_post.date
  75. self.last_post = last_post
  76. self.last_poster = last_post.user
  77. self.last_poster_name = last_post.user_name
  78. self.last_poster_slug = slugify(last_post.user_name)
  79. self.last_poster_style = last_post.user.rank.style if last_post.user else ''
  80. # Flags
  81. self.moderated = start_post.moderated
  82. self.deleted = start_post.deleted
  83. self.merges = last_post.merge
  84. def email_watchers(self, request, post):
  85. from misago.acl.builder import get_acl
  86. from misago.acl.utils import ACLError403, ACLError404
  87. for watch in ThreadWatch.objects.filter(thread=self).filter(email=True).filter(last_read__gte=self.previous_last):
  88. user = watch.user
  89. if user.pk != request.user.pk:
  90. try:
  91. acl = get_acl(request, user)
  92. acl.forums.allow_forum_view(self.forum)
  93. acl.threads.allow_thread_view(user, self)
  94. acl.threads.allow_post_view(user, self, post)
  95. if not user.is_ignoring(request.user):
  96. user.email_user(
  97. request,
  98. 'post_notification',
  99. _('New reply in thread "%(thread)s"') % {'thread': self.name},
  100. {'author': request.user, 'post': post, 'thread': self}
  101. )
  102. except (ACLError403, ACLError404):
  103. pass
  104. class PostManager(models.Manager):
  105. def filter_stats(self, start, end):
  106. return self.filter(date__gte=start).filter(date__lte=end)
  107. class Post(models.Model):
  108. forum = models.ForeignKey('forums.Forum')
  109. thread = models.ForeignKey(Thread)
  110. merge = models.PositiveIntegerField(default=0)
  111. user = models.ForeignKey('users.User', null=True, blank=True, on_delete=models.SET_NULL)
  112. user_name = models.CharField(max_length=255)
  113. ip = models.GenericIPAddressField()
  114. agent = models.CharField(max_length=255)
  115. post = models.TextField()
  116. post_preparsed = models.TextField()
  117. upvotes = models.PositiveIntegerField(default=0)
  118. downvotes = models.PositiveIntegerField(default=0)
  119. mentions = models.ManyToManyField('users.User', related_name="mention_set")
  120. checkpoints = models.BooleanField(default=False)
  121. date = models.DateTimeField()
  122. edits = models.PositiveIntegerField(default=0)
  123. edit_date = models.DateTimeField(null=True, blank=True)
  124. edit_reason = models.CharField(max_length=255, null=True, blank=True)
  125. edit_user = models.ForeignKey('users.User', related_name='+', null=True, blank=True, on_delete=models.SET_NULL)
  126. edit_user_name = models.CharField(max_length=255, null=True, blank=True)
  127. edit_user_slug = models.SlugField(max_length=255, null=True, blank=True)
  128. reported = models.BooleanField(default=False)
  129. moderated = models.BooleanField(default=False)
  130. deleted = models.BooleanField(default=False)
  131. protected = models.BooleanField(default=False)
  132. objects = PostManager()
  133. statistics_name = _('New Posts')
  134. def get_date(self):
  135. return self.date
  136. def move_to(self, thread):
  137. move_post.send(sender=self, move_to=thread)
  138. self.thread = thread
  139. self.forum = thread.forum
  140. def merge_with(self, post):
  141. post.post = '%s\n- - -\n%s' % (post.post, self.post)
  142. merge_post.send(sender=self, new_post=post)
  143. def set_checkpoint(self, request, action):
  144. if request.user.is_authenticated():
  145. self.checkpoints = True
  146. self.checkpoint_set.create(
  147. forum=self.forum,
  148. thread=self.thread,
  149. post=self,
  150. action=action,
  151. user=request.user,
  152. user_name=request.user.username,
  153. user_slug=request.user.username_slug,
  154. date=timezone.now(),
  155. ip=request.session.get_ip(request),
  156. agent=request.META.get('HTTP_USER_AGENT'),
  157. )
  158. def notify_mentioned(self, request, users):
  159. from misago.acl.builder import get_acl
  160. from misago.acl.utils import ACLError403, ACLError404
  161. mentioned = self.mentions.all()
  162. for slug, user in users.items():
  163. if user.pk != request.user.pk and user not in mentioned:
  164. self.mentions.add(user)
  165. try:
  166. acl = get_acl(request, user)
  167. acl.forums.allow_forum_view(self.forum)
  168. acl.threads.allow_thread_view(user, self.thread)
  169. acl.threads.allow_post_view(user, self.thread, self)
  170. if not user.is_ignoring(request.user):
  171. alert = user.alert(ugettext_lazy("%(username)s has mentioned you in his reply in thread %(thread)s").message)
  172. alert.profile('username', request.user)
  173. alert.post('thread', self.thread, self)
  174. alert.save_all()
  175. except (ACLError403, ACLError404):
  176. pass
  177. class Karma(models.Model):
  178. forum = models.ForeignKey('forums.Forum')
  179. thread = models.ForeignKey(Thread)
  180. post = models.ForeignKey(Post)
  181. user = models.ForeignKey('users.User', null=True, blank=True, on_delete=models.SET_NULL)
  182. user_name = models.CharField(max_length=255)
  183. user_slug = models.CharField(max_length=255)
  184. date = models.DateTimeField()
  185. ip = models.GenericIPAddressField()
  186. agent = models.CharField(max_length=255)
  187. score = models.IntegerField(default=0)
  188. class Change(models.Model):
  189. forum = models.ForeignKey('forums.Forum')
  190. thread = models.ForeignKey(Thread)
  191. post = models.ForeignKey(Post)
  192. user = models.ForeignKey('users.User', null=True, blank=True, on_delete=models.SET_NULL)
  193. user_name = models.CharField(max_length=255)
  194. user_slug = models.CharField(max_length=255)
  195. date = models.DateTimeField()
  196. ip = models.GenericIPAddressField()
  197. agent = models.CharField(max_length=255)
  198. reason = models.CharField(max_length=255, null=True, blank=True)
  199. thread_name_new = models.CharField(max_length=255, null=True, blank=True)
  200. thread_name_old = models.CharField(max_length=255, null=True, blank=True)
  201. post_content = models.TextField()
  202. size = models.IntegerField(default=0)
  203. change = models.IntegerField(default=0)
  204. class Checkpoint(models.Model):
  205. forum = models.ForeignKey('forums.Forum')
  206. thread = models.ForeignKey(Thread)
  207. post = models.ForeignKey(Post)
  208. action = models.CharField(max_length=255)
  209. user = models.ForeignKey('users.User', null=True, blank=True, on_delete=models.SET_NULL)
  210. user_name = models.CharField(max_length=255)
  211. user_slug = models.CharField(max_length=255)
  212. date = models.DateTimeField()
  213. ip = models.GenericIPAddressField()
  214. agent = models.CharField(max_length=255)
  215. """
  216. Signals
  217. """
  218. def rename_user_handler(sender, **kwargs):
  219. Thread.objects.filter(start_poster=sender).update(
  220. start_poster_name=sender.username,
  221. start_poster_slug=sender.username_slug,
  222. )
  223. Thread.objects.filter(last_poster=sender).update(
  224. last_poster_name=sender.username,
  225. last_poster_slug=sender.username_slug,
  226. )
  227. Post.objects.filter(user=sender).update(
  228. user_name=sender.username,
  229. )
  230. Post.objects.filter(edit_user=sender).update(
  231. edit_user_name=sender.username,
  232. edit_user_slug=sender.username_slug,
  233. )
  234. Karma.objects.filter(user=sender).update(
  235. user_name=sender.username,
  236. user_slug=sender.username_slug,
  237. )
  238. Change.objects.filter(user=sender).update(
  239. user_name=sender.username,
  240. user_slug=sender.username_slug,
  241. )
  242. Checkpoint.objects.filter(user=sender).update(
  243. user_name=sender.username,
  244. user_slug=sender.username_slug,
  245. )
  246. rename_user.connect(rename_user_handler, dispatch_uid="rename_user_threads")
  247. def delete_user_content_handler(sender, **kwargs):
  248. for thread in Thread.objects.filter(start_poster=sender):
  249. thread.delete()
  250. threads = []
  251. prev_posts = []
  252. for post in sender.post_set.filter(checkpoints=True):
  253. threads.append(post.thread_id)
  254. prev_post = Post.objects.filter(thread=post.thread_id).exclude(merge__gt=post.merge).exclude(user=sender).order_by('merge', '-id')[:1][0]
  255. post.checkpoint_set.update(post=prev_post)
  256. if not prev_post.pk in prev_posts:
  257. prev_posts.append(prev_post.pk)
  258. sender.post_set.all().delete()
  259. Post.objects.filter(id__in=prev_posts).update(checkpoints=True)
  260. for post in sender.post_set.distinct().values('thread_id').iterator():
  261. if not post['thread_id'] in threads:
  262. threads.append(post['thread_id'])
  263. for post in Post.objects.filter(user=sender):
  264. post.delete()
  265. for thread in Thread.objects.filter(id__in=threads):
  266. thread.sync()
  267. thread.save(force_update=True)
  268. delete_user_content.connect(delete_user_content_handler, dispatch_uid="delete_user_threads_posts")
  269. def move_forum_content_handler(sender, **kwargs):
  270. Thread.objects.filter(forum=sender).update(forum=kwargs['move_to'])
  271. Post.objects.filter(forum=sender).update(forum=kwargs['move_to'])
  272. Karma.objects.filter(forum=sender).update(forum=kwargs['move_to'])
  273. Change.objects.filter(forum=sender).update(forum=kwargs['move_to'])
  274. Checkpoint.objects.filter(forum=sender).update(forum=kwargs['move_to'])
  275. move_forum_content.connect(move_forum_content_handler, dispatch_uid="move_forum_threads_posts")
  276. def move_thread_handler(sender, **kwargs):
  277. Post.objects.filter(forum=sender.forum_pk).update(forum=kwargs['move_to'])
  278. Karma.objects.filter(forum=sender.forum_pk).update(forum=kwargs['move_to'])
  279. Change.objects.filter(forum=sender.forum_pk).update(forum=kwargs['move_to'])
  280. Checkpoint.objects.filter(forum=sender.forum_pk).update(forum=kwargs['move_to'])
  281. move_thread.connect(move_thread_handler, dispatch_uid="move_thread")
  282. def merge_thread_handler(sender, **kwargs):
  283. Post.objects.filter(thread=sender).update(thread=kwargs['new_thread'], merge=F('merge') + kwargs['merge'])
  284. Karma.objects.filter(thread=sender).update(thread=kwargs['new_thread'])
  285. Change.objects.filter(thread=sender).update(thread=kwargs['new_thread'])
  286. Checkpoint.objects.filter(thread=sender).delete()
  287. merge_thread.connect(merge_thread_handler, dispatch_uid="merge_threads")
  288. def move_posts_handler(sender, **kwargs):
  289. Change.objects.filter(post=sender).update(forum=kwargs['move_to'].forum, thread=kwargs['move_to'])
  290. Karma.objects.filter(post=sender).update(forum=kwargs['move_to'].forum, thread=kwargs['move_to'])
  291. if sender.checkpoints:
  292. prev_post = Post.objects.filter(thread=sender.thread_id).filter(merge__lte=sender.merge).exclude(id=sender.pk).order_by('merge', '-id')[:1][0]
  293. Checkpoint.objects.filter(post=sender).update(post=prev_post)
  294. prev_post.checkpoints = True
  295. prev_post.save(force_update=True)
  296. sender.checkpoints = False
  297. move_post.connect(move_posts_handler, dispatch_uid="move_posts")
  298. def merge_posts_handler(sender, **kwargs):
  299. Change.objects.filter(post=sender).update(post=kwargs['new_post'])
  300. Checkpoint.objects.filter(post=sender).update(post=kwargs['new_post'])
  301. Karma.objects.filter(post=sender).update(post=kwargs['new_post'])
  302. if sender.checkpoints:
  303. kwargs['new_post'].checkpoints = True
  304. kwargs['new_post'].upvotes += self.upvotes
  305. kwargs['new_post'].downvotes += self.downvotes
  306. kwargs['new_post'].score += self.score
  307. merge_post.connect(merge_posts_handler, dispatch_uid="merge_posts")