Add ability to create bans from reports

This is done in the admin view and opens a new iframed window. The ban
form is pretty barebones and doesn't have full functionality yet, but
that is coming.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-06-23 16:03:09 -07:00
parent 5763c33f39
commit 470a10d2a7
9 changed files with 260 additions and 26 deletions

View File

@@ -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'<img src="{obj.post.thumbnail.url}" />')
@@ -41,11 +54,19 @@ class ReportRecordAdmin(admin.ModelAdmin):
else:
html += "<div>"
if obj.post.subject:
html += f"<strong>{obj.post.subject}</strong>"
html += f"<p><strong>{obj.post.subject}</strong></p>"
html += f"<p>{obj.post.text}</p>"
html += "</div>"
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'<a href="#" data-ban-url="{ban_url}" class="ban_link">Ban</a>'
)
@admin.register(RangeBan)
class RangeBanAdmin(admin.ModelAdmin):

View File

@@ -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

View File

@@ -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}"

View File

@@ -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 {

View File

@@ -1,4 +1,5 @@
{% extends "admin/change_list.html" %}
{% load static %}
{% block extrastyle %}
{{ block.super }}
<style>
@@ -9,5 +10,53 @@
background-color: var(--message-error-bg);
color: var(--error-fg);
}
.wb-min {
display: none;
}
.wb-max {
display: none;
}
.wb-full {
display: none;
}
</style>
{% endblock extrastyle %}
{% endblock extrastyle %}
{% block extrahead %}
{{block.super}}
<script src="{% static 'board/jquery.js' %}"></script>
<script src="{% static 'board/winbox.bundle.js' %}"></script>
{% endblock extrahead %}
{% block footer %}
{{block.super}}
<script>
function openBanWindow(e) {
e.preventDefault();
let banUrl = e.target.getAttribute("data-ban-url");
if (window.banWindow) {
window.banWindow.close();
}
window.banWindow = new WinBox("New ban", {
url: banUrl,
x: "center",
y: "center",
root: document.body,
onclose: function(force) {
window.top.banWindow = null;
}
});
}
function onLoad(e) {
window.banWindow = null;
}
$(".ban_link").on("click", openBanWindow);
$(window).on("load", onLoad);
</script>
{% endblock %}

View File

@@ -0,0 +1,50 @@
{% extends "board/base.html" %}
{% load i18n %}
{% block content %}
<form id="form" action="{% url 'board:ban_create' url=board.url id=post.id %}" method="post">
{% csrf_token %}
<table>
<tr>
<th>{% translate 'Post ID' %}:</th>
<td>{{post.id}}</td>
</tr>
<tr>
<th>{% translate 'Board' %}:</th>
<td>{{board.url}}</td>
</tr>
<tr>
<th>IP:</th>
<td>{{post.ip}}</td>
</tr>
{{form.as_table}}
<tr>
<th>{% translate "Previous bans" %}</th>
<td>{{previous_bans|length}}</td>
</tr>
<tr>
<th>{% translate "Current bans" %}</th>
<td id="current_bans">{{current_bans|length}}</td>
</tr>
<tr>
<th></th>
<td><input type="submit" id="submit" value="{% translate 'Create ban' %}" /></td>
</tr>
</table>
</form>
<script>
function submitConfirm(e) {
console.log($("#current_bans").text());
if($("#current_bans").text() !== '0') {
let result = window.confirm("There is already at least one ban for this IP address on this board, are you sure you want to continue?");
if (!result) {
e.preventDefault();
return;
}
}
}
$("#submit").on("click", submitConfirm);
</script>
{% endblock content %}

View File

@@ -0,0 +1,29 @@
{% extends "board/base.html" %}
{% load l10n %}
{# Title #}
{% block title %}{% localize on %}Ban success{% endlocalize %}{% endblock %}
{# Body #}
{% block content %}
<div class="row" id="message">
{% localize on %}A ban has been created. This window will close in 1 second.{% endlocalize %}
</div>
<script>
function isIframe() {
try {
return window.self !== window.top;
} catch (_) {
return true;
}
}
setTimeout(function() {
if(isIframe()) {
window.top.banWindow.close();
} else {
window.close();
}
}, 1000);
</script>
{% endblock %}

View File

@@ -10,12 +10,18 @@ urlpatterns = [
path("<slug:url>/", BoardView.as_view(), name="board_detail"),
path("<slug:url>/page/<int:page>/", BoardView.as_view(), name="board_detail"),
path("<slug:url>/post/<int:id>/", PostView.as_view(), name="post_detail"),
path("<slug:url>/report/<int:id>/", ReportView.as_view(), name="report_form"),
path("report/<slug:url>/<int:id>/", ReportView.as_view(), name="report_form"),
path(
"report/success/",
TemplateView.as_view(template_name="board/report_success.html"),
name="report_success",
),
path("ban/<slug:url>/<int:id>/", BanCreateView.as_view(), name="ban_create"),
path(
"ban/success/",
BanSuccessView.as_view(),
name="ban_success",
),
path(
"banned",
BannedView.as_view(),

View File

@@ -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"