diff --git a/docs/release-notes/version-2.11.md b/docs/release-notes/version-2.11.md index b768e53a0d..82a68e06d6 100644 --- a/docs/release-notes/version-2.11.md +++ b/docs/release-notes/version-2.11.md @@ -4,6 +4,14 @@ **WARNING:** This is a beta release and is not suitable for production use. It is intended for development and evaluation purposes only. No upgrade path to the final v2.11 release will be provided from this beta, and users should assume that all data entered into the application will be lost. +### New Features + +#### Mark as Connected Without a Cable ([#3648](https://github.com/netbox-community/netbox/issues/3648)) + +Cable termination objects (circuit terminations, power feeds, and most device components) can now be marked as "connected" without actually attaching a cable. This helps simplify the process of modeling an infrastructure boundary where you don't necessarily know or care what is connected to the far end of a cable, but still need to designate the near end termination. + +In addition to the new `mark_connected` boolean field, the REST API representation of these objects now also includes a read-only boolean field named `_occupied`. This conveniently returns true if either a cable is attached or `mark_connected` is true. + ### Enhancements * [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models @@ -16,3 +24,19 @@ * [#1638](https://github.com/netbox-community/netbox/issues/1638) - Migrate all primary keys to 64-bit integers * [#5873](https://github.com/netbox-community/netbox/issues/5873) - Use numeric IDs in all object URLs + +### REST API Changes + +* All primary keys are now 64-bit integers +* All device components + * Added support for custom fields + * Added `created` and `last_updated` fields to track object creation and modification +* All device component templates + * Added `created` and `last_updated` fields to track object creation and modification +* All organizational models + * Added support for custom fields +* All cable termination models (cabled device components, power feeds, and circuit terminations) + * Added `mark_connected` boolean field to force connection status + * Added `_occupied` read-only boolean field as common attribute for determining whether an object is occupied +* extras.CustomField + * Added new custom field type: `multi-select` diff --git a/netbox/circuits/api/nested_serializers.py b/netbox/circuits/api/nested_serializers.py index 2d3457d2c7..3aae01bf1c 100644 --- a/netbox/circuits/api/nested_serializers.py +++ b/netbox/circuits/api/nested_serializers.py @@ -51,4 +51,4 @@ class NestedCircuitTerminationSerializer(WritableNestedSerializer): class Meta: model = CircuitTermination - fields = ['id', 'url', 'circuit', 'term_side', 'cable'] + fields = ['id', 'url', 'circuit', 'term_side', 'cable', '_occupied'] diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 5688bf1c7a..f6a9ae3a18 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -82,6 +82,6 @@ class Meta: model = CircuitTermination fields = [ 'id', 'url', 'circuit', 'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', - 'description', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', - 'connected_endpoint_reachable' + 'description', 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', + 'connected_endpoint_type', 'connected_endpoint_reachable', '_occupied', ] diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 4582184687..adbf78fdcd 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -330,7 +330,8 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm): class Meta: model = CircuitTermination fields = [ - 'term_side', 'region', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', + 'term_side', 'region', 'site', 'mark_connected', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', + 'description', ] help_texts = { 'port_speed': "Physical circuit speed", diff --git a/netbox/circuits/migrations/0026_mark_connected.py b/netbox/circuits/migrations/0026_mark_connected.py new file mode 100644 index 0000000000..8531e9715a --- /dev/null +++ b/netbox/circuits/migrations/0026_mark_connected.py @@ -0,0 +1,16 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('circuits', '0025_standardize_models'), + ] + + operations = [ + migrations.AddField( + model_name='circuittermination', + name='mark_connected', + field=models.BooleanField(default=False), + ), + ] diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py index 6df9315530..df4c2070e0 100644 --- a/netbox/circuits/tests/test_api.py +++ b/netbox/circuits/tests/test_api.py @@ -129,7 +129,7 @@ def setUpTestData(cls): class CircuitTerminationTest(APIViewTestCases.APIViewTestCase): model = CircuitTermination - brief_fields = ['cable', 'circuit', 'id', 'term_side', 'url'] + brief_fields = ['_occupied', 'cable', 'circuit', 'id', 'term_side', 'url'] @classmethod def setUpTestData(cls): diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py index d63d32d68a..7f0d402fff 100644 --- a/netbox/dcim/api/nested_serializers.py +++ b/netbox/dcim/api/nested_serializers.py @@ -230,7 +230,7 @@ class NestedConsoleServerPortSerializer(WritableNestedSerializer): class Meta: model = models.ConsoleServerPort - fields = ['id', 'url', 'device', 'name', 'cable'] + fields = ['id', 'url', 'device', 'name', 'cable', '_occupied'] class NestedConsolePortSerializer(WritableNestedSerializer): @@ -239,7 +239,7 @@ class NestedConsolePortSerializer(WritableNestedSerializer): class Meta: model = models.ConsolePort - fields = ['id', 'url', 'device', 'name', 'cable'] + fields = ['id', 'url', 'device', 'name', 'cable', '_occupied'] class NestedPowerOutletSerializer(WritableNestedSerializer): @@ -248,7 +248,7 @@ class NestedPowerOutletSerializer(WritableNestedSerializer): class Meta: model = models.PowerOutlet - fields = ['id', 'url', 'device', 'name', 'cable'] + fields = ['id', 'url', 'device', 'name', 'cable', '_occupied'] class NestedPowerPortSerializer(WritableNestedSerializer): @@ -257,7 +257,7 @@ class NestedPowerPortSerializer(WritableNestedSerializer): class Meta: model = models.PowerPort - fields = ['id', 'url', 'device', 'name', 'cable'] + fields = ['id', 'url', 'device', 'name', 'cable', '_occupied'] class NestedInterfaceSerializer(WritableNestedSerializer): @@ -266,7 +266,7 @@ class NestedInterfaceSerializer(WritableNestedSerializer): class Meta: model = models.Interface - fields = ['id', 'url', 'device', 'name', 'cable'] + fields = ['id', 'url', 'device', 'name', 'cable', '_occupied'] class NestedRearPortSerializer(WritableNestedSerializer): @@ -275,7 +275,7 @@ class NestedRearPortSerializer(WritableNestedSerializer): class Meta: model = models.RearPort - fields = ['id', 'url', 'device', 'name', 'cable'] + fields = ['id', 'url', 'device', 'name', 'cable', '_occupied'] class NestedFrontPortSerializer(WritableNestedSerializer): @@ -284,7 +284,7 @@ class NestedFrontPortSerializer(WritableNestedSerializer): class Meta: model = models.FrontPort - fields = ['id', 'url', 'device', 'name', 'cable'] + fields = ['id', 'url', 'device', 'name', 'cable', '_occupied'] class NestedDeviceBaySerializer(WritableNestedSerializer): @@ -350,4 +350,4 @@ class NestedPowerFeedSerializer(WritableNestedSerializer): class Meta: model = models.PowerFeed - fields = ['id', 'url', 'name', 'cable'] + fields = ['id', 'url', 'name', 'cable', '_occupied'] diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 363687d1eb..b5672c1a8b 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -509,9 +509,9 @@ class ConsoleServerPortSerializer(TaggedObjectSerializer, CableTerminationSerial class Meta: model = ConsoleServerPort fields = [ - 'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type', - 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', - 'created', 'last_updated', + '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', ] @@ -528,9 +528,9 @@ class ConsolePortSerializer(TaggedObjectSerializer, CableTerminationSerializer, class Meta: model = ConsolePort fields = [ - 'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type', - 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', - 'created', 'last_updated', + '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', ] @@ -557,9 +557,9 @@ class PowerOutletSerializer(TaggedObjectSerializer, CableTerminationSerializer, class Meta: model = PowerOutlet fields = [ - 'id', 'url', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', - 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', - 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'mark_connected', + 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', + 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] @@ -576,9 +576,9 @@ class PowerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co class Meta: model = PowerPort fields = [ - 'id', 'url', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', - 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', - 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', + 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', + 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] @@ -602,9 +602,9 @@ class Meta: model = Interface fields = [ 'id', 'url', 'device', 'name', 'label', 'type', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', - 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'cable', 'cable_peer', 'cable_peer_type', - 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', - 'created', 'last_updated', 'count_ipaddresses', + 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'cable_peer', + 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', + 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', '_occupied', ] def validate(self, data): @@ -630,8 +630,8 @@ class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Cus class Meta: model = RearPort fields = [ - 'id', 'url', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', - 'cable_peer_type', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'device', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', + 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', ] @@ -656,8 +656,9 @@ class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Cu class Meta: model = FrontPort fields = [ - 'id', 'url', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', - 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', + 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields', 'created', + 'last_updated', '_occupied', ] @@ -892,7 +893,7 @@ class Meta: model = PowerFeed fields = [ 'id', 'url', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', - 'max_utilization', 'comments', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', - 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', - 'last_updated', + 'max_utilization', 'comments', '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/forms.py b/netbox/dcim/forms.py index 4d23654542..fe050b99de 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2324,7 +2324,7 @@ class ConsolePortForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = ConsolePort fields = [ - 'device', 'name', 'label', 'type', 'description', 'tags', + 'device', 'name', 'label', 'type', 'mark_connected', 'description', 'tags', ] widgets = { 'device': forms.HiddenInput(), @@ -2338,19 +2338,19 @@ class ConsolePortCreateForm(ComponentCreateForm): required=False, widget=StaticSelect2() ) - field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'description', 'tags') + field_order = ('device', 'name_pattern', 'label_pattern', 'type', 'mark_connected', 'description', 'tags') class ConsolePortBulkCreateForm( - form_from_model(ConsolePort, ['type']), + form_from_model(ConsolePort, ['type', 'mark_connected']), DeviceBulkAddComponentForm ): model = ConsolePort - field_order = ('name_pattern', 'label_pattern', 'type', 'description', 'tags') + field_order = ('name_pattern', 'label_pattern', 'type', 'mark_connected', 'description', 'tags') class ConsolePortBulkEditForm( - form_from_model(ConsolePort, ['label', 'type', 'description']), + form_from_model(ConsolePort, ['label', 'type', 'mark_connected', 'description']), BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm @@ -2772,8 +2772,8 @@ class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm): class Meta: model = Interface fields = [ - 'device', 'name', 'label', 'type', 'enabled', 'lag', 'mac_address', 'mtu', 'mgmt_only', 'description', - 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', + 'device', 'name', 'label', 'type', 'enabled', 'lag', 'mac_address', 'mtu', 'mgmt_only', 'mark_connected', + 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', ] widgets = { 'device': forms.HiddenInput(), @@ -3625,7 +3625,7 @@ class ConnectCableToConsolePortForm(ConnectCableToDeviceForm): termination_b_id = DynamicModelChoiceField( queryset=ConsolePort.objects.all(), label='Name', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'device_id': '$termination_b_device' } @@ -3636,7 +3636,7 @@ class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm): termination_b_id = DynamicModelChoiceField( queryset=ConsoleServerPort.objects.all(), label='Name', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'device_id': '$termination_b_device' } @@ -3647,7 +3647,7 @@ class ConnectCableToPowerPortForm(ConnectCableToDeviceForm): termination_b_id = DynamicModelChoiceField( queryset=PowerPort.objects.all(), label='Name', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'device_id': '$termination_b_device' } @@ -3658,7 +3658,7 @@ class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm): termination_b_id = DynamicModelChoiceField( queryset=PowerOutlet.objects.all(), label='Name', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'device_id': '$termination_b_device' } @@ -3669,7 +3669,7 @@ class ConnectCableToInterfaceForm(ConnectCableToDeviceForm): termination_b_id = DynamicModelChoiceField( queryset=Interface.objects.all(), label='Name', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'device_id': '$termination_b_device', 'kind': 'physical', @@ -3681,7 +3681,7 @@ class ConnectCableToFrontPortForm(ConnectCableToDeviceForm): termination_b_id = DynamicModelChoiceField( queryset=FrontPort.objects.all(), label='Name', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'device_id': '$termination_b_device' } @@ -3692,7 +3692,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm): termination_b_id = DynamicModelChoiceField( queryset=RearPort.objects.all(), label='Name', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'device_id': '$termination_b_device' } @@ -3731,7 +3731,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, CustomFieldModelForm) queryset=CircuitTermination.objects.all(), label='Side', display_field='term_side', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'circuit_id': '$termination_b_circuit' } @@ -3788,7 +3788,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm): termination_b_id = DynamicModelChoiceField( queryset=PowerFeed.objects.all(), label='Name', - disabled_indicator='cable', + disabled_indicator='_occupied', query_params={ 'power_panel_id': '$termination_b_powerpanel' } @@ -4538,12 +4538,12 @@ class PowerFeedForm(BootstrapMixin, CustomFieldModelForm): class Meta: model = PowerFeed fields = [ - 'region', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', - 'max_utilization', 'comments', 'tags', + 'region', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', + 'voltage', 'amperage', 'max_utilization', 'comments', 'tags', ] fieldsets = ( ('Power Panel', ('region', 'site', 'power_panel')), - ('Power Feed', ('rack', 'name', 'status', 'type', 'tags')), + ('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')), ('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')), ) widgets = { diff --git a/netbox/dcim/migrations/0124_mark_connected.py b/netbox/dcim/migrations/0124_mark_connected.py new file mode 100644 index 0000000000..d7bf40a243 --- /dev/null +++ b/netbox/dcim/migrations/0124_mark_connected.py @@ -0,0 +1,51 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0123_standardize_models'), + ] + + operations = [ + migrations.AddField( + model_name='consoleport', + name='mark_connected', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='consoleserverport', + name='mark_connected', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='frontport', + name='mark_connected', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='interface', + name='mark_connected', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='powerfeed', + name='mark_connected', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='poweroutlet', + name='mark_connected', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='powerport', + name='mark_connected', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='rearport', + name='mark_connected', + field=models.BooleanField(default=False), + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index f78363ba9b..6e6f4ded50 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -126,6 +126,10 @@ class CableTermination(models.Model): ct_field='_cable_peer_type', fk_field='_cable_peer_id' ) + mark_connected = models.BooleanField( + default=False, + help_text="Treat as if a cable is connected" + ) # Generic relations to Cable. These ensure that an attached Cable is deleted if the terminated object is deleted. _cabled_as_a = GenericRelation( @@ -142,9 +146,19 @@ class CableTermination(models.Model): class Meta: abstract = True + def clean(self): + super().clean() + + if self.mark_connected and self.cable_id: + raise ValidationError("Cannot set mark_connected with a cable connected.") + def get_cable_peer(self): return self._cable_peer + @property + def _occupied(self): + return bool(self.mark_connected or self.cable_id) + class PathEndpoint(models.Model): """ @@ -212,7 +226,7 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'label', 'type', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'mark_connected', 'description'] class Meta: ordering = ('device', '_name') @@ -227,6 +241,7 @@ def to_csv(self): self.name, self.label, self.type, + self.mark_connected, self.description, ) @@ -248,7 +263,7 @@ class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'label', 'type', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'mark_connected', 'description'] class Meta: ordering = ('device', '_name') @@ -263,6 +278,7 @@ def to_csv(self): self.name, self.label, self.type, + self.mark_connected, self.description, ) @@ -296,7 +312,9 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description'] + csv_headers = [ + 'device', 'name', 'label', 'type', 'mark_connected', 'maximum_draw', 'allocated_draw', 'description', + ] class Meta: ordering = ('device', '_name') @@ -311,6 +329,7 @@ def to_csv(self): self.name, self.label, self.get_type_display(), + self.mark_connected, self.maximum_draw, self.allocated_draw, self.description, @@ -406,7 +425,7 @@ class PowerOutlet(CableTermination, PathEndpoint, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'mark_connected', 'power_port', 'feed_leg', 'description'] class Meta: ordering = ('device', '_name') @@ -421,6 +440,7 @@ def to_csv(self): self.name, self.label, self.get_type_display(), + self.mark_connected, self.power_port.name if self.power_port else None, self.get_feed_leg_display(), self.description, @@ -532,7 +552,8 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface): tags = TaggableManager(through=TaggedItem) csv_headers = [ - 'device', 'name', 'label', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'description', 'mode', + 'device', 'name', 'label', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', 'mtu', 'mgmt_only', + 'description', 'mode', ] class Meta: @@ -550,6 +571,7 @@ def to_csv(self): self.lag.name if self.lag else None, self.get_type_display(), self.enabled, + self.mark_connected, self.mac_address, self.mtu, self.mgmt_only, @@ -648,7 +670,9 @@ class FrontPort(CableTermination, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description'] + csv_headers = [ + 'device', 'name', 'label', 'type', 'mark_connected', 'rear_port', 'rear_port_position', 'description', + ] class Meta: ordering = ('device', '_name') @@ -666,6 +690,7 @@ def to_csv(self): self.name, self.label, self.get_type_display(), + self.mark_connected, self.rear_port.name, self.rear_port_position, self.description, @@ -706,7 +731,7 @@ class RearPort(CableTermination, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'label', 'type', 'positions', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'mark_connected', 'positions', 'description'] class Meta: ordering = ('device', '_name') @@ -732,6 +757,7 @@ def to_csv(self): self.name, self.label, self.get_type_display(), + self.mark_connected, self.positions, self.description, ) diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py index 25b13f10bc..208b998092 100644 --- a/netbox/dcim/models/power.py +++ b/netbox/dcim/models/power.py @@ -138,12 +138,12 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination): objects = RestrictedQuerySet.as_manager() csv_headers = [ - 'site', 'power_panel', 'rack_group', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', - 'amperage', 'max_utilization', 'comments', + 'site', 'power_panel', 'rack_group', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', + 'voltage', 'amperage', 'max_utilization', 'comments', ] clone_fields = [ - 'power_panel', 'rack', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', - 'available_power', + 'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage', + 'max_utilization', 'available_power', ] class Meta: @@ -165,6 +165,7 @@ def to_csv(self): self.name, self.get_status_display(), self.get_type_display(), + self.mark_connected, self.get_supply_display(), self.get_phase_display(), self.voltage, diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 20e04e7ccd..1cd3c10a60 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -213,6 +213,7 @@ class DeviceComponentTable(BaseTable): cable = tables.Column( linkify=True ) + mark_connected = BooleanColumn() class Meta(BaseTable.Meta): order_by = ('device', 'name') @@ -228,6 +229,7 @@ class CableTerminationTable(BaseTable): orderable=False, verbose_name='Cable Peer' ) + mark_connected = BooleanColumn() class PathEndpointTable(CableTerminationTable): @@ -247,7 +249,8 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', + 'pk', 'device', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', + 'connection', 'tags', ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') @@ -266,7 +269,8 @@ class DeviceConsolePortTable(ConsolePortTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', 'actions' + 'pk', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', 'connection', + 'tags', 'actions' ) default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions') row_attrs = { @@ -281,7 +285,10 @@ class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort - fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags') + fields = ( + 'pk', 'device', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', + 'connection', 'tags', + ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') @@ -300,7 +307,8 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort fields = ( - 'pk', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'connection', 'tags', 'actions' + 'pk', 'name', 'label', 'type', 'description', 'mark_connected', 'cable', 'cable_peer', 'connection', 'tags', + 'actions', ) default_columns = ('pk', 'name', 'label', 'type', 'description', 'cable', 'connection', 'actions') row_attrs = { @@ -316,8 +324,8 @@ class PowerPortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = PowerPort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'maximum_draw', 'allocated_draw', 'cable', - 'cable_peer', 'connection', 'tags', + 'pk', 'device', 'name', 'label', 'type', 'description', 'mark_connected', 'maximum_draw', 'allocated_draw', + 'cable', 'cable_peer', 'connection', 'tags', ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description') @@ -337,8 +345,8 @@ class DevicePowerPortTable(PowerPortTable): class Meta(DeviceComponentTable.Meta): model = PowerPort fields = ( - 'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'cable_peer', - 'connection', 'tags', 'actions', + 'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'mark_connected', 'cable', + 'cable_peer', 'connection', 'tags', 'actions', ) default_columns = ( 'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection', @@ -360,8 +368,8 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = PowerOutlet fields = ( - 'pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable', 'cable_peer', - 'connection', 'tags', + 'pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'mark_connected', 'cable', + 'cable_peer', 'connection', 'tags', ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description') @@ -380,8 +388,8 @@ class DevicePowerOutletTable(PowerOutletTable): class Meta(DeviceComponentTable.Meta): model = PowerOutlet fields = ( - 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'cable_peer', 'connection', - 'tags', 'actions', + 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable', + 'cable_peer', 'connection', 'tags', 'actions', ) default_columns = ( 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'connection', 'actions', @@ -416,7 +424,8 @@ class Meta(DeviceComponentTable.Meta): model = Interface fields = ( 'pk', 'device', 'name', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', - 'description', 'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', + 'description', 'mark_connected', 'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', + 'untagged_vlan', 'tagged_vlans', ) default_columns = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description') @@ -442,7 +451,8 @@ class Meta(DeviceComponentTable.Meta): model = Interface fields = ( 'pk', 'name', 'label', 'enabled', 'type', 'lag', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'description', - 'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions', + 'mark_connected', 'cable', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', + 'tagged_vlans', 'actions', ) default_columns = ( 'pk', 'name', 'label', 'enabled', 'type', 'lag', 'mtu', 'mode', 'description', 'ip_addresses', 'cable', @@ -468,8 +478,8 @@ class FrontPortTable(DeviceComponentTable, CableTerminationTable): class Meta(DeviceComponentTable.Meta): model = FrontPort fields = ( - 'pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', - 'cable_peer', 'tags', + 'pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'mark_connected', + 'cable', 'cable_peer', 'tags', ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description') @@ -489,8 +499,8 @@ class DeviceFrontPortTable(FrontPortTable): class Meta(DeviceComponentTable.Meta): model = FrontPort fields = ( - 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_peer', - 'tags', 'actions', + 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'mark_connected', 'cable', + 'cable_peer', 'tags', 'actions', ) default_columns = ( 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'cable_peer', @@ -508,7 +518,10 @@ class RearPortTable(DeviceComponentTable, CableTerminationTable): class Meta(DeviceComponentTable.Meta): model = RearPort - fields = ('pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'tags') + fields = ( + 'pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', + 'cable_peer', 'tags', + ) default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') @@ -527,7 +540,8 @@ class DeviceRearPortTable(RearPortTable): class Meta(DeviceComponentTable.Meta): model = RearPort fields = ( - 'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'tags', 'actions', + 'pk', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', 'cable_peer', 'tags', + 'actions', ) default_columns = ( 'pk', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'actions', diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index 6d6e2541ba..ac7590aa29 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -68,7 +68,7 @@ class Meta(BaseTable.Meta): model = PowerFeed fields = ( 'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', - 'max_utilization', 'cable', 'cable_peer', 'connection', 'available_power', 'tags', + 'max_utilization', 'mark_connected', 'cable', 'cable_peer', 'connection', 'available_power', 'tags', ) default_columns = ( 'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'cable', diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index ad1ca930c0..d02bc8a5cc 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -979,7 +979,7 @@ def test_unique_name_per_site_constraint(self): class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase): model = ConsolePort - brief_fields = ['cable', 'device', 'id', 'name', 'url'] + brief_fields = ['_occupied', 'cable', 'device', 'id', 'name', 'url'] bulk_update_data = { 'description': 'New description', } @@ -1018,7 +1018,7 @@ def setUpTestData(cls): class ConsoleServerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase): model = ConsoleServerPort - brief_fields = ['cable', 'device', 'id', 'name', 'url'] + brief_fields = ['_occupied', 'cable', 'device', 'id', 'name', 'url'] bulk_update_data = { 'description': 'New description', } @@ -1057,7 +1057,7 @@ def setUpTestData(cls): class PowerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase): model = PowerPort - brief_fields = ['cable', 'device', 'id', 'name', 'url'] + brief_fields = ['_occupied', 'cable', 'device', 'id', 'name', 'url'] bulk_update_data = { 'description': 'New description', } @@ -1096,7 +1096,7 @@ def setUpTestData(cls): class PowerOutletTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase): model = PowerOutlet - brief_fields = ['cable', 'device', 'id', 'name', 'url'] + brief_fields = ['_occupied', 'cable', 'device', 'id', 'name', 'url'] bulk_update_data = { 'description': 'New description', } @@ -1135,7 +1135,7 @@ def setUpTestData(cls): class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase): model = Interface - brief_fields = ['cable', 'device', 'id', 'name', 'url'] + brief_fields = ['_occupied', 'cable', 'device', 'id', 'name', 'url'] bulk_update_data = { 'description': 'New description', } @@ -1193,7 +1193,7 @@ def setUpTestData(cls): class FrontPortTest(APIViewTestCases.APIViewTestCase): model = FrontPort - brief_fields = ['cable', 'device', 'id', 'name', 'url'] + brief_fields = ['_occupied', 'cable', 'device', 'id', 'name', 'url'] bulk_update_data = { 'description': 'New description', } @@ -1251,7 +1251,7 @@ def setUpTestData(cls): class RearPortTest(APIViewTestCases.APIViewTestCase): model = RearPort - brief_fields = ['cable', 'device', 'id', 'name', 'url'] + brief_fields = ['_occupied', 'cable', 'device', 'id', 'name', 'url'] bulk_update_data = { 'description': 'New description', } @@ -1628,7 +1628,7 @@ def setUpTestData(cls): class PowerFeedTest(APIViewTestCases.APIViewTestCase): model = PowerFeed - brief_fields = ['cable', 'id', 'name', 'url'] + brief_fields = ['_occupied', 'cable', 'id', 'name', 'url'] bulk_update_data = { 'status': 'planned', } diff --git a/netbox/templates/circuits/circuittermination_edit.html b/netbox/templates/circuits/circuittermination_edit.html index 917fca9708..01aa96c141 100644 --- a/netbox/templates/circuits/circuittermination_edit.html +++ b/netbox/templates/circuits/circuittermination_edit.html @@ -28,6 +28,7 @@ {% render_field form.region %} {% render_field form.site %} + {% render_field form.mark_connected %}
diff --git a/netbox/templates/circuits/inc/circuit_termination.html b/netbox/templates/circuits/inc/circuit_termination.html index e4854e5abb..762dd16621 100644 --- a/netbox/templates/circuits/inc/circuit_termination.html +++ b/netbox/templates/circuits/inc/circuit_termination.html @@ -38,7 +38,10 @@ Termination - {% if termination.cable %} + {% if termination.mark_connected %} + + Marked as connected + {% elif termination.cable %} {% if perms.dcim.delete_cable %}
diff --git a/netbox/templates/dcim/consoleport.html b/netbox/templates/dcim/consoleport.html index f1e020fd68..701fc6ef61 100644 --- a/netbox/templates/dcim/consoleport.html +++ b/netbox/templates/dcim/consoleport.html @@ -43,7 +43,11 @@
Connection
- {% if object.cable %} + {% if object.mark_connected %} +
+ Marked as connected +
+ {% elif object.cable %} diff --git a/netbox/templates/dcim/consoleserverport.html b/netbox/templates/dcim/consoleserverport.html index b609eedb28..b4e6d6ab93 100644 --- a/netbox/templates/dcim/consoleserverport.html +++ b/netbox/templates/dcim/consoleserverport.html @@ -43,7 +43,11 @@
Connection
- {% if object.cable %} + {% if object.mark_connected %} +
+ Marked as connected +
+ {% elif object.cable %}
Cable
diff --git a/netbox/templates/dcim/frontport.html b/netbox/templates/dcim/frontport.html index ba2ba9d190..5a84889ab8 100644 --- a/netbox/templates/dcim/frontport.html +++ b/netbox/templates/dcim/frontport.html @@ -53,7 +53,11 @@
Connection
- {% if object.cable %} + {% if object.mark_connected %} +
+ Marked as connected +
+ {% elif object.cable %}
Cable
diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index 8f22d2f674..dbd66c7e7c 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -76,7 +76,11 @@
Connection
- {% if object.cable %} + {% if object.mark_connected %} +
+ Marked as connected +
+ {% elif object.cable %}
Cable
diff --git a/netbox/templates/dcim/interface_edit.html b/netbox/templates/dcim/interface_edit.html index 620a308457..d2440bd425 100644 --- a/netbox/templates/dcim/interface_edit.html +++ b/netbox/templates/dcim/interface_edit.html @@ -23,6 +23,7 @@ {% render_field form.mac_address %} {% render_field form.mtu %} {% render_field form.mgmt_only %} + {% render_field form.mark_connected %} {% render_field form.description %} {% render_field form.tags %} @@ -35,12 +36,14 @@ {% render_field form.tagged_vlans %} -
-
Custom Fields
-
- {% render_custom_fields form %} + {% if form.custom_fields %} +
+
Custom Fields
+
+ {% render_custom_fields form %} +
-
+ {% endif %} {% endblock %} {% block buttons %} diff --git a/netbox/templates/dcim/powerfeed.html b/netbox/templates/dcim/powerfeed.html index be7e03ec5e..e8c0f1d295 100644 --- a/netbox/templates/dcim/powerfeed.html +++ b/netbox/templates/dcim/powerfeed.html @@ -159,7 +159,11 @@

{% block title %}{{ object }}{% endblock %}

Connection
- {% if object.cable %} + {% if object.mark_connected %} +
+ Marked as connected +
+ {% elif object.cable %}
Cable
diff --git a/netbox/templates/dcim/poweroutlet.html b/netbox/templates/dcim/poweroutlet.html index ae09afb312..e76f067b1c 100644 --- a/netbox/templates/dcim/poweroutlet.html +++ b/netbox/templates/dcim/poweroutlet.html @@ -51,7 +51,11 @@
Connection
- {% if object.cable %} + {% if object.mark_connected %} +
+ Marked as connected +
+ {% elif object.cable %}
Cable
diff --git a/netbox/templates/dcim/powerport.html b/netbox/templates/dcim/powerport.html index aff32d4940..635251b0d0 100644 --- a/netbox/templates/dcim/powerport.html +++ b/netbox/templates/dcim/powerport.html @@ -51,7 +51,11 @@
Connection
- {% if object.cable %} + {% if object.mark_connected %} +
+ Marked as connected +
+ {% elif object.cable %}
Cable
diff --git a/netbox/templates/dcim/rearport.html b/netbox/templates/dcim/rearport.html index e4cada913a..01eb0e9e61 100644 --- a/netbox/templates/dcim/rearport.html +++ b/netbox/templates/dcim/rearport.html @@ -47,7 +47,11 @@
Connection
- {% if object.cable %} + {% if object.mark_connected %} +
+ Marked as connected +
+ {% elif object.cable %}
Cable
Cable