From 192f998febea392392d1674d528aa563a0446eda Mon Sep 17 00:00:00 2001 From: HelenaTLK Date: Tue, 16 Feb 2021 16:34:26 +0100 Subject: [PATCH 1/2] Add hard delete action --- safedelete/admin.py | 81 ++++++++++++++++++- .../safedelete/hard_delete_confirmation.html | 31 +++++++ safedelete/tests/test_admin.py | 20 +++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 safedelete/templates/safedelete/hard_delete_confirmation.html diff --git a/safedelete/admin.py b/safedelete/admin.py index afeff2e..c1a4240 100644 --- a/safedelete/admin.py +++ b/safedelete/admin.py @@ -14,6 +14,7 @@ from django.utils.translation import ugettext_lazy as _ from .utils import related_objects +from .models import HARD_DELETE # Django 3.0 compatibility try: @@ -48,11 +49,12 @@ class SafeDeleteAdmin(admin.ModelAdmin): ... list_filter = ("last_name",) + SafeDeleteAdmin.list_filter """ undelete_selected_confirmation_template = "safedelete/undelete_selected_confirmation.html" + hard_delete_confirmation_template = "safedelete/hard_delete_confirmation.html" list_display = ('deleted',) list_filter = ('deleted',) exclude = ('deleted',) - actions = ('undelete_selected',) + actions = ('undelete_selected', 'hard_delete') class Meta: abstract = True @@ -165,3 +167,80 @@ def undelete_selected(self, request, queryset): context, ) undelete_selected.short_description = _("Undelete selected %(verbose_name_plural)s.") + + def hard_delete(self, request, queryset): + """ Admin action to hard delete objects. """ + if not self.has_delete_permission(request): + raise PermissionDenied + + original_queryset = queryset.all() + undeleted_queryset = queryset.filter(deleted__isnull=True) + queryset = queryset.filter(deleted__isnull=False) + + if request.POST.get('post'): + requested = original_queryset.count() + changed = queryset.count() + + if changed: + for obj in queryset: + obj.delete(force_policy=HARD_DELETE) + if requested > changed: + self.message_user( + request, + "Successfully hard deleted %(count_changed)d of the " + "%(count_requested)d selected %(items)s." % { + "count_requested": requested, + "count_changed": changed, + "items": model_ngettext(self.opts, requested) + }, + messages.WARNING, + ) + else: + self.message_user( + request, + "Successfully hard deleted %(count)d %(items)s." % { + "count": changed, + "items": model_ngettext(self.opts, requested) + }, + messages.SUCCESS, + ) + else: + self.message_user( + request, + "No permission for hard delete. Execute soft delete first.", + messages.ERROR + ) + return None + + opts = self.model._meta + if len(original_queryset) == 1: + objects_name = force_text(opts.verbose_name) + else: + objects_name = force_text(opts.verbose_name_plural) + title = "Are you sure?" + + deletable_objects, model_count, perms_needed, protected = self.get_deleted_objects(queryset, request) + + context = { + 'title': title, + 'objects_name': objects_name, + 'queryset': original_queryset, + 'deletable_queryset': queryset, + 'undeleted_queryset': undeleted_queryset, + 'opts': opts, + 'app_label': opts.app_label, + 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, + 'model_count': dict(model_count).items(), + 'deletable_objects': [deletable_objects], + 'perms_lacking': perms_needed, + 'protected': protected, + 'media': self.media, + } + + return TemplateResponse( + request, + self.hard_delete_confirmation_template, + context, + ) + + hard_delete.short_description = "Hard delete selected %(verbose_name_plural)s." diff --git a/safedelete/templates/safedelete/hard_delete_confirmation.html b/safedelete/templates/safedelete/hard_delete_confirmation.html new file mode 100644 index 0000000..4347654 --- /dev/null +++ b/safedelete/templates/safedelete/hard_delete_confirmation.html @@ -0,0 +1,31 @@ +{% extends "admin/delete_selected_confirmation.html" %} +{% load i18n l10n %} + +{% block object-tools %}{% endblock %} + +{% block content %} +
+

{% blocktrans %}Are you sure you want to hard delete the selected {{ objects_name }}?{% endblocktrans %}

+{% include "admin/includes/object_delete_summary.html" %} +
{% csrf_token %} +
+ {% for obj in queryset %} + + {% endfor %} + +

{% translate "Objects" %}

+ {% for deletable_object in deletable_objects %} +
    {{ deletable_object|unordered_list }}
+ {% endfor %} + +

{% blocktrans %}The following {{ objects_name }} are not soft deleted yet and cannot be hard deleted. {% endblocktrans %}

+
    {{ undeleted_queryset|unordered_list }}
+ + + + + {% translate "No, take me back" %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/safedelete/tests/test_admin.py b/safedelete/tests/test_admin.py index 31b9c1a..bc3535b 100644 --- a/safedelete/tests/test_admin.py +++ b/safedelete/tests/test_admin.py @@ -122,3 +122,23 @@ def test_admin_undelete_action(self): pk=self.categories[1].pk ) self.assertFalse(category.deleted) + + def test_admin_hard_delete_action(self): + """Test objects are hard deleted and action is logged.""" + resp = self.client.post('/admin/safedelete/category/', data={ + 'index': 0, + 'action': ['hard_delete'], + '_selected_action': [self.categories[1].pk], + }) + self.assertTemplateUsed(resp, 'safedelete/hard_delete_confirmation.html') + self.assertTrue(self.categories[1].deleted) + + resp = self.client.post('/admin/safedelete/category/', data={ + 'index': 0, + 'action': ['hard_delete'], + 'post': True, + '_selected_action': [self.categories[1].pk], + }) + + with self.assertRaises(Category.DoesNotExist): + Category.objects.get(pk=self.categories[1].pk) From ded70ace4b51382d569ff6e4e884642c13fffcf2 Mon Sep 17 00:00:00 2001 From: HelenaTLK Date: Tue, 23 Feb 2021 13:40:49 +0100 Subject: [PATCH 2/2] Flake8 continuation lines --- safedelete/admin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/safedelete/admin.py b/safedelete/admin.py index c1a4240..5a5cdef 100644 --- a/safedelete/admin.py +++ b/safedelete/admin.py @@ -238,9 +238,9 @@ def hard_delete(self, request, queryset): } return TemplateResponse( - request, - self.hard_delete_confirmation_template, - context, - ) + request, + self.hard_delete_confirmation_template, + context, + ) hard_delete.short_description = "Hard delete selected %(verbose_name_plural)s."