Initial work on #10247

This commit is contained in:
jeremystretch 2022-09-07 16:57:19 -04:00
parent b702822857
commit 4988c7b7d2
14 changed files with 224 additions and 387 deletions

View File

@ -986,47 +986,57 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form):
# Device component templates # Device component templates
# #
class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
device_type = DynamicModelChoiceField(
queryset=DeviceType.objects.all()
)
class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
class ModularComponentTemplateForm(BootstrapMixin, forms.ModelForm):
device_type = DynamicModelChoiceField(
queryset=DeviceType.objects.all(),
required=False
)
module_type = DynamicModelChoiceField(
queryset=ModuleType.objects.all(),
required=False
)
class ConsolePortTemplateForm(ModularComponentTemplateForm):
class Meta: class Meta:
model = ConsolePortTemplate model = ConsolePortTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'description', 'device_type', 'module_type', 'name', 'label', 'type', 'description',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(),
'type': StaticSelect, 'type': StaticSelect,
} }
class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm): class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
class Meta: class Meta:
model = ConsoleServerPortTemplate model = ConsoleServerPortTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'description', 'device_type', 'module_type', 'name', 'label', 'type', 'description',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(),
'type': StaticSelect, 'type': StaticSelect,
} }
class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm): class PowerPortTemplateForm(ModularComponentTemplateForm):
class Meta: class Meta:
model = PowerPortTemplate model = PowerPortTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
} }
class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm): class PowerOutletTemplateForm(ModularComponentTemplateForm):
power_port = DynamicModelChoiceField( power_port = DynamicModelChoiceField(
queryset=PowerPortTemplate.objects.all(), queryset=PowerPortTemplate.objects.all(),
required=False, required=False,
@ -1041,29 +1051,25 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
'feed_leg': StaticSelect(), 'feed_leg': StaticSelect(),
} }
class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm): class InterfaceTemplateForm(ModularComponentTemplateForm):
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
'poe_mode': StaticSelect(), 'poe_mode': StaticSelect(),
'poe_type': StaticSelect(), 'poe_type': StaticSelect(),
} }
class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm): class FrontPortTemplateForm(ModularComponentTemplateForm):
rear_port = DynamicModelChoiceField( rear_port = DynamicModelChoiceField(
queryset=RearPortTemplate.objects.all(), queryset=RearPortTemplate.objects.all(),
required=False, required=False,
@ -1080,48 +1086,38 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
'description', 'description',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
} }
class RearPortTemplateForm(BootstrapMixin, forms.ModelForm): class RearPortTemplateForm(ModularComponentTemplateForm):
class Meta: class Meta:
model = RearPortTemplate model = RearPortTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description', 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
} }
class ModuleBayTemplateForm(BootstrapMixin, forms.ModelForm): class ModuleBayTemplateForm(ComponentTemplateForm):
class Meta: class Meta:
model = ModuleBayTemplate model = ModuleBayTemplate
fields = [ fields = [
'device_type', 'name', 'label', 'position', 'description', 'device_type', 'name', 'label', 'position', 'description',
] ]
widgets = {
'device_type': forms.HiddenInput(),
}
class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm): class DeviceBayTemplateForm(ComponentTemplateForm):
class Meta: class Meta:
model = DeviceBayTemplate model = DeviceBayTemplate
fields = [ fields = [
'device_type', 'name', 'label', 'description', 'device_type', 'name', 'label', 'description',
] ]
widgets = {
'device_type': forms.HiddenInput(),
}
class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm): class InventoryItemTemplateForm(ComponentTemplateForm):
parent = DynamicModelChoiceField( parent = DynamicModelChoiceField(
queryset=InventoryItemTemplate.objects.all(), queryset=InventoryItemTemplate.objects.all(),
required=False, required=False,
@ -1154,9 +1150,6 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description', 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
'component_type', 'component_id', 'component_type', 'component_id',
] ]
widgets = {
'device_type': forms.HiddenInput(),
}
# #
@ -1164,6 +1157,9 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
# #
class ConsolePortForm(NetBoxModelForm): class ConsolePortForm(NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
module = DynamicModelChoiceField( module = DynamicModelChoiceField(
queryset=Module.objects.all(), queryset=Module.objects.all(),
required=False, required=False,
@ -1178,13 +1174,15 @@ class ConsolePortForm(NetBoxModelForm):
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags', 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
'speed': StaticSelect(), 'speed': StaticSelect(),
} }
class ConsoleServerPortForm(NetBoxModelForm): class ConsoleServerPortForm(NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
module = DynamicModelChoiceField( module = DynamicModelChoiceField(
queryset=Module.objects.all(), queryset=Module.objects.all(),
required=False, required=False,
@ -1199,13 +1197,15 @@ class ConsoleServerPortForm(NetBoxModelForm):
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags', 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
'speed': StaticSelect(), 'speed': StaticSelect(),
} }
class PowerPortForm(NetBoxModelForm): class PowerPortForm(NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
module = DynamicModelChoiceField( module = DynamicModelChoiceField(
queryset=Module.objects.all(), queryset=Module.objects.all(),
required=False, required=False,
@ -1222,12 +1222,14 @@ class PowerPortForm(NetBoxModelForm):
'tags', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
} }
class PowerOutletForm(NetBoxModelForm): class PowerOutletForm(NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
module = DynamicModelChoiceField( module = DynamicModelChoiceField(
queryset=Module.objects.all(), queryset=Module.objects.all(),
required=False, required=False,
@ -1250,13 +1252,15 @@ class PowerOutletForm(NetBoxModelForm):
'tags', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
'feed_leg': StaticSelect(), 'feed_leg': StaticSelect(),
} }
class InterfaceForm(InterfaceCommonForm, NetBoxModelForm): class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
module = DynamicModelChoiceField( module = DynamicModelChoiceField(
queryset=Module.objects.all(), queryset=Module.objects.all(),
required=False, required=False,
@ -1352,7 +1356,6 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
'speed': SelectSpeedWidget(), 'speed': SelectSpeedWidget(),
'poe_mode': StaticSelect(), 'poe_mode': StaticSelect(),
@ -1383,6 +1386,9 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
class FrontPortForm(NetBoxModelForm): class FrontPortForm(NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
module = DynamicModelChoiceField( module = DynamicModelChoiceField(
queryset=Module.objects.all(), queryset=Module.objects.all(),
required=False, required=False,
@ -1404,12 +1410,14 @@ class FrontPortForm(NetBoxModelForm):
'description', 'tags', 'description', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
} }
class RearPortForm(NetBoxModelForm): class RearPortForm(NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
module = DynamicModelChoiceField( module = DynamicModelChoiceField(
queryset=Module.objects.all(), queryset=Module.objects.all(),
required=False, required=False,
@ -1424,33 +1432,32 @@ class RearPortForm(NetBoxModelForm):
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags', 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
} }
class ModuleBayForm(NetBoxModelForm): class ModuleBayForm(NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
class Meta: class Meta:
model = ModuleBay model = ModuleBay
fields = [ fields = [
'device', 'name', 'label', 'position', 'description', 'tags', 'device', 'name', 'label', 'position', 'description', 'tags',
] ]
widgets = {
'device': forms.HiddenInput(),
}
class DeviceBayForm(NetBoxModelForm): class DeviceBayForm(NetBoxModelForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all()
)
class Meta: class Meta:
model = DeviceBay model = DeviceBay
fields = [ fields = [
'device', 'name', 'label', 'description', 'tags', 'device', 'name', 'label', 'description', 'tags',
] ]
widgets = {
'device': forms.HiddenInput(),
}
class PopulateDeviceBayForm(BootstrapMixin, forms.Form): class PopulateDeviceBayForm(BootstrapMixin, forms.Form):

View File

@ -2,103 +2,108 @@ from django import forms
from dcim.models import * from dcim.models import *
from netbox.forms import NetBoxModelForm from netbox.forms import NetBoxModelForm
from utilities.forms import ( from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, from . import models as model_forms
)
__all__ = ( __all__ = (
'ComponentTemplateCreateForm', 'ComponentCreateForm',
'DeviceComponentCreateForm', 'ConsolePortCreateForm',
'ConsolePortTemplateCreateForm',
'ConsoleServerPortCreateForm',
'ConsoleServerPortTemplateCreateForm',
'DeviceBayCreateForm',
'DeviceBayTemplateCreateForm',
'FrontPortCreateForm', 'FrontPortCreateForm',
'FrontPortTemplateCreateForm', 'FrontPortTemplateCreateForm',
'InterfaceCreateForm',
'InterfaceTemplateCreateForm',
'InventoryItemCreateForm', 'InventoryItemCreateForm',
'ModularComponentTemplateCreateForm', 'InventoryItemTemplateCreateForm',
'ModuleBayCreateForm', 'ModuleBayCreateForm',
'ModuleBayTemplateCreateForm', 'ModuleBayTemplateCreateForm',
'PowerOutletCreateForm',
'PowerOutletTemplateCreateForm',
'PowerPortCreateForm',
'PowerPortTemplateCreateForm',
'RearPortCreateForm',
'RearPortTemplateCreateForm',
'VirtualChassisCreateForm', '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 device component or component templates based on
a name pattern. a name pattern.
""" """
name_pattern = ExpandableNameField( name = ExpandableNameField()
label='Name' label = ExpandableNameField(
)
label_pattern = ExpandableNameField(
label='Label',
required=False, 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 names being created.)'
) )
def clean(self): # TODO: Incorporate this validation
super().clean() # def clean(self):
# super().clean()
# Validate that all patterned fields generate an equal number of values #
patterned_fields = [ # # Validate that all patterned fields generate an equal number of values
field_name for field_name in self.fields if field_name.endswith('_pattern') # 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: # pattern_count = len(self.cleaned_data['name_pattern'])
value_count = len(self.cleaned_data[field_name]) # for field_name in patterned_fields:
if self.cleaned_data[field_name] and value_count != pattern_count: # value_count = len(self.cleaned_data[field_name])
raise forms.ValidationError({ # if self.cleaned_data[field_name] and value_count != pattern_count:
field_name: f'The provided pattern specifies {value_count} values, but {pattern_count} are ' # raise forms.ValidationError({
f'expected.' # field_name: f'The provided pattern specifies {value_count} values, but {pattern_count} are '
}, code='label_pattern_mismatch') # f'expected.'
# }, code='label_pattern_mismatch')
class ComponentTemplateCreateForm(ComponentCreateForm): # class ModularComponentTemplateCreateForm(ComponentCreateForm):
""" # """
Creation form for component templates that can be assigned only to a DeviceType. # Creation form for component templates that can be assigned to either a DeviceType *or* a ModuleType.
""" # """
device_type = DynamicModelChoiceField( # name = ExpandableNameField(
queryset=DeviceType.objects.all(), # label='Name',
) # help_text="""
field_order = ('device_type', 'name_pattern', 'label_pattern') # Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
# are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>. {module} is accepted as a substitution for
# the module bay position.
# """
# )
class ModularComponentTemplateCreateForm(ComponentCreateForm): #
""" # Device component templates
Creation form for component templates that can be assigned to either a DeviceType *or* a ModuleType. #
"""
name_pattern = ExpandableNameField( class ConsolePortTemplateCreateForm(ComponentCreateForm, model_forms.ConsolePortTemplateForm):
label='Name', pass
help_text="""
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>. {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 DeviceComponentCreateForm(ComponentCreateForm): class ConsoleServerPortTemplateCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortTemplateForm):
device = DynamicModelChoiceField( pass
queryset=Device.objects.all()
)
field_order = ('device', 'name_pattern', 'label_pattern')
class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm): class PowerPortTemplateCreateForm(ComponentCreateForm, model_forms.PowerPortTemplateForm):
pass
class PowerOutletTemplateCreateForm(ComponentCreateForm, model_forms.PowerOutletTemplateForm):
pass
class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemplateForm):
pass
class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
rear_port_set = forms.MultipleChoiceField( rear_port_set = forms.MultipleChoiceField(
choices=[], choices=[],
label='Rear ports', label='Rear ports',
help_text='Select one rear port assignment for each front port being created.', help_text='Select one rear port assignment for each front port being created.',
) )
field_order = (
'device_type', 'name_pattern', 'label_pattern', 'rear_port_set',
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -143,15 +148,56 @@ class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
} }
class FrontPortCreateForm(DeviceComponentCreateForm): class RearPortTemplateCreateForm(ComponentCreateForm, model_forms.RearPortTemplateForm):
pass
class DeviceBayTemplateCreateForm(ComponentCreateForm, model_forms.DeviceBayTemplateForm):
pass
class ModuleBayTemplateCreateForm(ComponentCreateForm, model_forms.ModuleBayTemplateForm):
position = ExpandableNameField(
label='Position',
required=False,
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
)
class InventoryItemTemplateCreateForm(ComponentCreateForm, model_forms.InventoryItemTemplateForm):
pass
#
# Device components
#
class ConsolePortCreateForm(ComponentCreateForm, model_forms.ConsolePortForm):
pass
class ConsoleServerPortCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortForm):
pass
class PowerPortCreateForm(ComponentCreateForm, model_forms.PowerPortForm):
pass
class PowerOutletCreateForm(ComponentCreateForm, model_forms.PowerOutletForm):
pass
class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
pass
class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
rear_port_set = forms.MultipleChoiceField( rear_port_set = forms.MultipleChoiceField(
choices=[], choices=[],
label='Rear ports', label='Rear ports',
help_text='Select one rear port assignment for each front port being created.', help_text='Select one rear port assignment for each front port being created.',
) )
field_order = (
'device', 'name_pattern', 'label_pattern', 'rear_port_set',
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -189,22 +235,20 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
} }
class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm): class RearPortCreateForm(ComponentCreateForm, model_forms.RearPortForm):
position_pattern = ExpandableNameField( pass
class DeviceBayCreateForm(ComponentCreateForm, model_forms.DeviceBayForm):
pass
class ModuleBayCreateForm(ComponentCreateForm, model_forms.ModuleBayForm):
position = ExpandableNameField(
label='Position', label='Position',
required=False, 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 names being created.)'
) )
field_order = ('device_type', 'name_pattern', 'label_pattern', 'position_pattern')
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): class InventoryItemCreateForm(ComponentCreateForm):
@ -212,6 +256,10 @@ class InventoryItemCreateForm(ComponentCreateForm):
field_order = ('name_pattern', 'label_pattern') field_order = ('name_pattern', 'label_pattern')
#
# Virtual chassis
#
class VirtualChassisCreateForm(NetBoxModelForm): class VirtualChassisCreateForm(NetBoxModelForm):
region = DynamicModelChoiceField( region = DynamicModelChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),

View File

@ -132,7 +132,7 @@ class LabelTestCase(TestCase):
'name_pattern': 'eth[0-9]', 'name_pattern': 'eth[0-9]',
'label_pattern': 'Interface[0-9]', 'label_pattern': 'Interface[0-9]',
} }
form = DeviceComponentCreateForm(interface_data) form = InterfaceCreateForm(interface_data)
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
@ -145,7 +145,7 @@ class LabelTestCase(TestCase):
'name_pattern': 'eth[0-9]', 'name_pattern': 'eth[0-9]',
'label_pattern': 'Interface[0-1]', 'label_pattern': 'Interface[0-1]',
} }
form = DeviceComponentCreateForm(bad_interface_data) form = InterfaceCreateForm(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)

View File

@ -1120,9 +1120,8 @@ class ModuleTypeBulkDeleteView(generic.BulkDeleteView):
class ConsolePortTemplateCreateView(generic.ComponentCreateView): class ConsolePortTemplateCreateView(generic.ComponentCreateView):
queryset = ConsolePortTemplate.objects.all() queryset = ConsolePortTemplate.objects.all()
form = forms.ModularComponentTemplateCreateForm form = forms.ConsolePortTemplateCreateForm
model_form = forms.ConsolePortTemplateForm model_form = forms.ConsolePortTemplateForm
template_name = 'dcim/component_template_create.html'
class ConsolePortTemplateEditView(generic.ObjectEditView): class ConsolePortTemplateEditView(generic.ObjectEditView):
@ -1155,9 +1154,8 @@ class ConsolePortTemplateBulkDeleteView(generic.BulkDeleteView):
class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView): class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
queryset = ConsoleServerPortTemplate.objects.all() queryset = ConsoleServerPortTemplate.objects.all()
form = forms.ModularComponentTemplateCreateForm form = forms.ConsoleServerPortTemplateCreateForm
model_form = forms.ConsoleServerPortTemplateForm model_form = forms.ConsoleServerPortTemplateForm
template_name = 'dcim/component_template_create.html'
class ConsoleServerPortTemplateEditView(generic.ObjectEditView): class ConsoleServerPortTemplateEditView(generic.ObjectEditView):
@ -1190,9 +1188,8 @@ class ConsoleServerPortTemplateBulkDeleteView(generic.BulkDeleteView):
class PowerPortTemplateCreateView(generic.ComponentCreateView): class PowerPortTemplateCreateView(generic.ComponentCreateView):
queryset = PowerPortTemplate.objects.all() queryset = PowerPortTemplate.objects.all()
form = forms.ModularComponentTemplateCreateForm form = forms.PowerPortTemplateCreateForm
model_form = forms.PowerPortTemplateForm model_form = forms.PowerPortTemplateForm
template_name = 'dcim/component_template_create.html'
class PowerPortTemplateEditView(generic.ObjectEditView): class PowerPortTemplateEditView(generic.ObjectEditView):
@ -1225,9 +1222,8 @@ class PowerPortTemplateBulkDeleteView(generic.BulkDeleteView):
class PowerOutletTemplateCreateView(generic.ComponentCreateView): class PowerOutletTemplateCreateView(generic.ComponentCreateView):
queryset = PowerOutletTemplate.objects.all() queryset = PowerOutletTemplate.objects.all()
form = forms.ModularComponentTemplateCreateForm form = forms.PowerOutletTemplateCreateForm
model_form = forms.PowerOutletTemplateForm model_form = forms.PowerOutletTemplateForm
template_name = 'dcim/component_template_create.html'
class PowerOutletTemplateEditView(generic.ObjectEditView): class PowerOutletTemplateEditView(generic.ObjectEditView):
@ -1260,9 +1256,8 @@ class PowerOutletTemplateBulkDeleteView(generic.BulkDeleteView):
class InterfaceTemplateCreateView(generic.ComponentCreateView): class InterfaceTemplateCreateView(generic.ComponentCreateView):
queryset = InterfaceTemplate.objects.all() queryset = InterfaceTemplate.objects.all()
form = forms.ModularComponentTemplateCreateForm form = forms.InterfaceTemplateCreateForm
model_form = forms.InterfaceTemplateForm model_form = forms.InterfaceTemplateForm
template_name = 'dcim/component_template_create.html'
class InterfaceTemplateEditView(generic.ObjectEditView): class InterfaceTemplateEditView(generic.ObjectEditView):
@ -1297,15 +1292,6 @@ class FrontPortTemplateCreateView(generic.ComponentCreateView):
queryset = FrontPortTemplate.objects.all() queryset = FrontPortTemplate.objects.all()
form = forms.FrontPortTemplateCreateForm form = forms.FrontPortTemplateCreateForm
model_form = forms.FrontPortTemplateForm model_form = forms.FrontPortTemplateForm
template_name = 'dcim/frontporttemplate_create.html'
def initialize_forms(self, request):
form, model_form = super().initialize_forms(request)
model_form.fields.pop('rear_port')
model_form.fields.pop('rear_port_position')
return form, model_form
class FrontPortTemplateEditView(generic.ObjectEditView): class FrontPortTemplateEditView(generic.ObjectEditView):
@ -1338,9 +1324,8 @@ class FrontPortTemplateBulkDeleteView(generic.BulkDeleteView):
class RearPortTemplateCreateView(generic.ComponentCreateView): class RearPortTemplateCreateView(generic.ComponentCreateView):
queryset = RearPortTemplate.objects.all() queryset = RearPortTemplate.objects.all()
form = forms.ModularComponentTemplateCreateForm form = forms.RearPortTemplateCreateForm
model_form = forms.RearPortTemplateForm model_form = forms.RearPortTemplateForm
template_name = 'dcim/component_template_create.html'
class RearPortTemplateEditView(generic.ObjectEditView): class RearPortTemplateEditView(generic.ObjectEditView):
@ -1375,7 +1360,6 @@ class ModuleBayTemplateCreateView(generic.ComponentCreateView):
queryset = ModuleBayTemplate.objects.all() queryset = ModuleBayTemplate.objects.all()
form = forms.ModuleBayTemplateCreateForm form = forms.ModuleBayTemplateCreateForm
model_form = forms.ModuleBayTemplateForm model_form = forms.ModuleBayTemplateForm
template_name = 'dcim/modulebaytemplate_create.html'
patterned_fields = ('name', 'label', 'position') patterned_fields = ('name', 'label', 'position')
@ -1409,9 +1393,8 @@ class ModuleBayTemplateBulkDeleteView(generic.BulkDeleteView):
class DeviceBayTemplateCreateView(generic.ComponentCreateView): class DeviceBayTemplateCreateView(generic.ComponentCreateView):
queryset = DeviceBayTemplate.objects.all() queryset = DeviceBayTemplate.objects.all()
form = forms.ComponentTemplateCreateForm form = forms.DeviceBayTemplateCreateForm
model_form = forms.DeviceBayTemplateForm model_form = forms.DeviceBayTemplateForm
template_name = 'dcim/component_template_create.html'
class DeviceBayTemplateEditView(generic.ObjectEditView): class DeviceBayTemplateEditView(generic.ObjectEditView):
@ -1444,9 +1427,8 @@ class DeviceBayTemplateBulkDeleteView(generic.BulkDeleteView):
class InventoryItemTemplateCreateView(generic.ComponentCreateView): class InventoryItemTemplateCreateView(generic.ComponentCreateView):
queryset = InventoryItemTemplate.objects.all() queryset = InventoryItemTemplate.objects.all()
form = forms.ModularComponentTemplateCreateForm form = forms.InventoryItemTemplateCreateForm
model_form = forms.InventoryItemTemplateForm model_form = forms.InventoryItemTemplateForm
template_name = 'dcim/inventoryitemtemplate_create.html'
def alter_object(self, instance, request): def alter_object(self, instance, request):
# Set component (if any) # Set component (if any)
@ -1874,7 +1856,7 @@ class ConsolePortView(generic.ObjectView):
class ConsolePortCreateView(generic.ComponentCreateView): class ConsolePortCreateView(generic.ComponentCreateView):
queryset = ConsolePort.objects.all() queryset = ConsolePort.objects.all()
form = forms.DeviceComponentCreateForm form = forms.ConsolePortCreateForm
model_form = forms.ConsolePortForm model_form = forms.ConsolePortForm
@ -1933,7 +1915,7 @@ class ConsoleServerPortView(generic.ObjectView):
class ConsoleServerPortCreateView(generic.ComponentCreateView): class ConsoleServerPortCreateView(generic.ComponentCreateView):
queryset = ConsoleServerPort.objects.all() queryset = ConsoleServerPort.objects.all()
form = forms.DeviceComponentCreateForm form = forms.ConsoleServerPortCreateForm
model_form = forms.ConsoleServerPortForm model_form = forms.ConsoleServerPortForm
@ -1992,7 +1974,7 @@ class PowerPortView(generic.ObjectView):
class PowerPortCreateView(generic.ComponentCreateView): class PowerPortCreateView(generic.ComponentCreateView):
queryset = PowerPort.objects.all() queryset = PowerPort.objects.all()
form = forms.DeviceComponentCreateForm form = forms.PowerPortCreateForm
model_form = forms.PowerPortForm model_form = forms.PowerPortForm
@ -2051,7 +2033,7 @@ class PowerOutletView(generic.ObjectView):
class PowerOutletCreateView(generic.ComponentCreateView): class PowerOutletCreateView(generic.ComponentCreateView):
queryset = PowerOutlet.objects.all() queryset = PowerOutlet.objects.all()
form = forms.DeviceComponentCreateForm form = forms.PowerOutletCreateForm
model_form = forms.PowerOutletForm model_form = forms.PowerOutletForm
@ -2154,42 +2136,13 @@ class InterfaceView(generic.ObjectView):
class InterfaceCreateView(generic.ComponentCreateView): class InterfaceCreateView(generic.ComponentCreateView):
queryset = Interface.objects.all() queryset = Interface.objects.all()
form = forms.DeviceComponentCreateForm form = forms.InterfaceCreateForm
model_form = forms.InterfaceForm model_form = forms.InterfaceForm
# template_name = 'dcim/interface_create.html'
# TODO: Figure out what to do with this
# def post(self, request):
# """
# Override inherited post() method to handle request to assign newly created
# interface objects (first object) to an IP Address object.
# """
# form = self.form(request.POST, initial=request.GET)
# new_objs = self.validate_form(request, form)
#
# if form.is_valid() and not form.errors:
# if '_addanother' in request.POST:
# return redirect(request.get_full_path())
# elif new_objs is not None and '_assignip' in request.POST and len(new_objs) >= 1 and \
# request.user.has_perm('ipam.add_ipaddress'):
# first_obj = new_objs[0].pk
# return redirect(
# f'/ipam/ip-addresses/add/?interface={first_obj}&return_url={self.get_return_url(request)}'
# )
# else:
# return redirect(self.get_return_url(request))
#
# return render(request, self.template_name, {
# 'obj_type': self.queryset.model._meta.verbose_name,
# 'form': form,
# 'return_url': self.get_return_url(request),
# })
class InterfaceEditView(generic.ObjectEditView): class InterfaceEditView(generic.ObjectEditView):
queryset = Interface.objects.all() queryset = Interface.objects.all()
form = forms.InterfaceForm form = forms.InterfaceForm
template_name = 'dcim/interface_edit.html'
class InterfaceDeleteView(generic.ObjectDeleteView): class InterfaceDeleteView(generic.ObjectDeleteView):
@ -2244,14 +2197,6 @@ class FrontPortCreateView(generic.ComponentCreateView):
form = forms.FrontPortCreateForm form = forms.FrontPortCreateForm
model_form = forms.FrontPortForm model_form = forms.FrontPortForm
def initialize_forms(self, request):
form, model_form = super().initialize_forms(request)
model_form.fields.pop('rear_port')
model_form.fields.pop('rear_port_position')
return form, model_form
class FrontPortEditView(generic.ObjectEditView): class FrontPortEditView(generic.ObjectEditView):
queryset = FrontPort.objects.all() queryset = FrontPort.objects.all()
@ -2308,7 +2253,7 @@ class RearPortView(generic.ObjectView):
class RearPortCreateView(generic.ComponentCreateView): class RearPortCreateView(generic.ComponentCreateView):
queryset = RearPort.objects.all() queryset = RearPort.objects.all()
form = forms.DeviceComponentCreateForm form = forms.RearPortCreateForm
model_form = forms.RearPortForm model_form = forms.RearPortForm
@ -2423,7 +2368,7 @@ class DeviceBayView(generic.ObjectView):
class DeviceBayCreateView(generic.ComponentCreateView): class DeviceBayCreateView(generic.ComponentCreateView):
queryset = DeviceBay.objects.all() queryset = DeviceBay.objects.all()
form = forms.DeviceComponentCreateForm form = forms.DeviceBayCreateForm
model_form = forms.DeviceBayForm model_form = forms.DeviceBayForm
@ -2552,7 +2497,6 @@ class InventoryItemCreateView(generic.ComponentCreateView):
queryset = InventoryItem.objects.all() queryset = InventoryItem.objects.all()
form = forms.InventoryItemCreateForm form = forms.InventoryItemCreateForm
model_form = forms.InventoryItemForm model_form = forms.InventoryItemForm
template_name = 'dcim/inventoryitem_create.html'
def alter_object(self, instance, request): def alter_object(self, instance, request):
# Set component (if any) # Set component (if any)

View File

@ -538,7 +538,7 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
""" """
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.
""" """
template_name = 'dcim/component_create.html' template_name = 'generic/object_edit.html'
form = None form = None
model_form = None model_form = None
patterned_fields = ('name', 'label') patterned_fields = ('name', 'label')
@ -549,44 +549,38 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
def alter_object(self, instance, request): def alter_object(self, instance, request):
return instance return instance
def initialize_forms(self, request): def initialize_form(self, request):
data = request.POST if request.method == 'POST' else None data = request.POST if request.method == 'POST' else None
initial_data = normalize_querydict(request.GET) initial_data = normalize_querydict(request.GET)
form = self.form(data=data, initial=request.GET) form = self.form(data=data, initial=initial_data)
model_form = self.model_form(data=data, initial=initial_data)
# These fields will be set from the pattern values return form
for field_name in self.patterned_fields:
model_form.fields[field_name].widget = HiddenInput()
return form, model_form
def get(self, request): def get(self, request):
form, model_form = self.initialize_forms(request) form = self.initialize_form(request)
instance = self.alter_object(self.queryset.model(), request) instance = self.alter_object(self.queryset.model(), request)
return render(request, self.template_name, { return render(request, self.template_name, {
'object': instance, 'object': instance,
'replication_form': form, 'form': form,
'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') logger = logging.getLogger('netbox.views.ComponentCreateView')
form, model_form = self.initialize_forms(request) form = self.initialize_form(request)
instance = self.alter_object(self.queryset.model(), request) instance = self.alter_object(self.queryset.model(), request)
if form.is_valid(): if form.is_valid():
new_components = [] new_components = []
data = deepcopy(request.POST) data = deepcopy(request.POST)
pattern_count = len(form.cleaned_data[f'{self.patterned_fields[0]}_pattern']) pattern_count = len(form.cleaned_data[self.patterned_fields[0]])
for i in range(pattern_count): for i in range(pattern_count):
for field_name in self.patterned_fields: for field_name in self.patterned_fields:
if form.cleaned_data.get(f'{field_name}_pattern'): if form.cleaned_data.get(field_name):
data[field_name] = form.cleaned_data[f'{field_name}_pattern'][i] data[field_name] = form.cleaned_data[field_name][i]
if hasattr(form, 'get_iterative_data'): if hasattr(form, 'get_iterative_data'):
data.update(form.get_iterative_data(i)) data.update(form.get_iterative_data(i))
@ -626,7 +620,6 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
return render(request, self.template_name, { return render(request, self.template_name, {
'object': instance, 'object': instance,
'replication_form': form, 'form': form,
'form': model_form,
'return_url': self.get_return_url(request), 'return_url': self.get_return_url(request),
}) })

View File

@ -1,38 +0,0 @@
{% extends 'generic/object_edit.html' %}
{% load form_helpers %}
{% block form %}
{% if form.module_type %}
<div class="row mb-2">
<div class="offset-sm-3">
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="nav-item">
<button role="tab" type="button" id="devicetype_tab" data-bs-toggle="tab" aria-controls="devicetype" data-bs-target="#devicetype" class="nav-link {% if not form.initial.module_type %}active{% endif %}">
Device Type
</button>
</li>
<li role="presentation" class="nav-item">
<button role="tab" type="button" id="moduletype_tab" data-bs-toggle="tab" aria-controls="moduletype" data-bs-target="#moduletype" class="nav-link {% if form.initial.module_type %}active{% endif %}">
Module Type
</button>
</li>
</ul>
</div>
</div>
<div class="tab-content p-0 border-0">
<div class="tab-pane {% if not form.initial.module_type %}active{% endif %}" id="devicetype" role="tabpanel">
{% render_field replication_form.device_type %}
</div>
<div class="tab-pane {% if form.initial.module_type %}active{% endif %}" id="moduletype" role="tabpanel">
{% render_field replication_form.module_type %}
</div>
</div>
{% else %}
{% render_field replication_form.device_type %}
{% endif %}
{% block replication_fields %}
{% render_field replication_form.name_pattern %}
{% render_field replication_form.label_pattern %}
{% endblock replication_fields %}
{{ block.super }}
{% endblock form %}

View File

@ -1,7 +0,0 @@
{% extends 'dcim/component_template_create.html' %}
{% load form_helpers %}
{% block replication_fields %}
{{ block.super }}
{% render_field replication_form.rear_port_set %}
{% endblock replication_fields %}

View File

@ -1,17 +0,0 @@
{% extends 'dcim/component_create.html' %}
{% load helpers %}
{% load form_helpers %}
{% block replication_fields %}
{{ block.super }}
{% if object.component %}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">
{{ object.component|meta:"verbose_name"|bettertitle }}
</label>
<div class="col">
<input class="form-control" value="{{ object.component }}" disabled />
</div>
</div>
{% endif %}
{% endblock replication_fields %}

View File

@ -1,17 +0,0 @@
{% extends 'dcim/component_template_create.html' %}
{% load helpers %}
{% load form_helpers %}
{% block replication_fields %}
{{ block.super }}
{% if object.component %}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">
{{ object.component|meta:"verbose_name"|bettertitle }}
</label>
<div class="col">
<input class="form-control" value="{{ object.component }}" disabled />
</div>
</div>
{% endif %}
{% endblock replication_fields %}

View File

@ -1,7 +0,0 @@
{% extends 'dcim/component_template_create.html' %}
{% load form_helpers %}
{% block replication_fields %}
{{ block.super }}
{% render_field replication_form.position_pattern %}
{% endblock replication_fields %}

View File

@ -1,69 +0,0 @@
{% extends 'generic/object_edit.html' %}
{% load form_helpers %}
{% block form %}
{# Render hidden fields #}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<div class="field-group my-5">
<div class="row mb-2">
<h5 class="offset-sm-3">Interface</h5>
</div>
{% if form.instance.virtual_machine %}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end required" for="id_device">Virtual Machine</label>
<div class="col">
<input class="form-control" value="{{ form.instance.virtual_machine }}" disabled />
</div>
</div>
{% endif %}
{% render_field form.name %}
{% render_field form.description %}
{% render_field form.tags %}
</div>
<div class="field-group my-5">
<div class="row mb-2">
<h5 class="offset-sm-3">Addressing</h5>
</div>
{% render_field form.vrf %}
{% render_field form.mac_address %}
</div>
<div class="field-group my-5">
<div class="row mb-2">
<h5 class="offset-sm-3">Operation</h5>
</div>
{% render_field form.mtu %}
{% render_field form.enabled %}
</div>
<div class="field-group my-5">
<div class="row mb-2">
<h5 class="offset-sm-3">Related Interfaces</h5>
</div>
{% render_field form.parent %}
{% render_field form.bridge %}
</div>
<div class="field-group my-5">
<div class="row mb-2">
<h5 class="offset-sm-3">802.1Q Switching</h5>
</div>
{% render_field form.mode %}
{% render_field form.vlan_group %}
{% render_field form.untagged_vlan %}
{% render_field form.tagged_vlans %}
</div>
{% if form.custom_fields %}
<div class="field-group my-5">
<div class="row mb-2">
<h5 class="offset-sm-3">Custom Fields</h5>
</div>
{% render_custom_fields form %}
</div>
{% endif %}
{% endblock %}

View File

@ -5,7 +5,6 @@ from django.core.exceptions import ValidationError
from dcim.forms.common import InterfaceCommonForm from dcim.forms.common import InterfaceCommonForm
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup 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 ipam.models import IPAddress, VLAN, VLANGroup, VRF
from netbox.forms import NetBoxModelForm from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
@ -278,6 +277,9 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm): class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
virtual_machine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all()
)
parent = DynamicModelChoiceField( parent = DynamicModelChoiceField(
queryset=VMInterface.objects.all(), queryset=VMInterface.objects.all(),
required=False, required=False,
@ -338,7 +340,6 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
] ]
widgets = { widgets = {
'virtual_machine': forms.HiddenInput(),
'mode': StaticSelect() 'mode': StaticSelect()
} }
labels = { labels = {
@ -347,3 +348,10 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
help_texts = { help_texts = {
'mode': INTERFACE_MODE_HELP_TEXT, '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

View File

@ -1,17 +1,10 @@
from django import forms from utilities.forms import ExpandableNameField
from .models import VMInterfaceForm
from utilities.forms import BootstrapMixin, DynamicModelChoiceField, ExpandableNameField
from .models import VirtualMachine
__all__ = ( __all__ = (
'VMInterfaceCreateForm', 'VMInterfaceCreateForm',
) )
class VMInterfaceCreateForm(BootstrapMixin, forms.Form): class VMInterfaceCreateForm(VMInterfaceForm):
virtual_machine = DynamicModelChoiceField( name = ExpandableNameField()
queryset=VirtualMachine.objects.all()
)
name_pattern = ExpandableNameField(
label='Name'
)

View File

@ -457,7 +457,6 @@ class VMInterfaceCreateView(generic.ComponentCreateView):
class VMInterfaceEditView(generic.ObjectEditView): class VMInterfaceEditView(generic.ObjectEditView):
queryset = VMInterface.objects.all() queryset = VMInterface.objects.all()
form = forms.VMInterfaceForm form = forms.VMInterfaceForm
template_name = 'virtualization/vminterface_edit.html'
class VMInterfaceDeleteView(generic.ObjectDeleteView): class VMInterfaceDeleteView(generic.ObjectDeleteView):