From 8de20fcd1fb8e9a775f643d45285a48edd07cedf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 3 Mar 2021 10:20:08 -0500 Subject: [PATCH] Closes #5375: Add 'speed' attribute to console port models --- docs/release-notes/version-2.11.md | 1 + netbox/dcim/api/serializers.py | 22 +++++-- netbox/dcim/choices.py | 23 ++++++++ netbox/dcim/forms.py | 59 +++++++++++++++---- .../migrations/0125_console_port_speed.py | 21 +++++++ netbox/dcim/models/device_components.py | 18 +++++- netbox/dcim/tables/devices.py | 20 +++---- netbox/templates/dcim/consoleport.html | 6 +- netbox/templates/dcim/consoleserverport.html | 6 +- netbox/utilities/forms/fields.py | 5 ++ 10 files changed, 149 insertions(+), 32 deletions(-) create mode 100644 netbox/dcim/migrations/0125_console_port_speed.py diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index 82a68e06d..742ef69da 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -15,6 +15,7 @@ In addition to the new `mark_connected` boolean field, the REST API representati ### Enhancements * [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models +* [#5375](https://github.com/netbox-community/netbox/issues/5375) - Add `speed` attribute to console port models * [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models * [#5451](https://github.com/netbox-community/netbox/issues/5451) - Add support for multiple-selection custom fields * [#5894](https://github.com/netbox-community/netbox/issues/5894) - Use primary keys when filtering object lists by related objects in the UI diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index b5672c1a8..f6067f530 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -504,14 +504,19 @@ class ConsoleServerPortSerializer(TaggedObjectSerializer, CableTerminationSerial allow_blank=True, required=False ) + speed = ChoiceField( + choices=ConsolePortSpeedChoices, + allow_blank=True, + required=False + ) cable = NestedCableSerializer(read_only=True) class Meta: model = ConsoleServerPort fields = [ - 'id', 'url', 'device', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', - 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', - 'custom_fields', 'created', 'last_updated', '_occupied', + 'id', 'url', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', + 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', + 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] @@ -523,14 +528,19 @@ class ConsolePortSerializer(TaggedObjectSerializer, CableTerminationSerializer, allow_blank=True, required=False ) + speed = ChoiceField( + choices=ConsolePortSpeedChoices, + allow_blank=True, + required=False + ) cable = NestedCableSerializer(read_only=True) class Meta: model = ConsolePort fields = [ - 'id', 'url', 'device', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', - 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', - 'custom_fields', 'created', 'last_updated', '_occupied', + 'id', 'url', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', + 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', + 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index d4cc0ecf4..ce013acc3 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -217,6 +217,29 @@ class ConsolePortTypeChoices(ChoiceSet): ) +class ConsolePortSpeedChoices(ChoiceSet): + + SPEED_1200 = 1200 + SPEED_2400 = 2400 + SPEED_4800 = 4800 + SPEED_9600 = 9600 + SPEED_19200 = 19200 + SPEED_38400 = 38400 + SPEED_57600 = 57600 + SPEED_115200 = 115200 + + CHOICES = ( + (SPEED_1200, '1200 bps'), + (SPEED_2400, '2400 bps'), + (SPEED_4800, '4800 bps'), + (SPEED_9600, '9600 bps'), + (SPEED_19200, '19.2 kbps'), + (SPEED_38400, '38.4 kbps'), + (SPEED_57600, '57.6 kbps'), + (SPEED_115200, '115.2 kbps'), + ) + + # # PowerPorts # diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index fe050b99d..65020bb1c 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -23,9 +23,10 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, - ColorSelect, CommentField, CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, DynamicModelChoiceField, - DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, NumericArrayField, SelectWithPK, - SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, + ColorSelect, CommentField, CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, + DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, + NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, + BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import Cluster, ClusterGroup from .choices import * @@ -2312,6 +2313,11 @@ class ConsolePortFilterForm(DeviceComponentFilterForm): required=False, widget=StaticSelect2Multiple() ) + speed = forms.MultipleChoiceField( + choices=ConsolePortSpeedChoices, + required=False, + widget=StaticSelect2Multiple() + ) tag = TagFilterField(model) @@ -2324,7 +2330,7 @@ class ConsolePortForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = ConsolePort fields = [ - 'device', 'name', 'label', 'type', 'mark_connected', 'description', 'tags', + 'device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags', ] widgets = { 'device': forms.HiddenInput(), @@ -2338,11 +2344,16 @@ class ConsolePortCreateForm(ComponentCreateForm): required=False, widget=StaticSelect2() ) - field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'mark_connected', 'description', 'tags') + speed = forms.ChoiceField( + choices=add_blank_choice(ConsolePortSpeedChoices), + required=False, + widget=StaticSelect2() + ) + field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'speed', 'mark_connected', 'description', 'tags') class ConsolePortBulkCreateForm( - form_from_model(ConsolePort, ['type', 'mark_connected']), + form_from_model(ConsolePort, ['type', 'speed', 'mark_connected']), DeviceBulkAddComponentForm ): model = ConsolePort @@ -2350,7 +2361,7 @@ class ConsolePortBulkCreateForm( class ConsolePortBulkEditForm( - form_from_model(ConsolePort, ['label', 'type', 'mark_connected', 'description']), + form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']), BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm @@ -2374,6 +2385,13 @@ class ConsolePortCSVForm(CustomFieldModelCSVForm): required=False, help_text='Port type' ) + speed = CSVTypedChoiceField( + choices=ConsolePortSpeedChoices, + coerce=int, + empty_value=None, + required=False, + help_text='Port speed in bps' + ) class Meta: model = ConsolePort @@ -2392,6 +2410,11 @@ class ConsoleServerPortFilterForm(DeviceComponentFilterForm): required=False, widget=StaticSelect2Multiple() ) + speed = forms.MultipleChoiceField( + choices=ConsolePortSpeedChoices, + required=False, + widget=StaticSelect2Multiple() + ) tag = TagFilterField(model) @@ -2404,7 +2427,7 @@ class ConsoleServerPortForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = ConsoleServerPort fields = [ - 'device', 'name', 'label', 'type', 'description', 'tags', + 'device', 'name', 'label', 'type', 'speed', 'description', 'tags', ] widgets = { 'device': forms.HiddenInput(), @@ -2418,19 +2441,24 @@ class ConsoleServerPortCreateForm(ComponentCreateForm): required=False, widget=StaticSelect2() ) - field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'description', 'tags') + speed = forms.ChoiceField( + choices=add_blank_choice(ConsolePortSpeedChoices), + required=False, + widget=StaticSelect2() + ) + field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'speed', 'description', 'tags') class ConsoleServerPortBulkCreateForm( - form_from_model(ConsoleServerPort, ['type']), + form_from_model(ConsoleServerPort, ['type', 'speed']), DeviceBulkAddComponentForm ): model = ConsoleServerPort - field_order = ('name_pattern', 'label_pattern', 'type', 'description', 'tags') + field_order = ('name_pattern', 'label_pattern', 'type', 'speed', 'description', 'tags') class ConsoleServerPortBulkEditForm( - form_from_model(ConsoleServerPort, ['label', 'type', 'description']), + form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'description']), BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm @@ -2454,6 +2482,13 @@ class ConsoleServerPortCSVForm(CustomFieldModelCSVForm): required=False, help_text='Port type' ) + speed = CSVTypedChoiceField( + choices=ConsolePortSpeedChoices, + coerce=int, + empty_value=None, + required=False, + help_text='Port speed in bps' + ) class Meta: model = ConsoleServerPort diff --git a/netbox/dcim/migrations/0125_console_port_speed.py b/netbox/dcim/migrations/0125_console_port_speed.py new file mode 100644 index 000000000..1a7f455d6 --- /dev/null +++ b/netbox/dcim/migrations/0125_console_port_speed.py @@ -0,0 +1,21 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0124_mark_connected'), + ] + + operations = [ + migrations.AddField( + model_name='consoleport', + name='speed', + field=models.PositiveSmallIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='consoleserverport', + name='speed', + field=models.PositiveSmallIntegerField(blank=True, null=True), + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 6e6f4ded5..98c04f1c4 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -224,9 +224,15 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel): blank=True, help_text='Physical port type' ) + speed = models.PositiveSmallIntegerField( + choices=ConsolePortSpeedChoices, + blank=True, + null=True, + help_text='Port speed in bits per second' + ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'label', 'type', 'mark_connected', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description'] class Meta: ordering = ('device', '_name') @@ -241,6 +247,7 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel): self.name, self.label, self.type, + self.speed, self.mark_connected, self.description, ) @@ -261,9 +268,15 @@ class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel): blank=True, help_text='Physical port type' ) + speed = models.PositiveSmallIntegerField( + choices=ConsolePortSpeedChoices, + blank=True, + null=True, + help_text='Port speed in bits per second' + ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'label', 'type', 'mark_connected', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description'] class Meta: ordering = ('device', '_name') @@ -278,6 +291,7 @@ class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel): self.name, self.label, self.type, + self.speed, self.mark_connected, self.description, ) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 1cd3c10a6..ba75df3a8 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -249,10 +249,10 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', + 'pk', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_peer', 'connection', 'tags', ) - default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') + default_columns = ('pk', 'device', 'name', 'label', 'type', 'speed', 'description') class DeviceConsolePortTable(ConsolePortTable): @@ -269,10 +269,10 @@ class DeviceConsolePortTable(ConsolePortTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', 'connection', - 'tags', 'actions' + 'pk', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_peer', + 'connection', 'tags', 'actions' ) - default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions') + default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions') row_attrs = { 'class': lambda record: record.cable.get_status_class() if record.cable else '' } @@ -286,10 +286,10 @@ class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', + 'pk', 'device', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_peer', 'connection', 'tags', ) - default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') + default_columns = ('pk', 'device', 'name', 'label', 'type', 'speed', 'description') class DeviceConsoleServerPortTable(ConsoleServerPortTable): @@ -307,10 +307,10 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort fields = ( - 'pk', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', 'connection', 'tags', - 'actions', + 'pk', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_peer', + 'connection', 'tags', 'actions', ) - default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions') + default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions') row_attrs = { 'class': lambda record: record.cable.get_status_class() if record.cable else '' } diff --git a/netbox/templates/dcim/consoleport.html b/netbox/templates/dcim/consoleport.html index 701fc6ef6..ce251670b 100644 --- a/netbox/templates/dcim/consoleport.html +++ b/netbox/templates/dcim/consoleport.html @@ -26,7 +26,11 @@ Type - {{ object.get_type_display }} + {{ object.get_type_display|placeholder }} + + + Speed + {{ object.get_speed_display|placeholder }} Description diff --git a/netbox/templates/dcim/consoleserverport.html b/netbox/templates/dcim/consoleserverport.html index b4e6d6ab9..69071c937 100644 --- a/netbox/templates/dcim/consoleserverport.html +++ b/netbox/templates/dcim/consoleserverport.html @@ -26,7 +26,11 @@ Type - {{ object.get_type_display }} + {{ object.get_type_display|placeholder }} + + + Speed + {{ object.get_speed_display|placeholder }} Description diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index 5f1e06c3b..dc5be0aac 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -24,6 +24,7 @@ __all__ = ( 'CSVContentTypeField', 'CSVDataField', 'CSVModelChoiceField', + 'CSVTypedChoiceField', 'DynamicModelChoiceField', 'DynamicModelMultipleChoiceField', 'ExpandableIPAddressField', @@ -125,6 +126,10 @@ class CSVChoiceField(forms.ChoiceField): self.choices = unpack_grouped_choices(choices) +class CSVTypedChoiceField(forms.TypedChoiceField): + STATIC_CHOICES = True + + class CSVModelChoiceField(forms.ModelChoiceField): """ Provides additional validation for model choices entered as CSV data.