@@ -43,10 +43,10 @@ class LocationManager(BaseLocationManager):
4343
4444 def __init__ (self , product : Product ) -> None :
4545 super ().__init__ (product )
46- # Maps findings to a list of locations
47- self ._locations_by_finding : dict [Finding , list [UnsavedLocation ]] = {}
48- # Product-only locations (not tied to a finding)
49- self ._product_locations : list [UnsavedLocation ] = []
46+ # Maps findings to a list of cleaned locations
47+ self ._locations_by_finding : dict [Finding , list [AbstractLocation ]] = {}
48+ # All locations needing product refs ( finding-associated + product-only), cleaned at record time
49+ self ._product_locations : list [AbstractLocation ] = []
5050 # Status update entries, which we'll use at persist-time to determine Location statuses by comparing
5151 # existing vs new finding entries.
5252 self ._status_updates : list [StatusUpdateEntry ] = []
@@ -66,8 +66,9 @@ def record_locations_for_finding(
6666 ) -> None :
6767 """Record locations to be associated with a finding (and its product). Flushed by persist()."""
6868 if locations :
69- self ._locations_by_finding .setdefault (finding , []).extend (locations )
70- self ._product_locations .extend (locations )
69+ cleaned = self .clean_unsaved_locations (locations )
70+ self ._locations_by_finding .setdefault (finding , []).extend (cleaned )
71+ self ._product_locations .extend (cleaned )
7172
7273 def update_location_status (
7374 self ,
@@ -132,10 +133,9 @@ def _persist_locations(self) -> None:
132133 if not self ._product_locations :
133134 return
134135
135- # Convert all UnsavedLocation objects (possibly a mix of AbstractLocation and LocationData objects) to cleaned
136- # concrete location objects. _product_locations is the superset of all locations (finding-associated +
137- # product-only), so cleaning it once covers everything.
138- all_locations = self .clean_unsaved_locations (self ._product_locations )
136+ # Locations are already cleaned at record time (in record_locations_for_finding). Deduplicate the
137+ # full set — _product_locations is the superset of all locations (finding-associated + product-only).
138+ all_locations = list ({(type (loc ), loc .identity_hash ): loc for loc in self ._product_locations }.values ())
139139 if not all_locations :
140140 self ._locations_by_finding .clear ()
141141 self ._product_locations .clear ()
@@ -146,9 +146,6 @@ def _persist_locations(self) -> None:
146146
147147 # Build a lookup from (type, identity_hash) -> saved location for finding ref creation.
148148 # identity_hash is only unique per concrete type, so we key by both.
149- #
150- # Finding/location mapping was tracked separately in _locations_by_finding which are still the raw
151- # UnsavedLocation objects; we'll need to line them up with the persisted locations.
152149 saved_by_key : dict [tuple [type , str ], AbstractLocation ] = {
153150 (type (loc ), loc .identity_hash ): loc for loc in saved
154151 }
@@ -191,13 +188,10 @@ def _persist_locations(self) -> None:
191188 ).values_list ("finding_id" , "location_id" ),
192189 )
193190
194- for finding , unsaved_locations in self ._locations_by_finding .items ():
195- # Clean per-finding UnsavedLocations to get cleaned AbstractLocations with identity_hashes. The
196- # identity_hash uniquely defines the location per type, so using these we can match up with actual
197- # persisted locations from above, all of which will be represented in saved_by_key. (Keep in mind,
198- # _locations_by_finding contains a subset of the locations across all its values in
199- # _product_locations.)
200- for location in self .clean_unsaved_locations (unsaved_locations ):
191+ for finding , cleaned_locations in self ._locations_by_finding .items ():
192+ # Locations were already cleaned at record time — identity_hash is set, so we can
193+ # look up the persisted location directly.
194+ for location in cleaned_locations :
201195 saved_loc = saved_by_key [type (location ), location .identity_hash ]
202196 finding_ref_key = (finding .id , saved_loc .location_id )
203197 if finding_ref_key not in existing_finding_ref_keys :
0 commit comments