diff --git a/netbox/dcim/forms/bulk_create.py b/netbox/dcim/forms/bulk_create.py
index 43b852928..f6bc27079 100644
--- a/netbox/dcim/forms/bulk_create.py
+++ b/netbox/dcim/forms/bulk_create.py
@@ -3,7 +3,7 @@ from django import forms
from dcim.models import *
from extras.forms import CustomFieldsMixin
from extras.models import Tag
-from utilities.forms import DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
+from utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
from .object_create import ComponentCreateForm
__all__ = (
@@ -24,7 +24,7 @@ __all__ = (
# Device components
#
-class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
+class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentCreateForm):
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
@@ -37,6 +37,7 @@ class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
queryset=Tag.objects.all(),
required=False
)
+ replication_fields = ('name', 'label')
class ConsolePortBulkCreateForm(
@@ -44,7 +45,7 @@ class ConsolePortBulkCreateForm(
DeviceBulkAddComponentForm
):
model = ConsolePort
- field_order = ('name_pattern', 'label_pattern', 'type', 'mark_connected', 'description', 'tags')
+ field_order = ('name', 'label', 'type', 'mark_connected', 'description', 'tags')
class ConsoleServerPortBulkCreateForm(
@@ -52,7 +53,7 @@ class ConsoleServerPortBulkCreateForm(
DeviceBulkAddComponentForm
):
model = ConsoleServerPort
- field_order = ('name_pattern', 'label_pattern', 'type', 'speed', 'description', 'tags')
+ field_order = ('name', 'label', 'type', 'speed', 'description', 'tags')
class PowerPortBulkCreateForm(
@@ -60,7 +61,7 @@ class PowerPortBulkCreateForm(
DeviceBulkAddComponentForm
):
model = PowerPort
- field_order = ('name_pattern', 'label_pattern', 'type', 'maximum_draw', 'allocated_draw', 'description', 'tags')
+ field_order = ('name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'tags')
class PowerOutletBulkCreateForm(
@@ -68,7 +69,7 @@ class PowerOutletBulkCreateForm(
DeviceBulkAddComponentForm
):
model = PowerOutlet
- field_order = ('name_pattern', 'label_pattern', 'type', 'feed_leg', 'description', 'tags')
+ field_order = ('name', 'label', 'type', 'feed_leg', 'description', 'tags')
class InterfaceBulkCreateForm(
@@ -79,7 +80,7 @@ class InterfaceBulkCreateForm(
):
model = Interface
field_order = (
- 'name_pattern', 'label_pattern', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
+ 'name', 'label', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
'poe_type', 'mark_connected', 'description', 'tags',
)
@@ -96,13 +97,13 @@ class RearPortBulkCreateForm(
DeviceBulkAddComponentForm
):
model = RearPort
- field_order = ('name_pattern', 'label_pattern', 'type', 'positions', 'mark_connected', 'description', 'tags')
+ field_order = ('name', 'label', 'type', 'positions', 'mark_connected', 'description', 'tags')
class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
model = ModuleBay
- field_order = ('name_pattern', 'label_pattern', 'position_pattern', 'description', 'tags')
-
+ field_order = ('name', 'label', 'position_pattern', 'description', 'tags')
+ replication_fields = ('name', 'label', 'position')
position_pattern = ExpandableNameField(
label='Position',
required=False,
@@ -112,7 +113,7 @@ class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
model = DeviceBay
- field_order = ('name_pattern', 'label_pattern', 'description', 'tags')
+ field_order = ('name', 'label', 'description', 'tags')
class InventoryItemBulkCreateForm(
@@ -121,6 +122,6 @@ class InventoryItemBulkCreateForm(
):
model = InventoryItem
field_order = (
- 'name_pattern', 'label_pattern', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
+ 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
'description', 'tags',
)
diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py
index a21265db4..4fa27ae69 100644
--- a/netbox/dcim/forms/models.py
+++ b/netbox/dcim/forms/models.py
@@ -986,47 +986,74 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form):
# Device component templates
#
+class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
+ device_type = DynamicModelChoiceField(
+ queryset=DeviceType.objects.all()
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # Disable reassignment of DeviceType when editing an existing instance
+ if self.instance.pk:
+ self.fields['device_type'].disabled = True
+
+
+class ModularComponentTemplateForm(ComponentTemplateForm):
+ module_type = DynamicModelChoiceField(
+ queryset=ModuleType.objects.all(),
+ required=False
+ )
+
+
+class ConsolePortTemplateForm(ModularComponentTemplateForm):
+ fieldsets = (
+ (None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
+ )
-class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ConsolePortTemplate
fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'description',
]
widgets = {
- 'device_type': forms.HiddenInput(),
- 'module_type': forms.HiddenInput(),
'type': StaticSelect,
}
-class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
+class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
+ fieldsets = (
+ (None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
+ )
+
class Meta:
model = ConsoleServerPortTemplate
fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'description',
]
widgets = {
- 'device_type': forms.HiddenInput(),
- 'module_type': forms.HiddenInput(),
'type': StaticSelect,
}
-class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
+class PowerPortTemplateForm(ModularComponentTemplateForm):
+ fieldsets = (
+ (None, (
+ 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
+ )),
+ )
+
class Meta:
model = PowerPortTemplate
fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
]
widgets = {
- 'device_type': forms.HiddenInput(),
- 'module_type': forms.HiddenInput(),
'type': StaticSelect(),
}
-class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
+class PowerOutletTemplateForm(ModularComponentTemplateForm):
power_port = DynamicModelChoiceField(
queryset=PowerPortTemplate.objects.all(),
required=False,
@@ -1035,35 +1062,40 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
}
)
+ fieldsets = (
+ (None, ('device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')),
+ )
+
class Meta:
model = PowerOutletTemplate
fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
]
widgets = {
- 'device_type': forms.HiddenInput(),
- 'module_type': forms.HiddenInput(),
'type': StaticSelect(),
'feed_leg': StaticSelect(),
}
-class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
+class InterfaceTemplateForm(ModularComponentTemplateForm):
+ fieldsets = (
+ (None, ('device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description')),
+ ('PoE', ('poe_mode', 'poe_type'))
+ )
+
class Meta:
model = InterfaceTemplate
fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
]
widgets = {
- 'device_type': forms.HiddenInput(),
- 'module_type': forms.HiddenInput(),
'type': StaticSelect(),
'poe_mode': StaticSelect(),
'poe_type': StaticSelect(),
}
-class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
+class FrontPortTemplateForm(ModularComponentTemplateForm):
rear_port = DynamicModelChoiceField(
queryset=RearPortTemplate.objects.all(),
required=False,
@@ -1073,6 +1105,13 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
}
)
+ fieldsets = (
+ (None, (
+ 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
+ 'description',
+ )),
+ )
+
class Meta:
model = FrontPortTemplate
fields = [
@@ -1080,48 +1119,50 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
'description',
]
widgets = {
- 'device_type': forms.HiddenInput(),
- 'module_type': forms.HiddenInput(),
'type': StaticSelect(),
}
-class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
+class RearPortTemplateForm(ModularComponentTemplateForm):
+ fieldsets = (
+ (None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description')),
+ )
+
class Meta:
model = RearPortTemplate
fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
]
widgets = {
- 'device_type': forms.HiddenInput(),
- 'module_type': forms.HiddenInput(),
'type': StaticSelect(),
}
-class ModuleBayTemplateForm(BootstrapMixin, forms.ModelForm):
+class ModuleBayTemplateForm(ComponentTemplateForm):
+ fieldsets = (
+ (None, ('device_type', 'name', 'label', 'position', 'description')),
+ )
+
class Meta:
model = ModuleBayTemplate
fields = [
'device_type', 'name', 'label', 'position', 'description',
]
- widgets = {
- 'device_type': forms.HiddenInput(),
- }
-class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
+class DeviceBayTemplateForm(ComponentTemplateForm):
+ fieldsets = (
+ (None, ('device_type', 'name', 'label', 'description')),
+ )
+
class Meta:
model = DeviceBayTemplate
fields = [
'device_type', 'name', 'label', 'description',
]
- widgets = {
- 'device_type': forms.HiddenInput(),
- }
-class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
+class InventoryItemTemplateForm(ComponentTemplateForm):
parent = DynamicModelChoiceField(
queryset=InventoryItemTemplate.objects.all(),
required=False,
@@ -1148,22 +1189,39 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
widget=forms.HiddenInput
)
+ fieldsets = (
+ (None, (
+ 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
+ 'component_type', 'component_id',
+ )),
+ )
+
class Meta:
model = InventoryItemTemplate
fields = [
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
'component_type', 'component_id',
]
- widgets = {
- 'device_type': forms.HiddenInput(),
- }
#
# Device components
#
-class ConsolePortForm(NetBoxModelForm):
+class DeviceComponentForm(NetBoxModelForm):
+ device = DynamicModelChoiceField(
+ queryset=Device.objects.all()
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # Disable reassignment of Device when editing an existing instance
+ if self.instance.pk:
+ self.fields['device'].disabled = True
+
+
+class ModularDeviceComponentForm(DeviceComponentForm):
module = DynamicModelChoiceField(
queryset=Module.objects.all(),
required=False,
@@ -1172,25 +1230,31 @@ class ConsolePortForm(NetBoxModelForm):
}
)
+
+class ConsolePortForm(ModularDeviceComponentForm):
+ fieldsets = (
+ (None, (
+ 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
+ )),
+ )
+
class Meta:
model = ConsolePort
fields = [
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
]
widgets = {
- 'device': forms.HiddenInput(),
'type': StaticSelect(),
'speed': StaticSelect(),
}
-class ConsoleServerPortForm(NetBoxModelForm):
- module = DynamicModelChoiceField(
- queryset=Module.objects.all(),
- required=False,
- query_params={
- 'device_id': '$device',
- }
+class ConsoleServerPortForm(ModularDeviceComponentForm):
+
+ fieldsets = (
+ (None, (
+ 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
+ )),
)
class Meta:
@@ -1199,42 +1263,32 @@ class ConsoleServerPortForm(NetBoxModelForm):
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
]
widgets = {
- 'device': forms.HiddenInput(),
'type': StaticSelect(),
'speed': StaticSelect(),
}
-class PowerPortForm(NetBoxModelForm):
- module = DynamicModelChoiceField(
- queryset=Module.objects.all(),
- required=False,
- query_params={
- 'device_id': '$device',
- }
+class PowerPortForm(ModularDeviceComponentForm):
+
+ fieldsets = (
+ (None, (
+ 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
+ 'description', 'tags',
+ )),
)
class Meta:
model = PowerPort
fields = [
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
- 'description',
- 'tags',
+ 'description', 'tags',
]
widgets = {
- 'device': forms.HiddenInput(),
'type': StaticSelect(),
}
-class PowerOutletForm(NetBoxModelForm):
- module = DynamicModelChoiceField(
- queryset=Module.objects.all(),
- required=False,
- query_params={
- 'device_id': '$device',
- }
- )
+class PowerOutletForm(ModularDeviceComponentForm):
power_port = DynamicModelChoiceField(
queryset=PowerPort.objects.all(),
required=False,
@@ -1243,6 +1297,13 @@ class PowerOutletForm(NetBoxModelForm):
}
)
+ fieldsets = (
+ (None, (
+ 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
+ 'tags',
+ )),
+ )
+
class Meta:
model = PowerOutlet
fields = [
@@ -1250,20 +1311,12 @@ class PowerOutletForm(NetBoxModelForm):
'tags',
]
widgets = {
- 'device': forms.HiddenInput(),
'type': StaticSelect(),
'feed_leg': StaticSelect(),
}
-class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
- module = DynamicModelChoiceField(
- queryset=Module.objects.all(),
- required=False,
- query_params={
- 'device_id': '$device',
- }
- )
+class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
parent = DynamicModelChoiceField(
queryset=Interface.objects.all(),
required=False,
@@ -1338,7 +1391,7 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
)
fieldsets = (
- ('Interface', ('device', 'module', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
+ ('Interface', ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
('Addressing', ('vrf', 'mac_address', 'wwn')),
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
('Related Interfaces', ('parent', 'bridge', 'lag')),
@@ -1358,7 +1411,6 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
]
widgets = {
- 'device': forms.HiddenInput(),
'type': StaticSelect(),
'speed': SelectSpeedWidget(),
'poe_mode': StaticSelect(),
@@ -1388,14 +1440,7 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
self.fields['bridge'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
-class FrontPortForm(NetBoxModelForm):
- module = DynamicModelChoiceField(
- queryset=Module.objects.all(),
- required=False,
- query_params={
- 'device_id': '$device',
- }
- )
+class FrontPortForm(ModularDeviceComponentForm):
rear_port = DynamicModelChoiceField(
queryset=RearPort.objects.all(),
query_params={
@@ -1403,6 +1448,13 @@ class FrontPortForm(NetBoxModelForm):
}
)
+ fieldsets = (
+ (None, (
+ 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
+ 'description', 'tags',
+ )),
+ )
+
class Meta:
model = FrontPort
fields = [
@@ -1410,18 +1462,15 @@ class FrontPortForm(NetBoxModelForm):
'description', 'tags',
]
widgets = {
- 'device': forms.HiddenInput(),
'type': StaticSelect(),
}
-class RearPortForm(NetBoxModelForm):
- module = DynamicModelChoiceField(
- queryset=Module.objects.all(),
- required=False,
- query_params={
- 'device_id': '$device',
- }
+class RearPortForm(ModularDeviceComponentForm):
+ fieldsets = (
+ (None, (
+ 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
+ )),
)
class Meta:
@@ -1430,33 +1479,32 @@ class RearPortForm(NetBoxModelForm):
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
]
widgets = {
- 'device': forms.HiddenInput(),
'type': StaticSelect(),
}
-class ModuleBayForm(NetBoxModelForm):
+class ModuleBayForm(DeviceComponentForm):
+ fieldsets = (
+ (None, ('device', 'name', 'label', 'position', 'description', 'tags',)),
+ )
class Meta:
model = ModuleBay
fields = [
'device', 'name', 'label', 'position', 'description', 'tags',
]
- widgets = {
- 'device': forms.HiddenInput(),
- }
-class DeviceBayForm(NetBoxModelForm):
+class DeviceBayForm(DeviceComponentForm):
+ fieldsets = (
+ (None, ('device', 'name', 'label', 'description', 'tags',)),
+ )
class Meta:
model = DeviceBay
fields = [
'device', 'name', 'label', 'description', 'tags',
]
- widgets = {
- 'device': forms.HiddenInput(),
- }
class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
@@ -1479,10 +1527,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
).exclude(pk=device_bay.device.pk)
-class InventoryItemForm(NetBoxModelForm):
- device = DynamicModelChoiceField(
- queryset=Device.objects.all()
- )
+class InventoryItemForm(DeviceComponentForm):
parent = DynamicModelChoiceField(
queryset=InventoryItem.objects.all(),
required=False,
diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py
index d2c941b34..a03597db1 100644
--- a/netbox/dcim/forms/object_create.py
+++ b/netbox/dcim/forms/object_create.py
@@ -2,46 +2,56 @@ from django import forms
from dcim.models import *
from netbox.forms import NetBoxModelForm
-from utilities.forms import (
- BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
-)
+from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
+from . import models as model_forms
__all__ = (
- 'ComponentTemplateCreateForm',
- 'DeviceComponentCreateForm',
+ 'ComponentCreateForm',
+ 'ConsolePortCreateForm',
+ 'ConsolePortTemplateCreateForm',
+ 'ConsoleServerPortCreateForm',
+ 'ConsoleServerPortTemplateCreateForm',
+ 'DeviceBayCreateForm',
+ 'DeviceBayTemplateCreateForm',
'FrontPortCreateForm',
'FrontPortTemplateCreateForm',
+ 'InterfaceCreateForm',
+ 'InterfaceTemplateCreateForm',
'InventoryItemCreateForm',
- 'ModularComponentTemplateCreateForm',
+ 'InventoryItemTemplateCreateForm',
'ModuleBayCreateForm',
'ModuleBayTemplateCreateForm',
+ 'PowerOutletCreateForm',
+ 'PowerOutletTemplateCreateForm',
+ 'PowerPortCreateForm',
+ 'PowerPortTemplateCreateForm',
+ 'RearPortCreateForm',
+ 'RearPortTemplateCreateForm',
'VirtualChassisCreateForm',
)
-class ComponentCreateForm(BootstrapMixin, forms.Form):
+class ComponentCreateForm(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 component or component template objects based on
a name pattern.
"""
- name_pattern = ExpandableNameField(
- label='Name'
- )
- label_pattern = ExpandableNameField(
- label='Label',
+ name = ExpandableNameField()
+ label = ExpandableNameField(
required=False,
- help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
+ help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
)
+ # Identify the fields which support replication (i.e. ExpandableNameFields). This is referenced by
+ # ComponentCreateView when creating objects.
+ replication_fields = ('name', 'label')
+
def clean(self):
super().clean()
- # Validate that all patterned fields generate an equal number of values
- patterned_fields = [
- field_name for field_name in self.fields if field_name.endswith('_pattern')
- ]
- pattern_count = len(self.cleaned_data['name_pattern'])
- for field_name in patterned_fields:
+ # Validate that all replication fields generate an equal number of values
+ pattern_count = len(self.cleaned_data[self.replication_fields[0]])
+ for field_name in self.replication_fields:
value_count = len(self.cleaned_data[field_name])
if self.cleaned_data[field_name] and value_count != pattern_count:
raise forms.ValidationError({
@@ -50,56 +60,55 @@ class ComponentCreateForm(BootstrapMixin, forms.Form):
}, code='label_pattern_mismatch')
-class ComponentTemplateCreateForm(ComponentCreateForm):
- """
- Creation form for component templates that can be assigned only to a DeviceType.
- """
- device_type = DynamicModelChoiceField(
- queryset=DeviceType.objects.all(),
- )
- field_order = ('device_type', 'name_pattern', 'label_pattern')
+#
+# Device component templates
+#
+
+class ConsolePortTemplateCreateForm(ComponentCreateForm, model_forms.ConsolePortTemplateForm):
+
+ class Meta(model_forms.ConsolePortTemplateForm.Meta):
+ exclude = ('name', 'label')
-class ModularComponentTemplateCreateForm(ComponentCreateForm):
- """
- Creation form for component templates that can be assigned to either a DeviceType *or* a ModuleType.
- """
- name_pattern = ExpandableNameField(
- label='Name',
- help_text="""
- Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
- are not supported. Example: [ge,xe]-0/0/[0-9]
. {module} is accepted as a substitution for
- the module bay position.
- """
- )
- device_type = DynamicModelChoiceField(
- queryset=DeviceType.objects.all(),
- required=False
- )
- module_type = DynamicModelChoiceField(
- queryset=ModuleType.objects.all(),
- required=False
- )
- field_order = ('device_type', 'module_type', 'name_pattern', 'label_pattern')
+class ConsoleServerPortTemplateCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortTemplateForm):
+
+ class Meta(model_forms.ConsoleServerPortTemplateForm.Meta):
+ exclude = ('name', 'label')
-class DeviceComponentCreateForm(ComponentCreateForm):
- device = DynamicModelChoiceField(
- queryset=Device.objects.all()
- )
- field_order = ('device', 'name_pattern', 'label_pattern')
+class PowerPortTemplateCreateForm(ComponentCreateForm, model_forms.PowerPortTemplateForm):
+
+ class Meta(model_forms.PowerPortTemplateForm.Meta):
+ exclude = ('name', 'label')
-class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
- rear_port_set = forms.MultipleChoiceField(
+class PowerOutletTemplateCreateForm(ComponentCreateForm, model_forms.PowerOutletTemplateForm):
+
+ class Meta(model_forms.PowerOutletTemplateForm.Meta):
+ exclude = ('name', 'label')
+
+
+class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemplateForm):
+
+ class Meta(model_forms.InterfaceTemplateForm.Meta):
+ exclude = ('name', 'label')
+
+
+class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
+ 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_pattern', 'label_pattern', 'rear_port_set',
+
+ # Override fieldsets from FrontPortTemplateForm to omit rear_port_position
+ fieldsets = (
+ (None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'description')),
)
+ class Meta(model_forms.FrontPortTemplateForm.Meta):
+ exclude = ('name', 'label', 'rear_port', 'rear_port_position')
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -130,12 +139,12 @@ class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
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),
@@ -143,16 +152,94 @@ class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
}
-class FrontPortCreateForm(DeviceComponentCreateForm):
- rear_port_set = forms.MultipleChoiceField(
+class RearPortTemplateCreateForm(ComponentCreateForm, model_forms.RearPortTemplateForm):
+
+ class Meta(model_forms.RearPortTemplateForm.Meta):
+ exclude = ('name', 'label')
+
+
+class DeviceBayTemplateCreateForm(ComponentCreateForm, model_forms.DeviceBayTemplateForm):
+
+ class Meta(model_forms.DeviceBayTemplateForm.Meta):
+ exclude = ('name', 'label')
+
+
+class ModuleBayTemplateCreateForm(ComponentCreateForm, model_forms.ModuleBayTemplateForm):
+ position = ExpandableNameField(
+ label='Position',
+ required=False,
+ help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
+ )
+ replication_fields = ('name', 'label', 'position')
+
+ class Meta(model_forms.ModuleBayTemplateForm.Meta):
+ exclude = ('name', 'label', 'position')
+
+
+class InventoryItemTemplateCreateForm(ComponentCreateForm, model_forms.InventoryItemTemplateForm):
+
+ class Meta(model_forms.InventoryItemTemplateForm.Meta):
+ exclude = ('name', 'label')
+
+
+#
+# Device components
+#
+
+class ConsolePortCreateForm(ComponentCreateForm, model_forms.ConsolePortForm):
+
+ class Meta(model_forms.ConsolePortForm.Meta):
+ exclude = ('name', 'label')
+
+
+class ConsoleServerPortCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortForm):
+
+ class Meta(model_forms.ConsoleServerPortForm.Meta):
+ exclude = ('name', 'label')
+
+
+class PowerPortCreateForm(ComponentCreateForm, model_forms.PowerPortForm):
+
+ class Meta(model_forms.PowerPortForm.Meta):
+ exclude = ('name', 'label')
+
+
+class PowerOutletCreateForm(ComponentCreateForm, model_forms.PowerOutletForm):
+
+ class Meta(model_forms.PowerOutletForm.Meta):
+ exclude = ('name', 'label')
+
+
+class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
+
+ class Meta(model_forms.InterfaceForm.Meta):
+ exclude = ('name', 'label')
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ if 'module' in self.fields:
+ self.fields['name'].help_text += ' The string {module}
will be replaced with the position ' \
+ 'of the assigned module, if any'
+
+
+class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
+ 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_pattern', 'label_pattern', 'rear_port_set',
+
+ # Override fieldsets from FrontPortForm to omit rear_port_position
+ fieldsets = (
+ (None, (
+ 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
+ )),
)
+ class Meta(model_forms.FrontPortForm.Meta):
+ exclude = ('name', 'label', 'rear_port', 'rear_port_position')
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -176,12 +263,12 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
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),
@@ -189,28 +276,39 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
}
-class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm):
- position_pattern = ExpandableNameField(
+class RearPortCreateForm(ComponentCreateForm, model_forms.RearPortForm):
+
+ class Meta(model_forms.RearPortForm.Meta):
+ exclude = ('name', 'label')
+
+
+class DeviceBayCreateForm(ComponentCreateForm, model_forms.DeviceBayForm):
+
+ class Meta(model_forms.DeviceBayForm.Meta):
+ exclude = ('name', 'label')
+
+
+class ModuleBayCreateForm(ComponentCreateForm, model_forms.ModuleBayForm):
+ position = ExpandableNameField(
label='Position',
required=False,
- help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
+ help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
)
- field_order = ('device_type', 'name_pattern', 'label_pattern', 'position_pattern')
+ replication_fields = ('name', 'label', 'position')
+
+ class Meta(model_forms.ModuleBayForm.Meta):
+ exclude = ('name', 'label', 'position')
-class ModuleBayCreateForm(DeviceComponentCreateForm):
- position_pattern = ExpandableNameField(
- label='Position',
- required=False,
- help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
- )
- field_order = ('device', 'name_pattern', 'label_pattern', 'position_pattern')
+class InventoryItemCreateForm(ComponentCreateForm, model_forms.InventoryItemForm):
+
+ class Meta(model_forms.InventoryItemForm.Meta):
+ exclude = ('name', 'label')
-class InventoryItemCreateForm(ComponentCreateForm):
- # Device is assigned by the model form
- field_order = ('name_pattern', 'label_pattern')
-
+#
+# Virtual chassis
+#
class VirtualChassisCreateForm(NetBoxModelForm):
region = DynamicModelChoiceField(
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/tables/template_code.py b/netbox/dcim/tables/template_code.py
index d34003ee5..dfc77b854 100644
--- a/netbox/dcim/tables/template_code.py
+++ b/netbox/dcim/tables/template_code.py
@@ -239,7 +239,7 @@ INTERFACE_BUTTONS = """
[ge,xe]-0/0/[0-9]
+ are not supported (example: [ge,xe]-0/0/[0-9]
).
"""
def to_python(self, value):
diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py
index 7fa9f66bc..93cb88088 100644
--- a/netbox/utilities/testing/views.py
+++ b/netbox/utilities/testing/views.py
@@ -466,6 +466,7 @@ class ViewTestCases:
"""
bulk_create_count = 3
bulk_create_data = {}
+ validation_excluded_fields = []
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
def test_create_multiple_objects_without_permission(self):
@@ -500,7 +501,7 @@ class ViewTestCases:
self.assertHttpStatus(response, 302)
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
- self.assertInstanceEqual(instance, self.bulk_create_data)
+ self.assertInstanceEqual(instance, self.bulk_create_data, exclude=self.validation_excluded_fields)
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
def test_create_multiple_objects_with_constrained_permission(self):
@@ -532,7 +533,7 @@ class ViewTestCases:
self.assertHttpStatus(response, 302)
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
- self.assertInstanceEqual(instance, self.bulk_create_data)
+ self.assertInstanceEqual(instance, self.bulk_create_data, exclude=self.validation_excluded_fields)
class BulkImportObjectsViewTestCase(ModelViewTestCase):
"""
diff --git a/netbox/virtualization/forms/bulk_create.py b/netbox/virtualization/forms/bulk_create.py
index 6cf7c0d7c..03997f88d 100644
--- a/netbox/virtualization/forms/bulk_create.py
+++ b/netbox/virtualization/forms/bulk_create.py
@@ -13,7 +13,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
queryset=VirtualMachine.objects.all(),
widget=forms.MultipleHiddenInput()
)
- name_pattern = ExpandableNameField(
+ name = ExpandableNameField(
label='Name'
)
@@ -27,4 +27,4 @@ class VMInterfaceBulkCreateForm(
form_from_model(VMInterface, ['enabled', 'mtu', 'description', 'tags']),
VirtualMachineBulkAddComponentForm
):
- pass
+ replication_fields = ('name',)
diff --git a/netbox/virtualization/forms/models.py b/netbox/virtualization/forms/models.py
index fca9c6b56..268afb9bb 100644
--- a/netbox/virtualization/forms/models.py
+++ b/netbox/virtualization/forms/models.py
@@ -5,7 +5,6 @@ from django.core.exceptions import ValidationError
from dcim.forms.common import InterfaceCommonForm
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
-from extras.models import Tag
from ipam.models import IPAddress, VLAN, VLANGroup, VRF
from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
@@ -278,6 +277,9 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
+ virtual_machine = DynamicModelChoiceField(
+ queryset=VirtualMachine.objects.all()
+ )
parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(),
required=False,
@@ -338,7 +340,6 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
]
widgets = {
- 'virtual_machine': forms.HiddenInput(),
'mode': StaticSelect()
}
labels = {
@@ -347,3 +348,10 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
help_texts = {
'mode': INTERFACE_MODE_HELP_TEXT,
}
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # Disable reassignment of VirtualMachine when editing an existing instance
+ if self.instance.pk:
+ self.fields['virtual_machine'].disabled = True
diff --git a/netbox/virtualization/forms/object_create.py b/netbox/virtualization/forms/object_create.py
index feab3bb3a..79457a56e 100644
--- a/netbox/virtualization/forms/object_create.py
+++ b/netbox/virtualization/forms/object_create.py
@@ -1,17 +1,14 @@
-from django import forms
-
-from utilities.forms import BootstrapMixin, DynamicModelChoiceField, ExpandableNameField
-from .models import VirtualMachine
+from utilities.forms import ExpandableNameField
+from .models import VMInterfaceForm
__all__ = (
'VMInterfaceCreateForm',
)
-class VMInterfaceCreateForm(BootstrapMixin, forms.Form):
- virtual_machine = DynamicModelChoiceField(
- queryset=VirtualMachine.objects.all()
- )
- name_pattern = ExpandableNameField(
- label='Name'
- )
+class VMInterfaceCreateForm(VMInterfaceForm):
+ name = ExpandableNameField()
+ replication_fields = ('name',)
+
+ class Meta(VMInterfaceForm.Meta):
+ exclude = ('name',)
diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py
index 01d4394f3..d00ceb5a2 100644
--- a/netbox/virtualization/tests/test_views.py
+++ b/netbox/virtualization/tests/test_views.py
@@ -251,6 +251,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
model = VMInterface
+ validation_excluded_fields = ('name',)
@classmethod
def setUpTestData(cls):
@@ -290,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',
@@ -306,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'),
diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py
index 5b26f8503..611725d62 100644
--- a/netbox/virtualization/views.py
+++ b/netbox/virtualization/views.py
@@ -451,13 +451,11 @@ class VMInterfaceCreateView(generic.ComponentCreateView):
queryset = VMInterface.objects.all()
form = forms.VMInterfaceCreateForm
model_form = forms.VMInterfaceForm
- patterned_fields = ('name',)
class VMInterfaceEditView(generic.ObjectEditView):
queryset = VMInterface.objects.all()
form = forms.VMInterfaceForm
- template_name = 'virtualization/vminterface_edit.html'
class VMInterfaceDeleteView(generic.ObjectDeleteView):