11import logging
22import re
33from datetime import timedelta
4+ from itertools import batched
45
6+ from django .db .models import Exists , OuterRef
57from django .utils import timezone
68
79from application .commons .models import Settings
8- from application .core .models import Branch , Observation , Product
10+ from application .core .models import Branch , Component , Observation , Product
11+ from application .licenses .models import License_Component
912
1013logger = logging .getLogger ("secobserve.core" )
1114
15+ BULK_BATCH_SIZE = 1000
1216
13- def delete_inactive_branches_and_set_flags () -> str :
17+
18+ def housekeeping () -> str :
1419 num_products = 0
1520 num_deleted_branches = 0
1621 products = Product .objects .filter (is_product_group = False )
@@ -21,7 +26,12 @@ def delete_inactive_branches_and_set_flags() -> str:
2126 num_products += 1
2227 set_product_flags (product )
2328
24- return f"Deleted { num_deleted_branches } inactive branches in { num_products } products."
29+ num_deleted_components = delete_orphaned_components ()
30+
31+ return (
32+ f"Deleted { num_deleted_branches } inactive branches in { num_products } products."
33+ f"\n Deleted { num_deleted_components } orphaned components."
34+ )
2535
2636
2737def delete_inactive_branches_for_product (product : Product ) -> int :
@@ -129,3 +139,23 @@ def set_product_flags(product: Product) -> None:
129139 or has_potential_duplicates_before != product .has_potential_duplicates
130140 ):
131141 product .save ()
142+
143+
144+ def delete_orphaned_components () -> int :
145+ orphaned_components = Component .objects .filter (
146+ ~ Exists (Observation .objects .filter (origin_component = OuterRef ("pk" ))),
147+ ~ Exists (License_Component .objects .filter (component = OuterRef ("pk" ))),
148+ )
149+ orphaned_component_ids = list (orphaned_components .values_list ("pk" , flat = True ))
150+
151+ num_deleted_components = 0
152+ for component_ids_batch in batched (orphaned_component_ids , BULK_BATCH_SIZE ):
153+ deleted_objects = orphaned_components .filter (pk__in = component_ids_batch ).delete ()
154+ num_deleted_components += deleted_objects [1 ].get ("core.Component" , 0 )
155+
156+ if num_deleted_components :
157+ logger .info ( # pylint: disable=logging-fstring-interpolation
158+ f"Deleted { num_deleted_components } orphaned components"
159+ )
160+
161+ return num_deleted_components
0 commit comments