kopia lustrzana https://github.com/wagtail/wagtail
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
rodzic
a10f56287c
commit
3195a5200f
|
@ -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``
|
||||
|
|
|
@ -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 %}
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue