소스 검색

Closes #9793: Add PoE attributes to interface templates

jeremystretch 3 년 전
부모
커밋
2c43c8d077

+ 1 - 1
docs/models/dcim/interfacetemplate.md

@@ -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.

+ 3 - 1
docs/release-notes/version-3.3.md

@@ -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

+ 11 - 1
netbox/dcim/api/serializers.py

@@ -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',
         ]
         ]
 
 
 
 

+ 6 - 0
netbox/dcim/filtersets.py

@@ -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

+ 15 - 1
netbox/dcim/forms/bulk_edit.py

@@ -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):

+ 4 - 2
netbox/dcim/forms/filtersets.py

@@ -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,

+ 3 - 1
netbox/dcim/forms/models.py

@@ -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(),
         }
         }
 
 
 
 

+ 12 - 2
netbox/dcim/forms/object_import.py

@@ -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',
         ]
         ]
 
 
 
 

+ 6 - 0
netbox/dcim/graphql/types.py

@@ -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):
 
 

+ 10 - 0
netbox/dcim/migrations/0155_interface_poe_mode_type.py

@@ -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),
+        ),
     ]
     ]

+ 15 - 1
netbox/dcim/models/device_component_templates.py

@@ -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
         )
         )
 
 

+ 2 - 0
netbox/dcim/models/devices.py

@@ -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()
             ]
             ]

+ 1 - 1
netbox/dcim/tables/devicetypes.py

@@ -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"
 
 
 
 

+ 10 - 2
netbox/dcim/tests/test_filtersets.py

@@ -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[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),
             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()