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:
@@ -1,8 +1,14 @@
|
|||||||
|
from django import forms
|
||||||
from django.contrib import admin
|
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 django.utils.safestring import mark_safe
|
||||||
from board.models import Ban, Board, Post, RangeBan, ReportReason, ReportRecord
|
from board.models import Ban, Board, Post, RangeBan, ReportReason, ReportRecord
|
||||||
|
|
||||||
|
#
|
||||||
|
# Admin sites
|
||||||
|
#
|
||||||
@admin.register(Board)
|
@admin.register(Board)
|
||||||
class BoardAdmin(admin.ModelAdmin):
|
class BoardAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
@@ -18,16 +24,23 @@ class ReportReasonAdmin(admin.ModelAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BanFromReportForm(forms.ModelForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ReportRecord)
|
@admin.register(ReportRecord)
|
||||||
class ReportRecordAdmin(admin.ModelAdmin):
|
class ReportRecordAdmin(admin.ModelAdmin):
|
||||||
ordering = (
|
ordering = (
|
||||||
"-urgent",
|
"-urgent",
|
||||||
"-weight",
|
"-weight",
|
||||||
)
|
)
|
||||||
readonly_fields = ("post",)
|
readonly_fields = ("post", "weight", "urgent")
|
||||||
list_display = ("post_thumbnail", "post_body")
|
list_display = ("post_id", "post_thumbnail", "post_body", "create_ban")
|
||||||
save_as = False
|
save_as = False
|
||||||
|
|
||||||
|
def post_id(self, obj):
|
||||||
|
return obj.post.id
|
||||||
|
|
||||||
def post_thumbnail(self, obj):
|
def post_thumbnail(self, obj):
|
||||||
if obj.post.thumbnail:
|
if obj.post.thumbnail:
|
||||||
return mark_safe(f'<img src="{obj.post.thumbnail.url}" />')
|
return mark_safe(f'<img src="{obj.post.thumbnail.url}" />')
|
||||||
@@ -41,11 +54,19 @@ class ReportRecordAdmin(admin.ModelAdmin):
|
|||||||
else:
|
else:
|
||||||
html += "<div>"
|
html += "<div>"
|
||||||
if obj.post.subject:
|
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 += f"<p>{obj.post.text}</p>"
|
||||||
html += "</div>"
|
html += "</div>"
|
||||||
return mark_safe(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'<a href="#" data-ban-url="{ban_url}" class="ban_link">Ban</a>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(RangeBan)
|
@admin.register(RangeBan)
|
||||||
class RangeBanAdmin(admin.ModelAdmin):
|
class RangeBanAdmin(admin.ModelAdmin):
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django import forms
|
||||||
from django.forms import ModelForm, ModelChoiceField
|
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
|
from hcaptcha.fields import hCaptchaField
|
||||||
|
|
||||||
|
|
||||||
@@ -74,3 +76,32 @@ class ReportForm(ModelForm):
|
|||||||
record = ReportRecord.objects.create(post=self.op)
|
record = ReportRecord.objects.create(post=self.op)
|
||||||
self.instance.record = record
|
self.instance.record = record
|
||||||
return super(ReportForm, self).clean()
|
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
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ class RangeBan(BanCommon):
|
|||||||
|
|
||||||
class Ban(BanCommon):
|
class Ban(BanCommon):
|
||||||
# IP address of the ban
|
# IP address of the ban
|
||||||
ip = models.GenericIPAddressField(unique=True)
|
ip = models.GenericIPAddressField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Ban for {self.ip}"
|
return f"Ban for {self.ip}"
|
||||||
|
|||||||
@@ -6,11 +6,16 @@ hr {
|
|||||||
color: #ededed;
|
color: #ededed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
float: left;
|
float: left;
|
||||||
width: 33.33%;
|
width: 33.33%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear floats after the columns */
|
/* Clear floats after the columns */
|
||||||
.row:after {
|
.row:after {
|
||||||
content: "";
|
content: "";
|
||||||
@@ -117,7 +122,7 @@ hr {
|
|||||||
/* Misc */
|
/* Misc */
|
||||||
|
|
||||||
a:link {
|
a:link {
|
||||||
color:#555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:visited {
|
a:visited {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "admin/change_list.html" %}
|
{% extends "admin/change_list.html" %}
|
||||||
|
{% load static %}
|
||||||
{% block extrastyle %}
|
{% block extrastyle %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<style>
|
<style>
|
||||||
@@ -9,5 +10,53 @@
|
|||||||
background-color: var(--message-error-bg);
|
background-color: var(--message-error-bg);
|
||||||
color: var(--error-fg);
|
color: var(--error-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wb-min {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wb-max {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wb-full {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</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 %}
|
||||||
50
board/templates/board/ban_form.html
Normal file
50
board/templates/board/ban_form.html
Normal 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 %}
|
||||||
29
board/templates/board/ban_success.html
Normal file
29
board/templates/board/ban_success.html
Normal 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 %}
|
||||||
@@ -10,12 +10,18 @@ urlpatterns = [
|
|||||||
path("<slug:url>/", BoardView.as_view(), name="board_detail"),
|
path("<slug:url>/", BoardView.as_view(), name="board_detail"),
|
||||||
path("<slug:url>/page/<int:page>/", 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>/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(
|
path(
|
||||||
"report/success/",
|
"report/success/",
|
||||||
TemplateView.as_view(template_name="board/report_success.html"),
|
TemplateView.as_view(template_name="board/report_success.html"),
|
||||||
name="report_success",
|
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(
|
path(
|
||||||
"banned",
|
"banned",
|
||||||
BannedView.as_view(),
|
BannedView.as_view(),
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
|
from typing import Any, Dict
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.views.generic.edit import CreateView
|
from django.views.generic.edit import CreateView
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from board.forms import PostForm, ReplyForm, ReportForm
|
from board.forms import BanForm, PostForm, ReplyForm, ReportForm
|
||||||
from board.models import Post, Board, Report
|
from board.models import Ban, Board, Post, Report
|
||||||
from board.utils import get_client_ip, get_ip_bans
|
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):
|
class BannedView(TemplateView):
|
||||||
@@ -37,18 +46,6 @@ class CreatePostView(CreateView):
|
|||||||
* self.board
|
* 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):
|
def get_form_kwargs(self):
|
||||||
kwargs = super(CreatePostView, self).get_form_kwargs()
|
kwargs = super(CreatePostView, self).get_form_kwargs()
|
||||||
kwargs["board"] = self.board
|
kwargs["board"] = self.board
|
||||||
@@ -56,7 +53,9 @@ class CreatePostView(CreateView):
|
|||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def dispatch(self, request, *args, **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)
|
ip = get_client_ip(request)
|
||||||
# Filter bans by board first
|
# Filter bans by board first
|
||||||
bans = [
|
bans = [
|
||||||
@@ -162,3 +161,47 @@ class ReportView(CreatePostView):
|
|||||||
post = get_object_or_404(Post, id=post_id)
|
post = get_object_or_404(Post, id=post_id)
|
||||||
kwargs["op"] = post
|
kwargs["op"] = post
|
||||||
return kwargs
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user