diff --git a/board/admin.py b/board/admin.py index 9207029..e4f29a8 100644 --- a/board/admin.py +++ b/board/admin.py @@ -1,8 +1,14 @@ +from django import forms from django.contrib import admin +from django.http.response import HttpResponseRedirect +from django.shortcuts import render +from django.urls import reverse from django.utils.safestring import mark_safe from board.models import Ban, Board, Post, RangeBan, ReportReason, ReportRecord - +# +# Admin sites +# @admin.register(Board) class BoardAdmin(admin.ModelAdmin): pass @@ -18,16 +24,23 @@ class ReportReasonAdmin(admin.ModelAdmin): pass +class BanFromReportForm(forms.ModelForm): + pass + + @admin.register(ReportRecord) class ReportRecordAdmin(admin.ModelAdmin): ordering = ( "-urgent", "-weight", ) - readonly_fields = ("post",) - list_display = ("post_thumbnail", "post_body") + readonly_fields = ("post", "weight", "urgent") + list_display = ("post_id", "post_thumbnail", "post_body", "create_ban") save_as = False + def post_id(self, obj): + return obj.post.id + def post_thumbnail(self, obj): if obj.post.thumbnail: return mark_safe(f'') @@ -41,11 +54,19 @@ class ReportRecordAdmin(admin.ModelAdmin): else: html += "
" if obj.post.subject: - html += f"{obj.post.subject}" + html += f"

{obj.post.subject}

" html += f"

{obj.post.text}

" html += "
" return mark_safe(html) + def create_ban(self, obj): + post = obj.post + board = obj.post.board + ban_url = reverse("board:ban_create", kwargs={"url": board.url, "id": post.id}) + return mark_safe( + f'Ban' + ) + @admin.register(RangeBan) class RangeBanAdmin(admin.ModelAdmin): diff --git a/board/forms.py b/board/forms.py index faf2c2d..2f8d039 100644 --- a/board/forms.py +++ b/board/forms.py @@ -1,8 +1,10 @@ from django.conf import settings from django.db import transaction from django.db.models import Q +from django import forms from django.forms import ModelForm, ModelChoiceField -from board.models import Post, Report, ReportReason, ReportRecord +from django.utils import timezone +from board.models import Ban, Post, Report, ReportReason, ReportRecord from hcaptcha.fields import hCaptchaField @@ -74,3 +76,32 @@ class ReportForm(ModelForm): record = ReportRecord.objects.create(post=self.op) self.instance.record = record return super(ReportForm, self).clean() + + +class BanForm(ModelForm): + """ + A form used to create bans based on specific posts. + """ + + # uses ban_form.html + duration = forms.IntegerField(label="Duration (days)", min_value=1, required=False) + + class Meta: + model = Ban + fields = ["ban_reason"] + + def __init__(self, *args, op, **kwargs): + super(BanForm, self).__init__(*args, **kwargs) + self.op = op + self.instance.board = op.board + self.instance.ip = op.ip + + def clean(self): + super(BanForm, self).clean() + now = timezone.now() + duration = self.cleaned_data["duration"] + if duration: + expires = now + timezone.timedelta(days=duration) + else: + expires = None + self.instance.expires = expires diff --git a/board/models.py b/board/models.py index 5b09283..d32b33b 100644 --- a/board/models.py +++ b/board/models.py @@ -304,7 +304,7 @@ class RangeBan(BanCommon): class Ban(BanCommon): # IP address of the ban - ip = models.GenericIPAddressField(unique=True) + ip = models.GenericIPAddressField() def __str__(self): return f"Ban for {self.ip}" diff --git a/board/static/board/style.css b/board/static/board/style.css index c47dd3d..8b1ac9e 100644 --- a/board/static/board/style.css +++ b/board/static/board/style.css @@ -6,11 +6,16 @@ hr { color: #ededed; } +th { + text-align: left; + vertical-align: top; +} + .column { float: left; width: 33.33%; } - + /* Clear floats after the columns */ .row:after { content: ""; @@ -117,7 +122,7 @@ hr { /* Misc */ a:link { - color:#555; + color: #555; } a:visited { diff --git a/board/templates/admin/board/reportrecord/change_list.html b/board/templates/admin/board/reportrecord/change_list.html index e7c073c..766a1c0 100644 --- a/board/templates/admin/board/reportrecord/change_list.html +++ b/board/templates/admin/board/reportrecord/change_list.html @@ -1,4 +1,5 @@ {% extends "admin/change_list.html" %} +{% load static %} {% block extrastyle %} {{ block.super }} -{% endblock extrastyle %} \ No newline at end of file +{% endblock extrastyle %} +{% block extrahead %} +{{block.super}} + + +{% endblock extrahead %} + +{% block footer %} +{{block.super}} + +{% endblock %} \ No newline at end of file diff --git a/board/templates/board/ban_form.html b/board/templates/board/ban_form.html new file mode 100644 index 0000000..918ff12 --- /dev/null +++ b/board/templates/board/ban_form.html @@ -0,0 +1,50 @@ +{% extends "board/base.html" %} +{% load i18n %} + +{% block content %} +
+ {% csrf_token %} + + + + + + + + + + + + + + {{form.as_table}} + + + + + + + + + + + + +
{% translate 'Post ID' %}:{{post.id}}
{% translate 'Board' %}:{{board.url}}
IP:{{post.ip}}
{% translate "Previous bans" %}{{previous_bans|length}}
{% translate "Current bans" %}{{current_bans|length}}
+
+ + +{% endblock content %} \ No newline at end of file diff --git a/board/templates/board/ban_success.html b/board/templates/board/ban_success.html new file mode 100644 index 0000000..21bb2e8 --- /dev/null +++ b/board/templates/board/ban_success.html @@ -0,0 +1,29 @@ +{% extends "board/base.html" %} +{% load l10n %} +{# Title #} +{% block title %}{% localize on %}Ban success{% endlocalize %}{% endblock %} +{# Body #} +{% block content %} +
+ {% localize on %}A ban has been created. This window will close in 1 second.{% endlocalize %} +
+ + +{% endblock %} \ No newline at end of file diff --git a/board/urls.py b/board/urls.py index c584be4..55bb1af 100644 --- a/board/urls.py +++ b/board/urls.py @@ -10,12 +10,18 @@ urlpatterns = [ path("/", BoardView.as_view(), name="board_detail"), path("/page//", BoardView.as_view(), name="board_detail"), path("/post//", PostView.as_view(), name="post_detail"), - path("/report//", ReportView.as_view(), name="report_form"), + path("report///", ReportView.as_view(), name="report_form"), path( "report/success/", TemplateView.as_view(template_name="board/report_success.html"), name="report_success", ), + path("ban///", BanCreateView.as_view(), name="ban_create"), + path( + "ban/success/", + BanSuccessView.as_view(), + name="ban_success", + ), path( "banned", BannedView.as_view(), diff --git a/board/views.py b/board/views.py index 377cb23..de82e65 100644 --- a/board/views.py +++ b/board/views.py @@ -1,15 +1,24 @@ +from typing import Any, Dict from django.conf import settings +from django.contrib.auth.mixins import PermissionRequiredMixin from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.views.generic.base import TemplateView from django.views.generic.edit import CreateView from django.urls import reverse, reverse_lazy from django.utils import timezone -from board.forms import PostForm, ReplyForm, ReportForm -from board.models import Post, Board, Report +from board.forms import BanForm, PostForm, ReplyForm, ReportForm +from board.models import Ban, Board, Post, Report from board.utils import get_client_ip, get_ip_bans -__all__ = ("BannedView", "BoardView", "PostView", "ReportView") +__all__ = ( + "BanCreateView", + "BanSuccessView", + "BannedView", + "BoardView", + "PostView", + "ReportView", +) class BannedView(TemplateView): @@ -37,18 +46,6 @@ class CreatePostView(CreateView): * self.board """ - def get(self, request, *args, **kwargs): - self._set_board(kwargs["url"]) - return super(CreatePostView, self).get(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - self._set_board(kwargs["url"]) - return super(CreatePostView, self).post(request, *args, **kwargs) - - def _set_board(self, board_url: str): - board = get_object_or_404(Board, url=board_url) - self.board = board - def get_form_kwargs(self): kwargs = super(CreatePostView, self).get_form_kwargs() kwargs["board"] = self.board @@ -56,7 +53,9 @@ class CreatePostView(CreateView): return kwargs def dispatch(self, request, *args, **kwargs): - self._set_board(kwargs["url"]) + # Set the board on this object + self.board = get_object_or_404(Board, url=kwargs["url"]) + ip = get_client_ip(request) # Filter bans by board first bans = [ @@ -162,3 +161,47 @@ class ReportView(CreatePostView): post = get_object_or_404(Post, id=post_id) kwargs["op"] = post return kwargs + + +class BanCreateView(PermissionRequiredMixin, CreateView): + model = Ban + form_class = BanForm + permission_required = "ban.create" + success_url = reverse_lazy("board:ban_success") + + 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 is not None and ban.expires < now + ] + context["current_bans"] = [ + ban for ban in bans if ban.expires is None or ban.expires > now + ] + + return context + + def get_form_kwargs(self) -> Dict[str, Any]: + kwargs = super(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, TemplateView): + permission_required = "ban.create" + template_name = "board/ban_success.html"