generic.py 8.5 KB

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