Skip to content

Commit 4e8f376

Browse files
committed
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.
1 parent 849360d commit 4e8f376

File tree

1 file changed

+18
-5
lines changed

1 file changed

+18
-5
lines changed

netbox/utilities/counters.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,17 @@ def post_delete_receiver(sender, instance, origin, **kwargs):
8787

8888
def connect_counters(*models):
8989
"""
90-
Register counter fields and connect post_save & post_delete signal handlers for the affected models.
90+
Register counter fields and connect signal handlers for their child models.
91+
Ensures exactly one receiver per child (sender), even when multiple counters
92+
reference the same sender (e.g., Device).
9193
"""
94+
connected = set() # child models we've already connected
95+
9296
for model in models:
9397

9498
# Find all CounterCacheFields on the model
9599
counter_fields = [
96-
field for field in model._meta.get_fields() if type(field) is CounterCacheField
100+
field for field in model._meta.get_fields() if isinstance(field, CounterCacheField)
97101
]
98102

99103
for field in counter_fields:
@@ -103,22 +107,31 @@ def connect_counters(*models):
103107
change_tracking_fields = registry['counter_fields'][to_model]
104108
change_tracking_fields[f"{field.to_field_name}_id"] = field.name
105109

110+
# Connect signals once per child model
111+
if to_model in connected:
112+
continue
113+
114+
# Ensure dispatch_uid is unique per model (sender), not per field
115+
uid_base = f"countercache.{to_model._meta.label_lower}"
116+
106117
# Connect the post_save and post_delete handlers
107118
post_save.connect(
108119
post_save_receiver,
109120
sender=to_model,
110121
weak=False,
111-
dispatch_uid=f'{model._meta.label}.{field.name}'
122+
dispatch_uid=f'{uid_base}.post_save'
112123
)
113124
pre_delete.connect(
114125
pre_delete_receiver,
115126
sender=to_model,
116127
weak=False,
117-
dispatch_uid=f'{model._meta.label}.{field.name}'
128+
dispatch_uid=f'{uid_base}.pre_delete'
118129
)
119130
post_delete.connect(
120131
post_delete_receiver,
121132
sender=to_model,
122133
weak=False,
123-
dispatch_uid=f'{model._meta.label}.{field.name}'
134+
dispatch_uid=f'{uid_base}.post_delete'
124135
)
136+
137+
connected.add(to_model)

0 commit comments

Comments
 (0)