From 3982f13569a22d58a71af926c5fe09788108a25d Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 28 Dec 2021 15:07:14 -0500 Subject: [PATCH] Show parent device/VM when creating new components --- netbox/dcim/forms/object_create.py | 25 ++++++++++++++++---- netbox/dcim/tests/test_forms.py | 13 +++++++--- netbox/dcim/views.py | 17 +++++++++++++ netbox/netbox/views/generic/object_views.py | 2 +- netbox/virtualization/forms/object_create.py | 6 ++++- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index 5e8daf38d..1fea886ea 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -8,7 +8,8 @@ from utilities.forms import ( ) __all__ = ( - 'ComponentCreateForm', + 'DeviceComponentCreateForm', + 'DeviceTypeComponentCreateForm', 'FrontPortCreateForm', 'FrontPortTemplateCreateForm', 'VirtualChassisCreateForm', @@ -43,14 +44,28 @@ class ComponentCreateForm(BootstrapMixin, forms.Form): }, code='label_pattern_mismatch') -class FrontPortTemplateCreateForm(ComponentCreateForm): +class DeviceTypeComponentCreateForm(ComponentCreateForm): + device_type = DynamicModelChoiceField( + queryset=DeviceType.objects.all(), + ) + field_order = ('device_type', 'name_pattern', 'label_pattern') + + +class DeviceComponentCreateForm(ComponentCreateForm): + device = DynamicModelChoiceField( + queryset=Device.objects.all() + ) + field_order = ('device', 'name_pattern', 'label_pattern') + + +class FrontPortTemplateCreateForm(DeviceTypeComponentCreateForm): rear_port_set = forms.MultipleChoiceField( choices=[], label='Rear ports', help_text='Select one rear port assignment for each front port being created.', ) field_order = ( - 'name_pattern', 'label_pattern', 'rear_port_set', + 'device_type', 'name_pattern', 'label_pattern', 'rear_port_set', ) def __init__(self, *args, **kwargs): @@ -88,14 +103,14 @@ class FrontPortTemplateCreateForm(ComponentCreateForm): } -class FrontPortCreateForm(ComponentCreateForm): +class FrontPortCreateForm(DeviceComponentCreateForm): rear_port_set = forms.MultipleChoiceField( choices=[], label='Rear ports', help_text='Select one rear port assignment for each front port being created.', ) field_order = ( - 'name_pattern', 'label_pattern', 'rear_port_set', + 'device', 'name_pattern', 'label_pattern', 'rear_port_set', ) def __init__(self, *args, **kwargs): diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py index 4c5de1284..53474314f 100644 --- a/netbox/dcim/tests/test_forms.py +++ b/netbox/dcim/tests/test_forms.py @@ -1,8 +1,9 @@ from django.test import TestCase -from dcim.choices import DeviceFaceChoices, DeviceStatusChoices, InterfaceTypeChoices +from dcim.choices import DeviceFaceChoices, DeviceStatusChoices from dcim.forms import * from dcim.models import * +from utilities.testing import create_test_device from virtualization.models import Cluster, ClusterGroup, ClusterType @@ -118,15 +119,20 @@ class DeviceTestCase(TestCase): class LabelTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.device = create_test_device('Device 1') + def test_interface_label_count_valid(self): """ Test that generating an equal number of names and labels passes form validation. """ interface_data = { + 'device': self.device.pk, 'name_pattern': 'eth[0-9]', 'label_pattern': 'Interface[0-9]', } - form = ComponentCreateForm(interface_data) + form = DeviceComponentCreateForm(interface_data) self.assertTrue(form.is_valid()) @@ -135,10 +141,11 @@ class LabelTestCase(TestCase): Check that attempting to generate a differing number of names and labels results in a validation error. """ bad_interface_data = { + 'device': self.device.pk, 'name_pattern': 'eth[0-9]', 'label_pattern': 'Interface[0-1]', } - form = ComponentCreateForm(bad_interface_data) + form = DeviceComponentCreateForm(bad_interface_data) self.assertFalse(form.is_valid()) self.assertIn('label_pattern', form.errors) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 81f45f7d0..4e63c0e76 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1054,6 +1054,7 @@ class ModuleTypeBulkDeleteView(generic.BulkDeleteView): class ConsolePortTemplateCreateView(generic.ComponentCreateView): queryset = ConsolePortTemplate.objects.all() + form = forms.DeviceTypeComponentCreateForm model_form = forms.ConsolePortTemplateForm @@ -1087,6 +1088,7 @@ class ConsolePortTemplateBulkDeleteView(generic.BulkDeleteView): class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView): queryset = ConsoleServerPortTemplate.objects.all() + form = forms.DeviceTypeComponentCreateForm model_form = forms.ConsoleServerPortTemplateForm @@ -1120,6 +1122,7 @@ class ConsoleServerPortTemplateBulkDeleteView(generic.BulkDeleteView): class PowerPortTemplateCreateView(generic.ComponentCreateView): queryset = PowerPortTemplate.objects.all() + form = forms.DeviceTypeComponentCreateForm model_form = forms.PowerPortTemplateForm @@ -1153,6 +1156,7 @@ class PowerPortTemplateBulkDeleteView(generic.BulkDeleteView): class PowerOutletTemplateCreateView(generic.ComponentCreateView): queryset = PowerOutletTemplate.objects.all() + form = forms.DeviceTypeComponentCreateForm model_form = forms.PowerOutletTemplateForm @@ -1186,6 +1190,7 @@ class PowerOutletTemplateBulkDeleteView(generic.BulkDeleteView): class InterfaceTemplateCreateView(generic.ComponentCreateView): queryset = InterfaceTemplate.objects.all() + form = forms.DeviceTypeComponentCreateForm model_form = forms.InterfaceTemplateForm @@ -1261,6 +1266,7 @@ class FrontPortTemplateBulkDeleteView(generic.BulkDeleteView): class RearPortTemplateCreateView(generic.ComponentCreateView): queryset = RearPortTemplate.objects.all() + form = forms.DeviceTypeComponentCreateForm model_form = forms.RearPortTemplateForm @@ -1294,6 +1300,7 @@ class RearPortTemplateBulkDeleteView(generic.BulkDeleteView): class ModuleBayTemplateCreateView(generic.ComponentCreateView): queryset = ModuleBayTemplate.objects.all() + form = forms.DeviceTypeComponentCreateForm model_form = forms.ModuleBayTemplateForm @@ -1327,6 +1334,7 @@ class ModuleBayTemplateBulkDeleteView(generic.BulkDeleteView): class DeviceBayTemplateCreateView(generic.ComponentCreateView): queryset = DeviceBayTemplate.objects.all() + form = forms.DeviceTypeComponentCreateForm model_form = forms.DeviceBayTemplateForm @@ -1741,6 +1749,7 @@ class ConsolePortView(generic.ObjectView): class ConsolePortCreateView(generic.ComponentCreateView): queryset = ConsolePort.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.ConsolePortForm @@ -1799,6 +1808,7 @@ class ConsoleServerPortView(generic.ObjectView): class ConsoleServerPortCreateView(generic.ComponentCreateView): queryset = ConsoleServerPort.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.ConsoleServerPortForm @@ -1857,6 +1867,7 @@ class PowerPortView(generic.ObjectView): class PowerPortCreateView(generic.ComponentCreateView): queryset = PowerPort.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.PowerPortForm @@ -1915,6 +1926,7 @@ class PowerOutletView(generic.ObjectView): class PowerOutletCreateView(generic.ComponentCreateView): queryset = PowerOutlet.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.PowerOutletForm @@ -2008,6 +2020,7 @@ class InterfaceView(generic.ObjectView): class InterfaceCreateView(generic.ComponentCreateView): queryset = Interface.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.InterfaceForm # template_name = 'dcim/interface_create.html' @@ -2161,6 +2174,7 @@ class RearPortView(generic.ObjectView): class RearPortCreateView(generic.ComponentCreateView): queryset = RearPort.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.RearPortForm @@ -2219,6 +2233,7 @@ class ModuleBayView(generic.ObjectView): class ModuleBayCreateView(generic.ComponentCreateView): queryset = ModuleBay.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.ModuleBayForm @@ -2273,6 +2288,7 @@ class DeviceBayView(generic.ObjectView): class DeviceBayCreateView(generic.ComponentCreateView): queryset = DeviceBay.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.DeviceBayForm @@ -2398,6 +2414,7 @@ class InventoryItemEditView(generic.ObjectEditView): class InventoryItemCreateView(generic.ComponentCreateView): queryset = InventoryItem.objects.all() + form = forms.DeviceComponentCreateForm model_form = forms.InventoryItemForm template_name = 'dcim/inventoryitem_create.html' diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 3f5b14658..5895a7a6e 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -681,7 +681,7 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine. """ queryset = None - form = ComponentCreateForm + form = None model_form = None template_name = 'dcim/component_create.html' patterned_fields = ('name', 'label') diff --git a/netbox/virtualization/forms/object_create.py b/netbox/virtualization/forms/object_create.py index f275469fd..feab3bb3a 100644 --- a/netbox/virtualization/forms/object_create.py +++ b/netbox/virtualization/forms/object_create.py @@ -1,6 +1,7 @@ from django import forms -from utilities.forms import BootstrapMixin, ExpandableNameField +from utilities.forms import BootstrapMixin, DynamicModelChoiceField, ExpandableNameField +from .models import VirtualMachine __all__ = ( 'VMInterfaceCreateForm', @@ -8,6 +9,9 @@ __all__ = ( class VMInterfaceCreateForm(BootstrapMixin, forms.Form): + virtual_machine = DynamicModelChoiceField( + queryset=VirtualMachine.objects.all() + ) name_pattern = ExpandableNameField( label='Name' )