jumps.py 16 KB

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