diff --git a/netbox/extras/tests/test_scripts_deletion.py b/netbox/extras/tests/test_scripts_deletion.py index 5b928bd0d..1d5c92581 100644 --- a/netbox/extras/tests/test_scripts_deletion.py +++ b/netbox/extras/tests/test_scripts_deletion.py @@ -199,6 +199,16 @@ class ConfirmCollectorTestCase(TestCase): self.assertEqual(list(wrapper), []) self.assertTrue(wrapper.count_only) + def test_confirm_collector_omits_jobs_when_none(self): + # A jobless object must not record a zero count, or the confirmation page would show a + # spurious "0 jobs" row (#22812 regression). + datasource = DataSource.objects.create(name='DS', type='local', source_url='/tmp/test') + + collector = ConfirmCollector(using=router.db_for_write(DataSource)) + collector.collect([datasource]) + + self.assertNotIn(Job, collector.generic_relation_counts) + class ObjectDeleteViewCountsTestCase(ViewTestCase): """ @@ -258,3 +268,18 @@ class ObjectDeleteViewCountsTestCase(ViewTestCase): self.assertIsInstance(dependent_objects[Job], CountOnly) self.assertEqual(len(dependent_objects[Job]), 50) self.assertTrue(dependent_objects[Job].count_only) + + def test_get_dependent_objects_omits_jobs_when_none(self): + from netbox.views.generic.object_views import ObjectDeleteView + + # A module with no jobs must not produce a CountOnly(0) entry (#22812 regression). + module = ScriptModule.objects.create( + file_root=ManagedFileRootPathChoices.SCRIPTS, + file_path=f'test_{uuid.uuid4().hex[:8]}.py', + ) + + view = ObjectDeleteView() + view.queryset = ScriptModule.objects.all() + dependent_objects = view._get_dependent_objects(module) + + self.assertNotIn(Job, dependent_objects) diff --git a/netbox/netbox/models/deletion.py b/netbox/netbox/models/deletion.py index 0d9aa2c19..a903b7dff 100644 --- a/netbox/netbox/models/deletion.py +++ b/netbox/netbox/models/deletion.py @@ -59,7 +59,12 @@ class ConfirmCollector(Collector): from core.models import Job if source is not None and getattr(objs, 'model', None) is Job: - self.generic_relation_counts[Job] = self.generic_relation_counts.get(Job, 0) + objs.count() + # Django calls this branch for the jobs relation even when there are none; only record + # a count when there are actually jobs, so jobless objects don't get a spurious + # "0 jobs" row on the delete-confirmation page. + count = objs.count() + if count: + self.generic_relation_counts[Job] = self.generic_relation_counts.get(Job, 0) + count return None return super().collect(objs, source=source, *args, **kwargs)