thread.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. from django.core.urlresolvers import reverse
  2. from django import forms
  3. from django.forms import ValidationError
  4. from django.shortcuts import redirect
  5. from django.template import RequestContext
  6. from django.utils.translation import ugettext as _
  7. from misago.acl.utils import ACLError403, ACLError404
  8. from misago.forms import Form, FormLayout, FormFields
  9. from misago.forums.models import Forum
  10. from misago.messages import Message
  11. from misago.readstracker.trackers import ThreadsTracker
  12. from misago.threads.forms import MoveThreadsForm, QuickReplyForm
  13. from misago.threads.models import Thread, Post
  14. from misago.threads.views.base import BaseView
  15. from misago.views import error403, error404
  16. from misago.utils import make_pagination
  17. class ThreadView(BaseView):
  18. def fetch_thread(self, thread):
  19. self.thread = Thread.objects.get(pk=thread)
  20. self.forum = self.thread.forum
  21. self.proxy = Forum.objects.parents_aware_forum(self.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. self.parents = Forum.objects.forum_parents(self.forum.pk, True)
  25. self.tracker = ThreadsTracker(self.request.user, self.forum)
  26. def fetch_posts(self, page):
  27. self.count = self.request.acl.threads.filter_posts(self.request, self.thread, Post.objects.filter(thread=self.thread)).count()
  28. self.posts = self.request.acl.threads.filter_posts(self.request, self.thread, Post.objects.filter(thread=self.thread)).prefetch_related('checkpoint_set', 'user', 'user__rank')
  29. if self.thread.merges > 0:
  30. self.posts = self.posts.order_by('merge', 'pk')
  31. else:
  32. self.posts = self.posts.order_by('pk')
  33. self.pagination = make_pagination(page, self.count, self.request.settings.posts_per_page)
  34. if self.request.settings.posts_per_page < self.count:
  35. self.posts = self.posts[self.pagination['start']:self.pagination['stop']]
  36. self.read_date = self.tracker.get_read_date(self.thread)
  37. for post in self.posts:
  38. post.message = self.request.messages.get_message('threads_%s' % post.pk)
  39. post.is_read = post.date <= self.read_date
  40. last_post = self.posts[len(self.posts) - 1]
  41. if not self.tracker.is_read(self.thread):
  42. self.tracker.set_read(self.thread, last_post)
  43. self.tracker.sync()
  44. def get_thread_actions(self):
  45. acl = self.request.acl.threads.get_role(self.thread.forum_id)
  46. actions = []
  47. try:
  48. if acl['can_approve'] and self.thread.moderated:
  49. actions.append(('accept', _('Accept this thread')))
  50. if acl['can_pin_threads'] == 2 and self.thread.weight < 2:
  51. actions.append(('annouce', _('Change this thread to annoucement')))
  52. if acl['can_pin_threads'] > 0 and self.thread.weight != 1:
  53. actions.append(('sticky', _('Change this thread to sticky')))
  54. if acl['can_pin_threads'] > 0:
  55. if self.thread.weight == 2:
  56. actions.append(('normal', _('Change this thread to normal')))
  57. if self.thread.weight == 1:
  58. actions.append(('normal', _('Unpin this thread')))
  59. if acl['can_move_threads_posts']:
  60. actions.append(('move', _('Move this thread')))
  61. if acl['can_close_threads']:
  62. if self.thread.closed:
  63. actions.append(('open', _('Open this thread')))
  64. else:
  65. actions.append(('close', _('Close this thread')))
  66. if acl['can_delete_threads']:
  67. if self.thread.deleted:
  68. actions.append(('undelete', _('Undelete this thread')))
  69. else:
  70. actions.append(('soft', _('Soft delete this thread')))
  71. if acl['can_delete_threads'] == 2:
  72. actions.append(('hard', _('Hard delete this thread')))
  73. except KeyError:
  74. pass
  75. return actions
  76. def make_thread_form(self):
  77. self.thread_form = None
  78. list_choices = self.get_thread_actions();
  79. if (not self.request.user.is_authenticated()
  80. or not list_choices):
  81. return
  82. form_fields = {'thread_action': forms.ChoiceField(choices=list_choices)}
  83. self.thread_form = type('ThreadViewForm', (Form,), form_fields)
  84. def handle_thread_form(self):
  85. if self.request.method == 'POST' and self.request.POST.get('origin') == 'thread_form':
  86. self.thread_form = self.thread_form(self.request.POST, request=self.request)
  87. if self.thread_form.is_valid():
  88. form_action = getattr(self, 'thread_action_' + self.thread_form.cleaned_data['thread_action'])
  89. try:
  90. response = form_action()
  91. if response:
  92. return response
  93. return redirect(self.request.path)
  94. except forms.ValidationError as e:
  95. self.message = Message(e.messages[0], 'error')
  96. else:
  97. if 'thread_action' in self.thread_form.errors:
  98. self.message = Message(_("Action requested is incorrect."), 'error')
  99. else:
  100. self.message = Message(form.non_field_errors()[0], 'error')
  101. else:
  102. self.thread_form = self.thread_form(request=self.request)
  103. def thread_action_accept(self):
  104. # Sync thread and post
  105. self.thread.moderated = False
  106. self.thread.replies_moderated -= 1
  107. self.thread.save(force_update=True)
  108. self.thread.start_post.moderated = False
  109. self.thread.start_post.save(force_update=True)
  110. self.thread.last_post.set_checkpoint(self.request, 'accepted')
  111. # Sync user
  112. if self.thread.last_post.user:
  113. self.thread.start_post.user.threads += 1
  114. self.thread.start_post.user.posts += 1
  115. self.thread.start_post.user.save(force_update=True)
  116. # Sync forum
  117. self.forum.threads_delta += 1
  118. self.forum.posts_delta += self.thread.replies + 1
  119. self.forum.sync()
  120. self.forum.save(force_update=True)
  121. # Update monitor
  122. self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
  123. self.request.monitor['posts'] = int(self.request.monitor['posts']) + self.thread.replies + 1
  124. self.request.messages.set_flash(Message(_('Thread has been marked as reviewed and made visible to other members.')), 'success', 'threads')
  125. def thread_action_annouce(self):
  126. self.thread.weight = 2
  127. self.thread.save(force_update=True)
  128. self.request.messages.set_flash(Message(_('Thread has been turned into annoucement.')), 'success', 'threads')
  129. def thread_action_sticky(self):
  130. self.thread.weight = 1
  131. self.thread.save(force_update=True)
  132. self.request.messages.set_flash(Message(_('Thread has been turned into sticky.')), 'success', 'threads')
  133. def thread_action_normal(self):
  134. self.thread.weight = 0
  135. self.thread.save(force_update=True)
  136. self.request.messages.set_flash(Message(_('Thread weight has been changed to normal.')), 'success', 'threads')
  137. def thread_action_move(self):
  138. message = None
  139. if self.request.POST.get('do') == 'move':
  140. form = MoveThreadsForm(self.request.POST,request=self.request,forum=self.forum)
  141. if form.is_valid():
  142. new_forum = form.cleaned_data['new_forum']
  143. self.thread.forum = new_forum
  144. self.thread.post_set.update(forum=new_forum)
  145. self.thread.change_set.update(forum=new_forum)
  146. self.thread.checkpoint_set.update(forum=new_forum)
  147. self.thread.save(force_update=True)
  148. self.forum.sync()
  149. self.forum.save(force_update=True)
  150. self.request.messages.set_flash(Message(_('Thread has been moved to "%(forum)s".') % {'forum': new_forum.name}), 'success', 'threads')
  151. return None
  152. message = Message(form.non_field_errors()[0], 'error')
  153. else:
  154. form = MoveThreadsForm(request=self.request,forum=self.forum)
  155. return self.request.theme.render_to_response('threads/move.html',
  156. {
  157. 'message': message,
  158. 'forum': self.forum,
  159. 'parents': self.parents,
  160. 'thread': self.thread,
  161. 'form': FormLayout(form),
  162. },
  163. context_instance=RequestContext(self.request));
  164. def thread_action_open(self):
  165. self.thread.closed = False
  166. self.thread.save(force_update=True)
  167. self.thread.last_post.set_checkpoint(self.request, 'opened')
  168. self.request.messages.set_flash(Message(_('Thread has been opened.')), 'success', 'threads')
  169. def thread_action_close(self):
  170. self.thread.closed = True
  171. self.thread.save(force_update=True)
  172. self.thread.last_post.set_checkpoint(self.request, 'closed')
  173. self.request.messages.set_flash(Message(_('Thread has been closed.')), 'success', 'threads')
  174. def thread_action_undelete(self):
  175. # Update thread
  176. self.thread.deleted = False
  177. self.thread.replies_deleted -= 1
  178. self.thread.save(force_update=True)
  179. # Update first post in thread
  180. self.thread.start_post.deleted = False
  181. self.thread.start_post.save(force_update=True)
  182. # Set checkpoint
  183. self.thread.last_post.set_checkpoint(self.request, 'undeleted')
  184. # Update forum
  185. self.forum.sync()
  186. self.forum.save(force_update=True)
  187. # Update monitor
  188. self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
  189. self.request.monitor['posts'] = int(self.request.monitor['posts']) + self.thread.replies + 1
  190. self.request.messages.set_flash(Message(_('Thread has been undeleted.')), 'success', 'threads')
  191. def thread_action_soft(self):
  192. # Update thread
  193. self.thread.deleted = True
  194. self.thread.replies_deleted += 1
  195. self.thread.save(force_update=True)
  196. # Update first post in thread
  197. self.thread.start_post.deleted = True
  198. self.thread.start_post.save(force_update=True)
  199. # Set checkpoint
  200. self.thread.last_post.set_checkpoint(self.request, 'deleted')
  201. # Update forum
  202. self.forum.sync()
  203. self.forum.save(force_update=True)
  204. # Update monitor
  205. self.request.monitor['threads'] = int(self.request.monitor['threads']) - 1
  206. self.request.monitor['posts'] = int(self.request.monitor['posts']) - self.thread.replies - 1
  207. self.request.messages.set_flash(Message(_('Thread has been deleted.')), 'success', 'threads')
  208. def thread_action_hard(self):
  209. # Delete thread
  210. self.thread.delete()
  211. # Update forum
  212. self.forum.sync()
  213. self.forum.save(force_update=True)
  214. # Update monitor
  215. self.request.monitor['threads'] = int(self.request.monitor['threads']) - 1
  216. self.request.monitor['posts'] = int(self.request.monitor['posts']) - self.thread.replies - 1
  217. self.request.messages.set_flash(Message(_('Thread "%(thread)s" has been deleted.') % {'thread': self.thread.name}), 'success', 'threads')
  218. return redirect(reverse('forum', kwargs={'forum': self.forum.pk, 'slug': self.forum.slug}))
  219. def __call__(self, request, slug=None, thread=None, page=0):
  220. self.request = request
  221. self.pagination = None
  222. self.parents = None
  223. try:
  224. self.fetch_thread(thread)
  225. self.fetch_posts(page)
  226. self.make_thread_form()
  227. if self.thread_form:
  228. response = self.handle_thread_form()
  229. if response:
  230. return response
  231. except Thread.DoesNotExist:
  232. return error404(self.request)
  233. except ACLError403 as e:
  234. return error403(request, e.message)
  235. except ACLError404 as e:
  236. return error404(request, e.message)
  237. # Merge proxy into forum
  238. self.forum.closed = self.proxy.closed
  239. return request.theme.render_to_response('threads/thread.html',
  240. {
  241. 'message': request.messages.get_message('threads'),
  242. 'forum': self.forum,
  243. 'parents': self.parents,
  244. 'thread': self.thread,
  245. 'is_read': self.tracker.is_read(self.thread),
  246. 'count': self.count,
  247. 'posts': self.posts,
  248. 'pagination': self.pagination,
  249. 'quick_reply': FormFields(QuickReplyForm(request=request)).fields,
  250. 'thread_form': FormFields(self.thread_form).fields if self.thread_form else None,
  251. },
  252. context_instance=RequestContext(request));