From f6338abf14ae1e43e213b13df0293931cb2543c0 Mon Sep 17 00:00:00 2001
From: "Jamie (Bear) Murphy" <1613241+ITJamie@users.noreply.github.com>
Date: Wed, 1 Nov 2023 19:13:45 +0000
Subject: [PATCH] Closes #13690: List all objects to be deleted (#14089)
* show objects that would be deleted by cascade
* some items were not showing (eg ips on devices)
* dont include the item being deleted in the list of related items
* Revert "dont include the item being deleted in the list of related items"
This reverts commit 298a7860b20c2fd90e887c66c4f196460097e71e.
* cleanup
- migrate code to use collector directly instead of the NestedObjects wrapper from admin.utils
- adjust object names and text output
* requested adjustments
* remove comma from end of list
* linting
* refactor, add accordion
* migrate to defaultdict, use title for capitalisation of accordian titles
* Misc cleanup
---------
Co-authored-by: Jeremy Stretch
---
netbox/netbox/views/generic/object_views.py | 28 ++++++++++++++++-
netbox/templates/htmx/delete_form.html | 34 +++++++++++++++++++++
2 files changed, 61 insertions(+), 1 deletion(-)
diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py
index 7c737aaf0..99508c9e3 100644
--- a/netbox/netbox/views/generic/object_views.py
+++ b/netbox/netbox/views/generic/object_views.py
@@ -1,9 +1,11 @@
import logging
+from collections import defaultdict
from copy import deepcopy
from django.contrib import messages
-from django.db import transaction
+from django.db import router, transaction
from django.db.models import ProtectedError, RestrictedError
+from django.db.models.deletion import Collector
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.html import escape
@@ -320,6 +322,27 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
def get_required_permission(self):
return get_permission_for_model(self.queryset.model, 'delete')
+ def _get_dependent_objects(self, obj):
+ """
+ Returns a dictionary mapping of dependent objects (organized by model) which will be deleted as a result of
+ deleting the requested object.
+
+ Args:
+ obj: The object to return dependent objects for
+ """
+ using = router.db_for_write(obj._meta.model)
+ collector = Collector(using=using)
+ collector.collect([obj])
+
+ # Compile a mapping of models to instances
+ dependent_objects = defaultdict(list)
+ for model, instance in collector.instances_with_model():
+ # Omit the root object
+ if instance != obj:
+ dependent_objects[model].append(instance)
+
+ return dict(dependent_objects)
+
#
# Request handlers
#
@@ -333,6 +356,7 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
"""
obj = self.get_object(**kwargs)
form = ConfirmationForm(initial=request.GET)
+ dependent_objects = self._get_dependent_objects(obj)
# If this is an HTMX request, return only the rendered deletion form as modal content
if is_htmx(request):
@@ -343,6 +367,7 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
'object_type': self.queryset.model._meta.verbose_name,
'form': form,
'form_url': form_url,
+ 'dependent_objects': dependent_objects,
**self.get_extra_context(request, obj),
})
@@ -350,6 +375,7 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
'object': obj,
'form': form,
'return_url': self.get_return_url(request, obj),
+ 'dependent_objects': dependent_objects,
**self.get_extra_context(request, obj),
})
diff --git a/netbox/templates/htmx/delete_form.html b/netbox/templates/htmx/delete_form.html
index 15f08ebfd..80aec2c82 100644
--- a/netbox/templates/htmx/delete_form.html
+++ b/netbox/templates/htmx/delete_form.html
@@ -12,6 +12,40 @@
Are you sure you want to delete {{ object_type }} {{ object }}?
{% endblocktrans %}
+ {% if dependent_objects %}
+
+ {% trans "The following objects will be deleted as a result of this action." %}
+
+
+ {% for model, instances in dependent_objects.items %}
+
+
+
+
+
+ {% for instance in instances %}
+ {% with url=instance.get_absolute_url %}
+
{{ instance }}
+ {% endwith %}
+ {% endfor %}
+
+
+
+
+ {% endfor %}
+
+ {% endif %}
{% render_form form %}