mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-11 02:49:35 -06:00
fix(utilities): Ensure unique signal handlers for counter models
Updates `connect_counters` to prevent duplicate signal handlers by using consistent `dispatch_uid` values per sender. Adds a check to avoid reconnecting models already processed during registration. Fixes #20697
This commit is contained in:
parent
6a21459ccc
commit
70bc1c226a
@ -77,7 +77,7 @@ def post_delete_receiver(sender, instance, origin, **kwargs):
|
|||||||
parent_pk = getattr(instance, field_name, None)
|
parent_pk = getattr(instance, field_name, None)
|
||||||
|
|
||||||
# Decrement the parent's counter by one
|
# Decrement the parent's counter by one
|
||||||
if parent_pk is not None and not hasattr(instance, "_previously_removed"):
|
if parent_pk is not None and not hasattr(instance, '_previously_removed'):
|
||||||
update_counter(parent_model, parent_pk, counter_name, -1)
|
update_counter(parent_model, parent_pk, counter_name, -1)
|
||||||
|
|
||||||
|
|
||||||
@ -87,38 +87,48 @@ def post_delete_receiver(sender, instance, origin, **kwargs):
|
|||||||
|
|
||||||
def connect_counters(*models):
|
def connect_counters(*models):
|
||||||
"""
|
"""
|
||||||
Register counter fields and connect post_save & post_delete signal handlers for the affected models.
|
Register counter fields and connect signal handlers for their child models.
|
||||||
|
Ensures exactly one receiver per child (sender), even when multiple counters
|
||||||
|
reference the same sender (e.g., Device).
|
||||||
"""
|
"""
|
||||||
for model in models:
|
connected = set() # child models we've already connected
|
||||||
|
|
||||||
|
for model in models:
|
||||||
# Find all CounterCacheFields on the model
|
# Find all CounterCacheFields on the model
|
||||||
counter_fields = [
|
counter_fields = [field for field in model._meta.get_fields() if isinstance(field, CounterCacheField)]
|
||||||
field for field in model._meta.get_fields() if type(field) is CounterCacheField
|
|
||||||
]
|
|
||||||
|
|
||||||
for field in counter_fields:
|
for field in counter_fields:
|
||||||
to_model = apps.get_model(field.to_model_name)
|
to_model = apps.get_model(field.to_model_name)
|
||||||
|
|
||||||
# Register the counter in the registry
|
# Register the counter in the registry
|
||||||
change_tracking_fields = registry['counter_fields'][to_model]
|
change_tracking_fields = registry['counter_fields'][to_model]
|
||||||
change_tracking_fields[f"{field.to_field_name}_id"] = field.name
|
change_tracking_fields[f'{field.to_field_name}_id'] = field.name
|
||||||
|
|
||||||
|
# Connect signals once per child model
|
||||||
|
if to_model in connected:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ensure dispatch_uid is unique per model (sender), not per field
|
||||||
|
uid_base = f'countercache.{to_model._meta.label_lower}'
|
||||||
|
|
||||||
# Connect the post_save and post_delete handlers
|
# Connect the post_save and post_delete handlers
|
||||||
post_save.connect(
|
post_save.connect(
|
||||||
post_save_receiver,
|
post_save_receiver,
|
||||||
sender=to_model,
|
sender=to_model,
|
||||||
weak=False,
|
weak=False,
|
||||||
dispatch_uid=f'{model._meta.label}.{field.name}'
|
dispatch_uid=f'{uid_base}.post_save',
|
||||||
)
|
)
|
||||||
pre_delete.connect(
|
pre_delete.connect(
|
||||||
pre_delete_receiver,
|
pre_delete_receiver,
|
||||||
sender=to_model,
|
sender=to_model,
|
||||||
weak=False,
|
weak=False,
|
||||||
dispatch_uid=f'{model._meta.label}.{field.name}'
|
dispatch_uid=f'{uid_base}.pre_delete',
|
||||||
)
|
)
|
||||||
post_delete.connect(
|
post_delete.connect(
|
||||||
post_delete_receiver,
|
post_delete_receiver,
|
||||||
sender=to_model,
|
sender=to_model,
|
||||||
weak=False,
|
weak=False,
|
||||||
dispatch_uid=f'{model._meta.label}.{field.name}'
|
dispatch_uid=f'{uid_base}.post_delete',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
connected.add(to_model)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user