actions.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. from django.contrib import messages
  2. from django.db.transaction import atomic
  3. from django.shortcuts import render
  4. from django.utils import timezone
  5. from django.utils.translation import ugettext_lazy, ugettext as _, ungettext
  6. from misago.categories.lists import get_category_path
  7. from misago.threads import moderation
  8. from misago.threads.forms.moderation import MergeThreadsForm, MoveThreadsForm
  9. from misago.threads.models import Thread
  10. from misago.threads.views.generic.threads import Actions, ReloadAfterDelete
  11. __all__ = ['ForumActions', 'ReloadAfterDelete']
  12. class ForumActions(Actions):
  13. def get_available_actions(self, kwargs):
  14. self.category = kwargs['category']
  15. actions = []
  16. if self.category.acl['can_change_threads_labels'] == 2:
  17. for label in self.category.labels:
  18. actions.append({
  19. 'action': 'label:%s' % label.slug,
  20. 'icon': 'tag',
  21. 'name': _('Label as "%(label)s"') % {'label': label.name}
  22. })
  23. if self.category.labels:
  24. actions.append({
  25. 'action': 'unlabel',
  26. 'icon': 'times-circle',
  27. 'name': _("Remove labels")
  28. })
  29. if self.category.acl['can_pin_threads']:
  30. actions.append({
  31. 'action': 'pin',
  32. 'icon': 'star',
  33. 'name': _("Pin threads")
  34. })
  35. actions.append({
  36. 'action': 'unpin',
  37. 'icon': 'circle',
  38. 'name': _("Unpin threads")
  39. })
  40. if self.category.acl['can_review_moderated_content']:
  41. actions.append({
  42. 'action': 'approve',
  43. 'icon': 'check',
  44. 'name': _("Approve threads")
  45. })
  46. if self.category.acl['can_move_threads']:
  47. actions.append({
  48. 'action': 'move',
  49. 'icon': 'arrow-right',
  50. 'name': _("Move threads")
  51. })
  52. if self.category.acl['can_merge_threads']:
  53. actions.append({
  54. 'action': 'merge',
  55. 'icon': 'reply-all',
  56. 'name': _("Merge threads")
  57. })
  58. if self.category.acl['can_close_threads']:
  59. actions.append({
  60. 'action': 'open',
  61. 'icon': 'unlock-alt',
  62. 'name': _("Open threads")
  63. })
  64. actions.append({
  65. 'action': 'close',
  66. 'icon': 'lock',
  67. 'name': _("Close threads")
  68. })
  69. if self.category.acl['can_hide_threads']:
  70. actions.append({
  71. 'action': 'unhide',
  72. 'icon': 'eye',
  73. 'name': _("Reveal threads")
  74. })
  75. actions.append({
  76. 'action': 'hide',
  77. 'icon': 'eye-slash',
  78. 'name': _("Hide threads")
  79. })
  80. if self.category.acl['can_hide_threads'] == 2:
  81. actions.append({
  82. 'action': 'delete',
  83. 'icon': 'times',
  84. 'name': _("Delete threads"),
  85. 'confirmation': _("Are you sure you want to delete selected "
  86. "threads? This action can't be undone.")
  87. })
  88. return actions
  89. def action_label(self, request, threads, label_slug):
  90. for label in self.category.labels:
  91. if label.slug == label_slug:
  92. break
  93. else:
  94. raise moderation.ModerationError(self.invalid_action_message)
  95. changed_threads = 0
  96. for thread in threads:
  97. if moderation.label_thread(request.user, thread, label):
  98. changed_threads += 1
  99. if changed_threads:
  100. message = ungettext(
  101. '%(changed)d thread was labeled "%(label)s".',
  102. '%(changed)d threads were labeled "%(label)s".',
  103. changed_threads)
  104. messages.success(request, message % {
  105. 'changed': changed_threads,
  106. 'label': label.name
  107. })
  108. else:
  109. message = _("No threads were labeled.")
  110. messages.info(request, message)
  111. def action_unlabel(self, request, threads):
  112. changed_threads = 0
  113. for thread in threads:
  114. if moderation.unlabel_thread(request.user, thread):
  115. changed_threads += 1
  116. if changed_threads:
  117. message = ungettext(
  118. '%(changed)d thread label was removed.',
  119. '%(changed)d threads labels were removed.',
  120. changed_threads)
  121. messages.success(request, message % {'changed': changed_threads})
  122. else:
  123. message = _("No threads were unlabeled.")
  124. messages.info(request, message)
  125. def action_pin(self, request, threads):
  126. changed_threads = 0
  127. for thread in threads:
  128. if moderation.pin_thread(request.user, thread):
  129. changed_threads += 1
  130. if changed_threads:
  131. message = ungettext(
  132. '%(changed)d thread was pinned.',
  133. '%(changed)d threads were pinned.',
  134. changed_threads)
  135. messages.success(request, message % {'changed': changed_threads})
  136. else:
  137. message = _("No threads were pinned.")
  138. messages.info(request, message)
  139. def action_unpin(self, request, threads):
  140. changed_threads = 0
  141. for thread in threads:
  142. if moderation.unpin_thread(request.user, thread):
  143. changed_threads += 1
  144. if changed_threads:
  145. message = ungettext(
  146. '%(changed)d thread was unpinned.',
  147. '%(changed)d threads were unpinned.',
  148. changed_threads)
  149. messages.success(request, message % {'changed': changed_threads})
  150. else:
  151. message = _("No threads were unpinned.")
  152. messages.info(request, message)
  153. def action_approve(self, request, threads):
  154. changed_threads = 0
  155. for thread in threads:
  156. if moderation.approve_thread(request.user, thread):
  157. changed_threads += 1
  158. if changed_threads:
  159. message = ungettext(
  160. '%(changed)d thread was approved.',
  161. '%(changed)d threads were approved.',
  162. changed_threads)
  163. messages.success(request, message % {'changed': changed_threads})
  164. else:
  165. message = _("No threads were approved.")
  166. messages.info(request, message)
  167. move_threads_full_template = 'misago/threads/move/full.html'
  168. move_threads_modal_template = 'misago/threads/move/modal.html'
  169. def action_move(self, request, threads):
  170. form = MoveThreadsForm(acl=request.user.acl, category=self.category)
  171. if 'submit' in request.POST:
  172. form = MoveThreadsForm(
  173. request.POST, acl=request.user.acl, category=self.category)
  174. if form.is_valid():
  175. new_category = form.cleaned_data['new_category']
  176. with atomic():
  177. for thread in threads:
  178. moderation.move_thread(request.user, thread, new_category)
  179. self.category.lock()
  180. new_category.lock()
  181. self.category.synchronize()
  182. self.category.save()
  183. new_category.synchronize()
  184. new_category.save()
  185. changed_threads = len(threads)
  186. message = ungettext(
  187. '%(changed)d thread was moved to "%(category)s".',
  188. '%(changed)d threads were moved to "%(category)s".',
  189. changed_threads)
  190. messages.success(request, message % {
  191. 'changed': changed_threads,
  192. 'category': new_category.name
  193. })
  194. return None # trigger threads list refresh
  195. if request.is_ajax():
  196. template = self.move_threads_modal_template
  197. else:
  198. template = self.move_threads_full_template
  199. return render(request, template, {
  200. 'form': form,
  201. 'category': self.category,
  202. 'path': get_category_path(self.category),
  203. 'threads': threads
  204. })
  205. merge_threads_full_template = 'misago/threads/merge/full.html'
  206. merge_threads_modal_template = 'misago/threads/merge/modal.html'
  207. def action_merge(self, request, threads):
  208. if len(threads) == 1:
  209. message = _("You have to select at least two threads to merge.")
  210. raise moderation.ModerationError(message)
  211. form = MergeThreadsForm()
  212. if 'submit' in request.POST:
  213. form = MergeThreadsForm(request.POST)
  214. if form.is_valid():
  215. with atomic():
  216. merged_thread = Thread()
  217. merged_thread.category = self.category
  218. merged_thread.set_title(
  219. form.cleaned_data['merged_thread_title'])
  220. merged_thread.starter_name = "-"
  221. merged_thread.starter_slug = "-"
  222. merged_thread.last_poster_name = "-"
  223. merged_thread.last_poster_slug = "-"
  224. merged_thread.started_on = timezone.now()
  225. merged_thread.last_post_on = timezone.now()
  226. merged_thread.is_pinned = max(t.is_pinned for t in threads)
  227. merged_thread.is_closed = max(t.is_closed for t in threads)
  228. merged_thread.save()
  229. for thread in threads:
  230. moderation.merge_thread(
  231. request.user, merged_thread, thread)
  232. merged_thread.synchronize()
  233. merged_thread.save()
  234. self.category.lock()
  235. self.category.synchronize()
  236. self.category.save()
  237. changed_threads = len(threads)
  238. message = ungettext(
  239. '%(changed)d thread was merged into "%(thread)s".',
  240. '%(changed)d threads were merged into "%(thread)s".',
  241. changed_threads)
  242. messages.success(request, message % {
  243. 'changed': changed_threads,
  244. 'thread': merged_thread.title
  245. })
  246. return None # trigger threads list refresh
  247. if request.is_ajax():
  248. template = self.merge_threads_modal_template
  249. else:
  250. template = self.merge_threads_full_template
  251. return render(request, template, {
  252. 'form': form,
  253. 'category': self.category,
  254. 'path': get_category_path(self.category),
  255. 'threads': threads
  256. })
  257. def action_close(self, request, threads):
  258. changed_threads = 0
  259. for thread in threads:
  260. if moderation.close_thread(request.user, thread):
  261. changed_threads += 1
  262. if changed_threads:
  263. message = ungettext(
  264. '%(changed)d thread was closed.',
  265. '%(changed)d threads were closed.',
  266. changed_threads)
  267. messages.success(request, message % {'changed': changed_threads})
  268. else:
  269. message = _("No threads were closed.")
  270. messages.info(request, message)
  271. def action_open(self, request, threads):
  272. changed_threads = 0
  273. for thread in threads:
  274. if moderation.open_thread(request.user, thread):
  275. changed_threads += 1
  276. if changed_threads:
  277. message = ungettext(
  278. '%(changed)d thread was opened.',
  279. '%(changed)d threads were opened.',
  280. changed_threads)
  281. messages.success(request, message % {'changed': changed_threads})
  282. else:
  283. message = _("No threads were opened.")
  284. messages.info(request, message)
  285. def action_unhide(self, request, threads):
  286. changed_threads = 0
  287. for thread in threads:
  288. if moderation.unhide_thread(request.user, thread):
  289. changed_threads += 1
  290. if changed_threads:
  291. with atomic():
  292. self.category.lock()
  293. self.category.synchronize()
  294. self.category.save()
  295. message = ungettext(
  296. '%(changed)d thread was made visible.',
  297. '%(changed)d threads were made visible.',
  298. changed_threads)
  299. messages.success(request, message % {'changed': changed_threads})
  300. else:
  301. message = _("No threads were made visible.")
  302. messages.info(request, message)
  303. def action_hide(self, request, threads):
  304. changed_threads = 0
  305. for thread in threads:
  306. if moderation.hide_thread(request.user, thread):
  307. changed_threads += 1
  308. if changed_threads:
  309. with atomic():
  310. self.category.lock()
  311. self.category.synchronize()
  312. self.category.save()
  313. if changed_threads:
  314. message = ungettext(
  315. '%(changed)d thread was hidden.',
  316. '%(changed)d threads were hidden.',
  317. changed_threads)
  318. messages.success(request, message % {'changed': changed_threads})
  319. else:
  320. message = _("No threads were hidden.")
  321. messages.info(request, message)
  322. def action_delete(self, request, threads):
  323. changed_threads = 0
  324. for thread in threads:
  325. if moderation.delete_thread(request.user, thread):
  326. changed_threads += 1
  327. if changed_threads:
  328. with atomic():
  329. self.category.lock()
  330. self.category.synchronize()
  331. self.category.save()
  332. if changed_threads:
  333. message = ungettext(
  334. '%(changed)d thread was deleted.',
  335. '%(changed)d threads were deleted.',
  336. changed_threads)
  337. messages.success(request, message % {'changed': changed_threads})
  338. return ReloadAfterDelete()
  339. else:
  340. message = _("No threads were deleted.")
  341. messages.info(request, message)