Просмотр исходного кода

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.
Jason Novinger 2 недель назад
Родитель
Сommit
7d46c995f7

+ 11 - 5
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)
         ])
 

+ 4 - 1
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):

+ 1 - 1
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 %}
-                  <div class="accordion-button collapsed no-toggle" style="cursor: default;">
+                  <div class="accordion-button collapsed" style="cursor: default;">
                     {{ object_count }}
                     {% if object_count == 1 %}{{ model|meta:"verbose_name" }}{% else %}{{ model|meta:"verbose_name_plural" }}{% endif %}
                   </div>