Files
interchan/board/views.py
Alek Ratzloff e686c3b235 Add ActionSuccessView
ActionSuccessView is a a generic view that indicates that something was
successful, e.g. deleting a post or banning a user. This hopefully
reduces the amount of boilerplate code used for creating success pages
since most of them can derive from this generic view.

The report and delete success views are updated to use this directly.

The ban and modify success views are updated to derive from this class,
with special permissions required.

The post success view is updated to derive from this class, using a
different template.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2022-07-17 15:08:33 -07:00

364 lines
12 KiB
Python

import functools
from typing import Any, Dict
from django.conf import settings
from django.contrib.auth import get_user
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Q
from django.http import Http404, HttpResponseRedirect
from django.http.request import QueryDict
from django.shortcuts import get_object_or_404
from django.views.generic.base import TemplateView
from django.views.generic import edit, list
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from guardian.shortcuts import get_objects_for_user
from board.forms import *
from board.models import Ban, BanTemplate, Board, NewsPost, Post, Report
from board.utils import *
__all__ = (
"ActionSuccessView",
"BanCreateView",
"BanSuccessView",
"BannedView",
"BoardView",
"NewsListView",
"PostCreateView",
"PostModifyView",
"PostModifySuccessView",
"PostView",
"PostSuccessView",
"PostDeleteView",
"ReplyCreateView",
"ReportView",
)
def can_modify(user):
if not user:
return False
# TODO add more permissions as required
return (
user.has_perm("board.set_sticky")
or user.has_perm("board.set_bump")
or user.has_perm("board.set_lock")
)
class ActionSuccessView(TemplateView):
template_name = "board/action_success.html"
message = "Action completed."
window_timeout = 0
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context["message"] = self.message
context["window_timeout"] = self.window_timeout
return context
class BannedView(TemplateView):
template_name = "board/banned.html"
def get_context_data(self, **kwargs):
context = super(TemplateView, self).get_context_data(**kwargs)
ip = get_client_ip(self.request)
bans = get_ip_bans(ip)
now = timezone.now()
active_bans = [ban for ban in bans if not ban.expires or ban.expires > now]
context["bans"] = bans
context["active_bans"] = active_bans
context["ip"] = ip
return context
class BoardMixin:
@functools.cached_property
def board(self) -> Board:
return get_object_or_404(Board, url=self.kwargs["url"]) # type: ignore
class CreateView(BoardMixin, edit.CreateView):
"""
Helper class that sets a few variables for posts and check against bans.
This should not be used by itself.
This class sets the following variables on GET and POST:
* self.board
"""
def get_form_kwargs(self):
kwargs = super(CreateView, self).get_form_kwargs()
kwargs["board"] = self.board
kwargs["ip"] = get_client_ip(self.request)
return kwargs
def dispatch(self, request, *args, **kwargs):
# Check for bans
ip = get_client_ip(request)
if request.method == "POST" and is_banned(ip, self.board):
return HttpResponseRedirect(reverse("board:banned"))
else:
return super(CreateView, self).dispatch(request, *args, **kwargs)
class BoardView(BoardMixin, TemplateView):
template_name = "board/board_detail.html"
def get(self, request, *args, **kwargs):
# If the page isn't set, then redirect to the /page/1 url
if "page" not in kwargs:
return HttpResponseRedirect(
reverse("board:board_detail", kwargs={"url": kwargs["url"], "page": 1})
)
return super(BoardView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
page = self.kwargs["page"]
if page not in range(1, self.board.max_pages + 1):
raise Http404()
threads_per_page = self.board.threads_per_page
start = (page - 1) * threads_per_page
end = start + threads_per_page
thread_count = self.board.threads.count()
last_page = (thread_count // threads_per_page) + 1
kwargs["board"] = self.board
kwargs["threads"] = self.board.threads.order_by("-sticky", "-last_bump")[
start:end
]
kwargs["current_page"] = page
kwargs["pages"] = range(1, last_page + 1)
kwargs["last_page"] = last_page
kwargs["max_upload_size"] = settings.MAX_UPLOAD_SIZE
kwargs["can_modify"] = can_modify(self.request.user)
return super(BoardView, self).get_context_data(**kwargs)
class PostCreateView(CreateView):
model = Post
form_class = PostForm
template_name = "board/post_create.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["board"] = self.board
context["max_upload_size"] = settings.MAX_UPLOAD_SIZE
context["capcodes"] = get_objects_for_user(
get_user(self.request), "board.use_capcode"
)
return context
def get_form_kwargs(self):
kwargs = super(PostCreateView, self).get_form_kwargs()
kwargs["user"] = self.request.user
if "user_token" not in self.request.session:
# Generate a user token
self.request.session["user_token"] = generate_user_token()
kwargs["user_token"] = self.request.session["user_token"]
return kwargs
def get_success_url(self) -> str:
query = QueryDict(mutable=True)
query["next"] = self.get_form().instance.get_absolute_url().split("#")[0]
print(query["next"])
return reverse("board:post_success") + "?" + query.urlencode()
class PostModifyView(PermissionRequiredMixin, edit.UpdateView):
model = Post
form_class = PostModifyForm
template_name = "board/post_modify.html"
success_url = reverse_lazy("board:post_modify_success")
raise_exception = True
def has_permission(self) -> bool:
return can_modify(self.request.user)
def dispatch(self, request, *args, **kwargs):
self.post_obj = get_object_or_404(Post, id=kwargs["pk"])
self.board = self.post_obj.board
return super(PostModifyView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self) -> Dict[str, Any]:
kwargs = super(PostModifyView, self).get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
class PostModifySuccessView(PermissionRequiredMixin, ActionSuccessView):
raise_exception = True
def has_permission(self) -> bool:
return can_modify(self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["can_modify"] = self.has_permission()
return context
class ReplyCreateView(CreateView):
model = Post
form_class = ReplyForm
template_name = "board/reply_create.html"
success_url = reverse_lazy("board:post_success")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["board"] = self.board
post_id = self.kwargs["id"]
context["post"] = get_object_or_404(Post, id=post_id)
context["max_upload_size"] = settings.MAX_UPLOAD_SIZE
context["capcodes"] = get_objects_for_user(
get_user(self.request), "board.use_capcode"
)
return context
def get_form_kwargs(self):
kwargs = super(ReplyCreateView, self).get_form_kwargs()
post_id = self.kwargs["id"]
post = get_object_or_404(Post, id=post_id)
kwargs["op"] = post
kwargs["user"] = self.request.user
if "user_token" not in self.request.session:
# Generate a user token
self.request.session["user_token"] = generate_user_token()
kwargs["user_token"] = self.request.session["user_token"]
return kwargs
def get_success_url(self) -> str:
query = QueryDict(mutable=True)
query["next"] = self.get_form().instance.get_absolute_url()
return reverse("board:post_success") + "?" + query.urlencode()
class PostView(TemplateView):
template_name = "board/post_detail.html"
def get_context_data(self, **kwargs):
kwargs["board"] = self.board
post_id = self.kwargs["id"]
kwargs["post"] = get_object_or_404(Post, id=post_id)
kwargs["max_upload_size"] = settings.MAX_UPLOAD_SIZE
kwargs["can_modify"] = can_modify(self.request.user)
return super(PostView, self).get_context_data(**kwargs)
def dispatch(self, request, *args, **kwargs):
# Set the board on this object
self.board = get_object_or_404(Board, url=kwargs["url"])
return super(PostView, self).dispatch(request, *args, **kwargs)
class PostSuccessView(ActionSuccessView):
template_name = "board/post_success.html"
window_timeout = settings.POST_WINDOW_CLOSE_TIMEOUT
class PostDeleteView(PermissionRequiredMixin, edit.DeleteView):
model = Post
form_class = PostDeleteForm
permission_required = ("board.delete_post",)
template_name = "board/post_confirm_delete.html"
success_url = reverse_lazy("board:post_delete_success")
raise_exception = True
def has_permission(self) -> bool:
object = self.get_object()
user_token = self.request.session.get("user_token", None)
return self.request.user.has_perm("board.delete_post") or (
user_token and object.user_token == user_token
)
def form_valid(self, form):
success_url = self.get_success_url()
if form["image_only"].value() != "0":
self.object.image.delete()
self.object.thumbnail.delete()
else:
self.object.delete()
return HttpResponseRedirect(success_url)
class ReportView(CreateView):
model = Report
form_class = ReportForm
success_url = reverse_lazy("board:report_success")
@property
def board_url(self) -> str:
return self.kwargs["url"]
@property
def post_id(self) -> int:
return self.kwargs["id"]
def get_form_kwargs(self):
kwargs = super(ReportView, self).get_form_kwargs()
post_id = self.kwargs["id"]
post = get_object_or_404(Post, id=post_id)
kwargs["op"] = post
return kwargs
class BanCreateView(PermissionRequiredMixin, edit.CreateView):
model = Ban
form_class = BanForm
permission_required = "board.add_ban"
success_url = reverse_lazy("board:ban_success")
raise_exception = True
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context["board"] = self.board
context["post"] = self.post_obj
ip = self.post_obj.ip
now = timezone.now()
bans = [
ban
for ban in get_ip_bans(ip)
if ban.board == self.board or ban.board is None
]
context["previous_bans"] = [
ban for ban in bans if ban.expires and ban.expires < now
]
context["current_bans"] = [
ban for ban in bans if not ban.expires or ban.expires > now
]
context["templates"] = BanTemplate.objects.filter(
Q(board=self.board) | Q(board=None)
)
return context
def get_form_kwargs(self) -> Dict[str, Any]:
kwargs = super(edit.CreateView, self).get_form_kwargs()
post_id = self.kwargs["id"]
post = get_object_or_404(Post, id=post_id)
kwargs["op"] = post
return kwargs
def dispatch(self, request, *args, **kwargs):
self.board = get_object_or_404(Board, url=kwargs["url"])
self.post_obj = get_object_or_404(Post, pk=kwargs["id"])
return super().dispatch(request, *args, **kwargs)
class BanSuccessView(PermissionRequiredMixin, ActionSuccessView):
permission_required = "ban.create"
raise_exception = True
class NewsListView(list.ListView):
model = NewsPost
template_name = "board/news_list.html"
ordering = ("-created",)