@@ -1783,35 +1783,18 @@ def async_delete_task(obj, **kwargs):
17831783
17841784 # Step 2: Prepare duplicate clusters (must happen before any deletion)
17851785 # When CASCADE_DELETE=True, reconfigure_duplicate_cluster skips reconfiguration —
1786- # we handle that below by expanding scope to include outside duplicates .
1786+ # and deletes any outside scope duplicates to avoid FK violations during chunked deletion .
17871787 prepare_duplicates_for_delete (obj )
17881788
1789- # Step 3: Delete outside-scope duplicates first — these point to findings
1790- # in the main scope via duplicate_finding FK, so they must be removed before
1791- # the originals to avoid FK violations during chunked deletion.
1792- scope_ids = finding_qs .values_list ("id" , flat = True )
1793- outside_dupes_qs = (
1794- Finding .objects .filter (duplicate_finding_id__in = scope_ids )
1795- .exclude (id__in = scope_ids )
1796- )
1789+ # Step 3: Delete the main scope findings
17971790 chunk_size = get_setting ("ASYNC_OBEJECT_DELETE_CHUNK_SIZE" )
1798- outside_count = outside_dupes_qs .count ()
1799- if outside_count :
1800- logger .info ("ASYNC_DELETE: Deleting %d outside-scope duplicates first" , outside_count )
1801- bulk_delete_findings (
1802- outside_dupes_qs ,
1803- chunk_size = chunk_size ,
1804- cascade_root = cascade_root ,
1805- )
1806-
1807- # Step 4: Delete the main scope findings
18081791 bulk_delete_findings (
18091792 finding_qs ,
18101793 chunk_size = chunk_size ,
18111794 cascade_root = cascade_root ,
18121795 )
18131796
1814- # Step 5 : Delete all remaining related objects (Tests, Engagements,
1797+ # Step 4 : Delete all remaining related objects (Tests, Engagements,
18151798 # Endpoints, etc.) via SQL cascade. Findings are already gone, so
18161799 # skip_relations={Finding} avoids walking empty relations.
18171800 # Single transaction is fine here — the heavy relations (Findings,
@@ -1820,12 +1803,12 @@ def async_delete_task(obj, **kwargs):
18201803 with transaction .atomic ():
18211804 cascade_delete_related_objects (type (obj ), pk_query , skip_relations = {Finding })
18221805
1823- # Step 6 : Delete the top-level object via ORM to fire Django signals
1806+ # Step 5 : Delete the top-level object via ORM to fire Django signals
18241807 # (post_delete notifications, pghistory audit, Pro signals).
18251808 # All children are already gone so this is a single-row DELETE.
18261809 obj .delete ()
18271810
1828- # Step 7 : Recalculate product grade once (Engagement/Test deletes only). Skip when the
1811+ # Step 6 : Recalculate product grade once (Engagement/Test deletes only). Skip when the
18291812 # deleted object is the Product itself — it is removed in step 6 and grading is pointless.
18301813 # For Product TYpe deletiongs we don't have a product instance, so this never fires.
18311814 if product and not isinstance (obj , Product ):
0 commit comments