Fixes #22812: Don't show a spurious "0 jobs" row for jobless objects

Django's Collector calls into the jobs GenericRelation branch unconditionally, so
ConfirmCollector recorded a zero count for objects with no jobs. _get_dependent_objects
then added a CountOnly(0), and the delete-confirmation page rendered "The following
objects will be deleted as a result of this action." plus a "0 jobs" row for every
jobless JobsMixin object. Only record a count when there are actually jobs.
This commit is contained in:
Jason Novinger 2026-08-05 13:59:02 -05:00
parent f2923f4ce4
commit be69c55a99
2 changed files with 31 additions and 1 deletions

View File

@ -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)

View File

@ -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)