Closes #3648: Mark cable termination models as connected without attaching a cable

This commit is contained in:
Jeremy Stretch 2021-03-01 21:34:42 -05:00
parent 1bf7d7e293
commit 9581d72f9d
26 changed files with 281 additions and 108 deletions

View File

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

View File

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

View File

@ -82,6 +82,6 @@ class CircuitTerminationSerializer(CableTerminationSerializer, ConnectedEndpoint
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',
]

View File

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

View File

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

View File

@ -129,7 +129,7 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
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):

View File

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

View File

@ -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 InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
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 PowerFeedSerializer(
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',
]

View File

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

View File

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

View File

@ -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 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
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 @@ class ConsoleServerPort(CableTermination, PathEndpoint, ComponentModel):
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 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel):
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 @@ class PowerOutlet(CableTermination, PathEndpoint, ComponentModel):
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 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
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 @@ class FrontPort(CableTermination, ComponentModel):
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 @@ class RearPort(CableTermination, ComponentModel):
self.name,
self.label,
self.get_type_display(),
self.mark_connected,
self.positions,
self.description,
)

View File

@ -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 @@ class PowerFeed(PrimaryModel, PathEndpoint, CableTermination):
self.name,
self.get_status_display(),
self.get_type_display(),
self.mark_connected,
self.get_supply_display(),
self.get_phase_display(),
self.voltage,

View File

@ -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 InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable
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 DeviceInterfaceTable(InterfaceTable):
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',

View File

@ -68,7 +68,7 @@ class PowerFeedTable(CableTerminationTable):
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',

View File

@ -979,7 +979,7 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
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 @@ class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCa
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 @@ class ConsoleServerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIView
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 @@ class PowerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
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 @@ class PowerOutletTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCa
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 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
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 @@ class FrontPortTest(APIViewTestCases.APIViewTestCase):
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 @@ class PowerPanelTest(APIViewTestCases.APIViewTestCase):
class PowerFeedTest(APIViewTestCases.APIViewTestCase):
model = PowerFeed
brief_fields = ['cable', 'id', 'name', 'url']
brief_fields = ['_occupied', 'cable', 'id', 'name', 'url']
bulk_update_data = {
'status': 'planned',
}

View File

@ -28,6 +28,7 @@
</div>
{% render_field form.region %}
{% render_field form.site %}
{% render_field form.mark_connected %}
</div>
</div>
<div class="panel panel-default">

View File

@ -38,7 +38,10 @@
<tr>
<td>Termination</td>
<td>
{% if termination.cable %}
{% if termination.mark_connected %}
<span class="text-success"><i class="mdi mdi-check-bold"></i></span>
<span class="text-muted">Marked as connected</span>
{% elif termination.cable %}
{% if perms.dcim.delete_cable %}
<div class="pull-right">
<a href="{% url 'dcim:cable_delete' pk=termination.cable.pk %}?return_url={{ termination.circuit.get_absolute_url }}" title="Remove cable" class="btn btn-danger btn-xs">

View File

@ -43,7 +43,11 @@
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if object.cable %}
{% if object.mark_connected %}
<div class="panel-body text-muted">
<span class="text-success"><i class="mdi mdi-check-bold"></i></span> Marked as connected
</div>
{% elif object.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>

View File

@ -43,7 +43,11 @@
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if object.cable %}
{% if object.mark_connected %}
<div class="panel-body text-muted">
<span class="text-success"><i class="mdi mdi-check-bold"></i></span> Marked as connected
</div>
{% elif object.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>

View File

@ -53,7 +53,11 @@
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if object.cable %}
{% if object.mark_connected %}
<div class="panel-body text-muted">
<span class="text-success"><i class="mdi mdi-check-bold"></i></span> Marked as connected
</div>
{% elif object.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>

View File

@ -76,7 +76,11 @@
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if object.cable %}
{% if object.mark_connected %}
<div class="panel-body text-muted">
<span class="text-success"><i class="mdi mdi-check-bold"></i></span> Marked as connected
</div>
{% elif object.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>

View File

@ -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 %}
</div>
@ -35,12 +36,14 @@
{% render_field form.tagged_vlans %}
</div>
</div>
{% if form.custom_fields %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div>
<div class="panel-body">
{% render_custom_fields form %}
</div>
</div>
{% endif %}
{% endblock %}
{% block buttons %}

View File

@ -159,7 +159,11 @@
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if object.cable %}
{% if object.mark_connected %}
<div class="panel-body text-muted">
<span class="text-success"><i class="mdi mdi-check-bold"></i></span> Marked as connected
</div>
{% elif object.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>

View File

@ -51,7 +51,11 @@
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if object.cable %}
{% if object.mark_connected %}
<div class="panel-body text-muted">
<span class="text-success"><i class="mdi mdi-check-bold"></i></span> Marked as connected
</div>
{% elif object.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>

View File

@ -51,7 +51,11 @@
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if object.cable %}
{% if object.mark_connected %}
<div class="panel-body text-muted">
<span class="text-success"><i class="mdi mdi-check-bold"></i></span> Marked as connected
</div>
{% elif object.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>

View File

@ -47,7 +47,11 @@
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if object.cable %}
{% if object.mark_connected %}
<div class="panel-body text-muted">
<span class="text-success"><i class="mdi mdi-check-bold"></i></span> Marked as connected
</div>
{% elif object.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>