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

Closes #1865: Add console port and console server port types

Jeremy Stretch пре 6 година
родитељ
комит
bb30f64252

+ 6 - 1
docs/release-notes/version-2.7.md

@@ -85,11 +85,16 @@ Full connection details are required in both sections, even if they are the same
 
 ## Enhancements
 
+* [#1865](https://github.com/digitalocean/netbox/issues/1865) - Add console port and console server port types
 * [#2902](https://github.com/digitalocean/netbox/issues/2902) - Replace supervisord with systemd
 * [#3455](https://github.com/digitalocean/netbox/issues/3455) - Add tenant assignment to cluster
 * [#3538](https://github.com/digitalocean/netbox/issues/3538) - 
 
 ## API Changes
 
-* Introduced `/api/extras/scripts/` endpoint for retreiving and executing custom scripts
+* Introduced `/api/extras/scripts/` endpoint for retrieving and executing custom scripts
+* dcim.ConsolePort: Added field `type`
+* dcim.ConsolePortTemplate: Added field `type`
+* dcim.ConsoleServerPort: Added field `type`
+* dcim.ConsoleServerPortTemplate: Added field `type`
 * virtualization.Cluster: Added field `tenant`

+ 10 - 6
netbox/dcim/api/serializers.py

@@ -200,18 +200,20 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
 
 class ConsolePortTemplateSerializer(ValidatedModelSerializer):
     device_type = NestedDeviceTypeSerializer()
+    type = ChoiceField(choices=CONSOLE_TYPE_CHOICES, required=False)
 
     class Meta:
         model = ConsolePortTemplate
-        fields = ['id', 'device_type', 'name']
+        fields = ['id', 'device_type', 'name', 'type']
 
 
 class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
     device_type = NestedDeviceTypeSerializer()
+    type = ChoiceField(choices=CONSOLE_TYPE_CHOICES, required=False)
 
     class Meta:
         model = ConsoleServerPortTemplate
-        fields = ['id', 'device_type', 'name']
+        fields = ['id', 'device_type', 'name', 'type']
 
 
 class PowerPortTemplateSerializer(ValidatedModelSerializer):
@@ -370,27 +372,29 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
 
 class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
     device = NestedDeviceSerializer()
+    type = ChoiceField(choices=CONSOLE_TYPE_CHOICES, required=False)
     cable = NestedCableSerializer(read_only=True)
     tags = TagListSerializerField(required=False)
 
     class Meta:
         model = ConsoleServerPort
         fields = [
-            'id', 'device', 'name', 'description', 'connected_endpoint_type', 'connected_endpoint', 'connection_status',
-            'cable', 'tags',
+            'id', 'device', 'name', 'type', 'description', 'connected_endpoint_type', 'connected_endpoint',
+            'connection_status', 'cable', 'tags',
         ]
 
 
 class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
     device = NestedDeviceSerializer()
+    type = ChoiceField(choices=CONSOLE_TYPE_CHOICES, required=False)
     cable = NestedCableSerializer(read_only=True)
     tags = TagListSerializerField(required=False)
 
     class Meta:
         model = ConsolePort
         fields = [
-            'id', 'device', 'name', 'description', 'connected_endpoint_type', 'connected_endpoint', 'connection_status',
-            'cable', 'tags',
+            'id', 'device', 'name', 'type', 'description', 'connected_endpoint_type', 'connected_endpoint',
+            'connection_status', 'cable', 'tags',
         ]
 
 

+ 4 - 1
netbox/dcim/api/views.py

@@ -42,7 +42,10 @@ from .exceptions import MissingFilterException
 class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
     fields = (
         (Cable, ['length_unit', 'status', 'termination_a_type', 'termination_b_type', 'type']),
-        (ConsolePort, ['connection_status']),
+        (ConsolePort, ['type', 'connection_status']),
+        (ConsolePortTemplate, ['type']),
+        (ConsoleServerPort, ['type']),
+        (ConsoleServerPortTemplate, ['type']),
         (Device, ['face', 'status']),
         (DeviceType, ['subdevice_role']),
         (FrontPort, ['type']),

+ 96 - 0
netbox/dcim/constants.py

@@ -57,6 +57,41 @@ SUBDEVICE_ROLE_CHOICES = (
     (SUBDEVICE_ROLE_CHILD, 'Child'),
 )
 
+#
+# Numeric console port types
+#
+
+CONSOLE_TYPE_DE9 = 1000
+CONSOLE_TYPE_DB25 = 1100
+CONSOLE_TYPE_RJ45 = 2000
+CONSOLE_TYPE_USB_A = 3000
+CONSOLE_TYPE_USB_B = 3010
+CONSOLE_TYPE_USB_C = 3020
+CONSOLE_TYPE_USB_MINI_A = 3100
+CONSOLE_TYPE_USB_MINI_B = 3110
+CONSOLE_TYPE_USB_MICRO_A = 3200
+CONSOLE_TYPE_USB_MICRO_B = 3210
+CONSOLE_TYPE_OTHER = 32767
+CONSOLE_TYPE_CHOICES = [
+    ['Serial', [
+        [CONSOLE_TYPE_DE9, 'DE-9'],
+        [CONSOLE_TYPE_DB25, 'DB-25'],
+        [CONSOLE_TYPE_RJ45, 'RJ-45'],
+    ]],
+    ['USB', [
+        [CONSOLE_TYPE_USB_A, 'USB Type A'],
+        [CONSOLE_TYPE_USB_B, 'USB Type B'],
+        [CONSOLE_TYPE_USB_C, 'USB Type C'],
+        [CONSOLE_TYPE_USB_MINI_A, 'USB Mini A'],
+        [CONSOLE_TYPE_USB_MINI_B, 'USB Mini B'],
+        [CONSOLE_TYPE_USB_MICRO_A, 'USB Micro A'],
+        [CONSOLE_TYPE_USB_MICRO_B, 'USB Micro B'],
+    ]],
+    ['Other', [
+        [CONSOLE_TYPE_OTHER, 'Other'],
+    ]],
+]
+
 #
 # Numeric interface types
 #
@@ -515,6 +550,67 @@ POWERFEED_LEG_CHOICES = (
 )
 
 
+#
+# Console port type values
+#
+
+class ConsolePortTypes:
+    """
+    ConsolePort/ConsoleServerPort.type slugs
+    """
+    TYPE_DE9 = 'de-9'
+    TYPE_DB25 = 'db-25'
+    TYPE_RJ45 = 'rj-45'
+    TYPE_USB_A = 'usb-a'
+    TYPE_USB_B = 'usb-b'
+    TYPE_USB_C = 'usb-c'
+    TYPE_USB_MINI_A = 'usb-mini-a'
+    TYPE_USB_MINI_B = 'usb-mini-b'
+    TYPE_USB_MICRO_A = 'usb-micro-a'
+    TYPE_USB_MICRO_B = 'usb-micro-b'
+    TYPE_OTHER = 'other'
+
+    TYPE_CHOICES = (
+        ('Serial', (
+            (TYPE_DE9, 'DE-9'),
+            (TYPE_DB25, 'DB-25'),
+            (TYPE_RJ45, 'RJ-45'),
+        )),
+        ('USB', (
+            (TYPE_USB_A, 'USB Type A'),
+            (TYPE_USB_B, 'USB Type B'),
+            (TYPE_USB_C, 'USB Type C'),
+            (TYPE_USB_MINI_A, 'USB Mini A'),
+            (TYPE_USB_MINI_B, 'USB Mini B'),
+            (TYPE_USB_MICRO_A, 'USB Micro A'),
+            (TYPE_USB_MICRO_B, 'USB Micro B'),
+        )),
+        ('Other', (
+            (TYPE_OTHER, 'Other'),
+        )),
+    )
+
+    @classmethod
+    def slug_to_integer(cls, slug):
+        """
+        Provide backward-compatible mapping of the type slug to integer.
+        """
+        return {
+            # Slug: integer
+            cls.TYPE_DE9: CONSOLE_TYPE_DE9,
+            cls.TYPE_DB25: CONSOLE_TYPE_DB25,
+            cls.TYPE_RJ45: CONSOLE_TYPE_RJ45,
+            cls.TYPE_USB_A: CONSOLE_TYPE_USB_A,
+            cls.TYPE_USB_B: CONSOLE_TYPE_USB_B,
+            cls.TYPE_USB_C: CONSOLE_TYPE_USB_C,
+            cls.TYPE_USB_MINI_A: CONSOLE_TYPE_USB_MINI_A,
+            cls.TYPE_USB_MINI_B: CONSOLE_TYPE_USB_MINI_B,
+            cls.TYPE_USB_MICRO_A: CONSOLE_TYPE_USB_MICRO_A,
+            cls.TYPE_USB_MICRO_B: CONSOLE_TYPE_USB_MICRO_B,
+            cls.TYPE_OTHER: CONSOLE_TYPE_OTHER,
+        }.get(slug)
+
+
 #
 # Interface type values
 #

+ 12 - 4
netbox/dcim/filters.py

@@ -346,14 +346,14 @@ class ConsolePortTemplateFilter(DeviceTypeComponentFilterSet):
 
     class Meta:
         model = ConsolePortTemplate
-        fields = ['id', 'name']
+        fields = ['id', 'name', 'type']
 
 
 class ConsoleServerPortTemplateFilter(DeviceTypeComponentFilterSet):
 
     class Meta:
         model = ConsoleServerPortTemplate
-        fields = ['id', 'name']
+        fields = ['id', 'name', 'type']
 
 
 class PowerPortTemplateFilter(DeviceTypeComponentFilterSet):
@@ -641,6 +641,10 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
 
 
 class ConsolePortFilter(DeviceComponentFilterSet):
+    type = django_filters.MultipleChoiceFilter(
+        choices=CONSOLE_TYPE_CHOICES,
+        null_value=None
+    )
     cabled = django_filters.BooleanFilter(
         field_name='cable',
         lookup_expr='isnull',
@@ -649,10 +653,14 @@ class ConsolePortFilter(DeviceComponentFilterSet):
 
     class Meta:
         model = ConsolePort
-        fields = ['id', 'name', 'description', 'connection_status']
+        fields = ['id', 'name', 'type', 'description', 'connection_status']
 
 
 class ConsoleServerPortFilter(DeviceComponentFilterSet):
+    type = django_filters.MultipleChoiceFilter(
+        choices=CONSOLE_TYPE_CHOICES,
+        null_value=None
+    )
     cabled = django_filters.BooleanFilter(
         field_name='cable',
         lookup_expr='isnull',
@@ -661,7 +669,7 @@ class ConsoleServerPortFilter(DeviceComponentFilterSet):
 
     class Meta:
         model = ConsoleServerPort
-        fields = ['id', 'name', 'description', 'connection_status']
+        fields = ['id', 'name', 'type', 'description', 'connection_status']
 
 
 class PowerPortFilter(DeviceComponentFilterSet):

+ 43 - 6
netbox/dcim/forms.py

@@ -941,7 +941,7 @@ class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
     class Meta:
         model = ConsolePortTemplate
         fields = [
-            'device_type', 'name',
+            'device_type', 'name', 'type',
         ]
         widgets = {
             'device_type': forms.HiddenInput(),
@@ -952,6 +952,10 @@ class ConsolePortTemplateCreateForm(ComponentForm):
     name_pattern = ExpandableNameField(
         label='Name'
     )
+    type = forms.ChoiceField(
+        choices=CONSOLE_TYPE_CHOICES,
+        widget=StaticSelect2()
+    )
 
 
 class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
@@ -959,7 +963,7 @@ class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
     class Meta:
         model = ConsoleServerPortTemplate
         fields = [
-            'device_type', 'name',
+            'device_type', 'name', 'type',
         ]
         widgets = {
             'device_type': forms.HiddenInput(),
@@ -970,6 +974,10 @@ class ConsoleServerPortTemplateCreateForm(ComponentForm):
     name_pattern = ExpandableNameField(
         label='Name'
     )
+    type = forms.ChoiceField(
+        choices=CONSOLE_TYPE_CHOICES,
+        widget=StaticSelect2()
+    )
 
 
 class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
@@ -1248,22 +1256,38 @@ class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm):
 
 
 class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
+    type = forms.ChoiceField(
+        choices=ConsolePortTypes.TYPE_CHOICES
+    )
 
     class Meta:
         model = ConsolePortTemplate
         fields = [
-            'device_type', 'name',
+            'device_type', 'name', 'type',
         ]
 
+    def clean_type(self):
+        # Convert slug value to field integer value
+        slug = self.cleaned_data['type']
+        return ConsolePortTypes.slug_to_integer(slug)
+
 
 class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
+    type = forms.ChoiceField(
+        choices=ConsolePortTypes.TYPE_CHOICES
+    )
 
     class Meta:
         model = ConsoleServerPortTemplate
         fields = [
-            'device_type', 'name',
+            'device_type', 'name', 'type',
         ]
 
+    def clean_type(self):
+        # Convert slug value to field integer value
+        slug = self.cleaned_data['type']
+        return ConsolePortTypes.slug_to_integer(slug)
+
 
 class PowerPortTemplateImportForm(ComponentTemplateImportForm):
 
@@ -2054,7 +2078,7 @@ class ConsolePortForm(BootstrapMixin, forms.ModelForm):
     class Meta:
         model = ConsolePort
         fields = [
-            'device', 'name', 'description', 'tags',
+            'device', 'name', 'type', 'description', 'tags',
         ]
         widgets = {
             'device': forms.HiddenInput(),
@@ -2065,6 +2089,10 @@ class ConsolePortCreateForm(ComponentForm):
     name_pattern = ExpandableNameField(
         label='Name'
     )
+    type = forms.ChoiceField(
+        choices=CONSOLE_TYPE_CHOICES,
+        widget=StaticSelect2(),
+    )
     description = forms.CharField(
         max_length=100,
         required=False
@@ -2086,7 +2114,7 @@ class ConsoleServerPortForm(BootstrapMixin, forms.ModelForm):
     class Meta:
         model = ConsoleServerPort
         fields = [
-            'device', 'name', 'description', 'tags',
+            'device', 'name', 'type', 'description', 'tags',
         ]
         widgets = {
             'device': forms.HiddenInput(),
@@ -2097,6 +2125,10 @@ class ConsoleServerPortCreateForm(ComponentForm):
     name_pattern = ExpandableNameField(
         label='Name'
     )
+    type = forms.ChoiceField(
+        choices=CONSOLE_TYPE_CHOICES,
+        widget=StaticSelect2(),
+    )
     description = forms.CharField(
         max_length=100,
         required=False
@@ -2111,6 +2143,11 @@ class ConsoleServerPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditF
         queryset=ConsoleServerPort.objects.all(),
         widget=forms.MultipleHiddenInput()
     )
+    type = forms.ChoiceField(
+        choices=add_blank_choice(CONSOLE_TYPE_CHOICES),
+        required=False,
+        widget=StaticSelect2()
+    )
     description = forms.CharField(
         max_length=100,
         required=False

+ 33 - 0
netbox/dcim/migrations/0076_console_port_types.py

@@ -0,0 +1,33 @@
+# Generated by Django 2.2.6 on 2019-10-30 17:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0075_cable_devices'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='consoleport',
+            name='type',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='consoleporttemplate',
+            name='type',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='consoleserverport',
+            name='type',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='consoleserverporttemplate',
+            name='type',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+    ]

+ 28 - 4
netbox/dcim/models.py

@@ -1014,6 +1014,11 @@ class ConsolePortTemplate(ComponentTemplateModel):
     name = models.CharField(
         max_length=50
     )
+    type = models.PositiveSmallIntegerField(
+        choices=CONSOLE_TYPE_CHOICES,
+        blank=True,
+        null=True
+    )
 
     objects = NaturalOrderingManager()
 
@@ -1027,7 +1032,8 @@ class ConsolePortTemplate(ComponentTemplateModel):
     def instantiate(self, device):
         return ConsolePort(
             device=device,
-            name=self.name
+            name=self.name,
+            type=self.type
         )
 
 
@@ -1043,6 +1049,11 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
     name = models.CharField(
         max_length=50
     )
+    type = models.PositiveSmallIntegerField(
+        choices=CONSOLE_TYPE_CHOICES,
+        blank=True,
+        null=True
+    )
 
     objects = NaturalOrderingManager()
 
@@ -1056,7 +1067,8 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
     def instantiate(self, device):
         return ConsoleServerPort(
             device=device,
-            name=self.name
+            name=self.name,
+            type=self.type
         )
 
 
@@ -1846,6 +1858,11 @@ class ConsolePort(CableTermination, ComponentModel):
     name = models.CharField(
         max_length=50
     )
+    type = models.PositiveSmallIntegerField(
+        choices=CONSOLE_TYPE_CHOICES,
+        blank=True,
+        null=True
+    )
     connected_endpoint = models.OneToOneField(
         to='dcim.ConsoleServerPort',
         on_delete=models.SET_NULL,
@@ -1861,7 +1878,7 @@ class ConsolePort(CableTermination, ComponentModel):
     objects = NaturalOrderingManager()
     tags = TaggableManager(through=TaggedItem)
 
-    csv_headers = ['device', 'name', 'description']
+    csv_headers = ['device', 'name', 'type', 'description']
 
     class Meta:
         ordering = ['device', 'name']
@@ -1877,6 +1894,7 @@ class ConsolePort(CableTermination, ComponentModel):
         return (
             self.device.identifier,
             self.name,
+            self.type,
             self.description,
         )
 
@@ -1897,6 +1915,11 @@ class ConsoleServerPort(CableTermination, ComponentModel):
     name = models.CharField(
         max_length=50
     )
+    type = models.PositiveSmallIntegerField(
+        choices=CONSOLE_TYPE_CHOICES,
+        blank=True,
+        null=True
+    )
     connection_status = models.NullBooleanField(
         choices=CONNECTION_STATUS_CHOICES,
         blank=True
@@ -1905,7 +1928,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
     objects = NaturalOrderingManager()
     tags = TaggableManager(through=TaggedItem)
 
-    csv_headers = ['device', 'name', 'description']
+    csv_headers = ['device', 'name', 'type', 'description']
 
     class Meta:
         unique_together = ['device', 'name']
@@ -1920,6 +1943,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
         return (
             self.device.identifier,
             self.name,
+            self.type,
             self.description,
         )
 

+ 2 - 2
netbox/dcim/tables.py

@@ -422,7 +422,7 @@ class ConsolePortTemplateTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = ConsolePortTemplate
-        fields = ('pk', 'name', 'actions')
+        fields = ('pk', 'name', 'type', 'actions')
         empty_text = "None"
 
 
@@ -647,7 +647,7 @@ class ConsolePortTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = ConsolePort
-        fields = ('name',)
+        fields = ('name', 'type')
 
 
 class ConsoleServerPortTable(BaseTable):

+ 8 - 0
netbox/dcim/tests/test_views.py

@@ -231,12 +231,18 @@ slug: test-1000
 u_height: 2
 console-ports:
   - name: Console Port 1
+    type: de-9
   - name: Console Port 2
+    type: de-9
   - name: Console Port 3
+    type: de-9
 console-server-ports:
   - name: Console Server Port 1
+    type: rj-45
   - name: Console Server Port 2
+    type: rj-45
   - name: Console Server Port 3
+    type: rj-45
 power-ports:
   - name: Power Port 1
   - name: Power Port 2
@@ -313,10 +319,12 @@ device-bays:
         self.assertEqual(dt.consoleport_templates.count(), 3)
         cp1 = ConsolePortTemplate.objects.first()
         self.assertEqual(cp1.name, 'Console Port 1')
+        self.assertEqual(cp1.type, CONSOLE_TYPE_DE9)
 
         self.assertEqual(dt.consoleserverport_templates.count(), 3)
         csp1 = ConsoleServerPortTemplate.objects.first()
         self.assertEqual(csp1.name, 'Console Server Port 1')
+        self.assertEqual(csp1.type, CONSOLE_TYPE_RJ45)
 
         self.assertEqual(dt.powerport_templates.count(), 3)
         pp1 = PowerPortTemplate.objects.first()

+ 1 - 0
netbox/templates/dcim/device.html

@@ -628,6 +628,7 @@
                                     <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
                                 {% endif %}
                                 <th>Name</th>
+                                <th>Type</th>
                                 <th>Description</th>
                                 <th>Cable</th>
                                 <th colspan="2">Connection</th>

+ 5 - 0
netbox/templates/dcim/inc/consoleport.html

@@ -6,6 +6,11 @@
     </td>
     <td></td>
 
+    {# Type #}
+    <td>
+        {% if cp.type %}{{ cp.get_type_display }}{% else %}&mdash;{% endif %}
+    </td>
+
     {# Description #}
     <td>
         {{ cp.description }}

+ 5 - 0
netbox/templates/dcim/inc/consoleserverport.html

@@ -14,6 +14,11 @@
         <i class="fa fa-fw fa-keyboard-o"></i> {{ csp }}
     </td>
 
+    {# Type #}
+    <td>
+        {% if csp.type %}{{ csp.get_type_display }}{% else %}&mdash;{% endif %}
+    </td>
+
     {# Description #}
     <td>
         {{ csp.description|placeholder }}