2
0
Эх сурвалжийг харах

Refactor ComponentCreateView to use separate forms for names/labels and model creation

jeremystretch 4 жил өмнө
parent
commit
a237c01b4b

+ 2 - 2
netbox/dcim/forms/bulk_create.py

@@ -4,7 +4,7 @@ from dcim.models import *
 from extras.forms import CustomFieldsMixin
 from extras.forms import CustomFieldsMixin
 from extras.models import Tag
 from extras.models import Tag
 from utilities.forms import DynamicModelMultipleChoiceField, form_from_model
 from utilities.forms import DynamicModelMultipleChoiceField, form_from_model
-from .object_create import ComponentForm
+from .object_create import ComponentCreateForm
 
 
 __all__ = (
 __all__ = (
     'ConsolePortBulkCreateForm',
     'ConsolePortBulkCreateForm',
@@ -24,7 +24,7 @@ __all__ = (
 # Device components
 # Device components
 #
 #
 
 
-class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentForm):
+class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         widget=forms.MultipleHiddenInput()
         widget=forms.MultipleHiddenInput()

+ 4 - 584
netbox/dcim/forms/object_create.py

@@ -1,44 +1,19 @@
 from django import forms
 from django import forms
-from django.contrib.contenttypes.models import ContentType
 
 
-from dcim.choices import *
-from dcim.constants import *
 from dcim.models import *
 from dcim.models import *
-from extras.forms import CustomFieldModelForm, CustomFieldsMixin
+from extras.forms import CustomFieldModelForm
 from extras.models import Tag
 from extras.models import Tag
-from ipam.models import VLAN
 from utilities.forms import (
 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__ = (
 __all__ = (
-    'ConsolePortCreateForm',
-    'ConsolePortTemplateCreateForm',
-    'ConsoleServerPortCreateForm',
-    'ConsoleServerPortTemplateCreateForm',
-    'DeviceBayCreateForm',
-    'DeviceBayTemplateCreateForm',
-    'FrontPortCreateForm',
-    'FrontPortTemplateCreateForm',
-    'InterfaceCreateForm',
-    'InterfaceTemplateCreateForm',
-    'InventoryItemCreateForm',
-    'ModuleBayCreateForm',
-    'ModuleBayTemplateCreateForm',
-    'PowerOutletCreateForm',
-    'PowerOutletTemplateCreateForm',
-    'PowerPortCreateForm',
-    'PowerPortTemplateCreateForm',
-    'RearPortCreateForm',
-    'RearPortTemplateCreateForm',
+    'ComponentCreateForm',
     'VirtualChassisCreateForm',
     '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
     Subclass this form when facilitating the creation of one or more device component or component templates based on
     a name pattern.
     a name pattern.
@@ -139,558 +114,3 @@ class VirtualChassisCreateForm(CustomFieldModelForm):
                 member.save()
                 member.save()
 
 
         return instance
         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',
-    )

+ 8 - 22
netbox/dcim/tests/test_forms.py

@@ -118,41 +118,27 @@ class DeviceTestCase(TestCase):
 
 
 class LabelTestCase(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):
     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 = {
         interface_data = {
-            'device': self.device.pk,
             'name_pattern': 'eth[0-9]',
             'name_pattern': 'eth[0-9]',
             'label_pattern': 'Interface[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())
         self.assertTrue(form.is_valid())
 
 
     def test_interface_label_count_mismatch(self):
     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 = {
         bad_interface_data = {
-            'device': self.device.pk,
             'name_pattern': 'eth[0-9]',
             'name_pattern': 'eth[0-9]',
             'label_pattern': 'Interface[0-1]',
             '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.assertFalse(form.is_valid())
         self.assertIn('label_pattern', form.errors)
         self.assertIn('label_pattern', form.errors)

+ 0 - 19
netbox/dcim/views.py

@@ -1054,7 +1054,6 @@ class ModuleTypeBulkDeleteView(generic.BulkDeleteView):
 
 
 class ConsolePortTemplateCreateView(generic.ComponentCreateView):
 class ConsolePortTemplateCreateView(generic.ComponentCreateView):
     queryset = ConsolePortTemplate.objects.all()
     queryset = ConsolePortTemplate.objects.all()
-    form = forms.ConsolePortTemplateCreateForm
     model_form = forms.ConsolePortTemplateForm
     model_form = forms.ConsolePortTemplateForm
 
 
 
 
@@ -1088,7 +1087,6 @@ class ConsolePortTemplateBulkDeleteView(generic.BulkDeleteView):
 
 
 class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
 class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
     queryset = ConsoleServerPortTemplate.objects.all()
     queryset = ConsoleServerPortTemplate.objects.all()
-    form = forms.ConsoleServerPortTemplateCreateForm
     model_form = forms.ConsoleServerPortTemplateForm
     model_form = forms.ConsoleServerPortTemplateForm
 
 
 
 
@@ -1122,7 +1120,6 @@ class ConsoleServerPortTemplateBulkDeleteView(generic.BulkDeleteView):
 
 
 class PowerPortTemplateCreateView(generic.ComponentCreateView):
 class PowerPortTemplateCreateView(generic.ComponentCreateView):
     queryset = PowerPortTemplate.objects.all()
     queryset = PowerPortTemplate.objects.all()
-    form = forms.PowerPortTemplateCreateForm
     model_form = forms.PowerPortTemplateForm
     model_form = forms.PowerPortTemplateForm
 
 
 
 
@@ -1156,7 +1153,6 @@ class PowerPortTemplateBulkDeleteView(generic.BulkDeleteView):
 
 
 class PowerOutletTemplateCreateView(generic.ComponentCreateView):
 class PowerOutletTemplateCreateView(generic.ComponentCreateView):
     queryset = PowerOutletTemplate.objects.all()
     queryset = PowerOutletTemplate.objects.all()
-    form = forms.PowerOutletTemplateCreateForm
     model_form = forms.PowerOutletTemplateForm
     model_form = forms.PowerOutletTemplateForm
 
 
 
 
@@ -1190,7 +1186,6 @@ class PowerOutletTemplateBulkDeleteView(generic.BulkDeleteView):
 
 
 class InterfaceTemplateCreateView(generic.ComponentCreateView):
 class InterfaceTemplateCreateView(generic.ComponentCreateView):
     queryset = InterfaceTemplate.objects.all()
     queryset = InterfaceTemplate.objects.all()
-    form = forms.InterfaceTemplateCreateForm
     model_form = forms.InterfaceTemplateForm
     model_form = forms.InterfaceTemplateForm
 
 
 
 
@@ -1224,7 +1219,6 @@ class InterfaceTemplateBulkDeleteView(generic.BulkDeleteView):
 
 
 class FrontPortTemplateCreateView(generic.ComponentCreateView):
 class FrontPortTemplateCreateView(generic.ComponentCreateView):
     queryset = FrontPortTemplate.objects.all()
     queryset = FrontPortTemplate.objects.all()
-    form = forms.FrontPortTemplateCreateForm
     model_form = forms.FrontPortTemplateForm
     model_form = forms.FrontPortTemplateForm
 
 
 
 
@@ -1258,7 +1252,6 @@ class FrontPortTemplateBulkDeleteView(generic.BulkDeleteView):
 
 
 class RearPortTemplateCreateView(generic.ComponentCreateView):
 class RearPortTemplateCreateView(generic.ComponentCreateView):
     queryset = RearPortTemplate.objects.all()
     queryset = RearPortTemplate.objects.all()
-    form = forms.RearPortTemplateCreateForm
     model_form = forms.RearPortTemplateForm
     model_form = forms.RearPortTemplateForm
 
 
 
 
@@ -1292,7 +1285,6 @@ class RearPortTemplateBulkDeleteView(generic.BulkDeleteView):
 
 
 class ModuleBayTemplateCreateView(generic.ComponentCreateView):
 class ModuleBayTemplateCreateView(generic.ComponentCreateView):
     queryset = ModuleBayTemplate.objects.all()
     queryset = ModuleBayTemplate.objects.all()
-    form = forms.ModuleBayTemplateCreateForm
     model_form = forms.ModuleBayTemplateForm
     model_form = forms.ModuleBayTemplateForm
 
 
 
 
@@ -1326,7 +1318,6 @@ class ModuleBayTemplateBulkDeleteView(generic.BulkDeleteView):
 
 
 class DeviceBayTemplateCreateView(generic.ComponentCreateView):
 class DeviceBayTemplateCreateView(generic.ComponentCreateView):
     queryset = DeviceBayTemplate.objects.all()
     queryset = DeviceBayTemplate.objects.all()
-    form = forms.DeviceBayTemplateCreateForm
     model_form = forms.DeviceBayTemplateForm
     model_form = forms.DeviceBayTemplateForm
 
 
 
 
@@ -1741,7 +1732,6 @@ class ConsolePortView(generic.ObjectView):
 
 
 class ConsolePortCreateView(generic.ComponentCreateView):
 class ConsolePortCreateView(generic.ComponentCreateView):
     queryset = ConsolePort.objects.all()
     queryset = ConsolePort.objects.all()
-    form = forms.ConsolePortCreateForm
     model_form = forms.ConsolePortForm
     model_form = forms.ConsolePortForm
 
 
 
 
@@ -1800,7 +1790,6 @@ class ConsoleServerPortView(generic.ObjectView):
 
 
 class ConsoleServerPortCreateView(generic.ComponentCreateView):
 class ConsoleServerPortCreateView(generic.ComponentCreateView):
     queryset = ConsoleServerPort.objects.all()
     queryset = ConsoleServerPort.objects.all()
-    form = forms.ConsoleServerPortCreateForm
     model_form = forms.ConsoleServerPortForm
     model_form = forms.ConsoleServerPortForm
 
 
 
 
@@ -1859,7 +1848,6 @@ class PowerPortView(generic.ObjectView):
 
 
 class PowerPortCreateView(generic.ComponentCreateView):
 class PowerPortCreateView(generic.ComponentCreateView):
     queryset = PowerPort.objects.all()
     queryset = PowerPort.objects.all()
-    form = forms.PowerPortCreateForm
     model_form = forms.PowerPortForm
     model_form = forms.PowerPortForm
 
 
 
 
@@ -1918,7 +1906,6 @@ class PowerOutletView(generic.ObjectView):
 
 
 class PowerOutletCreateView(generic.ComponentCreateView):
 class PowerOutletCreateView(generic.ComponentCreateView):
     queryset = PowerOutlet.objects.all()
     queryset = PowerOutlet.objects.all()
-    form = forms.PowerOutletCreateForm
     model_form = forms.PowerOutletForm
     model_form = forms.PowerOutletForm
 
 
 
 
@@ -2012,7 +1999,6 @@ class InterfaceView(generic.ObjectView):
 
 
 class InterfaceCreateView(generic.ComponentCreateView):
 class InterfaceCreateView(generic.ComponentCreateView):
     queryset = Interface.objects.all()
     queryset = Interface.objects.all()
-    form = forms.InterfaceCreateForm
     model_form = forms.InterfaceForm
     model_form = forms.InterfaceForm
     template_name = 'dcim/interface_create.html'
     template_name = 'dcim/interface_create.html'
 
 
@@ -2098,7 +2084,6 @@ class FrontPortView(generic.ObjectView):
 
 
 class FrontPortCreateView(generic.ComponentCreateView):
 class FrontPortCreateView(generic.ComponentCreateView):
     queryset = FrontPort.objects.all()
     queryset = FrontPort.objects.all()
-    form = forms.FrontPortCreateForm
     model_form = forms.FrontPortForm
     model_form = forms.FrontPortForm
 
 
 
 
@@ -2157,7 +2142,6 @@ class RearPortView(generic.ObjectView):
 
 
 class RearPortCreateView(generic.ComponentCreateView):
 class RearPortCreateView(generic.ComponentCreateView):
     queryset = RearPort.objects.all()
     queryset = RearPort.objects.all()
-    form = forms.RearPortCreateForm
     model_form = forms.RearPortForm
     model_form = forms.RearPortForm
 
 
 
 
@@ -2216,7 +2200,6 @@ class ModuleBayView(generic.ObjectView):
 
 
 class ModuleBayCreateView(generic.ComponentCreateView):
 class ModuleBayCreateView(generic.ComponentCreateView):
     queryset = ModuleBay.objects.all()
     queryset = ModuleBay.objects.all()
-    form = forms.ModuleBayCreateForm
     model_form = forms.ModuleBayForm
     model_form = forms.ModuleBayForm
 
 
 
 
@@ -2271,7 +2254,6 @@ class DeviceBayView(generic.ObjectView):
 
 
 class DeviceBayCreateView(generic.ComponentCreateView):
 class DeviceBayCreateView(generic.ComponentCreateView):
     queryset = DeviceBay.objects.all()
     queryset = DeviceBay.objects.all()
-    form = forms.DeviceBayCreateForm
     model_form = forms.DeviceBayForm
     model_form = forms.DeviceBayForm
 
 
 
 
@@ -2397,7 +2379,6 @@ class InventoryItemEditView(generic.ObjectEditView):
 
 
 class InventoryItemCreateView(generic.ComponentCreateView):
 class InventoryItemCreateView(generic.ComponentCreateView):
     queryset = InventoryItem.objects.all()
     queryset = InventoryItem.objects.all()
-    form = forms.InventoryItemCreateForm
     model_form = forms.InventoryItemForm
     model_form = forms.InventoryItemForm
 
 
 
 

+ 25 - 10
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.core.exceptions import ObjectDoesNotExist
 from django.db import transaction
 from django.db import transaction
 from django.db.models import ProtectedError
 from django.db.models import ProtectedError
+from django.forms.widgets import HiddenInput
 from django.http import HttpResponse
 from django.http import HttpResponse
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django.utils.html import escape
 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.views.generic import View
 from django_tables2.export import TableExport
 from django_tables2.export import TableExport
 
 
+from dcim.forms.object_create import ComponentCreateForm
 from extras.models import ExportTemplate
 from extras.models import ExportTemplate
 from extras.signals import clear_webhooks
 from extras.signals import clear_webhooks
 from utilities.error_handlers import handle_protectederror
 from utilities.error_handlers import handle_protectederror
@@ -674,33 +676,45 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
 # Device/VirtualMachine components
 # Device/VirtualMachine components
 #
 #
 
 
-# TODO: Replace with BulkCreateView
 class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
 class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
     """
     """
     Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
     Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
     """
     """
     queryset = None
     queryset = None
-    form = None
+    form = ComponentCreateForm
     model_form = None
     model_form = None
-    template_name = 'generic/object_edit.html'
+    template_name = 'dcim/component_create.html'
+    patterned_fields = ('name', 'label')
 
 
     def get_required_permission(self):
     def get_required_permission(self):
         return get_permission_for_model(self.queryset.model, 'add')
         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(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()
 
 
-        form = self.form(initial=request.GET)
+        return form, model_form
+
+    def get(self, request):
+        form, model_form = self.initialize_forms(request)
 
 
         return render(request, self.template_name, {
         return render(request, self.template_name, {
-            'obj': self.queryset.model(),
             'obj_type': self.queryset.model._meta.verbose_name,
             'obj_type': self.queryset.model._meta.verbose_name,
             'form': form,
             'form': form,
+            'model_form': model_form,
             'return_url': self.get_return_url(request),
             'return_url': self.get_return_url(request),
         })
         })
 
 
     def post(self, 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)
         self.validate_form(request, form)
 
 
         if form.is_valid() and not form.errors:
         if form.is_valid() and not form.errors:
@@ -712,6 +726,7 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
         return render(request, self.template_name, {
         return render(request, self.template_name, {
             'obj_type': self.queryset.model._meta.verbose_name,
             'obj_type': self.queryset.model._meta.verbose_name,
             'form': form,
             'form': form,
+            'model_form': model_form,
             'return_url': self.get_return_url(request),
             'return_url': self.get_return_url(request),
         })
         })
 
 
@@ -734,8 +749,8 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
                 data['name'] = name
                 data['name'] = name
                 data['label'] = label
                 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)
                 component_form = self.model_form(data)
 
 

+ 7 - 0
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 %}

+ 2 - 70
netbox/virtualization/forms/object_create.py

@@ -1,81 +1,13 @@
 from django import forms
 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__ = (
 __all__ = (
     'VMInterfaceCreateForm',
     'VMInterfaceCreateForm',
 )
 )
 
 
 
 
-class VMInterfaceCreateForm(BootstrapMixin, CustomFieldsMixin, InterfaceCommonForm):
-    model = VMInterface
-    virtual_machine = DynamicModelChoiceField(
-        queryset=VirtualMachine.objects.all()
-    )
+class VMInterfaceCreateForm(BootstrapMixin, forms.Form):
     name_pattern = ExpandableNameField(
     name_pattern = ExpandableNameField(
         label='Name'
         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)

+ 1 - 1
netbox/virtualization/views.py

@@ -447,11 +447,11 @@ class VMInterfaceView(generic.ObjectView):
         }
         }
 
 
 
 
-# TODO: This should not use ComponentCreateView
 class VMInterfaceCreateView(generic.ComponentCreateView):
 class VMInterfaceCreateView(generic.ComponentCreateView):
     queryset = VMInterface.objects.all()
     queryset = VMInterface.objects.all()
     form = forms.VMInterfaceCreateForm
     form = forms.VMInterfaceCreateForm
     model_form = forms.VMInterfaceForm
     model_form = forms.VMInterfaceForm
+    patterned_fields = ('name',)
 
 
 
 
 class VMInterfaceEditView(generic.ObjectEditView):
 class VMInterfaceEditView(generic.ObjectEditView):