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
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
* [#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
* [#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`
* Added the optional `poe_mode` and `poe_type` fields
* Added the `l2vpn_termination` read-only field
* dcim.InterfaceTemplate
* Added the optional `poe_mode` and `poe_type` fields
* dcim.Location
* Added required `status` field (default value: `active`)
* dcim.PowerOutlet

View File

@ -469,12 +469,22 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
default=None
)
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:
model = InterfaceTemplate
fields = [
'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,
null_value=None
)
poe_mode = django_filters.MultipleChoiceFilter(
choices=InterfacePoEModeChoices
)
poe_type = django_filters.MultipleChoiceFilter(
choices=InterfacePoETypeChoices
)
class Meta:
model = InterfaceTemplate

View File

@ -818,8 +818,22 @@ class InterfaceTemplateBulkEditForm(BulkEditForm):
description = forms.CharField(
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):

View File

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

View File

@ -1052,12 +1052,14 @@ class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = InterfaceTemplate
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 = {
'device_type': forms.HiddenInput(),
'module_type': forms.HiddenInput(),
'type': StaticSelect(),
'poe_mode': StaticSelect(),
'poe_type': StaticSelect(),
}

View File

@ -1,6 +1,6 @@
from django import forms
from dcim.choices import InterfaceTypeChoices, PortTypeChoices
from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
from dcim.models import *
from utilities.forms import BootstrapMixin
@ -112,11 +112,21 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
type = forms.ChoiceField(
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:
model = InterfaceTemplate
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__'
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):

View File

@ -20,4 +20,14 @@ class Migration(migrations.Migration):
name='poe_type',
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.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.db import models
from mptt.models import MPTTModel, TreeForeignKey
@ -318,6 +318,18 @@ class InterfaceTemplate(ModularComponentTemplateModel):
default=False,
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
@ -334,6 +346,8 @@ class InterfaceTemplate(ModularComponentTemplateModel):
label=self.resolve_label(kwargs.get('module')),
type=self.type,
mgmt_only=self.mgmt_only,
poe_mode=self.poe_mode,
poe_type=self.poe_type,
**kwargs
)

View File

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

View File

@ -172,7 +172,7 @@ class InterfaceTemplateTable(ComponentTemplateTable):
class Meta(ComponentTemplateTable.Meta):
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"

View File

@ -1089,8 +1089,8 @@ class InterfaceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
DeviceType.objects.bulk_create(device_types)
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[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_GBIC, mgmt_only=False),
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, 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),
))
@ -1113,6 +1113,14 @@ class InterfaceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'mgmt_only': 'false'}
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):
queryset = FrontPortTemplate.objects.all()