mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-21 04:42:22 -06:00
Merge branch 'feature' of https://github.com/netbox-community/netbox into feature
# Conflicts: # netbox/templates/generic/object_list.html
This commit is contained in:
@@ -114,6 +114,24 @@ class CustomField(BigIDModel):
|
||||
def __str__(self):
|
||||
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):
|
||||
"""
|
||||
Delete custom field data which is no longer relevant (either because the CustomField is
|
||||
|
||||
@@ -391,6 +391,8 @@ class ImageAttachment(BigIDModel):
|
||||
# Journal entries
|
||||
#
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class JournalEntry(ChangeLoggedModel):
|
||||
"""
|
||||
A historical remark concerning an object; collectively, these form an object's journal. The journal is used to
|
||||
|
||||
@@ -5,7 +5,7 @@ from cacheops.signals import cache_invalidated, cache_read
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
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_prometheus.models import model_deletes, model_inserts, model_updates
|
||||
from prometheus_client import Counter
|
||||
@@ -98,6 +98,14 @@ def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
|
||||
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):
|
||||
"""
|
||||
Handle the cleanup of old custom field data when a CustomField is deleted.
|
||||
@@ -106,6 +114,7 @@ def handle_cf_deleted(instance, **kwargs):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from dcim.models import Site
|
||||
from extras.choices import *
|
||||
from extras.models import CustomField, ObjectChange, Tag
|
||||
from utilities.testing import APITestCase
|
||||
from utilities.testing.utils import post_data
|
||||
from utilities.testing.utils import create_tags, post_data
|
||||
from utilities.testing.views import ModelViewTestCase
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class ChangeLogViewTest(ModelViewTestCase):
|
||||
cf_select.content_types.set([ct])
|
||||
|
||||
def test_create_object(self):
|
||||
tags = self.create_tags('Tag 1', 'Tag 2')
|
||||
tags = create_tags('Tag 1', 'Tag 2')
|
||||
form_data = {
|
||||
'name': 'Site 1',
|
||||
'slug': 'site-1',
|
||||
@@ -72,7 +72,7 @@ class ChangeLogViewTest(ModelViewTestCase):
|
||||
def test_update_object(self):
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
tags = self.create_tags('Tag 1', 'Tag 2', 'Tag 3')
|
||||
tags = create_tags('Tag 1', 'Tag 2', 'Tag 3')
|
||||
site.tags.set('Tag 1', 'Tag 2')
|
||||
|
||||
form_data = {
|
||||
@@ -116,7 +116,7 @@ class ChangeLogViewTest(ModelViewTestCase):
|
||||
}
|
||||
)
|
||||
site.save()
|
||||
self.create_tags('Tag 1', 'Tag 2')
|
||||
create_tags('Tag 1', 'Tag 2')
|
||||
site.tags.set('Tag 1', 'Tag 2')
|
||||
|
||||
request = {
|
||||
|
||||
@@ -91,6 +91,33 @@ class CustomFieldTest(TestCase):
|
||||
# Delete the custom field
|
||||
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):
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from django.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from dcim.models import Site
|
||||
from utilities.testing import APITestCase
|
||||
from utilities.testing import APITestCase, create_tags
|
||||
|
||||
|
||||
class TaggedItemTest(APITestCase):
|
||||
@@ -10,7 +10,7 @@ class TaggedItemTest(APITestCase):
|
||||
Test the application of Tags to and item (a Site, for example) upon creation (POST) and modification (PATCH).
|
||||
"""
|
||||
def test_create_tagged_item(self):
|
||||
tags = self.create_tags("Foo", "Bar", "Baz")
|
||||
tags = create_tags("Foo", "Bar", "Baz")
|
||||
data = {
|
||||
'name': 'Test Site',
|
||||
'slug': 'test-site',
|
||||
@@ -37,7 +37,7 @@ class TaggedItemTest(APITestCase):
|
||||
slug='test-site'
|
||||
)
|
||||
site.tags.add("Foo", "Bar", "Baz")
|
||||
self.create_tags("New Tag")
|
||||
create_tags("New Tag")
|
||||
data = {
|
||||
'tags': [
|
||||
{"name": "Foo"},
|
||||
|
||||
Reference in New Issue
Block a user