generic.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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.views.generic import View
  9. from misago.acl import add_acl
  10. from misago.core.shortcuts import get_object_or_404, paginate, validate_slug
  11. from misago.forums.lists import get_forums_list, get_forum_path
  12. from misago.forums.models import Forum
  13. from misago.forums.permissions import allow_see_forum, allow_browse_forum
  14. from misago.threads.posting import (PostingInterrupt, EditorFormset,
  15. START, REPLY, EDIT)
  16. from misago.threads.models import ANNOUNCEMENT, Thread, Post
  17. from misago.threads.permissions import allow_see_thread, allow_start_thread
  18. class ForumMixin(object):
  19. """
  20. Mixin for getting forums
  21. """
  22. def get_forum(self, request, lock=False, **kwargs):
  23. forum = self.fetch_forum(request, lock, **kwargs)
  24. self.check_forum_permissions(request, forum)
  25. if kwargs.get('forum_slug'):
  26. validate_slug(forum, kwargs.get('forum_slug'))
  27. return forum
  28. def fetch_forum(self, request, lock=False, **kwargs):
  29. queryset = Forum.objects
  30. if lock:
  31. queryset = queryset.select_for_update()
  32. return get_object_or_404(
  33. queryset, id=kwargs.get('forum_id'), role='forum')
  34. def check_forum_permissions(self, request, forum):
  35. add_acl(request.user, forum)
  36. allow_see_forum(request.user, forum)
  37. allow_browse_forum(request.user, forum)
  38. class ThreadMixin(object):
  39. """
  40. Mixin for getting thread
  41. """
  42. def get_thread(self, request, lock=False, **kwargs):
  43. thread = self.fetch_thread(request, lock, **kwargs)
  44. self.check_thread_permissions(request, thread)
  45. if kwargs.get('thread_slug'):
  46. validate_slug(thread, kwargs.get('thread_slug'))
  47. return thread
  48. def fetch_thread(self, request, lock=False, select_related=None, **kwargs):
  49. queryset = Thread.objects
  50. if lock:
  51. queryset = queryset.select_for_update()
  52. if select_related:
  53. queryset = queryset.select_related(*select_related)
  54. return get_object_or_404(queryset, id=kwargs.get('thread_id'))
  55. def check_thread_permissions(self, request, thread):
  56. add_acl(request.user, thread)
  57. allow_see_thread(request.user, thread)
  58. class PostMixin(object):
  59. pass
  60. class ViewBase(ForumMixin, ThreadMixin, PostMixin, View):
  61. templates_dir = ''
  62. template = ''
  63. def final_template(self):
  64. return '%s/%s' % (self.templates_dir, self.template)
  65. def process_context(self, request, context):
  66. """
  67. Simple hook for extending and manipulating template context.
  68. """
  69. return context
  70. def render(self, request, context=None, template=None):
  71. context = self.process_context(request, context or {})
  72. template = template or self.final_template()
  73. return render(request, template, context)
  74. class ThreadsView(ViewBase):
  75. def get_threads(self, request, kwargs):
  76. queryset = self.get_threads_queryset(request, forum)
  77. threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
  78. threads_qs = threads_qs.order_by('-weight', '-last_post_id')
  79. page = paginate(threads_qs, kwargs.get('page', 0), 30, 10)
  80. threads = []
  81. for announcement in queryset.filter(weight=ANNOUNCEMENT):
  82. threads.append(announcement)
  83. for thread in page.object_list:
  84. threads.append(thread)
  85. for thread in threads:
  86. thread.forum = forum
  87. return page, threads
  88. def get_threads_queryset(self, request):
  89. return forum.thread_set.all().order_by('-last_post_id')
  90. def add_threads_reads(self, request, threads):
  91. for thread in threads:
  92. thread.is_new = False
  93. import random
  94. for thread in threads:
  95. thread.is_new = random.choice((True, False))
  96. class ForumView(ThreadsView):
  97. """
  98. Basic view for threads lists
  99. """
  100. template = 'list.html'
  101. def get_threads(self, request, forum, kwargs):
  102. queryset = self.get_threads_queryset(request, forum)
  103. queryset = self.filter_all_querysets(request, forum, queryset)
  104. announcements_qs = queryset.filter(weight=ANNOUNCEMENT)
  105. threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
  106. announcements_qs = self.filter_announcements_queryset(
  107. request, forum, announcements_qs)
  108. threads_qs = self.filter_threads_queryset(request, forum, threads_qs)
  109. threads_qs = threads_qs.order_by('-weight', '-last_post_id')
  110. page = paginate(threads_qs, kwargs.get('page', 0), 30, 10)
  111. threads = []
  112. for announcement in queryset.filter(weight=ANNOUNCEMENT):
  113. threads.append(announcement)
  114. for thread in page.object_list:
  115. threads.append(thread)
  116. for thread in threads:
  117. thread.forum = forum
  118. return page, threads
  119. def filter_all_querysets(self, request, forum, queryset):
  120. if not forum.acl['can_review_moderated_content']:
  121. if request.user.is_authenticated():
  122. condition = Q(is_moderated=False)
  123. condition = condition | Q(starter_id=request.user.id)
  124. queryset = queryset.filter(condition)
  125. else:
  126. queryset = queryset.filter(is_moderated=False)
  127. if not forum.acl['can_hide_threads']:
  128. if request.user.is_authenticated():
  129. condition = Q(is_hidden=False)
  130. condition = condition | Q(starter_id=request.user.id)
  131. queryset = queryset.filter(condition)
  132. else:
  133. queryset = queryset.filter(is_hidden=False)
  134. return queryset
  135. def filter_threads_queryset(self, request, forum, queryset):
  136. if forum.acl['can_see_own_threads']:
  137. if request.user.is_authenticated():
  138. queryset = queryset.filter(starter_id=request.user.id)
  139. else:
  140. queryset = queryset.filter(starter_id=0)
  141. return queryset
  142. def filter_announcements_queryset(self, request, forum, queryset):
  143. return queryset
  144. def get_threads_queryset(self, request, forum):
  145. return forum.thread_set.all().order_by('-last_post_id')
  146. def dispatch(self, request, *args, **kwargs):
  147. forum = self.get_forum(request, **kwargs)
  148. forum.subforums = get_forums_list(request.user, forum)
  149. page, threads = self.get_threads(request, forum, kwargs)
  150. self.add_threads_reads(request, threads)
  151. return self.render(request, {
  152. 'forum': forum,
  153. 'path': get_forum_path(forum),
  154. 'page': page,
  155. 'threads': threads
  156. })
  157. class ThreadView(ViewBase):
  158. """
  159. Basic view for threads
  160. """
  161. template = 'thread.html'
  162. def dispatch(self, request, *args, **kwargs):
  163. if request.method == 'POST':
  164. with atomic():
  165. return self.real_dispatch(request, *args, **kwargs)
  166. else:
  167. return self.real_dispatch(request, *args, **kwargs)
  168. def real_dispatch(self, request, *args, **kwargs):
  169. relations = ['forum', 'starter', 'last_poster', 'first_post']
  170. thread = self.fetch_thread(request, select_related=relations, **kwargs)
  171. forum = thread.forum
  172. self.check_forum_permissions(request, forum)
  173. self.check_thread_permissions(request, thread)
  174. return self.render(request, {
  175. 'forum': forum,
  176. 'path': get_forum_path(forum),
  177. 'thread': thread
  178. })
  179. class PostView(ViewBase):
  180. """
  181. Basic view for posts
  182. """
  183. def fetch_post(self, request, **kwargs):
  184. pass
  185. def dispatch(self, request, *args, **kwargs):
  186. post = self.fetch_post(request, **kwargs)
  187. class EditorView(ViewBase):
  188. """
  189. Basic view for starting/replying/editing
  190. """
  191. template = 'editor.html'
  192. def find_mode(self, request, *args, **kwargs):
  193. """
  194. First step: guess from request what kind of view we are
  195. """
  196. is_post = request.method == 'POST'
  197. if 'forum_id' in kwargs:
  198. mode = START
  199. user = request.user
  200. forum = self.get_forum(request, lock=is_post, **kwargs)
  201. thread = Thread(forum=forum)
  202. post = Post(forum=forum, thread=thread)
  203. quote = Post(0)
  204. elif 'thread_id' in kwargs:
  205. thread = self.get_thread(request, lock=is_post, **kwargs)
  206. forum = thread.forum
  207. return mode, forum, thread, post, quote
  208. def allow_mode(self, user, mode, forum, thread, post, quote):
  209. """
  210. Second step: check start/reply/edit permissions
  211. """
  212. if mode == START:
  213. self.allow_start(user, forum)
  214. if mode == REPLY:
  215. self.allow_reply(user, forum, thread, quote)
  216. if mode == EDIT:
  217. self.allow_edit(user, forum, thread, post)
  218. def allow_start(self, user, forum):
  219. allow_start_thread(user, forum)
  220. def allow_reply(self, user, forum, thread, quote):
  221. raise NotImplementedError()
  222. def allow_edit(self, user, forum, thread, post):
  223. raise NotImplementedError()
  224. def dispatch(self, request, *args, **kwargs):
  225. if request.method == 'POST':
  226. with atomic():
  227. return self.real_dispatch(request, *args, **kwargs)
  228. else:
  229. return self.real_dispatch(request, *args, **kwargs)
  230. def real_dispatch(self, request, *args, **kwargs):
  231. mode_context = self.find_mode(request, *args, **kwargs)
  232. self.allow_mode(request.user, *mode_context)
  233. mode, forum, thread, post, quote = mode_context
  234. formset = EditorFormset(request=request,
  235. mode=mode,
  236. user=request.user,
  237. forum=forum,
  238. thread=thread,
  239. post=post,
  240. quote=quote)
  241. if request.method == 'POST':
  242. if 'submit' in request.POST and formset.is_valid():
  243. try:
  244. formset.save()
  245. return redirect(thread.get_absolute_url())
  246. except PostingInterrupt as e:
  247. messages.error(request, e.message)
  248. else:
  249. formset.update()
  250. return self.render(request, {
  251. 'mode': mode,
  252. 'formset': formset,
  253. 'forms': formset.get_forms_list(),
  254. 'main_forms': formset.get_main_forms(),
  255. 'supporting_forms': formset.get_supporting_forms(),
  256. 'forum': forum,
  257. 'path': get_forum_path(forum),
  258. 'thread': thread,
  259. 'post': post,
  260. 'quote': quote
  261. })