posting.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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. # Show message preview
  94. if 'preview' in request.POST:
  95. if form['post'].value():
  96. md, preparsed = post_markdown(request, form['post'].value())
  97. else:
  98. md, preparsed = None, None
  99. form.empty_errors()
  100. return request.theme.render_to_response('threads/posting.html',
  101. {
  102. 'mode': self.mode,
  103. 'forum': self.forum,
  104. 'thread': self.thread,
  105. 'post': self.post,
  106. 'quote': self.quote,
  107. 'parents': self.parents,
  108. 'message': message,
  109. 'preview': preparsed,
  110. 'form': FormLayout(form),
  111. },
  112. context_instance=RequestContext(request));
  113. # Commit form to database
  114. if form.is_valid():
  115. # Record original vars if user is editing
  116. if self.mode in ['edit_thread', 'edit_post']:
  117. old_name = self.thread.name
  118. old_post = self.post.post
  119. # If there is no change, throw user back
  120. changed_name = (old_name != form.cleaned_data['thread_name']) if self.mode == 'edit_thread' else False
  121. changed_post = old_post != form.cleaned_data['post']
  122. changed_anything = changed_name or changed_post
  123. # Some extra initialisation
  124. now = timezone.now()
  125. md = None
  126. moderation = False
  127. if not request.acl.threads.acl[self.forum.pk]['can_approve']:
  128. if self.mode == 'new_thread' and request.acl.threads.acl[self.forum.pk]['can_start_threads'] == 1:
  129. moderation = True
  130. if self.mode in ['new_post', 'new_post_quick'] and request.acl.threads.acl[self.forum.pk]['can_write_posts'] == 1:
  131. moderation = True
  132. # Get or create new thread
  133. if self.mode == 'new_thread':
  134. thread = Thread.objects.create(
  135. forum=self.forum,
  136. name=form.cleaned_data['thread_name'],
  137. slug=slugify(form.cleaned_data['thread_name']),
  138. start=now,
  139. last=now,
  140. moderated=moderation,
  141. score=request.settings['thread_ranking_initial_score'],
  142. )
  143. if moderation:
  144. thread.replies_moderated += 1
  145. else:
  146. thread = self.thread
  147. if self.mode == 'edit_thread':
  148. thread.name = form.cleaned_data['thread_name']
  149. thread.slug = slugify(form.cleaned_data['thread_name'])
  150. thread.previous_last = thread.last
  151. # Create new message
  152. if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
  153. # Use last post instead?
  154. if (self.mode in ['new_post', 'new_post_quick']
  155. and request.settings.post_merge_time
  156. and (now - self.thread.last).seconds < (request.settings.post_merge_time * 60)
  157. and self.thread.last_poster_id == request.user.id):
  158. # Overtake posting
  159. post = self.thread.last_post
  160. post.moderated = moderation
  161. post.date = now
  162. 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'])
  163. md, post.post_preparsed = post_markdown(request, post.post)
  164. post.save(force_update=True)
  165. # Ignore rest of posting action
  166. request.messages.set_flash(Message(_("Your reply has been added to previous one.")), 'success', 'threads_%s' % post.pk)
  167. return self.redirect_to_post(post)
  168. else:
  169. md, post_preparsed = post_markdown(request, form.cleaned_data['post'])
  170. post = Post.objects.create(
  171. forum=self.forum,
  172. thread=thread,
  173. merge=thread.merges,
  174. user=request.user,
  175. user_name=request.user.username,
  176. ip=request.session.get_ip(request),
  177. agent=request.META.get('HTTP_USER_AGENT'),
  178. post=form.cleaned_data['post'],
  179. post_preparsed=post_preparsed,
  180. date=now,
  181. moderated=moderation,
  182. )
  183. elif changed_post:
  184. # Change message
  185. post = self.post
  186. post.post = form.cleaned_data['post']
  187. md, post.post_preparsed = post_markdown(request, form.cleaned_data['post'])
  188. post.edits += 1
  189. post.edit_date = now
  190. post.edit_user = request.user
  191. post.edit_user_name = request.user.username
  192. post.edit_user_slug = request.user.username_slug
  193. post.save(force_update=True)
  194. # Record this edit in changelog?
  195. if self.mode in ['edit_thread', 'edit_post'] and changed_anything:
  196. self.post.change_set.create(
  197. forum=self.forum,
  198. thread=self.thread,
  199. post=self.post,
  200. user=request.user,
  201. user_name=request.user.username,
  202. user_slug=request.user.username_slug,
  203. date=now,
  204. ip=request.session.get_ip(request),
  205. agent=request.META.get('HTTP_USER_AGENT'),
  206. reason=form.cleaned_data['edit_reason'],
  207. size=len(self.post.post),
  208. change=len(self.post.post) - len(old_post),
  209. thread_name_old=old_name if self.mode == 'edit_thread' and form.cleaned_data['thread_name'] != old_name else None,
  210. thread_name_new=self.thread.name if self.mode == 'edit_thread' and form.cleaned_data['thread_name'] != old_name else None,
  211. post_content=old_post,
  212. )
  213. # Set thread start post and author data
  214. if self.mode == 'new_thread':
  215. thread.start_post = post
  216. thread.start_poster = request.user
  217. thread.start_poster_name = request.user.username
  218. thread.start_poster_slug = request.user.username_slug
  219. if request.user.rank and request.user.rank.style:
  220. thread.start_poster_style = request.user.rank.style
  221. # New post - increase post counters, thread score
  222. # Notify quoted post author and close thread if it has hit limit
  223. if self.mode in ['new_post', 'new_post_quick']:
  224. if moderation:
  225. thread.replies_moderated += 1
  226. else:
  227. thread.replies += 1
  228. if thread.last_poster_id != request.user.pk:
  229. thread.score += request.settings['thread_ranking_reply_score']
  230. # Notify quoted poster of reply?
  231. 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):
  232. alert = self.quote.user.alert(ugettext_lazy("%(username)s has replied to your post in thread %(thread)s").message)
  233. alert.profile('username', request.user)
  234. alert.post('thread', self.thread, post)
  235. alert.save_all()
  236. if (self.request.settings.thread_length > 0
  237. and not thread.closed
  238. and thread.replies >= self.request.settings.thread_length):
  239. thread.closed = True
  240. post.set_checkpoint(self.request, 'limit')
  241. # Update last poster data
  242. if not moderation and self.mode not in ['edit_thread', 'edit_post']:
  243. thread.last = now
  244. thread.last_post = post
  245. thread.last_poster = request.user
  246. thread.last_poster_name = request.user.username
  247. thread.last_poster_slug = request.user.username_slug
  248. if request.user.rank and request.user.rank.style:
  249. thread.last_poster_style = request.user.rank.style
  250. # Final update of thread entry
  251. if self.mode != 'edit_post':
  252. thread.save(force_update=True)
  253. # Update forum and monitor
  254. if not moderation:
  255. if self.mode == 'new_thread':
  256. self.request.monitor['threads'] = int(self.request.monitor['threads']) + 1
  257. self.forum.threads += 1
  258. self.forum.threads_delta += 1
  259. if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
  260. self.request.monitor['posts'] = int(self.request.monitor['posts']) + 1
  261. self.forum.posts += 1
  262. self.forum.posts_delta += 1
  263. self.forum.last_thread = thread
  264. self.forum.last_thread_name = thread.name
  265. self.forum.last_thread_slug = thread.slug
  266. self.forum.last_thread_date = thread.last
  267. self.forum.last_poster = thread.last_poster
  268. self.forum.last_poster_name = thread.last_poster_name
  269. self.forum.last_poster_slug = thread.last_poster_slug
  270. self.forum.last_poster_style = thread.last_poster_style
  271. self.forum.save(force_update=True)
  272. # Update user
  273. if not moderation:
  274. if self.mode == 'new_thread':
  275. request.user.threads += 1
  276. request.user.posts += 1
  277. if self.mode in ['new_thread', 'new_post', 'new_post_quick']:
  278. request.user.last_post = thread.last
  279. request.user.save(force_update=True)
  280. # Notify users about post
  281. if md:
  282. try:
  283. if self.quote and self.quote.user_id:
  284. del md.mentions[self.quote.user.username_slug]
  285. except KeyError:
  286. pass
  287. if md.mentions:
  288. post.notify_mentioned(request, md.mentions)
  289. post.save(force_update=True)
  290. # Set flash and redirect user to his post
  291. if self.mode == 'new_thread':
  292. if moderation:
  293. request.messages.set_flash(Message(_("New thread has been posted. It will be hidden from other members until moderator reviews it.")), 'success', 'threads')
  294. else:
  295. request.messages.set_flash(Message(_("New thread has been posted.")), 'success', 'threads')
  296. return redirect(reverse('thread', kwargs={'thread': thread.pk, 'slug': thread.slug}) + ('#post-%s' % post.pk))
  297. if self.mode in ['new_post', 'new_post_quick']:
  298. thread.email_watchers(request, post)
  299. if moderation:
  300. 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)
  301. else:
  302. request.messages.set_flash(Message(_("Your reply has been posted.")), 'success', 'threads_%s' % post.pk)
  303. return self.redirect_to_post(post)
  304. if self.mode == 'edit_thread':
  305. request.messages.set_flash(Message(_("Your thread has been edited.")), 'success', 'threads_%s' % self.post.pk)
  306. if self.mode == 'edit_post':
  307. request.messages.set_flash(Message(_("Your reply has been edited.")), 'success', 'threads_%s' % self.post.pk)
  308. return self.redirect_to_post(self.post)
  309. return redirect(reverse('thread', kwargs={'thread': self.thread.pk, 'slug': self.thread.slug}) + ('#post-%s' % self.post.pk))
  310. message = Message(form.non_field_errors()[0], 'error')
  311. else:
  312. form = self.get_form()
  313. # Merge proxy into forum
  314. self.forum.closed = self.proxy.closed
  315. return request.theme.render_to_response('threads/posting.html',
  316. {
  317. 'mode': self.mode,
  318. 'forum': self.forum,
  319. 'thread': self.thread,
  320. 'post': self.post,
  321. 'quote': self.quote,
  322. 'parents': self.parents,
  323. 'message': message,
  324. 'form': FormLayout(form),
  325. },
  326. context_instance=RequestContext(request));