Prevent deletion of collections that are non-empty

Since the collections infrastructure itself does not know about the object types that
can exist within collections, this requires a new hook `describe_collection_contents`
to allow collection contents to be discovered.
pull/2243/merge
Matt Westcott 2016-02-23 17:39:52 +02:00
rodzic a10f56287c
commit 3195a5200f
7 zmienionych plików z 142 dodań i 5 usunięć

Wyświetl plik

@ -91,6 +91,23 @@ Hooks for building new areas of the admin interface (alongside pages, images, do
menu_items[:] = [item for item in menu_items if item.name != 'explorer']
.. _describe_collection_contents:
``describe_collection_contents``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Called when Wagtail needs to find out what objects exist in a collection, if any. Currently this happens on the confirmation before deleting a collection, to ensure that non-empty collections cannot be deleted. The callable passed to this hook will receive a ``collection`` object, and should return either ``None`` (to indicate no objects in this collection), or a dict containing the following keys:
``count``
A numeric count of items in this collection
``count_text``
A human-readable string describing the number of items in this collection, such as "3 documents". (Sites with multi-language support should return a translatable string here, most likely using the ``django.utils.translation.ungettext`` function.)
``url`` (optional)
A URL to an index page that lists the objects being described.
.. _register_admin_menu_item:
``register_admin_menu_item``

Wyświetl plik

@ -0,0 +1,24 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}{{ view.page_title }}{% endblock %}
{% block content %}
{% include "wagtailadmin/shared/header.html" with title=view.page_title subtitle=view.get_page_subtitle icon=view.header_icon %}
<div class="nice-padding">
<p>
{% trans 'This collection cannot be deleted, because it is not empty. It contains:' %}
</p>
<ul>
{% for item_type in collection_contents %}
<li>
{% if item_type.url %}
<a href="{{ item_type.url }}">{{ item_type.count_text }}</a>
{% else %}
{{ item_type.count_text }}
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endblock %}

Wyświetl plik

@ -4,6 +4,7 @@ from django.core.urlresolvers import reverse
from wagtail.tests.utils import WagtailTestUtils
from wagtail.wagtailcore.models import Collection
from wagtail.wagtaildocs.models import Document
class TestCollectionsIndexView(TestCase, WagtailTestUtils):
@ -131,6 +132,7 @@ class TestDeleteCollection(TestCase, WagtailTestUtils):
def test_get(self):
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailadmin/generic/confirm_delete.html')
def test_cannot_delete_root_collection(self):
response = self.get(collection_id=self.root_collection.id)
@ -140,6 +142,15 @@ class TestDeleteCollection(TestCase, WagtailTestUtils):
response = self.get(collection_id=100000)
self.assertEqual(response.status_code, 404)
def test_get_nonempty_collection(self):
Document.objects.create(
title="Test document", collection=self.collection
)
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailadmin/collections/delete_not_empty.html')
def test_post(self):
response = self.post()
@ -149,3 +160,14 @@ class TestDeleteCollection(TestCase, WagtailTestUtils):
# Check that the collection was deleted
with self.assertRaises(Collection.DoesNotExist):
Collection.objects.get(id=self.collection.id)
def test_post_nonempty_collection(self):
Document.objects.create(
title="Test document", collection=self.collection
)
response = self.post()
self.assertEqual(response.status_code, 403)
# Check that the collection was not deleted
self.assertTrue(Collection.objects.get(id=self.collection.id))

Wyświetl plik

@ -1,7 +1,11 @@
from django.http import HttpResponseForbidden
from django.shortcuts import redirect, get_object_or_404
from django.utils.translation import ugettext_lazy as __
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import Collection
from wagtail.wagtailcore.permissions import collection_permission_policy
from wagtail.wagtailadmin import messages
from wagtail.wagtailadmin.forms import CollectionForm
from wagtail.wagtailadmin.views.generic import IndexView, CreateView, EditView, DeleteView
@ -70,3 +74,36 @@ class Delete(DeleteView):
def get_queryset(self):
# Only return children of the root node, so that the root is not editable
return Collection.get_first_root_node().get_children()
def get_collection_contents(self):
collection_contents = [
hook(self.instance)
for hook in hooks.get_hooks('describe_collection_contents')
]
# filter out any hook responses that report that the collection is empty
# (by returning None, or a dict with 'count': 0)
is_nonempty = lambda item_type: item_type and item_type['count'] > 0
return list(filter(is_nonempty, collection_contents))
def get_context(self):
context = super(Delete, self).get_context()
collection_contents = self.get_collection_contents()
if collection_contents:
# collection is non-empty; render the 'not allowed to delete' response
self.template_name = 'wagtailadmin/collections/delete_not_empty.html'
context['collection_contents'] = collection_contents
return context
def post(self, request, instance_id):
self.instance = get_object_or_404(self.get_queryset(), id=instance_id)
collection_contents = self.get_collection_contents()
if collection_contents:
# collection is non-empty; refuse to delete it
return HttpResponseForbidden()
self.instance.delete()
messages.success(request, self.success_message.format(self.instance))
return redirect(self.index_url_name)

Wyświetl plik

@ -198,9 +198,7 @@ class DeleteView(PermissionCheckedMixin, View):
def get_delete_url(self):
return reverse(self.delete_url_name, args=(self.instance.id,))
def get(self, request, instance_id):
self.instance = get_object_or_404(self.get_queryset(), id=instance_id)
def get_context(self):
context = {
'view': self,
'object': self.instance,
@ -208,6 +206,13 @@ class DeleteView(PermissionCheckedMixin, View):
if self.context_object_name:
context[self.context_object_name] = self.instance
return context
def get(self, request, instance_id):
self.instance = get_object_or_404(self.get_queryset(), id=instance_id)
context = self.get_context()
return render(request, self.template_name, context)
def post(self, request, instance_id):

Wyświetl plik

@ -1,7 +1,7 @@
from django.conf.urls import include, url
from django.core import urlresolvers
from django.utils.html import format_html, format_html_join
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext_lazy as _, ungettext
from django.contrib.staticfiles.templatetags.staticfiles import static
from wagtail.wagtailcore import hooks
@ -101,3 +101,19 @@ def register_documents_search_area():
@hooks.register('register_group_permission_panel')
def register_document_permissions_panel():
return GroupDocumentPermissionFormSet
@hooks.register('describe_collection_contents')
def describe_collection_docs(collection):
docs_count = get_document_model().objects.filter(collection=collection).count()
if docs_count:
url = urlresolvers.reverse('wagtaildocs:index') + ('?collection_id=%d' % collection.id)
return {
'count': docs_count,
'count_text': ungettext(
"%(count)s document",
"%(count)s documents",
docs_count
) % {'count': docs_count},
'url': url,
}

Wyświetl plik

@ -1,7 +1,7 @@
from django.conf.urls import include, url
from django.core import urlresolvers
from django.utils.html import format_html, format_html_join
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext_lazy as _, ungettext
from django.contrib.staticfiles.templatetags.staticfiles import static
from wagtail.wagtailcore import hooks
@ -110,3 +110,19 @@ def register_images_search_area():
@hooks.register('register_group_permission_panel')
def register_image_permissions_panel():
return GroupImagePermissionFormSet
@hooks.register('describe_collection_contents')
def describe_collection_docs(collection):
images_count = get_image_model().objects.filter(collection=collection).count()
if images_count:
url = urlresolvers.reverse('wagtailimages:index') + ('?collection_id=%d' % collection.id)
return {
'count': images_count,
'count_text': ungettext(
"%(count)s image",
"%(count)s images",
images_count
) % {'count': images_count},
'url': url,
}