mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
Merge pull request #4723 from jsenecal/4615_interface_label
Closes: #4615 Physical labels
This commit is contained in:
commit
9b48a26aef
@ -249,7 +249,7 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = ['id', 'device_type', 'name', 'type']
|
fields = ['id', 'device_type', 'name', 'label', 'type']
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
@ -262,7 +262,7 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = ['id', 'device_type', 'name', 'type']
|
fields = ['id', 'device_type', 'name', 'label', 'type']
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
@ -275,7 +275,7 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = ['id', 'device_type', 'name', 'type', 'maximum_draw', 'allocated_draw']
|
fields = ['id', 'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw']
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
||||||
@ -296,7 +296,7 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
fields = ['id', 'device_type', 'name', 'type', 'power_port', 'feed_leg']
|
fields = ['id', 'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg']
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
||||||
@ -305,7 +305,7 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = ['id', 'device_type', 'name', 'type', 'mgmt_only']
|
fields = ['id', 'device_type', 'name', 'label', 'type', 'mgmt_only']
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateSerializer(ValidatedModelSerializer):
|
class RearPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
@ -332,7 +332,7 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
fields = ['id', 'device_type', 'name']
|
fields = ['id', 'device_type', 'name', 'label']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -447,7 +447,7 @@ class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer)
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'name', 'type', 'description', 'connected_endpoint_type', 'connected_endpoint',
|
'id', 'device', 'name', 'label', 'type', 'description', 'connected_endpoint_type', 'connected_endpoint',
|
||||||
'connection_status', 'cable', 'tags',
|
'connection_status', 'cable', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -465,7 +465,7 @@ class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'name', 'type', 'description', 'connected_endpoint_type', 'connected_endpoint',
|
'id', 'device', 'name', 'label', 'type', 'description', 'connected_endpoint_type', 'connected_endpoint',
|
||||||
'connection_status', 'cable', 'tags',
|
'connection_status', 'cable', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -495,7 +495,7 @@ class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'name', 'type', 'power_port', 'feed_leg', 'description', 'connected_endpoint_type',
|
'id', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'connected_endpoint_type',
|
||||||
'connected_endpoint', 'connection_status', 'cable', 'tags',
|
'connected_endpoint', 'connection_status', 'cable', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -513,7 +513,7 @@ class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'name', 'type', 'maximum_draw', 'allocated_draw', 'description', 'connected_endpoint_type',
|
'id', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'connected_endpoint_type',
|
||||||
'connected_endpoint', 'connection_status', 'cable', 'tags',
|
'connected_endpoint', 'connection_status', 'cable', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -537,7 +537,7 @@ class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'name', 'type', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
'id', 'device', 'name', 'label', 'type', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
||||||
'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable', 'mode', 'untagged_vlan',
|
'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable', 'mode', 'untagged_vlan',
|
||||||
'tagged_vlans', 'tags', 'count_ipaddresses',
|
'tagged_vlans', 'tags', 'count_ipaddresses',
|
||||||
]
|
]
|
||||||
@ -604,7 +604,7 @@ class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = ['id', 'device', 'name', 'description', 'installed_device', 'tags']
|
fields = ['id', 'device', 'name', 'label', 'description', 'installed_device', 'tags']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -22,10 +22,10 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
|
|||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm,
|
||||||
BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField,
|
BOOLEAN_WITH_BLANK_CHOICES, BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField,
|
||||||
CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model,
|
CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
|
||||||
JSONField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
|
form_from_model, JSONField, LabeledComponentForm, SelectWithPK, SmallTextarea, SlugField, StaticSelect2,
|
||||||
BOOLEAN_WITH_BLANK_CHOICES,
|
StaticSelect2Multiple, TagFilterField,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster, ClusterGroup, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, VirtualMachine
|
||||||
from .choices import *
|
from .choices import *
|
||||||
@ -1037,20 +1037,17 @@ class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type',
|
'device_type', 'name', 'label', 'type',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
'device_type': forms.HiddenInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateCreateForm(BootstrapMixin, forms.Form):
|
class ConsolePortTemplateCreateForm(LabeledComponentForm):
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
queryset=DeviceType.objects.all()
|
queryset=DeviceType.objects.all()
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ConsolePortTypeChoices),
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -1077,20 +1074,17 @@ class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type',
|
'device_type', 'name', 'label', 'type',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
'device_type': forms.HiddenInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateCreateForm(BootstrapMixin, forms.Form):
|
class ConsoleServerPortTemplateCreateForm(LabeledComponentForm):
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
queryset=DeviceType.objects.all()
|
queryset=DeviceType.objects.all()
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ConsolePortTypeChoices),
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -1117,20 +1111,17 @@ class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type', 'maximum_draw', 'allocated_draw',
|
'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
'device_type': forms.HiddenInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateCreateForm(BootstrapMixin, forms.Form):
|
class PowerPortTemplateCreateForm(LabeledComponentForm):
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
queryset=DeviceType.objects.all()
|
queryset=DeviceType.objects.all()
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PowerPortTypeChoices),
|
choices=add_blank_choice(PowerPortTypeChoices),
|
||||||
required=False
|
required=False
|
||||||
@ -1177,7 +1168,7 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type', 'power_port', 'feed_leg',
|
'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
'device_type': forms.HiddenInput(),
|
||||||
@ -1194,13 +1185,10 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateCreateForm(BootstrapMixin, forms.Form):
|
class PowerOutletTemplateCreateForm(LabeledComponentForm):
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
queryset=DeviceType.objects.all()
|
queryset=DeviceType.objects.all()
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PowerOutletTypeChoices),
|
choices=add_blank_choice(PowerOutletTypeChoices),
|
||||||
required=False
|
required=False
|
||||||
@ -1252,7 +1240,7 @@ class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type', 'mgmt_only',
|
'device_type', 'name', 'label', 'type', 'mgmt_only',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
'device_type': forms.HiddenInput(),
|
||||||
@ -1260,13 +1248,10 @@ class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateCreateForm(BootstrapMixin, forms.Form):
|
class InterfaceTemplateCreateForm(LabeledComponentForm):
|
||||||
device_type = DynamicModelChoiceField(
|
device_type = DynamicModelChoiceField(
|
||||||
queryset=DeviceType.objects.all()
|
queryset=DeviceType.objects.all()
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=InterfaceTypeChoices,
|
choices=InterfaceTypeChoices,
|
||||||
widget=StaticSelect2()
|
widget=StaticSelect2()
|
||||||
@ -1509,7 +1494,7 @@ class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type',
|
'device_type', 'name', 'label', 'type',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -1518,7 +1503,7 @@ class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type',
|
'device_type', 'name', 'label', 'type',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -1527,7 +1512,7 @@ class PowerPortTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type', 'maximum_draw', 'allocated_draw',
|
'device_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -1541,7 +1526,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type', 'power_port', 'feed_leg',
|
'device_type', 'name', 'label', 'type', 'power_port', 'feed_leg',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -1553,7 +1538,7 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'type', 'mgmt_only',
|
'device_type', 'name', 'label', 'type', 'mgmt_only',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -2196,14 +2181,11 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
|
|||||||
# Bulk device component creation
|
# Bulk device component creation
|
||||||
#
|
#
|
||||||
|
|
||||||
class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form):
|
class DeviceBulkAddComponentForm(LabeledComponentForm):
|
||||||
pk = forms.ModelMultipleChoiceField(
|
pk = forms.ModelMultipleChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
|
|
||||||
def clean_tags(self):
|
def clean_tags(self):
|
||||||
# Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we
|
# Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we
|
||||||
@ -2234,20 +2216,17 @@ class ConsolePortForm(BootstrapMixin, forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'name', 'type', 'description', 'tags',
|
'device', 'name', 'label', 'type', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
'device': forms.HiddenInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortCreateForm(BootstrapMixin, forms.Form):
|
class ConsolePortCreateForm(LabeledComponentForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ConsolePortTypeChoices),
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
@ -2327,13 +2306,10 @@ class ConsoleServerPortForm(BootstrapMixin, forms.ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortCreateForm(BootstrapMixin, forms.Form):
|
class ConsoleServerPortCreateForm(LabeledComponentForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ConsolePortTypeChoices),
|
choices=add_blank_choice(ConsolePortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
@ -2427,13 +2403,10 @@ class PowerPortForm(BootstrapMixin, forms.ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPortCreateForm(BootstrapMixin, forms.Form):
|
class PowerPortCreateForm(LabeledComponentForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PowerPortTypeChoices),
|
choices=add_blank_choice(PowerPortTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
@ -2536,13 +2509,10 @@ class PowerOutletForm(BootstrapMixin, forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletCreateForm(BootstrapMixin, forms.Form):
|
class PowerOutletCreateForm(LabeledComponentForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(PowerOutletTypeChoices),
|
choices=add_blank_choice(PowerOutletTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
@ -2726,7 +2696,7 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'name', 'type', 'enabled', 'lag', 'mac_address', 'mtu', 'mgmt_only', 'description',
|
'device', 'name', 'label', 'type', 'enabled', 'lag', 'mac_address', 'mtu', 'mgmt_only', 'description',
|
||||||
'mode', 'untagged_vlan', 'tagged_vlans', 'tags',
|
'mode', 'untagged_vlan', 'tagged_vlans', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
@ -2761,13 +2731,10 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
|
|||||||
self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk)
|
self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceCreateForm(BootstrapMixin, InterfaceCommonForm, forms.Form):
|
class InterfaceCreateForm(InterfaceCommonForm, LabeledComponentForm):
|
||||||
device = DynamicModelChoiceField(
|
device = DynamicModelChoiceField(
|
||||||
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
queryset=Device.objects.prefetch_related('device_type__manufacturer')
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name'
|
|
||||||
)
|
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=InterfaceTypeChoices,
|
choices=InterfaceTypeChoices,
|
||||||
widget=StaticSelect2(),
|
widget=StaticSelect2(),
|
||||||
|
73
netbox/dcim/migrations/0107_component_labels.py
Normal file
73
netbox/dcim/migrations/0107_component_labels.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Generated by Django 3.0.7 on 2020-06-04 20:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0106_role_default_color'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interfacetemplate',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleport',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleporttemplate',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverport',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverporttemplate',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlettemplate',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerport',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerporttemplate',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebay',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='devicebaytemplate',
|
||||||
|
name='label',
|
||||||
|
field=models.CharField(blank=True, max_length=64),
|
||||||
|
),
|
||||||
|
]
|
@ -32,6 +32,11 @@ class ComponentTemplateModel(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.label:
|
||||||
|
return f"{self.name} ({self.label})"
|
||||||
|
return self.name
|
||||||
|
|
||||||
def instantiate(self, device):
|
def instantiate(self, device):
|
||||||
"""
|
"""
|
||||||
Instantiate a new component on the specified Device.
|
Instantiate a new component on the specified Device.
|
||||||
@ -71,6 +76,11 @@ class ConsolePortTemplate(ComponentTemplateModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
@ -81,9 +91,6 @@ class ConsolePortTemplate(ComponentTemplateModel):
|
|||||||
ordering = ('device_type', '_name')
|
ordering = ('device_type', '_name')
|
||||||
unique_together = ('device_type', 'name')
|
unique_together = ('device_type', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def instantiate(self, device):
|
def instantiate(self, device):
|
||||||
return ConsolePort(
|
return ConsolePort(
|
||||||
device=device,
|
device=device,
|
||||||
@ -109,6 +116,11 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
@ -119,9 +131,6 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
|
|||||||
ordering = ('device_type', '_name')
|
ordering = ('device_type', '_name')
|
||||||
unique_together = ('device_type', 'name')
|
unique_together = ('device_type', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def instantiate(self, device):
|
def instantiate(self, device):
|
||||||
return ConsoleServerPort(
|
return ConsoleServerPort(
|
||||||
device=device,
|
device=device,
|
||||||
@ -147,6 +156,11 @@ class PowerPortTemplate(ComponentTemplateModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerPortTypeChoices,
|
choices=PowerPortTypeChoices,
|
||||||
@ -169,9 +183,6 @@ class PowerPortTemplate(ComponentTemplateModel):
|
|||||||
ordering = ('device_type', '_name')
|
ordering = ('device_type', '_name')
|
||||||
unique_together = ('device_type', 'name')
|
unique_together = ('device_type', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def instantiate(self, device):
|
def instantiate(self, device):
|
||||||
return PowerPort(
|
return PowerPort(
|
||||||
device=device,
|
device=device,
|
||||||
@ -199,6 +210,11 @@ class PowerOutletTemplate(ComponentTemplateModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerOutletTypeChoices,
|
choices=PowerOutletTypeChoices,
|
||||||
@ -222,9 +238,6 @@ class PowerOutletTemplate(ComponentTemplateModel):
|
|||||||
ordering = ('device_type', '_name')
|
ordering = ('device_type', '_name')
|
||||||
unique_together = ('device_type', 'name')
|
unique_together = ('device_type', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
||||||
# Validate power port assignment
|
# Validate power port assignment
|
||||||
@ -265,6 +278,11 @@ class InterfaceTemplate(ComponentTemplateModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=InterfaceTypeChoices
|
choices=InterfaceTypeChoices
|
||||||
@ -278,9 +296,6 @@ class InterfaceTemplate(ComponentTemplateModel):
|
|||||||
ordering = ('device_type', '_name')
|
ordering = ('device_type', '_name')
|
||||||
unique_together = ('device_type', 'name')
|
unique_together = ('device_type', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def instantiate(self, device):
|
def instantiate(self, device):
|
||||||
return Interface(
|
return Interface(
|
||||||
device=device,
|
device=device,
|
||||||
@ -420,14 +435,16 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('device_type', '_name')
|
ordering = ('device_type', '_name')
|
||||||
unique_together = ('device_type', 'name')
|
unique_together = ('device_type', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def instantiate(self, device):
|
def instantiate(self, device):
|
||||||
return DeviceBay(
|
return DeviceBay(
|
||||||
device=device,
|
device=device,
|
||||||
|
@ -47,6 +47,11 @@ class ComponentModel(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.label:
|
||||||
|
return f"{self.name} ({self.label})"
|
||||||
|
return self.name
|
||||||
|
|
||||||
def to_objectchange(self, action):
|
def to_objectchange(self, action):
|
||||||
# Annotate the parent Device/VM
|
# Annotate the parent Device/VM
|
||||||
try:
|
try:
|
||||||
@ -234,6 +239,11 @@ class ConsolePort(CableTermination, ComponentModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=50
|
max_length=50
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
_name = NaturalOrderingField(
|
_name = NaturalOrderingField(
|
||||||
target_field='name',
|
target_field='name',
|
||||||
max_length=100,
|
max_length=100,
|
||||||
@ -264,9 +274,6 @@ class ConsolePort(CableTermination, ComponentModel):
|
|||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
unique_together = ('device', 'name')
|
unique_together = ('device', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return self.device.get_absolute_url()
|
return self.device.get_absolute_url()
|
||||||
|
|
||||||
@ -301,6 +308,11 @@ class ConsoleServerPort(CableTermination, ComponentModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
@ -319,9 +331,6 @@ class ConsoleServerPort(CableTermination, ComponentModel):
|
|||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
unique_together = ('device', 'name')
|
unique_together = ('device', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return self.device.get_absolute_url()
|
return self.device.get_absolute_url()
|
||||||
|
|
||||||
@ -356,6 +365,11 @@ class PowerPort(CableTermination, ComponentModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerPortTypeChoices,
|
choices=PowerPortTypeChoices,
|
||||||
@ -400,9 +414,6 @@ class PowerPort(CableTermination, ComponentModel):
|
|||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
unique_together = ('device', 'name')
|
unique_together = ('device', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return self.device.get_absolute_url()
|
return self.device.get_absolute_url()
|
||||||
|
|
||||||
@ -519,6 +530,11 @@ class PowerOutlet(CableTermination, ComponentModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PowerOutletTypeChoices,
|
choices=PowerOutletTypeChoices,
|
||||||
@ -550,9 +566,6 @@ class PowerOutlet(CableTermination, ComponentModel):
|
|||||||
ordering = ('device', '_name')
|
ordering = ('device', '_name')
|
||||||
unique_together = ('device', 'name')
|
unique_together = ('device', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return self.device.get_absolute_url()
|
return self.device.get_absolute_url()
|
||||||
|
|
||||||
@ -608,6 +621,11 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
_connected_interface = models.OneToOneField(
|
_connected_interface = models.OneToOneField(
|
||||||
to='self',
|
to='self',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
@ -688,9 +706,6 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
ordering = ('device', CollateAsChar('_name'))
|
ordering = ('device', CollateAsChar('_name'))
|
||||||
unique_together = ('device', 'name')
|
unique_together = ('device', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:interface', kwargs={'pk': self.pk})
|
return reverse('dcim:interface', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
@ -997,6 +1012,11 @@ class DeviceBay(ComponentModel):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
blank=True,
|
||||||
|
help_text="Physical label"
|
||||||
|
)
|
||||||
installed_device = models.OneToOneField(
|
installed_device = models.OneToOneField(
|
||||||
to='dcim.Device',
|
to='dcim.Device',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
@ -1013,6 +1033,8 @@ class DeviceBay(ComponentModel):
|
|||||||
unique_together = ('device', 'name')
|
unique_together = ('device', 'name')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
if self.label:
|
||||||
|
return '{} - {} ({})'.format(self.device.name, self.name, self.label)
|
||||||
return '{} - {}'.format(self.device.name, self.name)
|
return '{} - {}'.format(self.device.name, self.name)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
|
@ -116,3 +116,45 @@ class DeviceTestCase(TestCase):
|
|||||||
|
|
||||||
# Check that the initial value for the cluster group is set automatically when assigning the cluster
|
# Check that the initial value for the cluster group is set automatically when assigning the cluster
|
||||||
self.assertEqual(test.initial['cluster_group'], cluster.group.pk)
|
self.assertEqual(test.initial['cluster_group'], cluster.group.pk)
|
||||||
|
|
||||||
|
|
||||||
|
class LabelTestCase(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
site = Site.objects.create(name='Site 2', slug='site-2')
|
||||||
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 2', slug='manufacturer-2')
|
||||||
|
cls.device_type = DeviceType.objects.create(
|
||||||
|
manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', u_height=1
|
||||||
|
)
|
||||||
|
device_role = DeviceRole.objects.create(
|
||||||
|
name='Device Role 2', slug='device-role-2', color='ffff00'
|
||||||
|
)
|
||||||
|
cls.device = Device.objects.create(
|
||||||
|
name='Device 2', device_type=cls.device_type, device_role=device_role, site=site
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_interface_label_count_valid(self):
|
||||||
|
"""Test that a `label` can be generated for each generated `name` from `name_pattern` on InterfaceCreateForm"""
|
||||||
|
interface_data = {
|
||||||
|
'device': self.device.pk,
|
||||||
|
'name_pattern': 'eth[0-9]',
|
||||||
|
'label_pattern': 'Interface[0-9]',
|
||||||
|
'type': InterfaceTypeChoices.TYPE_100ME_FIXED,
|
||||||
|
}
|
||||||
|
form = InterfaceCreateForm(interface_data)
|
||||||
|
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
|
def test_interface_label_count_mismatch(self):
|
||||||
|
"""Test that a `label` cannot be generated for each generated `name` from `name_pattern` due to invalid `label_pattern` on InterfaceCreateForm"""
|
||||||
|
bad_interface_data = {
|
||||||
|
'device': self.device.pk,
|
||||||
|
'name_pattern': 'eth[0-9]',
|
||||||
|
'label_pattern': 'Interface[0-1]',
|
||||||
|
'type': InterfaceTypeChoices.TYPE_100ME_FIXED,
|
||||||
|
}
|
||||||
|
form = InterfaceCreateForm(bad_interface_data)
|
||||||
|
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
|
self.assertIn('label_pattern', form.errors)
|
||||||
|
@ -710,6 +710,8 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetypes[1].pk,
|
||||||
'name_pattern': 'Interface Template [4-6]',
|
'name_pattern': 'Interface Template [4-6]',
|
||||||
|
# Test that a label can be applied to each generated interface templates
|
||||||
|
'label_pattern': 'Interface Template Label [3-5]',
|
||||||
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
'mgmt_only': True,
|
'mgmt_only': True,
|
||||||
}
|
}
|
||||||
@ -1010,6 +1012,8 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Console Port [4-6]',
|
'name_pattern': 'Console Port [4-6]',
|
||||||
|
# Test that a label can be applied to each generated console ports
|
||||||
|
'label_pattern': 'Serial[3-5]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
'description': 'A console port',
|
'description': 'A console port',
|
||||||
'tags': 'Alpha,Bravo,Charlie',
|
'tags': 'Alpha,Bravo,Charlie',
|
||||||
|
@ -58,6 +58,10 @@
|
|||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>{{ interface.name }}</td>
|
<td>{{ interface.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Label</td>
|
||||||
|
<td>{{ interface.label|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
<td>{{ interface.get_type_display }}</td>
|
<td>{{ interface.get_type_display }}</td>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<div class="panel-heading"><strong>Interface</strong></div>
|
<div class="panel-heading"><strong>Interface</strong></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% render_field form.name %}
|
{% render_field form.name %}
|
||||||
|
{% render_field form.label %}
|
||||||
{% render_field form.type %}
|
{% render_field form.type %}
|
||||||
{% render_field form.enabled %}
|
{% render_field form.enabled %}
|
||||||
{% render_field form.lag %}
|
{% render_field form.lag %}
|
||||||
|
@ -530,6 +530,8 @@ class ExpandableNameField(forms.CharField):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
|
if value is None:
|
||||||
|
return list()
|
||||||
if re.search(ALPHANUMERIC_EXPANSION_PATTERN, value):
|
if re.search(ALPHANUMERIC_EXPANSION_PATTERN, value):
|
||||||
return list(expand_alphanumeric_pattern(value))
|
return list(expand_alphanumeric_pattern(value))
|
||||||
return [value]
|
return [value]
|
||||||
@ -802,6 +804,31 @@ class ImportForm(BootstrapMixin, forms.Form):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class LabeledComponentForm(BootstrapMixin, forms.Form):
|
||||||
|
"""
|
||||||
|
Base form for adding label pattern validation to `Create` forms
|
||||||
|
"""
|
||||||
|
name_pattern = ExpandableNameField(
|
||||||
|
label='Name'
|
||||||
|
)
|
||||||
|
label_pattern = ExpandableNameField(
|
||||||
|
label='Label',
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
# Validate that the number of components being created from both the name_pattern and label_pattern are equal
|
||||||
|
name_pattern_count = len(self.cleaned_data['name_pattern'])
|
||||||
|
label_pattern_count = len(self.cleaned_data['label_pattern'])
|
||||||
|
if label_pattern_count and name_pattern_count != label_pattern_count:
|
||||||
|
raise forms.ValidationError({
|
||||||
|
'label_pattern': 'The provided name pattern will create {} components, however {} labels will '
|
||||||
|
'be generated. These counts must match.'.format(
|
||||||
|
name_pattern_count, label_pattern_count)
|
||||||
|
}, code='label_pattern_mismatch')
|
||||||
|
|
||||||
|
|
||||||
class TableConfigForm(BootstrapMixin, forms.Form):
|
class TableConfigForm(BootstrapMixin, forms.Form):
|
||||||
"""
|
"""
|
||||||
Form for configuring user's table preferences.
|
Form for configuring user's table preferences.
|
||||||
|
@ -1088,10 +1088,13 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
|
|||||||
new_components = []
|
new_components = []
|
||||||
data = deepcopy(request.POST)
|
data = deepcopy(request.POST)
|
||||||
|
|
||||||
for i, name in enumerate(form.cleaned_data['name_pattern']):
|
names = form.cleaned_data['name_pattern']
|
||||||
|
labels = form.cleaned_data.get('label_pattern')
|
||||||
|
for i, name in enumerate(names):
|
||||||
|
label = labels[i] if labels else None
|
||||||
# Initialize the individual component form
|
# Initialize the individual component form
|
||||||
data['name'] = name
|
data['name'] = name
|
||||||
|
data['label'] = label
|
||||||
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))
|
||||||
component_form = self.model_form(data)
|
component_form = self.model_form(data)
|
||||||
@ -1100,9 +1103,11 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
|
|||||||
new_components.append(component_form)
|
new_components.append(component_form)
|
||||||
else:
|
else:
|
||||||
for field, errors in component_form.errors.as_data().items():
|
for field, errors in component_form.errors.as_data().items():
|
||||||
# Assign errors on the child form's name field to name_pattern on the parent form
|
# Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form
|
||||||
if field == 'name':
|
if field == 'name':
|
||||||
field = 'name_pattern'
|
field = 'name_pattern'
|
||||||
|
elif field == 'label':
|
||||||
|
field = 'label_pattern'
|
||||||
for e in errors:
|
for e in errors:
|
||||||
form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
|
form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
|
||||||
|
|
||||||
@ -1187,10 +1192,14 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
|
|||||||
for obj in data['pk']:
|
for obj in data['pk']:
|
||||||
|
|
||||||
names = data['name_pattern']
|
names = data['name_pattern']
|
||||||
for name in names:
|
labels = data['label_pattern']
|
||||||
|
for i, name in enumerate(names):
|
||||||
|
label = labels[i] if labels else None
|
||||||
|
|
||||||
component_data = {
|
component_data = {
|
||||||
self.parent_field: obj.pk,
|
self.parent_field: obj.pk,
|
||||||
'name': name,
|
'name': name,
|
||||||
|
'label': label
|
||||||
}
|
}
|
||||||
component_data.update(data)
|
component_data.update(data)
|
||||||
component_form = self.model_form(component_data)
|
component_form = self.model_form(component_data)
|
||||||
|
Loading…
Reference in New Issue
Block a user