1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #9793: Add PoE attributes to interface templates

This commit is contained in:
jeremystretch
2022-07-28 15:03:24 -04:00
parent c5fb7b72f0
commit 2c43c8d077
14 changed files with 99 additions and 13 deletions

View File

@ -1,3 +1,3 @@
## Interface Templates ## Interface Templates
A template for a network interface that will be created on all instantiations of the parent device type. Each interface may be assigned a physical or virtual type, and may be designated as "management-only." A template for a network interface that will be created on all instantiations of the parent device type. Each interface may be assigned a physical or virtual type, and may be designated as "management-only." Power over Ethernet (PoE) mode and type may also be assigned to interface templates.

View File

@ -97,7 +97,7 @@ Custom field UI visibility has no impact on API operation.
* [#9536](https://github.com/netbox-community/netbox/issues/9536) - Track API token usage times * [#9536](https://github.com/netbox-community/netbox/issues/9536) - Track API token usage times
* [#9582](https://github.com/netbox-community/netbox/issues/9582) - Enable assigning config contexts based on device location * [#9582](https://github.com/netbox-community/netbox/issues/9582) - Enable assigning config contexts based on device location
### Bug Fixes ### Bug Fixes (from Beta1)
* [#9728](https://github.com/netbox-community/netbox/issues/9728) - Fix validation when assigning a virtual machine to a device * [#9728](https://github.com/netbox-community/netbox/issues/9728) - Fix validation when assigning a virtual machine to a device
* [#9729](https://github.com/netbox-community/netbox/issues/9729) - Fix ordering of content type creation to ensure compatability with demo data * [#9729](https://github.com/netbox-community/netbox/issues/9729) - Fix ordering of content type creation to ensure compatability with demo data
@ -177,6 +177,8 @@ Custom field UI visibility has no impact on API operation.
* `connected_endpoint_reachable` has been renamed to `connected_endpoints_reachable` * `connected_endpoint_reachable` has been renamed to `connected_endpoints_reachable`
* Added the optional `poe_mode` and `poe_type` fields * Added the optional `poe_mode` and `poe_type` fields
* Added the `l2vpn_termination` read-only field * Added the `l2vpn_termination` read-only field
* dcim.InterfaceTemplate
* Added the optional `poe_mode` and `poe_type` fields
* dcim.Location * dcim.Location
* Added required `status` field (default value: `active`) * Added required `status` field (default value: `active`)
* dcim.PowerOutlet * dcim.PowerOutlet

View File

@ -469,12 +469,22 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
default=None default=None
) )
type = ChoiceField(choices=InterfaceTypeChoices) type = ChoiceField(choices=InterfaceTypeChoices)
poe_mode = ChoiceField(
choices=InterfacePoEModeChoices,
required=False,
allow_blank=True
)
poe_type = ChoiceField(
choices=InterfacePoETypeChoices,
required=False,
allow_blank=True
)
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate
fields = [ fields = [
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description',
'created', 'last_updated', 'poe_mode', 'poe_type', 'created', 'last_updated',
] ]

View File

@ -652,6 +652,12 @@ class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
choices=InterfaceTypeChoices, choices=InterfaceTypeChoices,
null_value=None null_value=None
) )
poe_mode = django_filters.MultipleChoiceFilter(
choices=InterfacePoEModeChoices
)
poe_type = django_filters.MultipleChoiceFilter(
choices=InterfacePoETypeChoices
)
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate

View File

@ -818,8 +818,22 @@ class InterfaceTemplateBulkEditForm(BulkEditForm):
description = forms.CharField( description = forms.CharField(
required=False required=False
) )
poe_mode = forms.ChoiceField(
choices=add_blank_choice(InterfacePoEModeChoices),
required=False,
initial='',
widget=StaticSelect(),
label='PoE mode'
)
poe_type = forms.ChoiceField(
choices=add_blank_choice(InterfacePoETypeChoices),
required=False,
initial='',
widget=StaticSelect(),
label='PoE type'
)
nullable_fields = ('label', 'description') nullable_fields = ('label', 'description', 'poe_mode', 'poe_type')
class FrontPortTemplateBulkEditForm(BulkEditForm): class FrontPortTemplateBulkEditForm(BulkEditForm):

View File

@ -1027,11 +1027,13 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
) )
poe_mode = MultipleChoiceField( poe_mode = MultipleChoiceField(
choices=InterfacePoEModeChoices, choices=InterfacePoEModeChoices,
required=False required=False,
label='PoE mode'
) )
poe_type = MultipleChoiceField( poe_type = MultipleChoiceField(
choices=InterfacePoEModeChoices, choices=InterfacePoEModeChoices,
required=False required=False,
label='PoE type'
) )
rf_role = MultipleChoiceField( rf_role = MultipleChoiceField(
choices=WirelessRoleChoices, choices=WirelessRoleChoices,

View File

@ -1052,12 +1052,14 @@ class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(), 'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(), 'module_type': forms.HiddenInput(),
'type': StaticSelect(), 'type': StaticSelect(),
'poe_mode': StaticSelect(),
'poe_type': StaticSelect(),
} }

View File

@ -1,6 +1,6 @@
from django import forms from django import forms
from dcim.choices import InterfaceTypeChoices, PortTypeChoices from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
from dcim.models import * from dcim.models import *
from utilities.forms import BootstrapMixin from utilities.forms import BootstrapMixin
@ -112,11 +112,21 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
type = forms.ChoiceField( type = forms.ChoiceField(
choices=InterfaceTypeChoices.CHOICES choices=InterfaceTypeChoices.CHOICES
) )
poe_mode = forms.ChoiceField(
choices=InterfacePoEModeChoices,
required=False,
label='PoE mode'
)
poe_type = forms.ChoiceField(
choices=InterfacePoETypeChoices,
required=False,
label='PoE type'
)
class Meta: class Meta:
model = InterfaceTemplate model = InterfaceTemplate
fields = [ fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
] ]

View File

@ -258,6 +258,12 @@ class InterfaceTemplateType(ComponentTemplateObjectType):
fields = '__all__' fields = '__all__'
filterset_class = filtersets.InterfaceTemplateFilterSet filterset_class = filtersets.InterfaceTemplateFilterSet
def resolve_poe_mode(self, info):
return self.poe_mode or None
def resolve_poe_type(self, info):
return self.poe_type or None
class InventoryItemType(ComponentObjectType): class InventoryItemType(ComponentObjectType):

View File

@ -20,4 +20,14 @@ class Migration(migrations.Migration):
name='poe_type', name='poe_type',
field=models.CharField(blank=True, max_length=50), field=models.CharField(blank=True, max_length=50),
), ),
migrations.AddField(
model_name='interfacetemplate',
name='poe_mode',
field=models.CharField(blank=True, max_length=50),
),
migrations.AddField(
model_name='interfacetemplate',
name='poe_type',
field=models.CharField(blank=True, max_length=50),
),
] ]

View File

@ -1,6 +1,6 @@
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
@ -318,6 +318,18 @@ class InterfaceTemplate(ModularComponentTemplateModel):
default=False, default=False,
verbose_name='Management only' verbose_name='Management only'
) )
poe_mode = models.CharField(
max_length=50,
choices=InterfacePoEModeChoices,
blank=True,
verbose_name='PoE mode'
)
poe_type = models.CharField(
max_length=50,
choices=InterfacePoETypeChoices,
blank=True,
verbose_name='PoE type'
)
component_model = Interface component_model = Interface
@ -334,6 +346,8 @@ class InterfaceTemplate(ModularComponentTemplateModel):
label=self.resolve_label(kwargs.get('module')), label=self.resolve_label(kwargs.get('module')),
type=self.type, type=self.type,
mgmt_only=self.mgmt_only, mgmt_only=self.mgmt_only,
poe_mode=self.poe_mode,
poe_type=self.poe_type,
**kwargs **kwargs
) )

View File

@ -229,6 +229,8 @@ class DeviceType(NetBoxModel):
'mgmt_only': c.mgmt_only, 'mgmt_only': c.mgmt_only,
'label': c.label, 'label': c.label,
'description': c.description, 'description': c.description,
'poe_mode': c.poe_mode,
'poe_type': c.poe_type,
} }
for c in self.interfacetemplates.all() for c in self.interfacetemplates.all()
] ]

View File

@ -172,7 +172,7 @@ class InterfaceTemplateTable(ComponentTemplateTable):
class Meta(ComponentTemplateTable.Meta): class Meta(ComponentTemplateTable.Meta):
model = InterfaceTemplate model = InterfaceTemplate
fields = ('pk', 'name', 'label', 'mgmt_only', 'type', 'description', 'actions') fields = ('pk', 'name', 'label', 'mgmt_only', 'type', 'description', 'poe_mode', 'poe_type', 'actions')
empty_text = "None" empty_text = "None"

View File

@ -1089,8 +1089,8 @@ class InterfaceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
DeviceType.objects.bulk_create(device_types) DeviceType.objects.bulk_create(device_types)
InterfaceTemplate.objects.bulk_create(( InterfaceTemplate.objects.bulk_create((
InterfaceTemplate(device_type=device_types[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED, mgmt_only=True), InterfaceTemplate(device_type=device_types[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED, mgmt_only=True, poe_mode=InterfacePoEModeChoices.MODE_PD, poe_type=InterfacePoETypeChoices.TYPE_1_8023AF),
InterfaceTemplate(device_type=device_types[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_GBIC, mgmt_only=False), InterfaceTemplate(device_type=device_types[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_GBIC, mgmt_only=False, poe_mode=InterfacePoEModeChoices.MODE_PSE, poe_type=InterfacePoETypeChoices.TYPE_2_8023AT),
InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_SFP, mgmt_only=False), InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_SFP, mgmt_only=False),
)) ))
@ -1113,6 +1113,14 @@ class InterfaceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'mgmt_only': 'false'} params = {'mgmt_only': 'false'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_poe_mode(self):
params = {'poe_mode': [InterfacePoEModeChoices.MODE_PD, InterfacePoEModeChoices.MODE_PSE]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_poe_type(self):
params = {'poe_type': [InterfacePoETypeChoices.TYPE_1_8023AF, InterfacePoETypeChoices.TYPE_2_8023AT]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class FrontPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests): class FrontPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = FrontPortTemplate.objects.all() queryset = FrontPortTemplate.objects.all()