Add preliminary report system
The report system is pretty low-tech. However the scaffolding is there for a lot of new stuff. What we currently have: * Users can create reports * Staff can view reports * Admins can create report templates There's a post drop-down menu available on all posts now, too. This is where "report post" menu item lives and other things like that can be added too. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from board.models import Post
|
from board.models import Post, Report
|
||||||
from hcaptcha.fields import hCaptchaField
|
from hcaptcha.fields import hCaptchaField
|
||||||
|
|
||||||
|
|
||||||
@@ -41,3 +41,20 @@ class ReplyForm(PostForm):
|
|||||||
super(ReplyForm, self).__init__(*args, **kwargs)
|
super(ReplyForm, self).__init__(*args, **kwargs)
|
||||||
self.instance.op = op
|
self.instance.op = op
|
||||||
self.instance.reply = reply
|
self.instance.reply = reply
|
||||||
|
|
||||||
|
|
||||||
|
class ReportForm(ModelForm):
|
||||||
|
"""
|
||||||
|
A form used to create reports on posts.
|
||||||
|
|
||||||
|
This requires the board and the IP address to be specified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Report
|
||||||
|
fields = ["reason"]
|
||||||
|
|
||||||
|
def __init__(self, *args, op, board, ip, **kwargs):
|
||||||
|
super(ReportForm, self).__init__(*args, **kwargs)
|
||||||
|
self.instance.ip = ip
|
||||||
|
self.instance.post = op
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ class ReportReason(models.Model):
|
|||||||
# Urgency. If true, this post is probably reported as illegal content.
|
# Urgency. If true, this post is probably reported as illegal content.
|
||||||
urgent = models.BooleanField(default=False)
|
urgent = models.BooleanField(default=False)
|
||||||
# This is a board-specific report reason
|
# This is a board-specific report reason
|
||||||
board = models.ForeignKey("Board", on_delete=models.CASCADE, null=True)
|
board = models.ForeignKey("Board", on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.reason
|
return self.reason
|
||||||
|
|||||||
@@ -1,9 +1,81 @@
|
|||||||
function doQuote(sender) {
|
const OPEN = "open";
|
||||||
let id_text = $("#id_text");
|
const CLOSED = "closed";
|
||||||
let caret = id_text[0].selectionStart;
|
|
||||||
let text = id_text.val();
|
function documentClick(e) {
|
||||||
let to_add = ">>" + sender.target.innerText + "\n";
|
let sender = e.target;
|
||||||
id_text.val(text.substring(0, caret) + to_add + text.substring(caret));
|
let id = sender.getAttribute("data-id");
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
case "post_menu_button": {
|
||||||
|
openMenu(e);
|
||||||
|
}; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doQuote(e) {
|
||||||
|
let idText = $("#id_text");
|
||||||
|
let caret = idText[0].selectionStart;
|
||||||
|
let text = idText.val();
|
||||||
|
let toAdd = ">>" + e.target.innerText + "\n";
|
||||||
|
idText.val(text.substring(0, caret) + toAdd + text.substring(caret));
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu(e) {
|
||||||
|
$(document).off("click", closeMenu);
|
||||||
|
$(".post_menu").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMenu(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let sender = e.target;
|
||||||
|
let reportButton = $("<a>")
|
||||||
|
.text("Report")
|
||||||
|
.attr("href", "#")
|
||||||
|
.on("click", (e) => { return openReportWindow(e, $(sender.parentElement)); });
|
||||||
|
let menuList = $("<ul></ul>")
|
||||||
|
.append($("<lh><strong>Actions</strong></lh>").addClass("post_menu_item"))
|
||||||
|
.append(
|
||||||
|
$('<li></li>')
|
||||||
|
.addClass("post_menu_item")
|
||||||
|
.append(reportButton)
|
||||||
|
)
|
||||||
|
.addClass("post_menu_items");
|
||||||
|
let rect = sender.getBoundingClientRect();
|
||||||
|
let menu = $("<div></div>")
|
||||||
|
.addClass("post_menu")
|
||||||
|
.css({
|
||||||
|
top: rect.bottom + 3 + window.pageYOffset + "px",
|
||||||
|
left: rect.left + 3 + window.pageXOffset + "px",
|
||||||
|
})
|
||||||
|
.append(menuList);
|
||||||
|
$("body").append(menu);
|
||||||
|
$(document).on("click", closeMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openReportWindow(e, postElement) {
|
||||||
|
e.preventDefault();
|
||||||
|
// If there's already a report window open, close it and open this one.
|
||||||
|
if (window.top.reportWindow) {
|
||||||
|
window.top.reportWindow.close();
|
||||||
|
}
|
||||||
|
//let postId = sender.parentElement.getAttribute("id").substring(1);
|
||||||
|
let reportUrl = postElement.attr("data-report-url");
|
||||||
|
window.reportWindow = new WinBox("New Report", {
|
||||||
|
url: reportUrl,
|
||||||
|
modal: true,
|
||||||
|
onclose: function (force) {
|
||||||
|
window.top.reportWindow = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoad(e) {
|
||||||
|
window.reportWindow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on("click", documentClick);
|
||||||
$(document).on("click", ".post_id", doQuote);
|
$(document).on("click", ".post_id", doQuote);
|
||||||
|
$(window).on("load", onLoad);
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
body {
|
||||||
|
background-color: #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
color: #ededed;
|
color: #ededed;
|
||||||
}
|
}
|
||||||
@@ -26,7 +30,29 @@ hr {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Posts */
|
.post_menu {
|
||||||
|
position: absolute;
|
||||||
|
background: #ededed;
|
||||||
|
outline: 1px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post_menu_items {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post_menu_item {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post_menu_button {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
Posts
|
||||||
|
********************************************************************************/
|
||||||
/*.post_body { }*/
|
/*.post_body { }*/
|
||||||
.post_image_info {
|
.post_image_info {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% load post_body %}
|
{% load post_body %}
|
||||||
{% load l10n %}
|
{% load l10n %}
|
||||||
<div id="p{{post.id}}">
|
<div id="p{{post.id}}" data-report-url="{% url 'board:report_form' board.url post.id %}">
|
||||||
{# Image #}
|
{# Image #}
|
||||||
{% if post.thumbnail %}
|
{% if post.thumbnail %}
|
||||||
{# Image info #}
|
{# Image info #}
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Post ID, username, time #}
|
{# Post ID, username, time #}
|
||||||
<div class="post_content">
|
|
||||||
<a href="#p{{post.id}}">#.</a>
|
<a href="#p{{post.id}}">#.</a>
|
||||||
<span class="post_id">{{post.id}}</span>
|
<span class="post_id">{{post.id}}</span>
|
||||||
{% if post.subject %}
|
{% if post.subject %}
|
||||||
@@ -32,8 +31,10 @@
|
|||||||
{% if reply_link %}
|
{% if reply_link %}
|
||||||
[<a href="{{post.get_absolute_url}}">{% localize on %}Reply{% endlocalize %}</a>]
|
[<a href="{{post.get_absolute_url}}">{% localize on %}Reply{% endlocalize %}</a>]
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="#" class="post_menu_button" data-id="post_menu_button">▶</a>
|
||||||
|
|
||||||
{# "X replies elided" dialog for OPs on the board #}
|
{# "X replies elided" dialog for OPs on the board #}
|
||||||
|
<div class="post_content">
|
||||||
{% if replies_elided > 0 %}
|
{% if replies_elided > 0 %}
|
||||||
<br/>
|
<br/>
|
||||||
<span class="replies_elided">
|
<span class="replies_elided">
|
||||||
|
|||||||
16
board/templates/board/report_form.html
Normal file
16
board/templates/board/report_form.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% extends "board/base.html" %}
|
||||||
|
{% load l10n %}
|
||||||
|
{# Title #}
|
||||||
|
{% block title %}{% localize on %}Reporting post {{post.id}}{% endlocalize %}{% endblock %}
|
||||||
|
{# Body #}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr><td> </td><td><input type="submit" value="Submit"></td></tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
29
board/templates/board/report_success.html
Normal file
29
board/templates/board/report_success.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "board/base.html" %}
|
||||||
|
{% load l10n %}
|
||||||
|
{# Title #}
|
||||||
|
{% block title %}{% localize on %}Report success{% endlocalize %}{% endblock %}
|
||||||
|
{# Body #}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row" id="message">
|
||||||
|
{% localize on %}Post reported. 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.reportWindow.close();
|
||||||
|
} else {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
from board.views import *
|
from board.views import *
|
||||||
|
|
||||||
|
|
||||||
@@ -9,6 +10,12 @@ 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/success/",
|
||||||
|
TemplateView.as_view(template_name="board/report_success.html"),
|
||||||
|
name="report_success",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
# TODO - make this conditional so we can serve images up with whatever server we want
|
# TODO - make this conditional so we can serve images up with whatever server we want
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ from django.http import Http404, HttpResponseRedirect
|
|||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.views.generic import DetailView
|
from django.views.generic import DetailView
|
||||||
from django.views.generic.edit import CreateView
|
from django.views.generic.edit import CreateView
|
||||||
from django.urls import reverse
|
from django.urls import reverse, reverse_lazy
|
||||||
from board.models import Post, Board
|
from board.models import Post, Board, Report
|
||||||
from board.forms import PostForm, ReplyForm
|
from board.forms import PostForm, ReplyForm, ReportForm
|
||||||
|
|
||||||
__all__ = ("BoardView", "PostView")
|
__all__ = ("BoardView", "PostView", "ReportView")
|
||||||
|
|
||||||
|
|
||||||
def get_client_ip(request):
|
def get_client_ip(request):
|
||||||
@@ -107,3 +107,27 @@ class PostView(CreatePostView):
|
|||||||
kwargs["op"] = post
|
kwargs["op"] = post
|
||||||
kwargs["reply"] = post
|
kwargs["reply"] = post
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class ReportView(CreatePostView):
|
||||||
|
model = Report
|
||||||
|
form_class = ReportForm
|
||||||
|
success_url = reverse_lazy("board:report_success")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
return super(ReportView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
@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
|
||||||
|
|||||||
Reference in New Issue
Block a user