Fix crash when deleting a single snippet using the bulk actions interface (#10447)

Fixes #10441
pull/10454/head
Sage Abdullah 2023-05-16 14:18:38 +01:00 zatwierdzone przez Matt Westcott
rodzic efb75c0ae3
commit cc30fd3a13
5 zmienionych plików z 65 dodań i 14 usunięć

Wyświetl plik

@ -21,6 +21,7 @@ Changelog
* Fix: Rectify previous fix for TableBlock becoming uneditable after save (Sage Abdullah)
* Fix: Ensure that copying page correctly picks up the latest revision (Matt Westcott)
* Fix: Ensure comment buttons always respect `WAGTAILADMIN_COMMENTS_ENABLED` (Thibaud Colas)
* Fix: Fix error when deleting a single snippet through the bulk actions interface (Sage Abdullah)
* Docs: Update documentation for `log_action` parameter on `RevisionMixin.save_revision` (Christer Jensen)

Wyświetl plik

@ -16,6 +16,7 @@ depth: 1
* Rectify previous fix for TableBlock becoming uneditable after save (Sage Abdullah)
* Ensure that copying page correctly picks up the latest revision (Matt Westcott)
* Ensure comment buttons always respect `WAGTAILADMIN_COMMENTS_ENABLED` (Thibaud Colas)
* Fix error when deleting a single snippet through the bulk actions interface (Sage Abdullah)
### Documentation

Wyświetl plik

@ -1,9 +1,12 @@
from django.contrib.admin.utils import quote
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from wagtail.admin.views.generic import BeforeAfterHookMixin
from wagtail.models import ReferenceIndex
from wagtail.snippets.bulk_actions.snippet_bulk_action import SnippetBulkAction
from wagtail.snippets.permissions import get_permission_name
@ -61,6 +64,25 @@ class DeleteBulkAction(BeforeAfterHookMixin, SnippetBulkAction):
).delete()
return len(objects), 0
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add usage information to the context only if there is a single item
if len(context["items"]) == 1:
item_context = context["items"][0]
item = item_context["item"]
item_context.update(
{
"usage_count": (
ReferenceIndex.get_grouped_references_to(item).count()
),
"usage_url": reverse(
item.snippet_viewset.get_url_name("usage"),
args=(quote(item.pk),),
),
}
)
return context
def get_success_message(self, num_parent_objects, num_child_objects):
if num_parent_objects == 1:
return _("%(model_name)s '%(object)s' deleted.") % {

Wyświetl plik

@ -3,7 +3,7 @@
{% block titletag %}
{% if items|length == 1 %}
{% blocktrans trimmed with snippet_type_name=model_opts.verbose_name %}Delete {{ snippet_type_name }}{% endblocktrans %} - {{ items|first }}
{% blocktrans trimmed with snippet_type_name=model_opts.verbose_name %}Delete {{ snippet_type_name }}{% endblocktrans %} - {{ items.0.item }}
{% else %}
{{ items|length }} {{ model_opts.verbose_name_plural|capfirst }}
{% endif %}
@ -22,7 +22,7 @@
{% if items %}
{% if items|length == 1 %}
<div class="usagecount">
<a href="{{ items.0.item.usage_url }}">{% blocktrans trimmed count usage_count=items.0.item.get_usage.count %}Used {{ usage_count }} time{% plural %}Used {{ usage_count }} times{% endblocktrans %}</a>
<a href="{{ items.0.usage_url }}">{% blocktrans trimmed count usage_count=items.0.usage_count %}Used {{ usage_count }} time{% plural %}Used {{ usage_count }} times{% endblocktrans %}</a>
</div>
<p>{% blocktrans trimmed with snippet_type_name=model_opts.verbose_name %}Are you sure you want to delete this {{ snippet_type_name }}?{% endblocktrans %}</p>
{% else %}

Wyświetl plik

@ -1,3 +1,4 @@
from django.contrib.admin.utils import quote
from django.contrib.auth.models import Permission
from django.http import HttpRequest, HttpResponse
from django.test import TestCase
@ -32,11 +33,13 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
)
+ "?"
)
for snippet in self.test_snippets:
self.url += f"id={snippet.pk}&"
def get_url(self, items=()):
items = items or self.test_snippets
return self.url + "&".join(f"id={item.pk}" for item in items)
def test_simple(self):
response = self.client.get(self.url)
response = self.client.get(self.get_url())
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailsnippets/bulk_actions/confirm_bulk_delete.html"
@ -45,8 +48,32 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
self.assertEqual(response.context["header_icon"], "cog")
self.assertContains(response, "icon icon-cog", count=1)
def test_get_single_delete(self):
item = self.test_snippets[0]
response = self.client.get(self.get_url(items=(item,)))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailsnippets/bulk_actions/confirm_bulk_delete.html"
)
self.assertTemplateUsed(response, "wagtailadmin/shared/header.html")
self.assertEqual(response.context["header_icon"], "cog")
self.assertContains(response, "icon icon-cog", count=1)
self.assertContains(
response,
"<title>Delete full-featured snippet - Title-1 - Wagtail</title>",
html=True,
)
self.assertContains(
response,
reverse(
self.snippet_model.snippet_viewset.get_url_name("usage"),
args=(quote(item.pk),),
),
)
self.assertContains(response, "Used 0 times")
def test_bulk_delete(self):
response = self.client.post(self.url)
response = self.client.post(self.get_url())
# Should redirect back to index
self.assertEqual(response.status_code, 302)
@ -64,7 +91,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
)
self.user.save()
response = self.client.get(self.url)
response = self.client.get(self.get_url())
self.assertEqual(response.status_code, 200)
html = response.content.decode()
@ -76,7 +103,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
for snippet in self.test_snippets:
self.assertInHTML(f"<li>{snippet.text}</li>", html)
response = self.client.post(self.url)
response = self.client.post(self.get_url())
# User should be redirected back to the index
self.assertEqual(response.status_code, 302)
@ -88,7 +115,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
with self.register_hook(
"before_bulk_action", lambda *args: HttpResponse("Overridden!")
):
response = self.client.get(self.url)
response = self.client.get(self.get_url())
self.assertEqual(response.status_code, 200)
@ -113,7 +140,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
return HttpResponse("Overridden!")
with self.register_hook("before_bulk_action", hook_func):
response = self.client.post(self.url)
response = self.client.post(self.get_url())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
@ -136,7 +163,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
return HttpResponse("Overridden!")
with self.register_hook("after_bulk_action", hook_func):
response = self.client.post(self.url)
response = self.client.post(self.get_url())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
@ -159,7 +186,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
return HttpResponse("Overridden!")
with self.register_hook("before_delete_snippet", hook_func):
response = self.client.get(self.url)
response = self.client.get(self.get_url())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
@ -180,7 +207,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
return HttpResponse("Overridden!")
with self.register_hook("before_delete_snippet", hook_func):
response = self.client.post(self.url)
response = self.client.post(self.get_url())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
@ -201,7 +228,7 @@ class TestSnippetDeleteView(WagtailTestUtils, TestCase):
return HttpResponse("Overridden!")
with self.register_hook("after_delete_snippet", hook_func):
response = self.client.post(self.url)
response = self.client.post(self.get_url())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")