mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-18 17:52:21 -06:00
Compare commits
21 Commits
36c065ec1f
...
dc1d8a54c7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc1d8a54c7 | ||
|
|
598f8d034d | ||
|
|
ec13a79907 | ||
|
|
cf16a29ad3 | ||
|
|
544c97d923 | ||
|
|
77ee6baa23 | ||
|
|
09d1049267 | ||
|
|
93e5f919ba | ||
|
|
929d024003 | ||
|
|
e4b614038e | ||
|
|
3016b1d90b | ||
|
|
57b47dc1ea | ||
|
|
da4c669312 | ||
|
|
71f707b7ac | ||
|
|
e11508dd6c | ||
|
|
5b5b5c8909 | ||
|
|
a49869af42 | ||
|
|
2e0ff04f84 | ||
|
|
bfeba36514 | ||
|
|
111aca115b | ||
|
|
b4160ad59b |
@@ -13,6 +13,7 @@ class DataSourceStatusChoices(ChoiceSet):
|
||||
SYNCING = 'syncing'
|
||||
COMPLETED = 'completed'
|
||||
FAILED = 'failed'
|
||||
READY = 'ready'
|
||||
|
||||
CHOICES = (
|
||||
(NEW, _('New'), 'blue'),
|
||||
@@ -20,6 +21,7 @@ class DataSourceStatusChoices(ChoiceSet):
|
||||
(SYNCING, _('Syncing'), 'cyan'),
|
||||
(COMPLETED, _('Completed'), 'green'),
|
||||
(FAILED, _('Failed'), 'red'),
|
||||
(READY, _('Ready'), 'green'),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from utilities.forms import get_field_value
|
||||
from utilities.forms.fields import CommentField, JSONField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import HTMXSelect
|
||||
from core.choices import DataSourceStatusChoices
|
||||
|
||||
__all__ = (
|
||||
'ConfigRevisionForm',
|
||||
@@ -79,14 +80,28 @@ class DataSourceForm(NetBoxModelForm):
|
||||
if self.instance and self.instance.parameters:
|
||||
self.fields[field_name].initial = self.instance.parameters.get(name)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
parameters = {}
|
||||
for name in self.fields:
|
||||
if name.startswith('backend_'):
|
||||
parameters[name[8:]] = self.cleaned_data[name]
|
||||
self.instance.parameters = parameters
|
||||
|
||||
# Determine initial status based on new/existing instance
|
||||
if not self.instance.pk:
|
||||
# New instance
|
||||
object_status = DataSourceStatusChoices.NEW
|
||||
else:
|
||||
# Existing instance
|
||||
if not self.cleaned_data.get("sync_interval"):
|
||||
object_status = DataSourceStatusChoices.READY
|
||||
else:
|
||||
object_status = self.instance.status
|
||||
|
||||
# # Final override only if the user explicitly provided a status
|
||||
self.instance.status = object_status
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -111,10 +111,7 @@ class DataSource(JobsMixin, PrimaryModel):
|
||||
|
||||
@property
|
||||
def ready_for_sync(self):
|
||||
return self.enabled and self.status not in (
|
||||
DataSourceStatusChoices.QUEUED,
|
||||
DataSourceStatusChoices.SYNCING
|
||||
)
|
||||
return self.enabled and self.status != DataSourceStatusChoices.SYNCING
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
@@ -1222,6 +1222,8 @@ class ModuleBay(ModularComponentModel, TrackingModelMixin, MPTTModel):
|
||||
def save(self, *args, **kwargs):
|
||||
if self.module:
|
||||
self.parent = self.module.module_bay
|
||||
else:
|
||||
self.parent = None
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -957,6 +957,11 @@ class Device(
|
||||
if cf_defaults := CustomField.objects.get_defaults_for_model(model):
|
||||
for component in components:
|
||||
component.custom_field_data = cf_defaults
|
||||
# Set denormalized references
|
||||
for component in components:
|
||||
component._site = self.site
|
||||
component._location = self.location
|
||||
component._rack = self.rack
|
||||
components = model.objects.bulk_create(components)
|
||||
# Prefetch related objects to minimize queries needed during post_save
|
||||
prefetch_fields = get_prefetchable_fields(model)
|
||||
|
||||
@@ -315,6 +315,12 @@ class Module(PrimaryModel, ConfigContextModel):
|
||||
for component in create_instances:
|
||||
component.custom_field_data = cf_defaults
|
||||
|
||||
# Set denormalized references
|
||||
for component in create_instances:
|
||||
component._site = self.device.site
|
||||
component._location = self.device.location
|
||||
component._rack = self.device.rack
|
||||
|
||||
if component_model is not ModuleBay:
|
||||
component_model.objects.bulk_create(create_instances)
|
||||
# Emit the post_save signal for each newly created object
|
||||
|
||||
@@ -44,6 +44,9 @@ def handle_location_site_change(instance, created, **kwargs):
|
||||
Device.objects.filter(location__in=locations).update(site=instance.site)
|
||||
PowerPanel.objects.filter(location__in=locations).update(site=instance.site)
|
||||
CableTermination.objects.filter(_location__in=locations).update(_site=instance.site)
|
||||
# Update component models for devices in these locations
|
||||
for model in COMPONENT_MODELS:
|
||||
model.objects.filter(device__location__in=locations).update(_site=instance.site)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Rack)
|
||||
@@ -53,6 +56,12 @@ def handle_rack_site_change(instance, created, **kwargs):
|
||||
"""
|
||||
if not created:
|
||||
Device.objects.filter(rack=instance).update(site=instance.site, location=instance.location)
|
||||
# Update component models for devices in this rack
|
||||
for model in COMPONENT_MODELS:
|
||||
model.objects.filter(device__rack=instance).update(
|
||||
_site=instance.site,
|
||||
_location=instance.location,
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Device)
|
||||
|
||||
@@ -841,6 +841,32 @@ class ModuleBayTestCase(TestCase):
|
||||
nested_bay = module.modulebays.get(name='SFP A-21')
|
||||
self.assertEqual(nested_bay.label, 'A-21')
|
||||
|
||||
@tag('regression') # #20912
|
||||
def test_module_bay_parent_cleared_when_module_removed(self):
|
||||
"""Test that the parent field is properly cleared when a module bay's module assignment is removed"""
|
||||
device = Device.objects.first()
|
||||
manufacturer = Manufacturer.objects.first()
|
||||
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Test Module Type')
|
||||
bay1 = ModuleBay.objects.create(device=device, name='Test Bay 1')
|
||||
bay2 = ModuleBay.objects.create(device=device, name='Test Bay 2')
|
||||
|
||||
# Install a module in bay1
|
||||
module1 = Module.objects.create(device=device, module_bay=bay1, module_type=module_type)
|
||||
|
||||
# Assign bay2 to module1 and verify parent is now set to bay1 (module1's bay)
|
||||
bay2.module = module1
|
||||
bay2.save()
|
||||
bay2.refresh_from_db()
|
||||
self.assertEqual(bay2.parent, bay1)
|
||||
self.assertEqual(bay2.module, module1)
|
||||
|
||||
# Clear the module assignment (return bay2 to device level) Verify parent is cleared
|
||||
bay2.module = None
|
||||
bay2.save()
|
||||
bay2.refresh_from_db()
|
||||
self.assertIsNone(bay2.parent)
|
||||
self.assertIsNone(bay2.module)
|
||||
|
||||
|
||||
class CableTestCase(TestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user