diff --git a/netbox/dcim/forms/bulk_create.py b/netbox/dcim/forms/bulk_create.py index e78e0ee19..02c8feb4b 100644 --- a/netbox/dcim/forms/bulk_create.py +++ b/netbox/dcim/forms/bulk_create.py @@ -4,7 +4,7 @@ from dcim.models import * from extras.forms import CustomFieldsMixin from extras.models import Tag from utilities.forms import DynamicModelMultipleChoiceField, form_from_model -from .object_create import ComponentForm +from .object_create import ComponentCreateForm __all__ = ( 'ConsolePortBulkCreateForm', @@ -24,7 +24,7 @@ __all__ = ( # Device components # -class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentForm): +class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm): pk = forms.ModelMultipleChoiceField( queryset=Device.objects.all(), widget=forms.MultipleHiddenInput() diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index 03c488865..8b8c00c6d 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -1,44 +1,19 @@ from django import forms -from django.contrib.contenttypes.models import ContentType -from dcim.choices import * -from dcim.constants import * from dcim.models import * -from extras.forms import CustomFieldModelForm, CustomFieldsMixin +from extras.forms import CustomFieldModelForm from extras.models import Tag -from ipam.models import VLAN from utilities.forms import ( - add_blank_choice, BootstrapMixin, ColorField, ContentTypeChoiceField, DynamicModelChoiceField, - DynamicModelMultipleChoiceField, ExpandableNameField, StaticSelect, + BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, ) -from wireless.choices import * -from .common import InterfaceCommonForm __all__ = ( - 'ConsolePortCreateForm', - 'ConsolePortTemplateCreateForm', - 'ConsoleServerPortCreateForm', - 'ConsoleServerPortTemplateCreateForm', - 'DeviceBayCreateForm', - 'DeviceBayTemplateCreateForm', - 'FrontPortCreateForm', - 'FrontPortTemplateCreateForm', - 'InterfaceCreateForm', - 'InterfaceTemplateCreateForm', - 'InventoryItemCreateForm', - 'ModuleBayCreateForm', - 'ModuleBayTemplateCreateForm', - 'PowerOutletCreateForm', - 'PowerOutletTemplateCreateForm', - 'PowerPortCreateForm', - 'PowerPortTemplateCreateForm', - 'RearPortCreateForm', - 'RearPortTemplateCreateForm', + 'ComponentCreateForm', 'VirtualChassisCreateForm', ) -class ComponentForm(BootstrapMixin, forms.Form): +class ComponentCreateForm(BootstrapMixin, forms.Form): """ Subclass this form when facilitating the creation of one or more device component or component templates based on a name pattern. @@ -139,558 +114,3 @@ class VirtualChassisCreateForm(CustomFieldModelForm): member.save() return instance - - -# -# Component templates -# - -class ComponentTemplateCreateForm(ComponentForm): - """ - Base form for the creation of device component templates (subclassed from ComponentTemplateModel). - """ - manufacturer = DynamicModelChoiceField( - queryset=Manufacturer.objects.all(), - required=False, - initial_params={ - 'device_types': 'device_type', - 'module_types': 'module_type', - } - ) - device_type = DynamicModelChoiceField( - queryset=DeviceType.objects.all(), - required=False, - query_params={ - 'manufacturer_id': '$manufacturer' - } - ) - description = forms.CharField( - required=False - ) - - -class ModularComponentTemplateCreateForm(ComponentTemplateCreateForm): - module_type = DynamicModelChoiceField( - queryset=ModuleType.objects.all(), - required=False, - query_params={ - 'manufacturer_id': '$manufacturer' - } - ) - - -class ConsolePortTemplateCreateForm(ModularComponentTemplateCreateForm): - type = forms.ChoiceField( - choices=add_blank_choice(ConsolePortTypeChoices), - widget=StaticSelect() - ) - field_order = ( - 'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'description', - ) - - -class ConsoleServerPortTemplateCreateForm(ModularComponentTemplateCreateForm): - type = forms.ChoiceField( - choices=add_blank_choice(ConsolePortTypeChoices), - widget=StaticSelect() - ) - field_order = ( - 'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'description', - ) - - -class PowerPortTemplateCreateForm(ModularComponentTemplateCreateForm): - type = forms.ChoiceField( - choices=add_blank_choice(PowerPortTypeChoices), - required=False - ) - maximum_draw = forms.IntegerField( - min_value=1, - required=False, - help_text="Maximum power draw (watts)" - ) - allocated_draw = forms.IntegerField( - min_value=1, - required=False, - help_text="Allocated power draw (watts)" - ) - field_order = ( - 'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'maximum_draw', - 'allocated_draw', 'description', - ) - - -class PowerOutletTemplateCreateForm(ModularComponentTemplateCreateForm): - type = forms.ChoiceField( - choices=add_blank_choice(PowerOutletTypeChoices), - required=False - ) - power_port = DynamicModelChoiceField( - queryset=PowerPortTemplate.objects.all(), - required=False, - query_params={ - 'devicetype_id': '$device_type', - 'moduletype_id': '$module_type', - } - ) - feed_leg = forms.ChoiceField( - choices=add_blank_choice(PowerOutletFeedLegChoices), - required=False, - widget=StaticSelect() - ) - field_order = ( - 'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'power_port', 'feed_leg', - 'description', - ) - - -class InterfaceTemplateCreateForm(ModularComponentTemplateCreateForm): - type = forms.ChoiceField( - choices=InterfaceTypeChoices, - widget=StaticSelect() - ) - mgmt_only = forms.BooleanField( - required=False, - label='Management only' - ) - field_order = ( - 'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'mgmt_only', - 'description', - ) - - -class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm): - type = forms.ChoiceField( - choices=PortTypeChoices, - widget=StaticSelect() - ) - color = ColorField( - required=False - ) - rear_port_set = forms.MultipleChoiceField( - choices=[], - label='Rear ports', - help_text='Select one rear port assignment for each front port being created.', - ) - field_order = ( - 'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'color', 'rear_port_set', - 'description', - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - device_type = DeviceType.objects.get( - pk=self.initial.get('device_type') or self.data.get('device_type') - ) - - # Determine which rear port positions are occupied. These will be excluded from the list of available mappings. - occupied_port_positions = [ - (front_port.rear_port_id, front_port.rear_port_position) - for front_port in device_type.frontporttemplates.all() - ] - - # Populate rear port choices - choices = [] - rear_ports = RearPortTemplate.objects.filter(device_type=device_type) - for rear_port in rear_ports: - for i in range(1, rear_port.positions + 1): - if (rear_port.pk, i) not in occupied_port_positions: - choices.append( - ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i)) - ) - self.fields['rear_port_set'].choices = choices - - def clean(self): - super().clean() - - # Validate that the number of ports being created equals the number of selected (rear port, position) tuples - front_port_count = len(self.cleaned_data['name_pattern']) - rear_port_count = len(self.cleaned_data['rear_port_set']) - if front_port_count != rear_port_count: - raise forms.ValidationError({ - 'rear_port_set': 'The provided name pattern will create {} ports, however {} rear port assignments ' - 'were selected. These counts must match.'.format(front_port_count, rear_port_count) - }) - - 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(':') - - return { - 'rear_port': int(rear_port), - 'rear_port_position': int(position), - } - - -class RearPortTemplateCreateForm(ModularComponentTemplateCreateForm): - type = forms.ChoiceField( - choices=PortTypeChoices, - widget=StaticSelect(), - ) - color = ColorField( - required=False - ) - positions = forms.IntegerField( - min_value=REARPORT_POSITIONS_MIN, - max_value=REARPORT_POSITIONS_MAX, - initial=1, - help_text='The number of front ports which may be mapped to each rear port' - ) - field_order = ( - 'manufacturer', 'device_type', 'module_type', 'name_pattern', 'label_pattern', 'type', 'color', 'positions', - 'description', - ) - - -class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm): - # TODO: Support patterned position assignment - field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'description') - - -class DeviceBayTemplateCreateForm(ComponentTemplateCreateForm): - field_order = ('manufacturer', 'device_type', 'name_pattern', 'label_pattern', 'description') - - -# -# Device components -# - -class ComponentCreateForm(CustomFieldsMixin, ComponentForm): - """ - Base form for the creation of device components (models subclassed from ComponentModel). - """ - device = DynamicModelChoiceField( - queryset=Device.objects.all() - ) - description = forms.CharField( - max_length=200, - required=False - ) - tags = DynamicModelMultipleChoiceField( - queryset=Tag.objects.all(), - required=False - ) - - -class ConsolePortCreateForm(ComponentCreateForm): - model = ConsolePort - type = forms.ChoiceField( - choices=add_blank_choice(ConsolePortTypeChoices), - required=False, - widget=StaticSelect() - ) - speed = forms.ChoiceField( - choices=add_blank_choice(ConsolePortSpeedChoices), - required=False, - widget=StaticSelect() - ) - field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'speed', 'mark_connected', 'description', 'tags') - - -class ConsoleServerPortCreateForm(ComponentCreateForm): - model = ConsoleServerPort - type = forms.ChoiceField( - choices=add_blank_choice(ConsolePortTypeChoices), - required=False, - widget=StaticSelect() - ) - speed = forms.ChoiceField( - choices=add_blank_choice(ConsolePortSpeedChoices), - required=False, - widget=StaticSelect() - ) - field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'speed', 'mark_connected', 'description', 'tags') - - -class PowerPortCreateForm(ComponentCreateForm): - model = PowerPort - type = forms.ChoiceField( - choices=add_blank_choice(PowerPortTypeChoices), - required=False, - widget=StaticSelect() - ) - maximum_draw = forms.IntegerField( - min_value=1, - required=False, - help_text="Maximum draw in watts" - ) - allocated_draw = forms.IntegerField( - min_value=1, - required=False, - help_text="Allocated draw in watts" - ) - field_order = ( - 'device', 'name_pattern', 'label_pattern', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', - 'description', 'tags', - ) - - -class PowerOutletCreateForm(ComponentCreateForm): - model = PowerOutlet - type = forms.ChoiceField( - choices=add_blank_choice(PowerOutletTypeChoices), - required=False, - widget=StaticSelect() - ) - power_port = forms.ModelChoiceField( - queryset=PowerPort.objects.all(), - required=False - ) - feed_leg = forms.ChoiceField( - choices=add_blank_choice(PowerOutletFeedLegChoices), - required=False - ) - field_order = ( - 'device', 'name_pattern', 'label_pattern', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description', - 'tags', - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Limit power_port queryset to PowerPorts which belong to the parent Device - device = Device.objects.get( - pk=self.initial.get('device') or self.data.get('device') - ) - self.fields['power_port'].queryset = PowerPort.objects.filter(device=device) - - -class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm): - model = Interface - type = forms.ChoiceField( - choices=InterfaceTypeChoices, - widget=StaticSelect(), - ) - enabled = forms.BooleanField( - required=False, - initial=True - ) - parent = DynamicModelChoiceField( - queryset=Interface.objects.all(), - required=False, - query_params={ - 'device_id': '$device', - } - ) - bridge = DynamicModelChoiceField( - queryset=Interface.objects.all(), - required=False, - query_params={ - 'device_id': '$device', - } - ) - lag = DynamicModelChoiceField( - queryset=Interface.objects.all(), - required=False, - query_params={ - 'device_id': '$device', - 'type': 'lag', - }, - label='LAG' - ) - mac_address = forms.CharField( - required=False, - label='MAC Address' - ) - wwn = forms.CharField( - required=False, - label='WWN' - ) - mgmt_only = forms.BooleanField( - required=False, - label='Management only', - help_text='This interface is used only for out-of-band management' - ) - mode = forms.ChoiceField( - choices=add_blank_choice(InterfaceModeChoices), - required=False, - widget=StaticSelect() - ) - rf_role = forms.ChoiceField( - choices=add_blank_choice(WirelessRoleChoices), - required=False, - widget=StaticSelect(), - label='Wireless role' - ) - rf_channel = forms.ChoiceField( - choices=add_blank_choice(WirelessChannelChoices), - required=False, - widget=StaticSelect(), - label='Wireless channel' - ) - rf_channel_frequency = forms.DecimalField( - required=False, - label='Channel frequency (MHz)' - ) - rf_channel_width = forms.DecimalField( - required=False, - label='Channel width (MHz)' - ) - untagged_vlan = DynamicModelChoiceField( - queryset=VLAN.objects.all(), - required=False, - label='Untagged VLAN' - ) - tagged_vlans = DynamicModelMultipleChoiceField( - queryset=VLAN.objects.all(), - required=False, - label='Tagged VLANs' - ) - field_order = ( - 'device', 'name_pattern', 'label_pattern', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mtu', 'mac_address', - 'wwn', 'description', 'mgmt_only', 'mark_connected', 'rf_role', 'rf_channel', 'rf_channel_frequency', - 'rf_channel_width', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags' - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Limit VLAN choices by device - device_id = self.initial.get('device') or self.data.get('device') - self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device_id) - self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device_id) - - -class FrontPortCreateForm(ComponentCreateForm): - model = FrontPort - type = forms.ChoiceField( - choices=PortTypeChoices, - widget=StaticSelect(), - ) - color = ColorField( - required=False - ) - rear_port_set = forms.MultipleChoiceField( - choices=[], - label='Rear ports', - help_text='Select one rear port assignment for each front port being created.', - ) - field_order = ( - 'device', 'name_pattern', 'label_pattern', 'type', 'color', 'rear_port_set', 'mark_connected', 'description', - 'tags', - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - device = Device.objects.get( - pk=self.initial.get('device') or self.data.get('device') - ) - - # Determine which rear port positions are occupied. These will be excluded from the list of available - # mappings. - occupied_port_positions = [ - (front_port.rear_port_id, front_port.rear_port_position) - for front_port in device.frontports.all() - ] - - # Populate rear port choices - choices = [] - rear_ports = RearPort.objects.filter(device=device) - for rear_port in rear_ports: - for i in range(1, rear_port.positions + 1): - if (rear_port.pk, i) not in occupied_port_positions: - choices.append( - ('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i)) - ) - self.fields['rear_port_set'].choices = choices - - def clean(self): - super().clean() - - # Validate that the number of ports being created equals the number of selected (rear port, position) tuples - front_port_count = len(self.cleaned_data['name_pattern']) - rear_port_count = len(self.cleaned_data['rear_port_set']) - if front_port_count != rear_port_count: - raise forms.ValidationError({ - 'rear_port_set': 'The provided name pattern will create {} ports, however {} rear port assignments ' - 'were selected. These counts must match.'.format(front_port_count, rear_port_count) - }) - - 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(':') - - return { - 'rear_port': int(rear_port), - 'rear_port_position': int(position), - } - - -class RearPortCreateForm(ComponentCreateForm): - model = RearPort - type = forms.ChoiceField( - choices=PortTypeChoices, - widget=StaticSelect(), - ) - color = ColorField( - required=False - ) - positions = forms.IntegerField( - min_value=REARPORT_POSITIONS_MIN, - max_value=REARPORT_POSITIONS_MAX, - initial=1, - help_text='The number of front ports which may be mapped to each rear port' - ) - field_order = ( - 'device', 'name_pattern', 'label_pattern', 'type', 'color', 'positions', 'mark_connected', 'description', - 'tags', - ) - - -class ModuleBayCreateForm(ComponentCreateForm): - model = ModuleBay - field_order = ('device', 'name_pattern', 'label_pattern', 'description', 'tags') - - -class DeviceBayCreateForm(ComponentCreateForm): - model = DeviceBay - field_order = ('device', 'name_pattern', 'label_pattern', 'description', 'tags') - - -class InventoryItemCreateForm(ComponentCreateForm): - model = InventoryItem - parent = DynamicModelChoiceField( - queryset=InventoryItem.objects.all(), - required=False, - query_params={ - 'device_id': '$device' - } - ) - role = DynamicModelChoiceField( - queryset=InventoryItemRole.objects.all(), - required=False - ) - manufacturer = DynamicModelChoiceField( - queryset=Manufacturer.objects.all(), - required=False - ) - part_id = forms.CharField( - max_length=50, - required=False, - label='Part ID' - ) - serial = forms.CharField( - max_length=50, - required=False, - ) - asset_tag = forms.CharField( - max_length=50, - required=False, - ) - component_type = ContentTypeChoiceField( - queryset=ContentType.objects.all(), - limit_choices_to=MODULAR_COMPONENT_MODELS, - required=False, - widget=StaticSelect - ) - component_id = forms.IntegerField( - required=False - ) - field_order = ( - 'device', 'parent', 'name_pattern', 'label_pattern', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', - 'description', 'component_type', 'component_id', 'tags', - ) diff --git a/netbox/dcim/tests/test_forms.py b/netbox/dcim/tests/test_forms.py index 3b2a9eff0..4c5de1284 100644 --- a/netbox/dcim/tests/test_forms.py +++ b/netbox/dcim/tests/test_forms.py @@ -118,41 +118,27 @@ class DeviceTestCase(TestCase): class LabelTestCase(TestCase): - @classmethod - def setUpTestData(cls): - site = Site.objects.create(name='Site 2', slug='site-2') - manufacturer = Manufacturer.objects.create(name='Manufacturer 2', slug='manufacturer-2') - cls.device_type = DeviceType.objects.create( - manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', u_height=1 - ) - device_role = DeviceRole.objects.create( - name='Device Role 2', slug='device-role-2', color='ffff00' - ) - cls.device = Device.objects.create( - name='Device 2', device_type=cls.device_type, device_role=device_role, site=site - ) - def test_interface_label_count_valid(self): - """Test that a `label` can be generated for each generated `name` from `name_pattern` on InterfaceCreateForm""" + """ + 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]', - 'type': InterfaceTypeChoices.TYPE_100ME_FIXED, } - form = InterfaceCreateForm(interface_data) + form = ComponentCreateForm(interface_data) self.assertTrue(form.is_valid()) def test_interface_label_count_mismatch(self): - """Test that a `label` cannot be generated for each generated `name` from `name_pattern` due to invalid `label_pattern` on InterfaceCreateForm""" + """ + 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]', - 'type': InterfaceTypeChoices.TYPE_100ME_FIXED, } - form = InterfaceCreateForm(bad_interface_data) + form = ComponentCreateForm(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 bfa2fecae..1616b95e9 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1054,7 +1054,6 @@ class ModuleTypeBulkDeleteView(generic.BulkDeleteView): class ConsolePortTemplateCreateView(generic.ComponentCreateView): queryset = ConsolePortTemplate.objects.all() - form = forms.ConsolePortTemplateCreateForm model_form = forms.ConsolePortTemplateForm @@ -1088,7 +1087,6 @@ class ConsolePortTemplateBulkDeleteView(generic.BulkDeleteView): class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView): queryset = ConsoleServerPortTemplate.objects.all() - form = forms.ConsoleServerPortTemplateCreateForm model_form = forms.ConsoleServerPortTemplateForm @@ -1122,7 +1120,6 @@ class ConsoleServerPortTemplateBulkDeleteView(generic.BulkDeleteView): class PowerPortTemplateCreateView(generic.ComponentCreateView): queryset = PowerPortTemplate.objects.all() - form = forms.PowerPortTemplateCreateForm model_form = forms.PowerPortTemplateForm @@ -1156,7 +1153,6 @@ class PowerPortTemplateBulkDeleteView(generic.BulkDeleteView): class PowerOutletTemplateCreateView(generic.ComponentCreateView): queryset = PowerOutletTemplate.objects.all() - form = forms.PowerOutletTemplateCreateForm model_form = forms.PowerOutletTemplateForm @@ -1190,7 +1186,6 @@ class PowerOutletTemplateBulkDeleteView(generic.BulkDeleteView): class InterfaceTemplateCreateView(generic.ComponentCreateView): queryset = InterfaceTemplate.objects.all() - form = forms.InterfaceTemplateCreateForm model_form = forms.InterfaceTemplateForm @@ -1224,7 +1219,6 @@ class InterfaceTemplateBulkDeleteView(generic.BulkDeleteView): class FrontPortTemplateCreateView(generic.ComponentCreateView): queryset = FrontPortTemplate.objects.all() - form = forms.FrontPortTemplateCreateForm model_form = forms.FrontPortTemplateForm @@ -1258,7 +1252,6 @@ class FrontPortTemplateBulkDeleteView(generic.BulkDeleteView): class RearPortTemplateCreateView(generic.ComponentCreateView): queryset = RearPortTemplate.objects.all() - form = forms.RearPortTemplateCreateForm model_form = forms.RearPortTemplateForm @@ -1292,7 +1285,6 @@ class RearPortTemplateBulkDeleteView(generic.BulkDeleteView): class ModuleBayTemplateCreateView(generic.ComponentCreateView): queryset = ModuleBayTemplate.objects.all() - form = forms.ModuleBayTemplateCreateForm model_form = forms.ModuleBayTemplateForm @@ -1326,7 +1318,6 @@ class ModuleBayTemplateBulkDeleteView(generic.BulkDeleteView): class DeviceBayTemplateCreateView(generic.ComponentCreateView): queryset = DeviceBayTemplate.objects.all() - form = forms.DeviceBayTemplateCreateForm model_form = forms.DeviceBayTemplateForm @@ -1741,7 +1732,6 @@ class ConsolePortView(generic.ObjectView): class ConsolePortCreateView(generic.ComponentCreateView): queryset = ConsolePort.objects.all() - form = forms.ConsolePortCreateForm model_form = forms.ConsolePortForm @@ -1800,7 +1790,6 @@ class ConsoleServerPortView(generic.ObjectView): class ConsoleServerPortCreateView(generic.ComponentCreateView): queryset = ConsoleServerPort.objects.all() - form = forms.ConsoleServerPortCreateForm model_form = forms.ConsoleServerPortForm @@ -1859,7 +1848,6 @@ class PowerPortView(generic.ObjectView): class PowerPortCreateView(generic.ComponentCreateView): queryset = PowerPort.objects.all() - form = forms.PowerPortCreateForm model_form = forms.PowerPortForm @@ -1918,7 +1906,6 @@ class PowerOutletView(generic.ObjectView): class PowerOutletCreateView(generic.ComponentCreateView): queryset = PowerOutlet.objects.all() - form = forms.PowerOutletCreateForm model_form = forms.PowerOutletForm @@ -2012,7 +1999,6 @@ class InterfaceView(generic.ObjectView): class InterfaceCreateView(generic.ComponentCreateView): queryset = Interface.objects.all() - form = forms.InterfaceCreateForm model_form = forms.InterfaceForm template_name = 'dcim/interface_create.html' @@ -2098,7 +2084,6 @@ class FrontPortView(generic.ObjectView): class FrontPortCreateView(generic.ComponentCreateView): queryset = FrontPort.objects.all() - form = forms.FrontPortCreateForm model_form = forms.FrontPortForm @@ -2157,7 +2142,6 @@ class RearPortView(generic.ObjectView): class RearPortCreateView(generic.ComponentCreateView): queryset = RearPort.objects.all() - form = forms.RearPortCreateForm model_form = forms.RearPortForm @@ -2216,7 +2200,6 @@ class ModuleBayView(generic.ObjectView): class ModuleBayCreateView(generic.ComponentCreateView): queryset = ModuleBay.objects.all() - form = forms.ModuleBayCreateForm model_form = forms.ModuleBayForm @@ -2271,7 +2254,6 @@ class DeviceBayView(generic.ObjectView): class DeviceBayCreateView(generic.ComponentCreateView): queryset = DeviceBay.objects.all() - form = forms.DeviceBayCreateForm model_form = forms.DeviceBayForm @@ -2397,7 +2379,6 @@ class InventoryItemEditView(generic.ObjectEditView): class InventoryItemCreateView(generic.ComponentCreateView): queryset = InventoryItem.objects.all() - form = forms.InventoryItemCreateForm model_form = forms.InventoryItemForm diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index fed4b2f60..009422038 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.db.models import ProtectedError +from django.forms.widgets import HiddenInput from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.utils.html import escape @@ -14,6 +15,7 @@ from django.utils.safestring import mark_safe from django.views.generic import View from django_tables2.export import TableExport +from dcim.forms.object_create import ComponentCreateForm from extras.models import ExportTemplate from extras.signals import clear_webhooks from utilities.error_handlers import handle_protectederror @@ -674,33 +676,45 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): # Device/VirtualMachine components # -# TODO: Replace with BulkCreateView class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): """ Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine. """ queryset = None - form = None + form = ComponentCreateForm model_form = None - template_name = 'generic/object_edit.html' + template_name = 'dcim/component_create.html' + patterned_fields = ('name', 'label') def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'add') - def get(self, request): + def initialize_forms(self, request): + data = request.POST if request.method == 'POST' else None + initial_data = normalize_querydict(request.GET) - form = self.form(initial=request.GET) + form = self.form(data=data, initial=request.GET) + model_form = self.model_form(data=data, initial=initial_data) + + # These fields will be set from the pattern values + for field_name in self.patterned_fields: + model_form.fields[field_name].widget = HiddenInput() + + return form, model_form + + def get(self, request): + form, model_form = self.initialize_forms(request) return render(request, self.template_name, { - 'obj': self.queryset.model(), 'obj_type': self.queryset.model._meta.verbose_name, 'form': form, + 'model_form': model_form, 'return_url': self.get_return_url(request), }) def post(self, request): - logger = logging.getLogger('netbox.views.ComponentCreateView') - form = self.form(request.POST, initial=request.GET) + form, model_form = self.initialize_forms(request) + self.validate_form(request, form) if form.is_valid() and not form.errors: @@ -712,6 +726,7 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View return render(request, self.template_name, { 'obj_type': self.queryset.model._meta.verbose_name, 'form': form, + 'model_form': model_form, 'return_url': self.get_return_url(request), }) @@ -734,8 +749,8 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View data['name'] = name data['label'] = label - if hasattr(form, 'get_iterative_data'): - data.update(form.get_iterative_data(i)) + # if hasattr(form, 'get_iterative_data'): + # data.update(form.get_iterative_data(i)) component_form = self.model_form(data) diff --git a/netbox/templates/dcim/component_create.html b/netbox/templates/dcim/component_create.html new file mode 100644 index 000000000..367fc2d11 --- /dev/null +++ b/netbox/templates/dcim/component_create.html @@ -0,0 +1,7 @@ +{% extends 'generic/object_edit.html' %} +{% load form_helpers %} + +{% block form %} + {% render_form form %} + {% render_form model_form %} +{% endblock form %} diff --git a/netbox/virtualization/forms/object_create.py b/netbox/virtualization/forms/object_create.py index 332334594..f275469fd 100644 --- a/netbox/virtualization/forms/object_create.py +++ b/netbox/virtualization/forms/object_create.py @@ -1,81 +1,13 @@ from django import forms -from dcim.choices import InterfaceModeChoices -from dcim.forms.common import InterfaceCommonForm -from extras.forms import CustomFieldsMixin -from extras.models import Tag -from ipam.models import VLAN -from utilities.forms import ( - add_blank_choice, BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, - StaticSelect, -) -from virtualization.models import VMInterface, VirtualMachine +from utilities.forms import BootstrapMixin, ExpandableNameField __all__ = ( 'VMInterfaceCreateForm', ) -class VMInterfaceCreateForm(BootstrapMixin, CustomFieldsMixin, InterfaceCommonForm): - model = VMInterface - virtual_machine = DynamicModelChoiceField( - queryset=VirtualMachine.objects.all() - ) +class VMInterfaceCreateForm(BootstrapMixin, forms.Form): name_pattern = ExpandableNameField( label='Name' ) - enabled = forms.BooleanField( - required=False, - initial=True - ) - parent = DynamicModelChoiceField( - queryset=VMInterface.objects.all(), - required=False, - query_params={ - 'virtual_machine_id': '$virtual_machine', - } - ) - bridge = DynamicModelChoiceField( - queryset=VMInterface.objects.all(), - required=False, - query_params={ - 'virtual_machine_id': '$virtual_machine', - } - ) - mac_address = forms.CharField( - required=False, - label='MAC Address' - ) - description = forms.CharField( - max_length=200, - required=False - ) - mode = forms.ChoiceField( - choices=add_blank_choice(InterfaceModeChoices), - required=False, - widget=StaticSelect(), - ) - untagged_vlan = DynamicModelChoiceField( - queryset=VLAN.objects.all(), - required=False - ) - tagged_vlans = DynamicModelMultipleChoiceField( - queryset=VLAN.objects.all(), - required=False - ) - tags = DynamicModelMultipleChoiceField( - queryset=Tag.objects.all(), - required=False - ) - field_order = ( - 'virtual_machine', 'name_pattern', 'enabled', 'parent', 'bridge', 'mtu', 'mac_address', 'description', 'mode', - 'untagged_vlan', 'tagged_vlans', 'tags' - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - vm_id = self.initial.get('virtual_machine') or self.data.get('virtual_machine') - - # Limit VLAN choices by virtual machine - self.fields['untagged_vlan'].widget.add_query_param('available_on_virtualmachine', vm_id) - self.fields['tagged_vlans'].widget.add_query_param('available_on_virtualmachine', vm_id) diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 8183555bd..742d6d9ea 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -447,11 +447,11 @@ class VMInterfaceView(generic.ObjectView): } -# TODO: This should not use ComponentCreateView class VMInterfaceCreateView(generic.ComponentCreateView): queryset = VMInterface.objects.all() form = forms.VMInterfaceCreateForm model_form = forms.VMInterfaceForm + patterned_fields = ('name',) class VMInterfaceEditView(generic.ObjectEditView):