posting.py 20 KB

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