_generic.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. """
  2. Module with basic views for use by inheriting actions
  3. """
  4. from django.contrib import messages
  5. from django.db.models import Q
  6. from django.db.transaction import atomic
  7. from django.shortcuts import redirect, render
  8. from django.utils.translation import ugettext_lazy, ugettext as _
  9. from django.views.generic import View
  10. from misago.acl import add_acl
  11. from misago.core.shortcuts import get_object_or_404, paginate, validate_slug
  12. from misago.forums.lists import get_forums_list, get_forum_path
  13. from misago.forums.models import Forum
  14. from misago.forums.permissions import allow_see_forum, allow_browse_forum
  15. from misago.threads.posting import (PostingInterrupt, EditorFormset,
  16. START, REPLY, EDIT)
  17. from misago.threads.models import ANNOUNCEMENT, Thread, Post
  18. from misago.threads.permissions import allow_see_thread, allow_start_thread
  19. class ForumMixin(object):
  20. """
  21. Mixin for getting forums
  22. """
  23. def get_forum(self, request, lock=False, **kwargs):
  24. forum = self.fetch_forum(request, lock, **kwargs)
  25. self.check_forum_permissions(request, forum)
  26. if kwargs.get('forum_slug'):
  27. validate_slug(forum, kwargs.get('forum_slug'))
  28. return forum
  29. def fetch_forum(self, request, lock=False, **kwargs):
  30. queryset = Forum.objects
  31. if lock:
  32. queryset = queryset.select_for_update()
  33. return get_object_or_404(
  34. queryset, id=kwargs.get('forum_id'), role='forum')
  35. def check_forum_permissions(self, request, forum):
  36. add_acl(request.user, forum)
  37. allow_see_forum(request.user, forum)
  38. allow_browse_forum(request.user, forum)
  39. class ThreadMixin(object):
  40. """
  41. Mixin for getting thread
  42. """
  43. def get_thread(self, request, lock=False, **kwargs):
  44. thread = self.fetch_thread(request, lock, **kwargs)
  45. self.check_thread_permissions(request, thread)
  46. if kwargs.get('thread_slug'):
  47. validate_slug(thread, kwargs.get('thread_slug'))
  48. return thread
  49. def fetch_thread(self, request, lock=False, select_related=None, **kwargs):
  50. queryset = Thread.objects
  51. if lock:
  52. queryset = queryset.select_for_update()
  53. if select_related:
  54. queryset = queryset.select_related(*select_related)
  55. return get_object_or_404(queryset, id=kwargs.get('thread_id'))
  56. def check_thread_permissions(self, request, thread):
  57. add_acl(request.user, thread)
  58. allow_see_thread(request.user, thread)
  59. class PostMixin(object):
  60. pass
  61. class ViewBase(ForumMixin, ThreadMixin, PostMixin, View):
  62. templates_dir = ''
  63. template = ''
  64. def final_template(self):
  65. return '%s/%s' % (self.templates_dir, self.template)
  66. def process_context(self, request, context):
  67. """
  68. Simple hook for extending and manipulating template context.
  69. """
  70. return context
  71. def render(self, request, context=None, template=None):
  72. context = self.process_context(request, context or {})
  73. template = template or self.final_template()
  74. return render(request, template, context)
  75. class OrderThreadsMixin(object):
  76. order_by = (
  77. ('recently-replied', ugettext_lazy("Recently replied")),
  78. ('last-replied', ugettext_lazy("Last replied")),
  79. ('most-replied', ugettext_lazy("Most replied")),
  80. ('least-replied', ugettext_lazy("Least replied")),
  81. ('newest', ugettext_lazy("Newest")),
  82. ('oldest', ugettext_lazy("Oldest")),
  83. )
  84. def get_ordering(self, kwargs):
  85. if kwargs.get('sort') in [o[0] for o in self.order_by]:
  86. return kwargs.get('sort')
  87. else:
  88. return self.order_by[0][0]
  89. def is_ordering_default(self, order_by):
  90. return self.order_by[0][0] == order_by
  91. def get_ordering_name(self, order_by):
  92. for ordering in self.order_by:
  93. if ordering[0] == order_by:
  94. return ordering[1]
  95. def get_orderings_dicts(self):
  96. dicts = []
  97. for ordering in self.order_by:
  98. dicts.append({
  99. 'url': ordering[0],
  100. 'name': ordering[1],
  101. })
  102. return dicts
  103. class ThreadsView(ViewBase):
  104. def get_threads(self, request, kwargs):
  105. queryset = self.get_threads_queryset(request, forum)
  106. threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
  107. threads_qs = threads_qs.order_by('-weight', '-last_post_id')
  108. page = paginate(threads_qs, kwargs.get('page', 0), 30, 10)
  109. threads = []
  110. for announcement in queryset.filter(weight=ANNOUNCEMENT):
  111. threads.append(announcement)
  112. for thread in page.object_list:
  113. threads.append(thread)
  114. for thread in threads:
  115. thread.forum = forum
  116. return page, threads
  117. def get_threads_queryset(self, request):
  118. return forum.thread_set.all().order_by('-last_post_id')
  119. def add_threads_reads(self, request, threads):
  120. for thread in threads:
  121. thread.is_new = False
  122. import random
  123. for thread in threads:
  124. thread.is_new = random.choice((True, False))
  125. class ForumView(OrderThreadsMixin, ThreadsView):
  126. """
  127. Basic view for threads lists
  128. """
  129. template = 'list.html'
  130. def get_threads(self, request, forum, kwargs, order_by=None, limit=None):
  131. queryset = self.get_threads_queryset(request, forum)
  132. queryset = self.filter_all_querysets(request, forum, queryset)
  133. announcements_qs = queryset.filter(weight=ANNOUNCEMENT)
  134. threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
  135. announcements_qs = self.filter_announcements_queryset(
  136. request, forum, announcements_qs)
  137. threads_qs = self.filter_threads_queryset(request, forum, threads_qs)
  138. threads_qs, announcements_qs = self.order_querysets(
  139. order_by, threads_qs, announcements_qs)
  140. page = paginate(threads_qs, kwargs.get('page', 0), 20, 10)
  141. threads = []
  142. for announcement in announcements_qs:
  143. threads.append(announcement)
  144. for thread in page.object_list:
  145. threads.append(thread)
  146. for thread in threads:
  147. thread.forum = forum
  148. return page, threads
  149. def order_querysets(self, order_by, threads, announcements):
  150. if order_by == 'recently-replied':
  151. threads = threads.order_by('-weight', '-last_post')
  152. announcements = announcements.order_by('-last_post')
  153. if order_by == 'last-replied':
  154. threads = threads.order_by('weight', 'last_post')
  155. announcements = announcements.order_by('last_post')
  156. if order_by == 'most-replied':
  157. threads = threads.order_by('-weight', '-replies')
  158. announcements = announcements.order_by('-replies')
  159. if order_by == 'least-replied':
  160. threads = threads.order_by('weight', 'replies')
  161. announcements = announcements.order_by('replies')
  162. if order_by == 'newest':
  163. threads = threads.order_by('-weight', '-id')
  164. announcements = announcements.order_by('-id')
  165. if order_by == 'oldest':
  166. threads = threads.order_by('weight', 'id')
  167. announcements = announcements.order_by('id')
  168. return threads, announcements
  169. def filter_all_querysets(self, request, forum, queryset):
  170. if request.user.is_authenticated():
  171. condition_author = Q(starter_id=request.user.id)
  172. can_mod = forum.acl['can_review_moderated_content']
  173. can_hide = forum.acl['can_hide_threads']
  174. if not can_mod and not can_hide:
  175. condition = Q(is_moderated=False) & Q(is_hidden=False)
  176. queryset = queryset.filter(condition_author | condition)
  177. elif not can_mod:
  178. condition = Q(is_moderated=False)
  179. queryset = queryset.filter(condition_author | condition)
  180. elif not can_hide:
  181. condition = Q(is_hidden=False)
  182. queryset = queryset.filter(condition_author | condition)
  183. else:
  184. if not forum.acl['can_review_moderated_content']:
  185. queryset = queryset.filter(is_moderated=False)
  186. if not forum.acl['can_hide_threads']:
  187. queryset = queryset.filter(is_hidden=False)
  188. return queryset
  189. def filter_threads_queryset(self, request, forum, queryset):
  190. if forum.acl['can_see_own_threads']:
  191. if request.user.is_authenticated():
  192. queryset = queryset.filter(starter_id=request.user.id)
  193. else:
  194. queryset = queryset.filter(starter_id=0)
  195. return queryset
  196. def filter_announcements_queryset(self, request, forum, queryset):
  197. return queryset
  198. def get_threads_queryset(self, request, forum):
  199. return forum.thread_set.all().order_by('-last_post_id')
  200. def dispatch(self, request, *args, **kwargs):
  201. forum = self.get_forum(request, **kwargs)
  202. links_params = {'forum_slug': forum.slug, 'forum_id': forum.id}
  203. forum.subforums = get_forums_list(request.user, forum)
  204. order_by = self.get_ordering(kwargs)
  205. if self.is_ordering_default(kwargs.get('sort')):
  206. kwargs.pop('sort')
  207. return redirect('misago:forum', **kwargs)
  208. else:
  209. links_params['sort'] = order_by
  210. page, threads = self.get_threads(request, forum, kwargs, order_by)
  211. self.add_threads_reads(request, threads)
  212. return self.render(request, {
  213. 'forum': forum,
  214. 'path': get_forum_path(forum),
  215. 'page': page,
  216. 'paginator': page.paginator,
  217. 'threads': threads,
  218. 'links_params': links_params,
  219. 'order_name': self.get_ordering_name(order_by),
  220. 'order_by': self.get_orderings_dicts(),
  221. })
  222. class ThreadView(ViewBase):
  223. """
  224. Basic view for threads
  225. """
  226. template = 'thread.html'
  227. def dispatch(self, request, *args, **kwargs):
  228. if request.method == 'POST':
  229. with atomic():
  230. return self.real_dispatch(request, *args, **kwargs)
  231. else:
  232. return self.real_dispatch(request, *args, **kwargs)
  233. def real_dispatch(self, request, *args, **kwargs):
  234. relations = ['forum', 'starter', 'last_poster', 'first_post']
  235. thread = self.fetch_thread(request, select_related=relations, **kwargs)
  236. forum = thread.forum
  237. self.check_forum_permissions(request, forum)
  238. self.check_thread_permissions(request, thread)
  239. return self.render(request, {
  240. 'forum': forum,
  241. 'path': get_forum_path(forum),
  242. 'thread': thread
  243. })
  244. class PostView(ViewBase):
  245. """
  246. Basic view for posts
  247. """
  248. def fetch_post(self, request, **kwargs):
  249. pass
  250. def dispatch(self, request, *args, **kwargs):
  251. post = self.fetch_post(request, **kwargs)
  252. class EditorView(ViewBase):
  253. """
  254. Basic view for starting/replying/editing
  255. """
  256. template = 'editor.html'
  257. def find_mode(self, request, *args, **kwargs):
  258. """
  259. First step: guess from request what kind of view we are
  260. """
  261. is_post = request.method == 'POST'
  262. if 'forum_id' in kwargs:
  263. mode = START
  264. user = request.user
  265. forum = self.get_forum(request, lock=is_post, **kwargs)
  266. thread = Thread(forum=forum)
  267. post = Post(forum=forum, thread=thread)
  268. quote = Post(0)
  269. elif 'thread_id' in kwargs:
  270. thread = self.get_thread(request, lock=is_post, **kwargs)
  271. forum = thread.forum
  272. return mode, forum, thread, post, quote
  273. def allow_mode(self, user, mode, forum, thread, post, quote):
  274. """
  275. Second step: check start/reply/edit permissions
  276. """
  277. if mode == START:
  278. self.allow_start(user, forum)
  279. if mode == REPLY:
  280. self.allow_reply(user, forum, thread, quote)
  281. if mode == EDIT:
  282. self.allow_edit(user, forum, thread, post)
  283. def allow_start(self, user, forum):
  284. allow_start_thread(user, forum)
  285. def allow_reply(self, user, forum, thread, quote):
  286. raise NotImplementedError()
  287. def allow_edit(self, user, forum, thread, post):
  288. raise NotImplementedError()
  289. def dispatch(self, request, *args, **kwargs):
  290. if request.method == 'POST':
  291. with atomic():
  292. return self.real_dispatch(request, *args, **kwargs)
  293. else:
  294. return self.real_dispatch(request, *args, **kwargs)
  295. def real_dispatch(self, request, *args, **kwargs):
  296. mode_context = self.find_mode(request, *args, **kwargs)
  297. self.allow_mode(request.user, *mode_context)
  298. mode, forum, thread, post, quote = mode_context
  299. formset = EditorFormset(request=request,
  300. mode=mode,
  301. user=request.user,
  302. forum=forum,
  303. thread=thread,
  304. post=post,
  305. quote=quote)
  306. if request.method == 'POST':
  307. if 'submit' in request.POST and formset.is_valid():
  308. try:
  309. formset.save()
  310. return redirect(thread.get_absolute_url())
  311. except PostingInterrupt as e:
  312. messages.error(request, e.message)
  313. else:
  314. formset.update()
  315. return self.render(request, {
  316. 'mode': mode,
  317. 'formset': formset,
  318. 'forms': formset.get_forms_list(),
  319. 'main_forms': formset.get_main_forms(),
  320. 'supporting_forms': formset.get_supporting_forms(),
  321. 'forum': forum,
  322. 'path': get_forum_path(forum),
  323. 'thread': thread,
  324. 'post': post,
  325. 'quote': quote,
  326. })