forum.py 13 KB

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