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:
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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,
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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()
|
||||||
]
|
]
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
Reference in New Issue
Block a user