posting.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. from django.core.urlresolvers import reverse
  2. from django.shortcuts import redirect
  3. from django.template import RequestContext
  4. from django.utils import timezone
  5. from django.utils.translation import ugettext as _
  6. from misago.acl.utils import ACLError403, ACLError404
  7. from misago.forms import FormLayout
  8. from misago.forums.models import Forum
  9. from misago.markdown import post_markdown
  10. from misago.messages import Message
  11. from misago.threads.forms import PostForm
  12. from misago.threads.models import Thread, Post
  13. from misago.threads.views.base import BaseView
  14. from misago.views import error403, error404
  15. from misago.utils import make_pagination, slugify, ugettext_lazy
  16. class PostingView(BaseView):
  17. def fetch_target(self, kwargs):
  18. if self.mode == 'new_thread':
  19. self.fetch_forum(kwargs)
  20. else:
  21. self.fetch_thread(kwargs)
  22. if self.mode == 'edit_thread':
  23. self.fetch_post(self.thread.start_post_id)
  24. if self.mode == 'edit_post':
  25. self.fetch_post(kwargs['post'])
  26. def fetch_forum(self, kwargs):
  27. self.forum = Forum.objects.get(pk=kwargs['forum'], type='forum')
  28. self.proxy = Forum.objects.parents_aware_forum(self.forum)
  29. self.request.acl.forums.allow_forum_view(self.forum)
  30. self.request.acl.threads.allow_new_threads(self.proxy)
  31. self.parents = Forum.objects.forum_parents(self.forum.pk, True)
  32. def fetch_thread(self, kwargs):
  33. self.thread = Thread.objects.get(pk=kwargs['thread'])
  34. self.forum = self.thread.forum
  35. self.proxy = Forum.objects.parents_aware_forum(self.forum)
  36. self.request.acl.forums.allow_forum_view(self.forum)
  37. self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
  38. self.request.acl.threads.allow_reply(self.proxy, self.thread)
  39. self.parents = Forum.objects.forum_parents(self.forum.pk, True)
  40. if kwargs.get('quote'):
  41. self.quote = Post.objects.select_related('user').get(pk=kwargs['quote'], thread=self.thread.pk)
  42. self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.quote)
  43. def fetch_post(self, post):
  44. self.post = self.thread.post_set.get(pk=post)
  45. self.request.acl.threads.allow_post_view(self.request.user, self.thread, self.post)
  46. if self.mode == 'edit_thread':
  47. self.request.acl.threads.allow_thread_edit(self.request.user, self.proxy, self.thread, self.post)
  48. if self.mode == 'edit_post':
  49. self.request.acl.threads.allow_post_edit(self.request.user, self.proxy, self.thread, self.post)
  50. def get_form(self, bound=False):
  51. initial = {}
  52. if self.mode == 'edit_thread':
  53. initial['thread_name'] = self.thread.name
  54. if self.mode in ['edit_thread', 'edit_post']:
  55. initial['post'] = self.post.post
  56. if self.quote:
  57. quote_post = []
  58. if self.quote.user:
  59. quote_post.append('@%s' % self.quote.user.username)
  60. else:
  61. quote_post.append('@%s' % self.quote.user_name)
  62. for line in self.quote.post.splitlines():
  63. quote_post.append('> %s' % line)
  64. quote_post.append('\n')
  65. initial['post'] = '\n'.join(quote_post)
  66. if bound:
  67. return PostForm(self.request.POST,request=self.request,mode=self.mode,initial=initial)
  68. return PostForm(request=self.request,mode=self.mode,initial=initial)
  69. def __call__(self, request, **kwargs):
  70. self.request = request
  71. self.forum = None
  72. self.thread = None
  73. self.quote = None
  74. self.post = None
  75. self.parents = None
  76. self.mode = kwargs.get('mode')
  77. if self.request.POST.get('quick_reply') and self.mode == 'new_post':
  78. self.mode = 'new_post_quick'
  79. try:
  80. self.fetch_target(kwargs)
  81. if not request.user.is_authenticated():
  82. raise ACLError403(_("Guest, you have to sign-in in order to post replies."))
  83. except (Forum.DoesNotExist, Thread.DoesNotExist, Post.DoesNotExist):
  84. return error404(self.request)
  85. except ACLError403 as e:
  86. return error403(request, e.message)
  87. except ACLError404 as e:
  88. return error404(request, e.message)
  89. message = request.messages.get_message('threads')
  90. if request.method == 'POST':
  91. form = self.get_form(True)
  92. if form.is_valid():
  93. # Record original vars if user is editing
  94. if self.mode in ['edit_thread', 'edit_post']:
  95. old_name = self.thread.name
  96. old_post = self.post.post
  97. # If there is no change, throw user back
  98. changed_name = old_name != form.cleaned_data['thread_name']
  99. changed_post = old_post != form.cleaned_data['post']
  100. changed_anything = changed_name or changed_post
  101. # Some extra initialisation
  102. now = timezone.now()
  103. moderation = False
  104. if not request.acl.threads.acl[self.forum.pk]['can_approve']:
  105. if self.mode == 'new_thread' and request.acl.threads.acl[self.forum.pk]['can_start_threads'] == 1:
  106. moderation = True
  107. if self.mode in ['new_post', 'new_post_quick'] and request.acl.threads.acl[self.forum.pk]['can_write_posts'] == 1:
  108. moderation = True
  109. # Get or create new thread
  110. if self.mode == 'new_thread':
  111. thread = Thread.objects.create(
  112. forum=self.forum,
  113. name=form.cleaned_data['thread_name'],
  114. slug=slugify(form.cleaned_data['thread_name']),
  115. start=now,
  116. last=now,
  117. moderated=moderation,
  118. score=request.settings['thread_ranking_initial_score'],
  119. )
  120. if moderation:
  121. thread.replies_moderated += 1
  122. else:
  123. thread = self.thread
  124. if self.mode == 'edit_thread':
  125. thread.name = form.cleaned_data['thread_name']
  126. thread.slug = slugify(form.cleaned_data['thread_name'])
  127. # Create new message
  128. if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
  129. post = Post.objects.create(
  130. forum=self.forum,
  131. thread=thread,
  132. merge=thread.merges,
  133. user=request.user,
  134. user_name=request.user.username,
  135. ip=request.session.get_ip(request),
  136. agent=request.META.get('HTTP_USER_AGENT'),
  137. post=form.cleaned_data['post'],
  138. post_preparsed=post_markdown(request, form.cleaned_data['post']),
  139. date=now,
  140. moderated=moderation,
  141. )
  142. elif changed_post:
  143. # Change message
  144. post = self.post
  145. post.post = form.cleaned_data['post']
  146. post.post_preparsed = post_markdown(request, form.cleaned_data['post'])
  147. post.edits += 1
  148. post.edit_date = now
  149. post.edit_user = request.user
  150. post.edit_user_name = request.user.username
  151. post.edit_user_slug = request.user.username_slug
  152. post.save(force_update=True)
  153. # Record this edit in changelog?
  154. if self.mode in ['edit_thread', 'edit_post'] and changed_anything:
  155. self.post.change_set.create(
  156. forum=self.forum,
  157. thread=self.thread,
  158. post=self.post,
  159. user=request.user,
  160. user_name=request.user.username,
  161. user_slug=request.user.username_slug,
  162. date=now,
  163. ip=request.session.get_ip(request),
  164. agent=request.META.get('HTTP_USER_AGENT'),
  165. reason=form.cleaned_data['edit_reason'],
  166. size=len(self.post.post),
  167. change=len(self.post.post) - len(old_post),
  168. thread_name_old=old_name if form.cleaned_data['thread_name'] != old_name else None,
  169. thread_name_new=self.thread.name if form.cleaned_data['thread_name'] != old_name else None,
  170. post_content=old_post,
  171. )
  172. # Set thread start post and author data
  173. if self.mode == 'new_thread':
  174. thread.start_post = post
  175. thread.start_poster = request.user
  176. thread.start_poster_name = request.user.username
  177. thread.start_poster_slug = request.user.username_slug
  178. if request.user.rank and request.user.rank.style:
  179. thread.start_poster_style = request.user.rank.style
  180. # New post - increase post counters, thread score
  181. # Notify quoted post author and close thread if it has hit limit
  182. if self.mode in ['new_post', 'new_post_quick']:
  183. if moderation:
  184. thread.replies_moderated += 1
  185. else:
  186. thread.replies += 1
  187. if thread.last_poster_id != request.user.pk:
  188. thread.score += request.settings['thread_ranking_reply_score']
  189. # Notify quoted poster of reply?
  190. if self.quote and self.quote.user_id and self.quote.user_id != request.user.pk:
  191. alert = self.quote.user.alert(ugettext_lazy("%(username)s has replied to your post in thread %(thread)s").message)
  192. alert.profile('username', request.user)
  193. alert.post('thread', self.thread, post)
  194. alert.save_all()
  195. if (self.request.settings.thread_length > 0
  196. and not thread.closed
  197. and thread.replies >= self.request.settings.thread_length):
  198. thread.closed = True
  199. post.set_checkpoint(self.request, 'limit')
  200. # Update last poster data
  201. if not moderation and self.mode not in ['edit_thread', 'edit_post']:
  202. thread.last = now
  203. thread.last_post = post
  204. thread.last_poster = request.user
  205. thread.last_poster_name = request.user.username
  206. thread.last_poster_slug = request.user.username_slug
  207. if request.user.rank and request.user.rank.style:
  208. thread.last_poster_style = request.user.rank.style
  209. # Final update of thread entry
  210. if self.mode != 'edit_post':
  211. thread.save(force_update=True)
  212. # Update forum and monitor
  213. if not moderation:
  214. if self.mode == 'new_thread':
  215. self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
  216. self.forum.threads += 1
  217. self.forum.threads_delta += 1
  218. if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
  219. self.request.monitor['posts'] = int(self.request.monitor['posts']) + 1
  220. self.forum.posts += 1
  221. self.forum.posts_delta += 1
  222. self.forum.last_thread = thread
  223. self.forum.last_thread_name = thread.name
  224. self.forum.last_thread_slug = thread.slug
  225. self.forum.last_thread_date = thread.last
  226. self.forum.last_poster = thread.last_poster
  227. self.forum.last_poster_name = thread.last_poster_name
  228. self.forum.last_poster_slug = thread.last_poster_slug
  229. self.forum.last_poster_style = thread.last_poster_style
  230. self.forum.save(force_update=True)
  231. # Update user
  232. if not moderation:
  233. if self.mode == 'new_thread':
  234. request.user.threads += 1
  235. request.user.posts += 1
  236. if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
  237. request.user.last_post = thread.last
  238. request.user.save(force_update=True)
  239. # Set flash and redirect user to his post
  240. if self.mode == 'new_thread':
  241. if moderation:
  242. request.messages.set_flash(Message(_("New thread has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads')
  243. else:
  244. request.messages.set_flash(Message(_("New thread has been posted.")), 'success', 'threads')
  245. return redirect(reverse('thread', kwargs={'thread': thread.pk, 'slug': thread.slug}) + ('#post-%s' % post.pk))
  246. if self.mode in ['new_post', 'new_post_quick']:
  247. if moderation:
  248. request.messages.set_flash(Message(_("Your reply has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads_%s' % post.pk)
  249. else:
  250. request.messages.set_flash(Message(_("Your reply has been posted.")), 'success', 'threads_%s' % post.pk)
  251. pagination = make_pagination(0, self.request.acl.threads.filter_posts(self.request, self.thread, self.thread.post_set).count(), self.request.settings.posts_per_page)
  252. if pagination['total'] > 1:
  253. return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'page': pagination['total']}) + ('#post-%s' % post.pk))
  254. return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % post.pk))
  255. if self.mode == 'edit_thread':
  256. request.messages.set_flash(Message(_("Your thread has been edited.")), 'success', 'threads_%s' % self.post.pk)
  257. if self.mode == 'edit_post':
  258. request.messages.set_flash(Message(_("Your reply has been edited.")), 'success', 'threads_%s' % self.post.pk)
  259. pagination = make_pagination(0, self.request.acl.threads.filter_posts(self.request, self.thread, self.thread.post_set).filter(id__lte=self.post.pk).count(), self.request.settings.posts_per_page)
  260. if pagination['total'] > 1:
  261. return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug, 'page': pagination['total']}) + ('#post-%s' % self.post.pk))
  262. return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
  263. message = Message(form.non_field_errors()[0], 'error')
  264. else:
  265. form = self.get_form()
  266. # Merge proxy into forum
  267. self.forum.closed = self.proxy.closed
  268. return request.theme.render_to_response('threads/posting.html',
  269. {
  270. 'mode': self.mode,
  271. 'forum': self.forum,
  272. 'thread': self.thread,
  273. 'post': self.post,
  274. 'quote': self.quote,
  275. 'parents': self.parents,
  276. 'message': message,
  277. 'form': FormLayout(form),
  278. },
  279. context_instance=RequestContext(request));