From 7d46c995f79637f0361fb230a4d8c746d6deb5df Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Wed, 5 Aug 2026 14:04:19 -0500 Subject: [PATCH] Fixes #22812: Defer large Job payload fields during batched deletion The batched job delete can't fast-delete (a global pre_delete receiver forces per-instance signals), so each batch still instantiates its Job rows. Load only the PK via only('pk') so those instances don't pull the large data/log_entries payloads, cutting the resident set per batch. Also drop a dead `no-toggle` CSS class from the delete-confirmation template (it is defined nowhere and, under Tabler, has no effect) and use JobStatusChoices.STATUS_COMPLETED in the tests instead of a string literal. --- netbox/extras/tests/test_scripts_deletion.py | 16 +++++++++++----- netbox/netbox/models/features.py | 5 ++++- netbox/templates/htmx/delete_form.html | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/netbox/extras/tests/test_scripts_deletion.py b/netbox/extras/tests/test_scripts_deletion.py index 1d5c92581..f6d9bad48 100644 --- a/netbox/extras/tests/test_scripts_deletion.py +++ b/netbox/extras/tests/test_scripts_deletion.py @@ -7,7 +7,7 @@ from django.db.models import QuerySet from django.test import TestCase, override_settings from django.urls import reverse -from core.choices import ManagedFileRootPathChoices +from core.choices import JobStatusChoices, ManagedFileRootPathChoices from core.models import DataSource, Job from extras.models import Script, ScriptModule from extras.validators import CustomValidator @@ -43,7 +43,7 @@ class ScriptDeletionTestCase(TestCase): object_type=object_type, object_id=obj.pk, name='testjob', - status='completed', + status=JobStatusChoices.STATUS_COMPLETED, job_id=uuid.uuid4(), data={'output': 'x' * 50}, ) @@ -164,7 +164,7 @@ class ConfirmCollectorTestCase(TestCase): script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}') ct = ContentType.objects.get_for_model(Script, for_concrete_model=False) Job.objects.bulk_create([ - Job(object_type=ct, object_id=script.pk, name='j', status='completed', + Job(object_type=ct, object_id=script.pk, name='j', status=JobStatusChoices.STATUS_COMPLETED, job_id=uuid.uuid4(), data={'output': 'x' * 50}) for _ in range(count) ]) @@ -226,7 +226,10 @@ class ObjectDeleteViewCountsTestCase(ViewTestCase): script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}') ct = ContentType.objects.get_for_model(Script, for_concrete_model=False) Job.objects.bulk_create([ - Job(object_type=ct, object_id=script.pk, name='j', status='completed', job_id=uuid.uuid4()) + Job( + object_type=ct, object_id=script.pk, name='j', + status=JobStatusChoices.STATUS_COMPLETED, job_id=uuid.uuid4(), + ) for _ in range(50) ]) @@ -249,7 +252,10 @@ class ObjectDeleteViewCountsTestCase(ViewTestCase): script = Script.objects.create(module=module, name=f'S{uuid.uuid4().hex[:8]}') ct = ContentType.objects.get_for_model(Script, for_concrete_model=False) Job.objects.bulk_create([ - Job(object_type=ct, object_id=script.pk, name='j', status='completed', job_id=uuid.uuid4()) + Job( + object_type=ct, object_id=script.pk, name='j', + status=JobStatusChoices.STATUS_COMPLETED, job_id=uuid.uuid4(), + ) for _ in range(50) ]) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index f608da50c..4f1d5b684 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -464,7 +464,10 @@ def batch_delete_jobs(job_queryset): # Re-slice the queryset each iteration: it re-queries after each batch delete, so the # remaining set shrinks and the loop terminates (do not hoist this into a cursor). while pks := list(job_pks[:JOB_DELETE_BATCH_SIZE]): - Job.objects.filter(pk__in=pks).delete() + # only('pk'): the batch still can't fast-delete (a global pre_delete receiver forces + # per-instance signals), so each Job in the batch is instantiated. Loading just the PK + # avoids pulling the large data/log_entries payloads into those instances. + Job.objects.filter(pk__in=pks).only('pk').delete() class JobsMixin(models.Model): diff --git a/netbox/templates/htmx/delete_form.html b/netbox/templates/htmx/delete_form.html index 8f24816a4..c07e83d0a 100644 --- a/netbox/templates/htmx/delete_form.html +++ b/netbox/templates/htmx/delete_form.html @@ -30,7 +30,7 @@ {# High-cardinality relations (e.g. jobs) are summarized by count and are not #} {# expandable, since their instances are intentionally not loaded (see #22812). #} {% if instances.count_only %} -