Преглед изворни кода

Closes #9793: Add PoE attributes to interface templates

jeremystretch пре 3 година
родитељ
комит
2c43c8d077

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

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

+ 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
 * [#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

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

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

+ 6 - 0
netbox/dcim/filtersets.py

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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