forum.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. from django.contrib import messages
  2. from django.core.urlresolvers import reverse
  3. from django.db.transaction import atomic
  4. from django.shortcuts import redirect
  5. from django.utils.translation import ugettext_lazy, ugettext as _, ungettext
  6. from misago.core.shortcuts import paginate
  7. from misago.forums.lists import get_forums_list, get_forum_path
  8. from misago.threads import moderation
  9. from misago.threads.models import ANNOUNCEMENT, Thread, Label
  10. from misago.threads.permissions import exclude_invisible_threads
  11. from misago.threads.views.generic.threads import (Actions, Sorting, Threads,
  12. ThreadsView)
  13. __all__ = ['ForumFiltering', 'ForumThreads', 'ForumView']
  14. class ForumActions(Actions):
  15. def get_available_actions(self, kwargs):
  16. self.forum = kwargs['forum']
  17. actions = []
  18. if self.forum.acl['can_change_threads_weight'] == 2:
  19. actions.append({
  20. 'action': 'announce',
  21. 'icon': 'star',
  22. 'name': _("Change to announcements")
  23. })
  24. if self.forum.acl['can_change_threads_weight']:
  25. actions.append({
  26. 'action': 'pin',
  27. 'icon': 'bookmark',
  28. 'name': _("Change to pinned")
  29. })
  30. actions.append({
  31. 'action': 'reset',
  32. 'icon': 'circle',
  33. 'name': _("Reset weight")
  34. })
  35. if self.forum.acl['can_close_threads']:
  36. actions.append({
  37. 'action': 'open',
  38. 'icon': 'unlock-alt',
  39. 'name': _("Open threads")
  40. })
  41. actions.append({
  42. 'action': 'close',
  43. 'icon': 'lock',
  44. 'name': _("Close threads")
  45. })
  46. if self.forum.acl['can_hide_threads']:
  47. actions.append({
  48. 'action': 'unhide',
  49. 'icon': 'eye',
  50. 'name': _("Unhide threads")
  51. })
  52. actions.append({
  53. 'action': 'hide',
  54. 'icon': 'eye-slash',
  55. 'name': _("Hide threads")
  56. })
  57. if self.forum.acl['can_hide_threads'] == 2:
  58. actions.append({
  59. 'action': 'delete',
  60. 'icon': 'times',
  61. 'name': _("Delete threads")
  62. })
  63. return actions
  64. def action_announce(self, request, threads):
  65. changed_threads = 0
  66. for thread in threads:
  67. if moderation.announce_thread(request.user, thread):
  68. changed_threads += 1
  69. if changed_threads:
  70. message = ungettext(
  71. '%(changed)d thread was changed to announcement.',
  72. '%(changed)d threads were changed to announcements.',
  73. changed_threads)
  74. messages.success(request, message % {'changed': changed_threads})
  75. else:
  76. message = ("No threads were changed to announcements.")
  77. messages.info(request, message)
  78. def action_pin(self, request, threads):
  79. changed_threads = 0
  80. for thread in threads:
  81. if moderation.pin_thread(request.user, thread):
  82. changed_threads += 1
  83. if changed_threads:
  84. message = ungettext(
  85. '%(changed)d thread was pinned.',
  86. '%(changed)d threads were pinned.',
  87. changed_threads)
  88. messages.success(request, message % {'changed': changed_threads})
  89. else:
  90. message = ("No threads were pinned.")
  91. messages.info(request, message)
  92. def action_reset(self, request, threads):
  93. changed_threads = 0
  94. for thread in threads:
  95. if moderation.reset_thread(request.user, thread):
  96. changed_threads += 1
  97. if changed_threads:
  98. message = ungettext(
  99. '%(changed)d thread weight was reset.',
  100. '%(changed)d threads weight was reset.',
  101. changed_threads)
  102. messages.success(request, message % {'changed': changed_threads})
  103. else:
  104. message = ("No threads weight was reset.")
  105. messages.info(request, message)
  106. def action_close(self, request, threads):
  107. changed_threads = 0
  108. for thread in threads:
  109. if moderation.close_thread(request.user, thread):
  110. changed_threads += 1
  111. if changed_threads:
  112. message = ungettext(
  113. '%(changed)d thread was closed.',
  114. '%(changed)d threads were closed.',
  115. changed_threads)
  116. messages.success(request, message % {'changed': changed_threads})
  117. else:
  118. message = ("No threads were closed.")
  119. messages.info(request, message)
  120. def action_open(self, request, threads):
  121. changed_threads = 0
  122. for thread in threads:
  123. if moderation.open_thread(request.user, thread):
  124. changed_threads += 1
  125. if changed_threads:
  126. message = ungettext(
  127. '%(changed)d thread was opened.',
  128. '%(changed)d threads were opened.',
  129. changed_threads)
  130. messages.success(request, message % {'changed': changed_threads})
  131. else:
  132. message = ("No threads were opened.")
  133. messages.info(request, message)
  134. def action_unhide(self, request, threads):
  135. changed_threads = 0
  136. for thread in threads:
  137. if moderation.unhide_thread(request.user, thread):
  138. changed_threads += 1
  139. if changed_threads:
  140. with atomic():
  141. self.forum.synchronize()
  142. self.forum.save()
  143. message = ungettext(
  144. '%(changed)d thread was made visible.',
  145. '%(changed)d threads were made visible.',
  146. changed_threads)
  147. messages.success(request, message % {'changed': changed_threads})
  148. else:
  149. message = ("No threads were made visible.")
  150. messages.info(request, message)
  151. def action_hide(self, request, threads):
  152. changed_threads = 0
  153. for thread in threads:
  154. if moderation.hide_thread(request.user, thread):
  155. changed_threads += 1
  156. if changed_threads:
  157. with atomic():
  158. self.forum.synchronize()
  159. self.forum.save()
  160. message = ungettext(
  161. '%(changed)d thread was hidden.',
  162. '%(changed)d threads were hidden.',
  163. changed_threads)
  164. messages.success(request, message % {'changed': changed_threads})
  165. else:
  166. message = ("No threads were hidden.")
  167. messages.info(request, message)
  168. def action_delete(self, request, threads):
  169. changed_threads = 0
  170. for thread in threads:
  171. if moderation.delete_thread(request.user, thread):
  172. changed_threads += 1
  173. if changed_threads:
  174. with atomic():
  175. self.forum.synchronize()
  176. self.forum.save()
  177. message = ungettext(
  178. '%(changed)d thread was deleted.',
  179. '%(changed)d threads were deleted.',
  180. changed_threads)
  181. messages.success(request, message % {'changed': changed_threads})
  182. else:
  183. message = ("No threads were deleted.")
  184. messages.info(request, message)
  185. class ForumFiltering(object):
  186. def __init__(self, forum, link_name, link_params):
  187. self.forum = forum
  188. self.link_name = link_name
  189. self.link_params = link_params.copy()
  190. self.filters = self.get_available_filters()
  191. def get_available_filters(self):
  192. filters = []
  193. if self.forum.acl['can_see_all_threads']:
  194. filters.append({
  195. 'type': 'my-threads',
  196. 'name': _("My threads"),
  197. 'is_label': False,
  198. })
  199. if self.forum.acl['can_see_reports']:
  200. filters.append({
  201. 'type': 'reported',
  202. 'name': _("With reported posts"),
  203. 'is_label': False,
  204. })
  205. if self.forum.acl['can_review_moderated_content']:
  206. filters.extend(({
  207. 'type': 'moderated-threads',
  208. 'name': _("Moderated threads"),
  209. 'is_label': False,
  210. },
  211. {
  212. 'type': 'moderated-posts',
  213. 'name': _("With moderated posts"),
  214. 'is_label': False,
  215. }))
  216. for label in self.forum.labels:
  217. filters.append({
  218. 'type': label.slug,
  219. 'name': label.name,
  220. 'is_label': True,
  221. 'css_class': label.css_class,
  222. })
  223. return filters
  224. def clean_kwargs(self, kwargs):
  225. show = kwargs.get('show')
  226. if show:
  227. available_filters = [method['type'] for method in self.filters]
  228. if show in available_filters:
  229. self.show = show
  230. else:
  231. kwargs.pop('show')
  232. else:
  233. self.show = None
  234. return kwargs
  235. def filter(self, threads):
  236. threads.filter(self.show)
  237. def get_filtering_dics(self):
  238. try:
  239. return self._dicts
  240. except AttributeError:
  241. self._dicts = self.create_dicts()
  242. return self._dicts
  243. def create_dicts(self):
  244. dicts = []
  245. if self.forum.acl['can_see_all_threads']:
  246. default_name = _("All threads")
  247. else:
  248. default_name = _("Your threads")
  249. self.link_params.pop('show', None)
  250. dicts.append({
  251. 'type': None,
  252. 'url': reverse(self.link_name, kwargs=self.link_params),
  253. 'name': default_name,
  254. 'is_label': False,
  255. })
  256. for filtering in self.filters:
  257. self.link_params['show'] = filtering['type']
  258. filtering['url'] = reverse(self.link_name, kwargs=self.link_params)
  259. dicts.append(filtering)
  260. return dicts
  261. @property
  262. def is_active(self):
  263. return bool(self.show)
  264. @property
  265. def current(self):
  266. try:
  267. return self._current
  268. except AttributeError:
  269. for filtering in self.get_filtering_dics():
  270. if filtering['type'] == self.show:
  271. self._current = filtering
  272. return filtering
  273. def choices(self):
  274. if self.show:
  275. choices = []
  276. for filtering in self.get_filtering_dics():
  277. if filtering['type'] != self.show:
  278. choices.append(filtering)
  279. return choices
  280. else:
  281. return self.get_filtering_dics()[1:]
  282. class ForumThreads(Threads):
  283. def __init__(self, user, forum):
  284. self.user = user
  285. self.forum = forum
  286. self.filter_by = None
  287. self.sort_by = ('-weight', '-last_post_on')
  288. def filter(self, filter_by):
  289. self.filter_by = filter_by
  290. def sort(self, sort_by):
  291. if sort_by[0] == '-':
  292. weight = '-weight'
  293. else:
  294. weight = 'weight'
  295. self.sort_by = (weight, sort_by)
  296. def list(self, page=0):
  297. queryset = self.get_queryset()
  298. queryset = queryset.order_by(*self.sort_by)
  299. announcements_qs = queryset.filter(weight=ANNOUNCEMENT)
  300. threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
  301. self._page = paginate(threads_qs, page, 20, 10)
  302. self._paginator = self._page.paginator
  303. threads = []
  304. for announcement in announcements_qs:
  305. threads.append(announcement)
  306. for thread in self._page.object_list:
  307. threads.append(thread)
  308. for thread in threads:
  309. thread.forum = self.forum
  310. self.label_threads(threads, self.forum.labels)
  311. self.make_threads_read_aware(threads)
  312. return threads
  313. def filter_threads(self, queryset):
  314. if self.filter_by == 'my-threads':
  315. return queryset.filter(starter_id=self.user.id)
  316. else:
  317. if self.forum.acl['can_see_own_threads']:
  318. if self.user.is_authenticated():
  319. queryset = queryset.filter(starter_id=self.user.id)
  320. else:
  321. queryset = queryset.filter(starter_id=0)
  322. if self.filter_by == 'reported':
  323. return queryset.filter(has_reported_posts=True)
  324. elif self.filter_by == 'moderated-threads':
  325. return queryset.filter(is_moderated=True)
  326. elif self.filter_by == 'moderated-posts':
  327. return queryset.filter(has_moderated_posts=True)
  328. else:
  329. for label in self.forum.labels:
  330. if label.slug == self.filter_by:
  331. return queryset.filter(label_id=label.pk)
  332. else:
  333. return queryset
  334. def get_queryset(self):
  335. queryset = exclude_invisible_threads(
  336. self.user, self.forum, self.forum.thread_set)
  337. return self.filter_threads(queryset)
  338. error_message = ("threads list has to be loaded via call to list() before "
  339. "pagination data will be available")
  340. @property
  341. def paginator(self):
  342. try:
  343. return self._paginator
  344. except AttributeError:
  345. raise AttributeError(self.error_message)
  346. @property
  347. def page(self):
  348. try:
  349. return self._page
  350. except AttributeError:
  351. raise AttributeError(self.error_message)
  352. class ForumView(ThreadsView):
  353. """
  354. Basic view for forum threads lists
  355. """
  356. template = 'misago/threads/forum.html'
  357. Threads = ForumThreads
  358. Sorting = Sorting
  359. Filtering = ForumFiltering
  360. Actions = ForumActions
  361. def dispatch(self, request, *args, **kwargs):
  362. forum = self.get_forum(request, **kwargs)
  363. forum.labels = Label.objects.get_forum_labels(forum)
  364. if forum.lft + 1 < forum.rght:
  365. forum.subforums = get_forums_list(request.user, forum)
  366. else:
  367. forum.subforums = []
  368. page_number = kwargs.pop('page', None)
  369. cleaned_kwargs = self.clean_kwargs(request, kwargs)
  370. sorting = self.Sorting(self.link_name, cleaned_kwargs)
  371. cleaned_kwargs = sorting.clean_kwargs(cleaned_kwargs)
  372. filtering = self.Filtering(forum, self.link_name, cleaned_kwargs)
  373. cleaned_kwargs = filtering.clean_kwargs(cleaned_kwargs)
  374. if cleaned_kwargs != kwargs:
  375. return redirect('misago:forum', **cleaned_kwargs)
  376. threads = self.Threads(request.user, forum)
  377. sorting.sort(threads)
  378. filtering.filter(threads)
  379. actions = self.Actions(user=request.user, forum=forum)
  380. if request.method == 'POST':
  381. # see if we can delegate anything to actions manager
  382. response = actions.handle_post(request, threads.get_queryset())
  383. if response:
  384. return response
  385. return self.render(request, {
  386. 'link_name': self.link_name,
  387. 'links_params': cleaned_kwargs,
  388. 'forum': forum,
  389. 'path': get_forum_path(forum),
  390. 'threads': threads.list(page_number),
  391. 'page': threads.page,
  392. 'paginator': threads.paginator,
  393. 'list_actions': actions.get_list(),
  394. 'selected_threads': actions.get_selected_ids(),
  395. 'sorting': sorting,
  396. 'filtering': filtering,
  397. })