@@ -2026,23 +2026,16 @@ def is_finding_groups_enabled():
20262026 return get_system_setting ("enable_finding_groups" )
20272027
20282028
2029- # Mapping of object types to their related models for cascading deletes
2030- ASYNC_DELETE_MAPPING = {
2031- "Product_Type" : [
2032- (Endpoint , "product__prod_type__id" ),
2033- (Finding , "test__engagement__product__prod_type__id" ),
2034- (Test , "engagement__product__prod_type__id" ),
2035- (Engagement , "product__prod_type__id" ),
2036- (Product , "prod_type__id" )],
2037- "Product" : [
2038- (Endpoint , "product__id" ),
2039- (Finding , "test__engagement__product__id" ),
2040- (Test , "engagement__product__id" ),
2041- (Engagement , "product__id" )],
2042- "Engagement" : [
2043- (Finding , "test__engagement__id" ),
2044- (Test , "engagement__id" )],
2045- "Test" : [(Finding , "test__id" )],
2029+ # Supported object types for async cascade deletion
2030+ ASYNC_DELETE_SUPPORTED_TYPES = (Product_Type , Product , Engagement , Test )
2031+
2032+ # Finding scope filters per model type — used to build the finding queryset
2033+ # for bulk deletion before cascade_delete handles the rest.
2034+ FINDING_SCOPE_FILTERS = {
2035+ Product_Type : "test__engagement__product__prod_type" ,
2036+ Product : "test__engagement__product" ,
2037+ Engagement : "test__engagement" ,
2038+ Test : "test" ,
20462039}
20472040
20482041
@@ -2054,7 +2047,7 @@ def _get_object_name(obj):
20542047
20552048
20562049@app .task
2057- def async_delete_crawl_task (obj , model_list , ** kwargs ):
2050+ def async_delete_crawl_task (obj , ** kwargs ):
20582051 """
20592052 Delete an object and all its related objects using the SQL cascade walker.
20602053
@@ -2083,26 +2076,14 @@ def async_delete_crawl_task(obj, model_list, **kwargs):
20832076 product = obj .engagement .product
20842077
20852078 # Step 1: Determine finding scope
2086- finding_filter = None
2087- for model , query_path in model_list :
2088- if model is Finding :
2089- finding_filter = {query_path : obj .id }
2090- break
2091-
2092- if finding_filter :
2093- finding_qs = Finding .objects .filter (** finding_filter )
2079+ scope_field = FINDING_SCOPE_FILTERS .get (type (obj ))
2080+ if scope_field :
2081+ finding_qs = Finding .objects .filter (** {scope_field : obj })
20942082
20952083 # Step 2: Prepare duplicate clusters (must happen before any deletion)
2096- # When CASCADE_DELETE=True, reconfigure_duplicate_cluster skips deletion —
2084+ # When CASCADE_DELETE=True, reconfigure_duplicate_cluster skips reconfiguration —
20972085 # we handle that below by expanding scope to include outside duplicates.
2098- if isinstance (obj , Product_Type ):
2099- prepare_duplicates_for_delete (product_type = obj )
2100- elif isinstance (obj , Product ):
2101- prepare_duplicates_for_delete (product = obj )
2102- elif isinstance (obj , Engagement ):
2103- prepare_duplicates_for_delete (engagement = obj )
2104- elif isinstance (obj , Test ):
2105- prepare_duplicates_for_delete (test = obj )
2086+ prepare_duplicates_for_delete (obj )
21062087
21072088 # Step 3: Delete outside-scope duplicates first — these point to findings
21082089 # in the main scope via duplicate_finding FK, so they must be removed before
@@ -2119,22 +2100,14 @@ def async_delete_crawl_task(obj, model_list, **kwargs):
21192100 # Step 4: Delete the main scope findings
21202101 bulk_delete_findings (finding_qs , chunk_size = chunk_size )
21212102
2122- # Delete remaining non-Finding children (Tests, Engagements, Endpoints)
2123- # These are now lightweight since their Findings are gone
2124- for model , query_path in model_list :
2125- if model is not Finding :
2126- filter_dict = {query_path : obj .id }
2127- qs = model .objects .filter (** filter_dict )
2128- if qs .exists ():
2129- with transaction .atomic ():
2130- cascade_delete (model , qs , skip_relations = {Finding })
2131-
2132- # Step 6: Delete the top-level object itself
2103+ # Step 5: Delete the top-level object and all remaining children (Tests,
2104+ # Engagements, Endpoints, etc.) via cascade_delete. Findings are already
2105+ # gone, so skip_relations={Finding} avoids walking empty relations.
21332106 pk_query = type (obj ).objects .filter (pk = obj .pk )
21342107 with transaction .atomic ():
21352108 cascade_delete (type (obj ), pk_query , skip_relations = {Finding })
21362109
2137- # Step 7 : Recalculate product grade once (not per-object)
2110+ # Step 6 : Recalculate product grade once (not per-object)
21382111 # The custom delete() methods on Finding/Test/Engagement each call
21392112 # perform_product_grading — cascade_delete bypasses custom delete().
21402113 if product :
@@ -2153,14 +2126,11 @@ def async_delete_task(obj, **kwargs):
21532126 """
21542127 from dojo .celery_dispatch import dojo_dispatch_task # noqa: PLC0415 circular import
21552128
2156- logger .debug ("ASYNC_DELETE: Deleting " + _get_object_name (obj ) + ": " + str (obj ))
2157- model_list = ASYNC_DELETE_MAPPING .get (_get_object_name (obj ))
2158- if model_list :
2159- # The object to be deleted was found in the object list
2160- dojo_dispatch_task (async_delete_crawl_task , obj , model_list )
2129+ logger .debug ("ASYNC_DELETE: Deleting %s: %s" , _get_object_name (obj ), obj )
2130+ if isinstance (obj , ASYNC_DELETE_SUPPORTED_TYPES ):
2131+ dojo_dispatch_task (async_delete_crawl_task , obj )
21612132 else :
2162- # The object is not supported in async delete, delete normally
2163- logger .debug ("ASYNC_DELETE: " + _get_object_name (obj ) + " async delete not supported. Deleteing normally: " + str (obj ))
2133+ logger .debug ("ASYNC_DELETE: %s async delete not supported. Deleting normally: %s" , _get_object_name (obj ), obj )
21642134 obj .delete ()
21652135
21662136
@@ -2177,10 +2147,6 @@ class async_delete:
21772147 which properly handles user context injection and pghistory context.
21782148 """
21792149
2180- def __init__ (self , * args , ** kwargs ):
2181- # Keep mapping reference for backwards compatibility
2182- self .mapping = ASYNC_DELETE_MAPPING
2183-
21842150 def delete (self , obj , ** kwargs ):
21852151 """
21862152 Entry point to delete an object asynchronously.
@@ -2192,18 +2158,10 @@ def delete(self, obj, **kwargs):
21922158
21932159 dojo_dispatch_task (async_delete_task , obj , ** kwargs )
21942160
2195- # Keep helper methods for backwards compatibility and potential direct use
21962161 @staticmethod
21972162 def get_object_name (obj ):
21982163 return _get_object_name (obj )
21992164
2200- @staticmethod
2201- def chunk_list (model , full_list ):
2202- chunk_size = get_setting ("ASYNC_OBEJECT_DELETE_CHUNK_SIZE" )
2203- chunk_list = [full_list [i :i + chunk_size ] for i in range (0 , len (full_list ), chunk_size )]
2204- logger .debug ("ASYNC_DELETE: Split " + _get_object_name (model ) + " into " + str (len (chunk_list )) + " chunks of " + str (chunk_size ))
2205- return chunk_list
2206-
22072165
22082166@receiver (user_logged_in )
22092167def log_user_login (sender , request , user , ** kwargs ):
0 commit comments