generic.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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, **kwargs):
  48. queryset = Thread.objects
  49. if lock:
  50. queryset = queryset.select_for_update()
  51. return get_object_or_404(queryset, id=kwargs.get('thread_id'))
  52. def check_thread_permissions(self, request, thread):
  53. add_acl(request.user, thread)
  54. allow_see_thread(request.user, thread)
  55. class ViewBase(ForumMixin, View):
  56. templates_dir = ''
  57. template = ''
  58. def final_template(self):
  59. return '%s/%s' % (self.templates_dir, self.template)
  60. def process_context(self, request, context):
  61. """
  62. Simple hook for extending and manipulating template context.
  63. """
  64. return context
  65. def render(self, request, context=None, template=None):
  66. context = self.process_context(request, context or {})
  67. template = template or self.final_template()
  68. return render(request, template, context)
  69. class ForumView(ViewBase):
  70. """
  71. Basic view for threads lists
  72. """
  73. template = 'list.html'
  74. def get_threads(self, request, forum, kwargs):
  75. queryset = self.get_threads_queryset(request, forum)
  76. threads_qs = queryset.filter(weight__lt=ANNOUNCEMENT)
  77. threads_qs = threads_qs.order_by('-weight', '-last_post_on')
  78. page = paginate(threads_qs, kwargs.get('page', 0), 30, 10)
  79. threads = []
  80. for announcement in queryset.filter(weight=ANNOUNCEMENT):
  81. threads.append(announcement)
  82. for thread in page.object_list:
  83. threads.append(thread)
  84. return page, threads
  85. def get_threads_queryset(self, request, forum):
  86. return forum.thread_set.all().order_by('-last_post_on')
  87. def add_threads_reads(self, request, forum, threads):
  88. for thread in threads:
  89. thread.is_new = False
  90. import random
  91. for thread in threads:
  92. thread.is_new = random.choice((True, False))
  93. def dispatch(self, request, *args, **kwargs):
  94. forum = self.get_forum(request, **kwargs)
  95. forum.subforums = get_forums_list(request.user, forum)
  96. page, threads = self.get_threads(request, forum, kwargs)
  97. self.add_threads_reads(request, forum, threads)
  98. return self.render(request, {
  99. 'forum': forum,
  100. 'path': get_forum_path(forum),
  101. 'page': page,
  102. 'threads': threads
  103. })
  104. class ThreadView(ViewBase):
  105. """
  106. Basic view for threads
  107. """
  108. def fetch_thread(self, request, **kwargs):
  109. pass
  110. def dispatch(self, request, *args, **kwargs):
  111. thread = self.fetch_thread(request, **kwargs)
  112. class PostView(ViewBase):
  113. """
  114. Basic view for posts
  115. """
  116. def fetch_post(self, request, **kwargs):
  117. pass
  118. def dispatch(self, request, *args, **kwargs):
  119. post = self.fetch_post(request, **kwargs)
  120. class EditorView(ViewBase):
  121. """
  122. Basic view for starting/replying/editing
  123. """
  124. template = 'editor.html'
  125. def find_mode(self, request, *args, **kwargs):
  126. """
  127. First step: guess from request what kind of view we are
  128. """
  129. is_post = request.method == 'POST'
  130. if 'forum_id' in kwargs:
  131. mode = START
  132. user = request.user
  133. forum = self.get_forum(request, lock=is_post, **kwargs)
  134. thread = Thread(forum=forum)
  135. post = Post(forum=forum, thread=thread)
  136. quote = Post(0)
  137. elif 'thread_id' in kwargs:
  138. thread = self.get_thread(request, lock=is_post, **kwargs)
  139. forum = thread.forum
  140. return mode, forum, thread, post, quote
  141. def allow_mode(self, user, mode, forum, thread, post, quote):
  142. """
  143. Second step: check start/reply/edit permissions
  144. """
  145. if mode == START:
  146. self.allow_start(user, forum)
  147. if mode == REPLY:
  148. self.allow_reply(user, forum, thread, quote)
  149. if mode == EDIT:
  150. self.allow_edit(user, forum, thread, post)
  151. def allow_start(self, user, forum):
  152. allow_start_thread(user, forum)
  153. def allow_reply(self, user, forum, thread, quote):
  154. raise NotImplementedError()
  155. def allow_edit(self, user, forum, thread, post):
  156. raise NotImplementedError()
  157. def dispatch(self, request, *args, **kwargs):
  158. if request.method == 'POST':
  159. with atomic():
  160. return self.real_dispatch(request, *args, **kwargs)
  161. else:
  162. return self.real_dispatch(request, *args, **kwargs)
  163. def real_dispatch(self, request, *args, **kwargs):
  164. mode_context = self.find_mode(request, *args, **kwargs)
  165. self.allow_mode(request.user, *mode_context)
  166. mode, forum, thread, post, quote = mode_context
  167. formset = EditorFormset(request=request,
  168. mode=mode,
  169. user=request.user,
  170. forum=forum,
  171. thread=thread,
  172. post=post,
  173. quote=quote)
  174. if request.method == 'POST':
  175. if 'submit' in request.POST and formset.is_valid():
  176. try:
  177. formset.save()
  178. return redirect(forum.get_absolute_url())
  179. except InterruptChanges as e:
  180. messages.error(request, e.message)
  181. else:
  182. formset.update()
  183. return self.render(request, {
  184. 'mode': mode,
  185. 'formset': formset,
  186. 'forms': formset.get_forms_list(),
  187. 'main_forms': formset.get_main_forms(),
  188. 'supporting_forms': formset.get_supporting_forms(),
  189. 'forum': forum,
  190. 'path': get_forum_path(forum),
  191. 'thread': thread,
  192. 'post': post,
  193. 'quote': quote
  194. })