jumps.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. from django.core.urlresolvers import reverse
  2. from django.shortcuts import redirect
  3. from django.utils import timezone
  4. from django.utils.translation import ugettext as _
  5. from misago.acl.exceptions import ACLError403, ACLError404
  6. from misago.apps.errors import error403, error404
  7. from misago.conf import settings
  8. from misago.decorators import block_guest, check_csrf
  9. from misago.markdown import post_markdown
  10. from misago.messages import Message
  11. from misago.models import Forum, Checkpoint, Thread, Post, Karma, WatchedThread
  12. from misago.monitor import monitor, UpdatingMonitor
  13. from misago.readstrackers import ThreadsTracker
  14. from misago.utils.strings import short_string, slugify
  15. from misago.utils.views import json_response
  16. from misago.apps.threadtype.base import ViewBase
  17. class JumpView(ViewBase):
  18. def fetch_thread(self, thread):
  19. self.thread = Thread.objects.get(pk=thread)
  20. self.forum = self.thread.forum
  21. self.request.acl.forums.allow_forum_view(self.forum)
  22. self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
  23. def fetch_post(self, post):
  24. self.post = self.thread.post_set.get(pk=post)
  25. self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
  26. def make_jump(self):
  27. raise NotImplementedError('JumpView cannot be called directly.')
  28. def __call__(self, request, slug=None, thread=None, post=None):
  29. self.request = request
  30. self.parents = []
  31. try:
  32. self._type_available()
  33. self.fetch_thread(thread)
  34. if self.forum.level:
  35. self.parents = Forum.objects.forum_parents(self.forum.pk, True)
  36. self.check_forum_type()
  37. self._check_permissions()
  38. if post:
  39. self.fetch_post(post)
  40. return self.make_jump()
  41. except (Thread.DoesNotExist, Post.DoesNotExist):
  42. return error404(self.request)
  43. except ACLError403 as e:
  44. return error403(request, e)
  45. except ACLError404 as e:
  46. return error404(request, e)
  47. class LastReplyBaseView(JumpView):
  48. def make_jump(self):
  49. return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
  50. class FindReplyBaseView(JumpView):
  51. def make_jump(self):
  52. return self.redirect_to_post(self.post)
  53. class NewReplyBaseView(JumpView):
  54. def make_jump(self):
  55. if not self.request.user.is_authenticated():
  56. return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
  57. tracker = ThreadsTracker(self.request, self.forum)
  58. read_date = tracker.read_date(self.thread)
  59. post = self.thread.post_set.filter(date__gt=read_date).order_by('id')[:1]
  60. if not post:
  61. return self.redirect_to_post(self.thread.post_set.order_by('-id')[:1][0])
  62. return self.redirect_to_post(post[0])
  63. class FirstModeratedBaseView(JumpView):
  64. def make_jump(self):
  65. if not self.request.acl.threads.can_approve(self.forum):
  66. raise ACLError404()
  67. try:
  68. return self.redirect_to_post(
  69. self.thread.post_set.get(moderated=True))
  70. except Post.DoesNotExist:
  71. return error404(self.request)
  72. class FirstReportedBaseView(JumpView):
  73. def make_jump(self):
  74. if not self.request.acl.threads.can_mod_posts(self.forum):
  75. raise ACLError404()
  76. try:
  77. return self.redirect_to_post(
  78. self.thread.post_set.get(reported=True))
  79. except Post.DoesNotExist:
  80. return error404(self.request)
  81. class ShowHiddenRepliesBaseView(JumpView):
  82. def make_jump(self):
  83. @block_guest
  84. @check_csrf
  85. def view(request):
  86. ignored_exclusions = request.session.get('unignore_threads', [])
  87. ignored_exclusions.append(self.thread.pk)
  88. request.session['unignore_threads'] = ignored_exclusions
  89. request.messages.set_flash(Message(_('Replies made to this thread by members on your ignore list have been revealed.')), 'success', 'threads')
  90. return redirect(reverse(self.type_prefix, kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}))
  91. return view(self.request)
  92. class WatchThreadBaseView(JumpView):
  93. def get_retreat(self):
  94. return redirect(self.request.POST.get('retreat', reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug})))
  95. def update_watcher(self, request, watcher):
  96. request.messages.set_flash(Message(_('This thread has been added to your watched threads list.')), 'success', 'threads')
  97. def make_jump(self):
  98. @block_guest
  99. @check_csrf
  100. def view(request):
  101. try:
  102. watcher = WatchedThread.objects.get(user=request.user, thread=self.thread)
  103. except WatchedThread.DoesNotExist:
  104. watcher = WatchedThread()
  105. watcher.user = request.user
  106. watcher.forum = self.forum
  107. watcher.thread = self.thread
  108. watcher.starter_id = self.thread.start_poster_id
  109. watcher.last_read = timezone.now()
  110. self.update_watcher(request, watcher)
  111. if watcher.pk:
  112. watcher.save(force_update=True)
  113. else:
  114. watcher.save(force_insert=True)
  115. return self.get_retreat()
  116. return view(self.request)
  117. class WatchEmailThreadBaseView(WatchThreadBaseView):
  118. def update_watcher(self, request, watcher):
  119. watcher.email = True
  120. if watcher.pk:
  121. request.messages.set_flash(Message(_('You will now receive e-mail with notification when somebody replies to this thread.')), 'success', 'threads')
  122. else:
  123. request.messages.set_flash(Message(_('This thread has been added to your watched threads list. You will also receive e-mail with notification when somebody replies to it.')), 'success', 'threads')
  124. class UnwatchThreadBaseView(WatchThreadBaseView):
  125. def update_watcher(self, request, watcher):
  126. watcher.deleted = True
  127. watcher.delete()
  128. if watcher.email:
  129. request.messages.set_flash(Message(_('This thread has been removed from your watched threads list. You will no longer receive e-mails with notifications when somebody replies to it.')), 'success', 'threads')
  130. else:
  131. request.messages.set_flash(Message(_('This thread has been removed from your watched threads list.')), 'success', 'threads')
  132. class UnwatchEmailThreadBaseView(WatchThreadBaseView):
  133. def update_watcher(self, request, watcher):
  134. watcher.email = False
  135. request.messages.set_flash(Message(_('You will no longer receive e-mails with notifications when somebody replies to this thread.')), 'success', 'threads')
  136. class UpvotePostBaseView(JumpView):
  137. def make_jump(self):
  138. @block_guest
  139. @check_csrf
  140. def view(request):
  141. if self.post.user_id == request.user.id:
  142. return error404(request)
  143. self.check_acl(request)
  144. try:
  145. vote = Karma.objects.get(user=request.user, post=self.post)
  146. if self.thread.start_post_id == self.post.pk:
  147. if vote.score > 0:
  148. self.thread.upvotes -= 1
  149. else:
  150. self.thread.downvotes -= 1
  151. if vote.score > 0:
  152. self.post.upvotes -= 1
  153. request.user.karma_given_p -= 1
  154. if self.post.user_id:
  155. self.post.user.karma_p -= 1
  156. else:
  157. self.post.downvotes -= 1
  158. request.user.karma_given_n -= 1
  159. if self.post.user_id:
  160. self.post.user.karma_n -= 1
  161. except Karma.DoesNotExist:
  162. vote = Karma()
  163. vote.forum = self.forum
  164. vote.thread = self.thread
  165. vote.post = self.post
  166. vote.user = request.user
  167. vote.user_name = request.user.username
  168. vote.user_slug = request.user.username_slug
  169. vote.date = timezone.now()
  170. vote.ip = request.session.get_ip(request)
  171. vote.agent = request.META.get('HTTP_USER_AGENT')
  172. self.make_vote(request, vote)
  173. if vote.pk:
  174. vote.save(force_update=True)
  175. else:
  176. vote.save(force_insert=True)
  177. if self.thread.start_post_id == self.post.pk:
  178. if vote.score > 0:
  179. self.thread.upvotes += 1
  180. else:
  181. self.thread.downvotes += 1
  182. self.thread.save(force_update=True)
  183. if vote.score > 0:
  184. self.post.upvotes += 1
  185. request.user.karma_given_p += 1
  186. if self.post.user_id:
  187. self.post.user.karma_p += 1
  188. self.post.user.score += settings.score_reward_karma_positive
  189. else:
  190. self.post.downvotes += 1
  191. request.user.karma_given_n += 1
  192. if self.post.user_id:
  193. self.post.user.karma_n += 1
  194. self.post.user.score -= settings.score_reward_karma_negative
  195. self.post.save(force_update=True)
  196. request.user.save(force_update=True)
  197. if self.post.user_id:
  198. self.post.user.save(force_update=True)
  199. if request.is_ajax():
  200. return json_response(request, {
  201. 'score_total': self.post.upvotes - self.post.downvotes,
  202. 'score_upvotes': self.post.upvotes,
  203. 'score_downvotes': self.post.downvotes,
  204. 'user_vote': vote.score,
  205. })
  206. request.messages.set_flash(Message(_('Your vote has been saved.')), 'success', 'threads_%s' % self.post.pk)
  207. return self.redirect_to_post(self.post)
  208. return view(self.request)
  209. def check_acl(self, request):
  210. request.acl.threads.allow_post_upvote(self.forum)
  211. def make_vote(self, request, vote):
  212. vote.score = 1
  213. class DownvotePostBaseView(UpvotePostBaseView):
  214. def check_acl(self, request):
  215. request.acl.threads.allow_post_downvote(self.forum)
  216. def make_vote(self, request, vote):
  217. vote.score = -1
  218. class ReportPostBaseView(JumpView):
  219. def make_jump(self):
  220. self.request.acl.reports.allow_report()
  221. @block_guest
  222. @check_csrf
  223. def view(request):
  224. report = None
  225. made_report = False
  226. if self.post.reported:
  227. report = self.post.live_report()
  228. if report and report.start_poster_id != request.user.pk:
  229. # Append our q.q to existing report?
  230. try:
  231. report.checkpoint_set.get(user=request.user, action="reported")
  232. except Checkpoint.DoesNotExist:
  233. report.set_checkpoint(self.request, 'reported', user)
  234. made_report = True
  235. if not report:
  236. # File up new report
  237. now = timezone.now()
  238. reason_post = _('''
  239. Member @%(reporter)s has reported following post by @%(reported)s:
  240. %(quote)s
  241. **Post link:** <%(post)s>
  242. ''')
  243. reason_post = reason_post.strip() % {
  244. 'reporter': request.user.username,
  245. 'reported': self.post.user_name,
  246. 'post': settings.BOARD_ADDRESS + self.redirect_to_post(self.post)['Location'],
  247. 'quote': self.post.quote(),
  248. }
  249. md, reason_post_preparsed = post_markdown(reason_post)
  250. reports = Forum.objects.special_model('reports')
  251. report = Thread.objects.create(
  252. forum=reports,
  253. weight=2,
  254. name=self.thread.name,
  255. slug=slugify(self.thread.slug),
  256. start=now,
  257. start_poster=request.user,
  258. start_poster_name=request.user.username,
  259. start_poster_slug=request.user.username_slug,
  260. start_poster_style=request.user.rank.style,
  261. last=now,
  262. last_poster=request.user,
  263. last_poster_name=request.user.username,
  264. last_poster_slug=request.user.username_slug,
  265. last_poster_style=request.user.rank.style,
  266. report_for=self.post,
  267. )
  268. reason = Post.objects.create(
  269. forum=reports,
  270. thread=report,
  271. user=request.user,
  272. user_name=request.user.username,
  273. ip=request.session.get_ip(self.request),
  274. agent=request.META.get('HTTP_USER_AGENT'),
  275. post=reason_post,
  276. post_preparsed=reason_post_preparsed,
  277. date=now,
  278. )
  279. report.start_post = reason
  280. report.last_post = reason
  281. report.save(force_update=True)
  282. for m in self.post.mentions.all():
  283. reason.mentions.add(m)
  284. self.post.reported = True
  285. self.post.save(force_update=True)
  286. self.thread.replies_reported += 1
  287. self.thread.save(force_update=True)
  288. with UpdatingMonitor() as cm:
  289. monitor.increase('reported_posts')
  290. made_report = True
  291. if made_report:
  292. if request.is_ajax():
  293. return json_response(request, message=_("Selected post has been reported and will receive moderator attention. Thank you."))
  294. self.request.messages.set_flash(Message(_("Selected post has been reported and will receive moderator attention. Thank you.")), 'info', 'threads_%s' % self.post.pk)
  295. else:
  296. if request.is_ajax():
  297. return json_response(request, message=_("You have already reported this post. One of moderators will handle it as soon as it is possible. Thank you for your patience."))
  298. self.request.messages.set_flash(Message(_("You have already reported this post. One of moderators will handle it as soon as it is possible. Thank you for your patience.")), 'info', 'threads_%s' % self.post.pk)
  299. return self.redirect_to_post(self.post)
  300. return view(self.request)
  301. class ShowPostReportBaseView(JumpView):
  302. def make_jump(self):
  303. self.request.acl.reports.allow_report()
  304. @block_guest
  305. def view(request):
  306. if not self.post.reported:
  307. return error404(request)
  308. reports = Forum.objects.special_model('reports')
  309. self.request.acl.forums.allow_forum_view(reports)
  310. report = self.post.live_report()
  311. if not report:
  312. return error404(request)
  313. return redirect(reverse('report', kwargs={'thread': report.pk, 'slug': report.slug}))
  314. return view(self.request)