Add post and image deletion

This one was kind of a doozy. This also adds a custom 403 error page and
fixes some permission denied behavior that I was having issues with for
a while.

This is also set up in a way that hopefully will allow me to easily
implement user post deletion.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-07-13 15:19:20 -07:00
parent 6ff64a3299
commit 83533b5fb4
9 changed files with 147 additions and 3 deletions

View File

@@ -155,6 +155,10 @@ class BanForm(ModelForm):
self.instance.expires = expires
class PostDeleteForm(forms.Form):
image_only = forms.CharField(widget=forms.HiddenInput(), initial="0")
class PostModifyForm(ModelForm):
"""
A form used to modify the attributes of a post (sticky, locked, sink, etc)

View File

@@ -3,7 +3,8 @@ const OPEN = "open";
const CLOSED = "closed";
const replyWindowName = "reply-window";
const postWindowName = "post-window";
const reportWindowName = "report-window"
const reportWindowName = "report-window";
const deleteWindowName = "delete-window";
const WINDOW_INNER_PADDING = 25;
@@ -204,6 +205,31 @@ function openReportWindow(reportUrl) {
reportWindow.show();
}
////////////////////////////////////////////////////////////////////////////////
// Post delete window
////////////////////////////////////////////////////////////////////////////////
function getDeleteWindow() {
return window.top.jsFrame.getWindowByName(deleteWindowName);
}
function openDeleteWindow(deleteUrl) {
if (window.top.jsFrame.containsWindowName(deleteWindowName)) {
getDeleteWindow().closeFrame();
}
let deleteWindow = window.top.jsFrame.create({
title: "Deleting post",
name: deleteWindowName,
width: 475,
url: deleteUrl,
});
$(deleteWindow.iframe, "iframe").on("load", () => {
fitWindowToContent(deleteWindow);
deleteWindow.setResizable(false);
});
deleteWindow.show();
}
////////////////////////////////////////////////////////////////////////////////
// Post hiding
////////////////////////////////////////////////////////////////////////////////
@@ -365,4 +391,13 @@ window.menuItemFactories.push(
addHiddenPost(id);
}
})
);
window.menuItemFactories.push(
(postElement) => $("<a>")
.text("Delete post")
.attr("href", "#")
.on("click", (e) => {
e.preventDefault();
openDeleteWindow($(postElement).attr("data-delete-url"));
})
);

8
board/templates/403.html Normal file
View File

@@ -0,0 +1,8 @@
{% extends "board/base.html" %}
{% block title %}
Permission denied
{% endblock title %}
{% block content %}
<h3>You do not have permission to do this.</h3>
{% endblock content %}

View File

@@ -0,0 +1,23 @@
{% extends "board/base.html" %}
{% load i18n %}
{% block content %}
<form id="form" method="post" action="{% url 'board:post_delete' post.id %}">
{% csrf_token %}
<table>
<tr>
<td><input type="submit" value="{% translate 'Delete post' %}" /></td>
<td><input id="delete_image" type="submit" value="{% translate 'Delete image only' %}" /></td>
</tr>
</table>
{{form.image_only}}
</form>
<script>
$(document).on("click","#delete_image", (e) => {
e.preventDefault();
$("#{{form.image_only.auto_id}}").val(1);
$("#form").submit();
});
</script>
{% endblock content %}

View File

@@ -0,0 +1,33 @@
{% extends "board/base.html" %}
{% load i18n static %}
{# Title #}
{% block title %}{% translate "Post delete success" %}{% endblock %}
{# Body #}
{% block content %}
<div class="row" id="message">
{# We do not use pluralize filter for "seconds" because it's a pain to get it to translate. #}
{% blocktranslate %}Delete successful. This window will close in {{window_timeout}} second(s).{% endblocktranslate %}
</div>
<script>
function isIframe() {
try {
return window.self !== window.top;
} catch (_) {
return true;
}
}
setTimeout(function() {
if(isIframe()) {
let deleteWindow = getDeleteWindow();
if(deleteWindow) {
deleteWindow.closeFrame();
}
} else {
window.close();
}
}, 1000 * {{window_timeout}});
</script>
{% endblock %}

View File

@@ -4,6 +4,7 @@
id="p{{post.id}}"
class="post"
data-report-url="{% url 'board:report_form' board.url post.id %}"
data-delete-url="{% url 'board:post_delete' post.id %}"
{% if perms.board.add_ban %}
data-ban-url="{% url 'board:ban_create' board.url post.id %}"
{% endif %}

View File

@@ -19,12 +19,18 @@ urlpatterns = [
# Bans
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(), name="banned"),
path("banned/", BannedView.as_view(), name="banned"),
# Other moderation pages
path("modify/<int:pk>/", PostModifyView.as_view(), name="post_modify"),
path(
"modify/success/", PostModifySuccessView.as_view(), name="post_modify_success"
),
path("post/delete/<int:pk>/", PostDeleteView.as_view(), name="post_delete"),
path(
"post/delete/success/",
PostDeleteSuccessView.as_view(),
name="post_delete_success",
),
]
# 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)

View File

@@ -8,7 +8,7 @@ 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
from django.views.generic import detail, edit
from django.urls import reverse, reverse_lazy
from django.utils import timezone
@@ -28,6 +28,8 @@ __all__ = (
"PostModifySuccessView",
"PostView",
"PostSuccessView",
"PostDeleteView",
"PostDeleteSuccessView",
"ReplyCreateView",
"ReportView",
"ReportSuccessView",
@@ -163,6 +165,7 @@ class PostModifyView(PermissionRequiredMixin, edit.UpdateView):
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)
@@ -180,6 +183,7 @@ class PostModifyView(PermissionRequiredMixin, edit.UpdateView):
class PostModifySuccessView(PermissionRequiredMixin, TemplateView):
template_name = "board/post_modify_success.html"
raise_exception = True
def has_permission(self) -> bool:
return can_modify(self.request.user)
@@ -255,6 +259,33 @@ class PostSuccessView(TemplateView):
return context
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 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 PostDeleteSuccessView(TemplateView):
template_name = "board/post_delete_success.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["window_timeout"] = settings.BAN_WINDOW_CLOSE_TIMEOUT
return context
class ReportView(CreateView):
model = Report
form_class = ReportForm
@@ -290,6 +321,7 @@ class BanCreateView(PermissionRequiredMixin, edit.CreateView):
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)
@@ -330,6 +362,7 @@ class BanCreateView(PermissionRequiredMixin, edit.CreateView):
class BanSuccessView(PermissionRequiredMixin, TemplateView):
permission_required = "ban.create"
template_name = "board/ban_success.html"
raise_exception = True
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

View File

@@ -124,6 +124,7 @@ USE_I18N = True
USE_TZ = True
GUARDIAN_RENDER_403 = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/