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:
@@ -155,6 +155,10 @@ class BanForm(ModelForm):
|
|||||||
self.instance.expires = expires
|
self.instance.expires = expires
|
||||||
|
|
||||||
|
|
||||||
|
class PostDeleteForm(forms.Form):
|
||||||
|
image_only = forms.CharField(widget=forms.HiddenInput(), initial="0")
|
||||||
|
|
||||||
|
|
||||||
class PostModifyForm(ModelForm):
|
class PostModifyForm(ModelForm):
|
||||||
"""
|
"""
|
||||||
A form used to modify the attributes of a post (sticky, locked, sink, etc)
|
A form used to modify the attributes of a post (sticky, locked, sink, etc)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ const OPEN = "open";
|
|||||||
const CLOSED = "closed";
|
const CLOSED = "closed";
|
||||||
const replyWindowName = "reply-window";
|
const replyWindowName = "reply-window";
|
||||||
const postWindowName = "post-window";
|
const postWindowName = "post-window";
|
||||||
const reportWindowName = "report-window"
|
const reportWindowName = "report-window";
|
||||||
|
const deleteWindowName = "delete-window";
|
||||||
const WINDOW_INNER_PADDING = 25;
|
const WINDOW_INNER_PADDING = 25;
|
||||||
|
|
||||||
|
|
||||||
@@ -204,6 +205,31 @@ function openReportWindow(reportUrl) {
|
|||||||
reportWindow.show();
|
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
|
// Post hiding
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -365,4 +391,13 @@ window.menuItemFactories.push(
|
|||||||
addHiddenPost(id);
|
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
8
board/templates/403.html
Normal 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 %}
|
||||||
23
board/templates/board/post_confirm_delete.html
Normal file
23
board/templates/board/post_confirm_delete.html
Normal 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 %}
|
||||||
33
board/templates/board/post_delete_success.html
Normal file
33
board/templates/board/post_delete_success.html
Normal 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 %}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
id="p{{post.id}}"
|
id="p{{post.id}}"
|
||||||
class="post"
|
class="post"
|
||||||
data-report-url="{% url 'board:report_form' board.url post.id %}"
|
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 %}
|
{% if perms.board.add_ban %}
|
||||||
data-ban-url="{% url 'board:ban_create' board.url post.id %}"
|
data-ban-url="{% url 'board:ban_create' board.url post.id %}"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -19,12 +19,18 @@ urlpatterns = [
|
|||||||
# Bans
|
# Bans
|
||||||
path("ban/<slug:url>/<int:id>/", BanCreateView.as_view(), name="ban_create"),
|
path("ban/<slug:url>/<int:id>/", BanCreateView.as_view(), name="ban_create"),
|
||||||
path("ban/success/", BanSuccessView.as_view(), name="ban_success"),
|
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
|
# Other moderation pages
|
||||||
path("modify/<int:pk>/", PostModifyView.as_view(), name="post_modify"),
|
path("modify/<int:pk>/", PostModifyView.as_view(), name="post_modify"),
|
||||||
path(
|
path(
|
||||||
"modify/success/", PostModifySuccessView.as_view(), name="post_modify_success"
|
"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
|
# 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)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.http import Http404, HttpResponseRedirect
|
|||||||
from django.http.request import QueryDict
|
from django.http.request import QueryDict
|
||||||
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 import edit
|
from django.views.generic import detail, edit
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
@@ -28,6 +28,8 @@ __all__ = (
|
|||||||
"PostModifySuccessView",
|
"PostModifySuccessView",
|
||||||
"PostView",
|
"PostView",
|
||||||
"PostSuccessView",
|
"PostSuccessView",
|
||||||
|
"PostDeleteView",
|
||||||
|
"PostDeleteSuccessView",
|
||||||
"ReplyCreateView",
|
"ReplyCreateView",
|
||||||
"ReportView",
|
"ReportView",
|
||||||
"ReportSuccessView",
|
"ReportSuccessView",
|
||||||
@@ -163,6 +165,7 @@ class PostModifyView(PermissionRequiredMixin, edit.UpdateView):
|
|||||||
form_class = PostModifyForm
|
form_class = PostModifyForm
|
||||||
template_name = "board/post_modify.html"
|
template_name = "board/post_modify.html"
|
||||||
success_url = reverse_lazy("board:post_modify_success")
|
success_url = reverse_lazy("board:post_modify_success")
|
||||||
|
raise_exception = True
|
||||||
|
|
||||||
def has_permission(self) -> bool:
|
def has_permission(self) -> bool:
|
||||||
return can_modify(self.request.user)
|
return can_modify(self.request.user)
|
||||||
@@ -180,6 +183,7 @@ class PostModifyView(PermissionRequiredMixin, edit.UpdateView):
|
|||||||
|
|
||||||
class PostModifySuccessView(PermissionRequiredMixin, TemplateView):
|
class PostModifySuccessView(PermissionRequiredMixin, TemplateView):
|
||||||
template_name = "board/post_modify_success.html"
|
template_name = "board/post_modify_success.html"
|
||||||
|
raise_exception = True
|
||||||
|
|
||||||
def has_permission(self) -> bool:
|
def has_permission(self) -> bool:
|
||||||
return can_modify(self.request.user)
|
return can_modify(self.request.user)
|
||||||
@@ -255,6 +259,33 @@ class PostSuccessView(TemplateView):
|
|||||||
return context
|
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):
|
class ReportView(CreateView):
|
||||||
model = Report
|
model = Report
|
||||||
form_class = ReportForm
|
form_class = ReportForm
|
||||||
@@ -290,6 +321,7 @@ class BanCreateView(PermissionRequiredMixin, edit.CreateView):
|
|||||||
form_class = BanForm
|
form_class = BanForm
|
||||||
permission_required = "board.add_ban"
|
permission_required = "board.add_ban"
|
||||||
success_url = reverse_lazy("board:ban_success")
|
success_url = reverse_lazy("board:ban_success")
|
||||||
|
raise_exception = True
|
||||||
|
|
||||||
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
@@ -330,6 +362,7 @@ class BanCreateView(PermissionRequiredMixin, edit.CreateView):
|
|||||||
class BanSuccessView(PermissionRequiredMixin, TemplateView):
|
class BanSuccessView(PermissionRequiredMixin, TemplateView):
|
||||||
permission_required = "ban.create"
|
permission_required = "ban.create"
|
||||||
template_name = "board/ban_success.html"
|
template_name = "board/ban_success.html"
|
||||||
|
raise_exception = True
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ USE_I18N = True
|
|||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
GUARDIAN_RENDER_403 = True
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||||
|
|||||||
Reference in New Issue
Block a user