Skip to content

Commit 9b0258f

Browse files
committed
Fixes #6686: Force assignment of null custom field values to objects
1 parent 5b89cdc commit 9b0258f

File tree

4 files changed

+38
-11
lines changed

4 files changed

+38
-11
lines changed

docs/release-notes/version-2.11.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
### Bug Fixes
1111

1212
* [#5968](https://github.com/netbox-community/netbox/issues/5968) - Model forms should save empty custom field values as null
13+
* [#6686](https://github.com/netbox-community/netbox/issues/6686) - Force assignment of null custom field values to objects
1314

1415
---
1516

netbox/extras/models/customfields.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,16 @@ def __init__(self, *args, **kwargs):
120120
# Cache instance's original name so we can check later whether it has changed
121121
self._name = self.name
122122

123-
def rename_object_data(self, old_name, new_name):
123+
def populate_initial_data(self, content_types):
124124
"""
125-
Called when a CustomField has been renamed. Updates all assigned object data.
125+
Populate initial custom field data upon either a) the creation of a new CustomField, or
126+
b) the assignment of an existing CustomField to new object types.
126127
"""
127-
for ct in self.content_types.all():
128+
for ct in content_types:
128129
model = ct.model_class()
129-
params = {f'custom_field_data__{old_name}__isnull': False}
130-
instances = model.objects.filter(**params)
131-
for instance in instances:
132-
instance.custom_field_data[new_name] = instance.custom_field_data.pop(old_name)
133-
model.objects.bulk_update(instances, ['custom_field_data'], batch_size=100)
130+
for obj in model.objects.exclude(**{f'custom_field_data__contains': self.name}):
131+
obj.custom_field_data[self.name] = self.default
132+
obj.save()
134133

135134
def remove_stale_data(self, content_types):
136135
"""
@@ -143,6 +142,18 @@ def remove_stale_data(self, content_types):
143142
del(obj.custom_field_data[self.name])
144143
obj.save()
145144

145+
def rename_object_data(self, old_name, new_name):
146+
"""
147+
Called when a CustomField has been renamed. Updates all assigned object data.
148+
"""
149+
for ct in self.content_types.all():
150+
model = ct.model_class()
151+
params = {f'custom_field_data__{old_name}__isnull': False}
152+
instances = model.objects.filter(**params)
153+
for instance in instances:
154+
instance.custom_field_data[new_name] = instance.custom_field_data.pop(old_name)
155+
model.objects.bulk_update(instances, ['custom_field_data'], batch_size=100)
156+
146157
def clean(self):
147158
super().clean()
148159

netbox/extras/signals.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ def _handle_deleted_object(request, webhook_queue, sender, instance, **kwargs):
108108
# Custom fields
109109
#
110110

111+
def handle_cf_added_obj_types(instance, action, pk_set, **kwargs):
112+
"""
113+
Handle the population of default/null values when a CustomField is added to one or more ContentTypes.
114+
"""
115+
if action == 'post_add':
116+
instance.populate_initial_data(ContentType.objects.filter(pk__in=pk_set))
117+
118+
111119
def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
112120
"""
113121
Handle the cleanup of old custom field data when a CustomField is removed from one or more ContentTypes.
@@ -131,9 +139,10 @@ def handle_cf_deleted(instance, **kwargs):
131139
instance.remove_stale_data(instance.content_types.all())
132140

133141

134-
m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_types.through)
135142
post_save.connect(handle_cf_renamed, sender=CustomField)
136143
pre_delete.connect(handle_cf_deleted, sender=CustomField)
144+
m2m_changed.connect(handle_cf_added_obj_types, sender=CustomField.content_types.through)
145+
m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_types.through)
137146

138147

139148
#

netbox/extras/tests/test_customfields.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ def test_simple_fields(self):
4242
cf.save()
4343
cf.content_types.set([obj_type])
4444

45-
# Assign a value to the first Site
45+
# Check that the field has a null initial value
4646
site = Site.objects.first()
47+
self.assertIsNone(site.custom_field_data[cf.name])
48+
49+
# Assign a value to the first Site
4750
site.custom_field_data[cf.name] = data['field_value']
4851
site.save()
4952

@@ -73,8 +76,11 @@ def test_select_field(self):
7376
cf.save()
7477
cf.content_types.set([obj_type])
7578

76-
# Assign a value to the first Site
79+
# Check that the field has a null initial value
7780
site = Site.objects.first()
81+
self.assertIsNone(site.custom_field_data[cf.name])
82+
83+
# Assign a value to the first Site
7884
site.custom_field_data[cf.name] = 'Option A'
7985
site.save()
8086

0 commit comments

Comments
 (0)