goto.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. from math import ceil
  2. from django.core.exceptions import PermissionDenied
  3. from django.db.models import Q
  4. from django.shortcuts import get_object_or_404, redirect
  5. from django.utils.translation import gettext as _
  6. from django.views import View
  7. from ...conf import settings
  8. from ...readtracker.cutoffdate import get_cutoff_date
  9. from ..permissions import exclude_invisible_posts
  10. from ..viewmodels import ForumThread, PrivateThread
  11. class GotoView(View):
  12. thread = None
  13. def get(self, request, pk, slug, **kwargs):
  14. thread = self.get_thread(request, pk, slug).unwrap()
  15. self.test_permissions(request, thread)
  16. posts_queryset = exclude_invisible_posts(
  17. request.user_acl, thread.category, thread.post_set
  18. )
  19. target_post = self.get_target_post(
  20. request.user, thread, posts_queryset.order_by("id"), **kwargs
  21. )
  22. target_page = self.compute_post_page(target_post, posts_queryset)
  23. return self.get_redirect(thread, target_post, target_page)
  24. def get_thread(self, request, pk, slug):
  25. return self.thread(request, pk, slug) # pylint: disable=not-callable
  26. def test_permissions(self, request, thread):
  27. pass
  28. def get_target_post(self, user, thread, posts_queryset):
  29. raise NotImplementedError(
  30. "goto views should define their own get_target_post method"
  31. )
  32. def compute_post_page(self, target_post, posts_queryset):
  33. # filter out events, order queryset
  34. posts_queryset = posts_queryset.filter(is_event=False).order_by("id")
  35. thread_length = posts_queryset.count()
  36. # is target an event?
  37. if target_post.is_event:
  38. target_event = target_post
  39. previous_posts = posts_queryset.filter(id__lt=target_event.id)
  40. else:
  41. previous_posts = posts_queryset.filter(id__lte=target_post.id)
  42. post_position = previous_posts.count()
  43. per_page = self.request.settings.posts_per_page - 1
  44. orphans = self.request.settings.posts_per_page_orphans
  45. if orphans:
  46. orphans += 1
  47. hits = max(1, thread_length - orphans)
  48. thread_pages = int(ceil(hits / float(per_page)))
  49. if post_position >= thread_pages * per_page:
  50. return thread_pages
  51. return int(ceil(float(post_position) / (per_page)))
  52. def get_redirect(self, thread, target_post, target_page):
  53. thread_url = thread.thread_type.get_thread_absolute_url(thread, target_page)
  54. return redirect("%s#post-%s" % (thread_url, target_post.pk))
  55. class ThreadGotoPostView(GotoView):
  56. thread = ForumThread
  57. def get_target_post(self, user, thread, posts_queryset, **kwargs):
  58. return get_object_or_404(posts_queryset, pk=kwargs["post"])
  59. class ThreadGotoLastView(GotoView):
  60. thread = ForumThread
  61. def get_target_post(self, user, thread, posts_queryset, **kwargs):
  62. return posts_queryset.order_by("id").last()
  63. class GetFirstUnreadPostMixin:
  64. def get_first_unread_post(self, user, posts_queryset):
  65. if user.is_authenticated:
  66. cutoff_date = get_cutoff_date(self.request.settings, user)
  67. expired_posts = Q(posted_on__lt=cutoff_date)
  68. read_posts = Q(id__in=user.postread_set.values("post"))
  69. first_unread = (
  70. posts_queryset.exclude(expired_posts | read_posts)
  71. .order_by("id")
  72. .first()
  73. )
  74. if first_unread:
  75. return first_unread
  76. return posts_queryset.order_by("id").last()
  77. class ThreadGotoNewView(GotoView, GetFirstUnreadPostMixin):
  78. thread = ForumThread
  79. def get_target_post(self, user, thread, posts_queryset, **kwargs):
  80. return self.get_first_unread_post(user, posts_queryset)
  81. class ThreadGotoBestAnswerView(GotoView):
  82. thread = ForumThread
  83. def get_target_post(self, user, thread, posts_queryset, **kwargs):
  84. return thread.best_answer or thread.first_post
  85. class ThreadGotoUnapprovedView(GotoView):
  86. thread = ForumThread
  87. def test_permissions(self, request, thread):
  88. if not thread.acl["can_approve"]:
  89. raise PermissionDenied(
  90. _(
  91. "You need permission to approve content to "
  92. "be able to go to first unapproved post."
  93. )
  94. )
  95. def get_target_post(self, user, thread, posts_queryset, **kwargs):
  96. unapproved_post = (
  97. posts_queryset.filter(is_unapproved=True).order_by("id").first()
  98. )
  99. if unapproved_post:
  100. return unapproved_post
  101. return posts_queryset.order_by("id").last()
  102. class PrivateThreadGotoPostView(GotoView):
  103. thread = PrivateThread
  104. def get_target_post(self, user, thread, posts_queryset, **kwargs):
  105. return get_object_or_404(posts_queryset, pk=kwargs["post"])
  106. class PrivateThreadGotoLastView(GotoView):
  107. thread = PrivateThread
  108. def get_target_post(self, user, thread, posts_queryset, **kwargs):
  109. return posts_queryset.order_by("id").last()
  110. class PrivateThreadGotoNewView(GotoView, GetFirstUnreadPostMixin):
  111. thread = PrivateThread
  112. def get_target_post(self, user, thread, posts_queryset, **kwargs):
  113. return self.get_first_unread_post(user, posts_queryset)