mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
Fixes #5652: Update object data when renaming a custom field
This commit is contained in:
parent
a2203da1c6
commit
92df40a6a0
@ -12,6 +12,7 @@
|
|||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* [#5419](https://github.com/netbox-community/netbox/issues/5419) - Update parent device/VM when deleting a primary IP
|
* [#5419](https://github.com/netbox-community/netbox/issues/5419) - Update parent device/VM when deleting a primary IP
|
||||||
|
* [#5652](https://github.com/netbox-community/netbox/issues/5652) - Update object data when renaming a custom field
|
||||||
* [#6056](https://github.com/netbox-community/netbox/issues/6056) - Optimize change log cleanup
|
* [#6056](https://github.com/netbox-community/netbox/issues/6056) - Optimize change log cleanup
|
||||||
* [#6144](https://github.com/netbox-community/netbox/issues/6144) - Fix MAC address field display in VM interfaces search form
|
* [#6144](https://github.com/netbox-community/netbox/issues/6144) - Fix MAC address field display in VM interfaces search form
|
||||||
* [#6152](https://github.com/netbox-community/netbox/issues/6152) - Fix custom field filtering for cables, virtual chassis
|
* [#6152](https://github.com/netbox-community/netbox/issues/6152) - Fix custom field filtering for cables, virtual chassis
|
||||||
|
@ -162,6 +162,24 @@ class CustomField(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.label or self.name.replace('_', ' ').capitalize()
|
return self.label or self.name.replace('_', ' ').capitalize()
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Cache instance's original name so we can check later whether it has changed
|
||||||
|
self._name = self.name
|
||||||
|
|
||||||
|
def rename_object_data(self, old_name, new_name):
|
||||||
|
"""
|
||||||
|
Called when a CustomField has been renamed. Updates all assigned object data.
|
||||||
|
"""
|
||||||
|
for ct in self.content_types.all():
|
||||||
|
model = ct.model_class()
|
||||||
|
params = {f'custom_field_data__{old_name}__isnull': False}
|
||||||
|
instances = model.objects.filter(**params)
|
||||||
|
for instance in instances:
|
||||||
|
instance.custom_field_data[new_name] = instance.custom_field_data.pop(old_name)
|
||||||
|
model.objects.bulk_update(instances, ['custom_field_data'], batch_size=100)
|
||||||
|
|
||||||
def remove_stale_data(self, content_types):
|
def remove_stale_data(self, content_types):
|
||||||
"""
|
"""
|
||||||
Delete custom field data which is no longer relevant (either because the CustomField is
|
Delete custom field data which is no longer relevant (either because the CustomField is
|
||||||
|
@ -5,7 +5,7 @@ from cacheops.signals import cache_invalidated, cache_read
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import DEFAULT_DB_ALIAS
|
from django.db import DEFAULT_DB_ALIAS
|
||||||
from django.db.models.signals import m2m_changed, pre_delete
|
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
||||||
from prometheus_client import Counter
|
from prometheus_client import Counter
|
||||||
@ -86,6 +86,14 @@ def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
|
|||||||
instance.remove_stale_data(ContentType.objects.filter(pk__in=pk_set))
|
instance.remove_stale_data(ContentType.objects.filter(pk__in=pk_set))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_cf_renamed(instance, created, **kwargs):
|
||||||
|
"""
|
||||||
|
Handle the renaming of custom field data on objects when a CustomField is renamed.
|
||||||
|
"""
|
||||||
|
if not created and instance.name != instance._name:
|
||||||
|
instance.rename_object_data(old_name=instance._name, new_name=instance.name)
|
||||||
|
|
||||||
|
|
||||||
def handle_cf_deleted(instance, **kwargs):
|
def handle_cf_deleted(instance, **kwargs):
|
||||||
"""
|
"""
|
||||||
Handle the cleanup of old custom field data when a CustomField is deleted.
|
Handle the cleanup of old custom field data when a CustomField is deleted.
|
||||||
@ -94,6 +102,7 @@ def handle_cf_deleted(instance, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_types.through)
|
m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_types.through)
|
||||||
|
post_save.connect(handle_cf_renamed, sender=CustomField)
|
||||||
pre_delete.connect(handle_cf_deleted, sender=CustomField)
|
pre_delete.connect(handle_cf_deleted, sender=CustomField)
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,6 +91,33 @@ class CustomFieldTest(TestCase):
|
|||||||
# Delete the custom field
|
# Delete the custom field
|
||||||
cf.delete()
|
cf.delete()
|
||||||
|
|
||||||
|
def test_rename_customfield(self):
|
||||||
|
obj_type = ContentType.objects.get_for_model(Site)
|
||||||
|
FIELD_DATA = 'abc'
|
||||||
|
|
||||||
|
# Create a custom field
|
||||||
|
cf = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='field1')
|
||||||
|
cf.save()
|
||||||
|
cf.content_types.set([obj_type])
|
||||||
|
|
||||||
|
# Assign custom field data to an object
|
||||||
|
site = Site.objects.create(
|
||||||
|
name='Site 1',
|
||||||
|
slug='site-1',
|
||||||
|
custom_field_data={'field1': FIELD_DATA}
|
||||||
|
)
|
||||||
|
site.refresh_from_db()
|
||||||
|
self.assertEqual(site.custom_field_data['field1'], FIELD_DATA)
|
||||||
|
|
||||||
|
# Rename the custom field
|
||||||
|
cf.name = 'field2'
|
||||||
|
cf.save()
|
||||||
|
|
||||||
|
# Check that custom field data on the object has been updated
|
||||||
|
site.refresh_from_db()
|
||||||
|
self.assertNotIn('field1', site.custom_field_data)
|
||||||
|
self.assertEqual(site.custom_field_data['field2'], FIELD_DATA)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldManagerTest(TestCase):
|
class CustomFieldManagerTest(TestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user