diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index 4924587a0..858173cca 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -114,15 +114,17 @@ class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemp class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm): - rear_port_set = forms.MultipleChoiceField( + rear_port = forms.MultipleChoiceField( choices=[], label='Rear ports', help_text='Select one rear port assignment for each front port being created.', ) - field_order = ('device_type', 'name', 'label') + field_order = ( + 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'description', + ) class Meta(model_forms.FrontPortTemplateForm.Meta): - exclude = ('name', 'label') + exclude = ('name', 'label', 'rear_port', 'rear_port_position') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -154,12 +156,12 @@ class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemp choices.append( ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i)) ) - self.fields['rear_port_set'].choices = choices + self.fields['rear_port'].choices = choices def get_iterative_data(self, iteration): # Assign rear port and position from selected set - rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':') + rear_port, position = self.cleaned_data['rear_port'][iteration].split(':') return { 'rear_port': int(rear_port), @@ -240,15 +242,15 @@ class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm): class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm): - rear_port_set = forms.MultipleChoiceField( + rear_port = forms.MultipleChoiceField( choices=[], label='Rear ports', help_text='Select one rear port assignment for each front port being created.', ) - field_order = ('device', 'name', 'label') + field_order = ('device', 'module', 'name', 'label', 'type', 'color', 'rear_port') class Meta(model_forms.FrontPortForm.Meta): - exclude = ('name', 'label') + exclude = ('name', 'label', 'rear_port', 'rear_port_position') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -273,12 +275,12 @@ class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm): choices.append( ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i)) ) - self.fields['rear_port_set'].choices = choices + self.fields['rear_port'].choices = choices def get_iterative_data(self, iteration): # Assign rear port and position from selected set - rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':') + rear_port, position = self.cleaned_data['rear_port'][iteration].split(':') return { 'rear_port': int(rear_port), diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 838336e21..8f1285901 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -908,18 +908,20 @@ class FrontPort(ModularComponentModel, CabledObjectModel): def clean(self): super().clean() - # Validate rear port assignment - if self.rear_port.device != self.device: - raise ValidationError({ - "rear_port": f"Rear port ({self.rear_port}) must belong to the same device" - }) + if hasattr(self, 'rear_port'): - # Validate rear port position assignment - if self.rear_port_position > self.rear_port.positions: - raise ValidationError({ - "rear_port_position": f"Invalid rear port position ({self.rear_port_position}): Rear port " - f"{self.rear_port.name} has only {self.rear_port.positions} positions" - }) + # Validate rear port assignment + if self.rear_port.device != self.device: + raise ValidationError({ + "rear_port": f"Rear port ({self.rear_port}) must belong to the same device" + }) + + # Validate rear port position assignment + if self.rear_port_position > self.rear_port.positions: + raise ValidationError({ + "rear_port_position": f"Invalid rear port position ({self.rear_port_position}): Rear port " + f"{self.rear_port.name} has only {self.rear_port.positions} positions" + }) class RearPort(ModularComponentModel, CabledObjectModel): diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py index 5dbcf89ae..1cd75765a 100644 --- a/netbox/dcim/tests/test_forms.py +++ b/netbox/dcim/tests/test_forms.py @@ -1,6 +1,6 @@ from django.test import TestCase -from dcim.choices import DeviceFaceChoices, DeviceStatusChoices +from dcim.choices import DeviceFaceChoices, DeviceStatusChoices, InterfaceTypeChoices from dcim.forms import * from dcim.models import * from utilities.testing import create_test_device @@ -131,6 +131,7 @@ class LabelTestCase(TestCase): 'device': self.device.pk, 'name': 'eth[0-9]', 'label': 'Interface[0-9]', + 'type': InterfaceTypeChoices.TYPE_1GE_GBIC, } form = InterfaceCreateForm(interface_data) @@ -144,6 +145,7 @@ class LabelTestCase(TestCase): 'device': self.device.pk, 'name': 'eth[0-9]', 'label': 'Interface[0-1]', + 'type': InterfaceTypeChoices.TYPE_1GE_GBIC, } form = InterfaceCreateForm(bad_interface_data) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index d06a495f3..3b8e8750b 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1279,7 +1279,7 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase): model = FrontPortTemplate - validation_excluded_fields = ('name', 'label') + validation_excluded_fields = ('name', 'label', 'rear_port') @classmethod def setUpTestData(cls): @@ -1314,9 +1314,7 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas 'device_type': devicetype.pk, 'name': 'Front Port [4-6]', 'type': PortTypeChoices.TYPE_8P8C, - 'rear_port_set': [ - '{}:1'.format(rp.pk) for rp in rearports[3:6] - ], + 'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]], } cls.bulk_edit_data = { @@ -2292,7 +2290,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase): model = FrontPort - validation_excluded_fields = ('name', 'label') + validation_excluded_fields = ('name', 'label', 'rear_port') @classmethod def setUpTestData(cls): @@ -2330,9 +2328,7 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase): 'device': device.pk, 'name': 'Front Port [4-6]', 'type': PortTypeChoices.TYPE_8P8C, - 'rear_port_set': [ - '{}:1'.format(rp.pk) for rp in rearports[3:6] - ], + 'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]], 'description': 'New description', 'tags': [t.pk for t in tags], } diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 4693d0ce2..d00ceb5a2 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -251,7 +251,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase): class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): model = VMInterface - validation_excluded_fields = ('name', 'label') + validation_excluded_fields = ('name',) @classmethod def setUpTestData(cls): @@ -291,10 +291,10 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): tags = create_tags('Alpha', 'Bravo', 'Charlie') cls.form_data = { - 'virtual_machine': virtualmachines[1].pk, + 'virtual_machine': virtualmachines[0].pk, 'name': 'Interface X', 'enabled': False, - 'bridge': interfaces[3].pk, + 'bridge': interfaces[1].pk, 'mac_address': EUI('01-02-03-04-05-06'), 'mtu': 65000, 'description': 'New description', @@ -307,7 +307,7 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): cls.bulk_create_data = { 'virtual_machine': virtualmachines[1].pk, - 'name_pattern': 'Interface [4-6]', + 'name': 'Interface [4-6]', 'enabled': False, 'bridge': interfaces[3].pk, 'mac_address': EUI('01-02-03-04-05-06'),