Kaynağa Gözat

Closes #6715: Add tenant assignment for cables

jeremystretch 4 yıl önce
ebeveyn
işleme
0afd3e6189

+ 5 - 0
docs/release-notes/version-3.1.md

@@ -3,11 +3,16 @@
 !!! warning "PostgreSQL 10 Required"
 !!! warning "PostgreSQL 10 Required"
     NetBox v3.1 requires PostgreSQL 10 or later.
     NetBox v3.1 requires PostgreSQL 10 or later.
 
 
+### Breaking Changes
+
+* The `tenant` and `tenant_id` filters for the Cable model now filter on the tenant assigned directly to each cable, rather than on the parent object of either termination.
+
 ### Enhancements
 ### Enhancements
 
 
 * [#1337](https://github.com/netbox-community/netbox/issues/1337) - Add WWN field to interfaces
 * [#1337](https://github.com/netbox-community/netbox/issues/1337) - Add WWN field to interfaces
 * [#3839](https://github.com/netbox-community/netbox/issues/3839) - Add `airflow` field for devices types and devices
 * [#3839](https://github.com/netbox-community/netbox/issues/3839) - Add `airflow` field for devices types and devices
 * [#6711](https://github.com/netbox-community/netbox/issues/6711) - Add `longtext` custom field type with Markdown support
 * [#6711](https://github.com/netbox-community/netbox/issues/6711) - Add `longtext` custom field type with Markdown support
+* [#6715](https://github.com/netbox-community/netbox/issues/6715) - Add tenant assignment for cables
 * [#6874](https://github.com/netbox-community/netbox/issues/6874) - Add tenant assignment for locations
 * [#6874](https://github.com/netbox-community/netbox/issues/6874) - Add tenant assignment for locations
 
 
 ### Other Changes
 ### Other Changes

+ 3 - 2
netbox/dcim/api/serializers.py

@@ -758,14 +758,15 @@ class CableSerializer(PrimaryModelSerializer):
     termination_a = serializers.SerializerMethodField(read_only=True)
     termination_a = serializers.SerializerMethodField(read_only=True)
     termination_b = serializers.SerializerMethodField(read_only=True)
     termination_b = serializers.SerializerMethodField(read_only=True)
     status = ChoiceField(choices=CableStatusChoices, required=False)
     status = ChoiceField(choices=CableStatusChoices, required=False)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
     length_unit = ChoiceField(choices=CableLengthUnitChoices, allow_blank=True, required=False)
     length_unit = ChoiceField(choices=CableLengthUnitChoices, allow_blank=True, required=False)
 
 
     class Meta:
     class Meta:
         model = Cable
         model = Cable
         fields = [
         fields = [
             'id', 'url', 'display', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type',
             'id', 'url', 'display', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type',
-            'termination_b_id', 'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
-            'custom_fields',
+            'termination_b_id', 'termination_b', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit',
+            'tags', 'custom_fields',
         ]
         ]
 
 
     def _get_termination(self, obj, side):
     def _get_termination(self, obj, side):

+ 1 - 9
netbox/dcim/filtersets.py

@@ -1189,7 +1189,7 @@ class VirtualChassisFilterSet(PrimaryModelFilterSet):
         return queryset.filter(qs_filter).distinct()
         return queryset.filter(qs_filter).distinct()
 
 
 
 
-class CableFilterSet(PrimaryModelFilterSet):
+class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
         label='Search',
         label='Search',
@@ -1230,14 +1230,6 @@ class CableFilterSet(PrimaryModelFilterSet):
         method='filter_device',
         method='filter_device',
         field_name='device__site__slug'
         field_name='device__site__slug'
     )
     )
-    tenant_id = MultiValueNumberFilter(
-        method='filter_device',
-        field_name='device__tenant_id'
-    )
-    tenant = MultiValueNumberFilter(
-        method='filter_device',
-        field_name='device__tenant__slug'
-    )
     tag = TagFilter()
     tag = TagFilter()
 
 
     class Meta:
     class Meta:

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

@@ -468,6 +468,10 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkE
         widget=StaticSelect(),
         widget=StaticSelect(),
         initial=''
         initial=''
     )
     )
+    tenant = DynamicModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
     label = forms.CharField(
     label = forms.CharField(
         max_length=100,
         max_length=100,
         required=False
         required=False
@@ -488,7 +492,7 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkE
 
 
     class Meta:
     class Meta:
         nullable_fields = [
         nullable_fields = [
-            'type', 'status', 'label', 'color', 'length',
+            'type', 'status', 'tenant', 'label', 'color', 'length',
         ]
         ]
 
 
     def clean(self):
     def clean(self):

+ 7 - 1
netbox/dcim/forms/bulk_import.py

@@ -821,6 +821,12 @@ class CableCSVForm(CustomFieldModelCSVForm):
         required=False,
         required=False,
         help_text='Physical medium classification'
         help_text='Physical medium classification'
     )
     )
+    tenant = CSVModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False,
+        to_field_name='name',
+        help_text='Assigned tenant'
+    )
     length_unit = CSVChoiceField(
     length_unit = CSVChoiceField(
         choices=CableLengthUnitChoices,
         choices=CableLengthUnitChoices,
         required=False,
         required=False,
@@ -831,7 +837,7 @@ class CableCSVForm(CustomFieldModelCSVForm):
         model = Cable
         model = Cable
         fields = [
         fields = [
             'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
             'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
-            'status', 'label', 'color', 'length', 'length_unit',
+            'status', 'tenant', 'label', 'color', 'length', 'length_unit',
         ]
         ]
         help_texts = {
         help_texts = {
             'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
             'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),

+ 10 - 7
netbox/dcim/forms/connections.py

@@ -2,6 +2,7 @@ from circuits.models import Circuit, CircuitTermination, Provider
 from dcim.models import *
 from dcim.models import *
 from extras.forms import CustomFieldModelForm
 from extras.forms import CustomFieldModelForm
 from extras.models import Tag
 from extras.models import Tag
+from tenancy.forms import TenancyForm
 from utilities.forms import BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
 from utilities.forms import BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
 
 
 __all__ = (
 __all__ = (
@@ -17,7 +18,7 @@ __all__ = (
 )
 )
 
 
 
 
-class ConnectCableToDeviceForm(BootstrapMixin, CustomFieldModelForm):
+class ConnectCableToDeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     """
     """
     Base form for connecting a Cable to a Device component
     Base form for connecting a Cable to a Device component
     """
     """
@@ -78,7 +79,8 @@ class ConnectCableToDeviceForm(BootstrapMixin, CustomFieldModelForm):
         model = Cable
         model = Cable
         fields = [
         fields = [
             'termination_b_region', 'termination_b_site', 'termination_b_rack', 'termination_b_device',
             'termination_b_region', 'termination_b_site', 'termination_b_rack', 'termination_b_device',
-            'termination_b_id', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
+            'termination_b_id', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit',
+            'tags',
         ]
         ]
         widgets = {
         widgets = {
             'status': StaticSelect,
             'status': StaticSelect,
@@ -169,7 +171,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
     )
     )
 
 
 
 
-class ConnectCableToCircuitTerminationForm(BootstrapMixin, CustomFieldModelForm):
+class ConnectCableToCircuitTerminationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     termination_b_provider = DynamicModelChoiceField(
     termination_b_provider = DynamicModelChoiceField(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         label='Provider',
         label='Provider',
@@ -219,7 +221,8 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, CustomFieldModelForm)
         model = Cable
         model = Cable
         fields = [
         fields = [
             'termination_b_provider', 'termination_b_region', 'termination_b_site', 'termination_b_circuit',
             'termination_b_provider', 'termination_b_region', 'termination_b_site', 'termination_b_circuit',
-            'termination_b_id', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
+            'termination_b_id', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit',
+            'tags',
         ]
         ]
 
 
     def clean_termination_b_id(self):
     def clean_termination_b_id(self):
@@ -227,7 +230,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, CustomFieldModelForm)
         return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
         return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
 
 
 
 
-class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm):
+class ConnectCableToPowerFeedForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     termination_b_region = DynamicModelChoiceField(
     termination_b_region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
         label='Region',
         label='Region',
@@ -280,8 +283,8 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, CustomFieldModelForm):
     class Meta:
     class Meta:
         model = Cable
         model = Cable
         fields = [
         fields = [
-            'termination_b_location', 'termination_b_powerpanel', 'termination_b_id', 'type', 'status', 'label',
-            'color', 'length', 'length_unit', 'tags',
+            'termination_b_location', 'termination_b_powerpanel', 'termination_b_id', 'type', 'status', 'tenant_group',
+            'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
         ]
         ]
 
 
     def clean_termination_b_id(self):
     def clean_termination_b_id(self):

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

@@ -691,13 +691,13 @@ class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
 
 
-class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
+class CableFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = Cable
     model = Cable
     field_groups = [
     field_groups = [
         ['q', 'tag'],
         ['q', 'tag'],
         ['site_id', 'rack_id', 'device_id'],
         ['site_id', 'rack_id', 'device_id'],
         ['type', 'status', 'color'],
         ['type', 'status', 'color'],
-        ['tenant_id'],
+        ['tenant_group_id', 'tenant_id'],
     ]
     ]
     q = forms.CharField(
     q = forms.CharField(
         required=False,
         required=False,
@@ -719,12 +719,6 @@ class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
         label=_('Site'),
         label=_('Site'),
         fetch_trigger='open'
         fetch_trigger='open'
     )
     )
-    tenant_id = DynamicModelMultipleChoiceField(
-        queryset=Tenant.objects.all(),
-        required=False,
-        label=_('Tenant'),
-        fetch_trigger='open'
-    )
     rack_id = DynamicModelMultipleChoiceField(
     rack_id = DynamicModelMultipleChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         required=False,
         required=False,

+ 2 - 2
netbox/dcim/forms/models.py

@@ -601,7 +601,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
             self.fields['position'].widget.choices = [(position, f'U{position}')]
             self.fields['position'].widget.choices = [(position, f'U{position}')]
 
 
 
 
-class CableForm(BootstrapMixin, CustomFieldModelForm):
+class CableForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     tags = DynamicModelMultipleChoiceField(
     tags = DynamicModelMultipleChoiceField(
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
         required=False
         required=False
@@ -610,7 +610,7 @@ class CableForm(BootstrapMixin, CustomFieldModelForm):
     class Meta:
     class Meta:
         model = Cable
         model = Cable
         fields = [
         fields = [
-            'type', 'status', 'label', 'color', 'length', 'length_unit', 'tags',
+            'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
         ]
         ]
         widgets = {
         widgets = {
             'status': StaticSelect,
             'status': StaticSelect,

+ 5 - 0
netbox/dcim/migrations/0135_location_tenant.py → netbox/dcim/migrations/0135_tenancy_extensions.py

@@ -15,4 +15,9 @@ class Migration(migrations.Migration):
             name='tenant',
             name='tenant',
             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='locations', to='tenancy.tenant'),
             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='locations', to='tenancy.tenant'),
         ),
         ),
+        migrations.AddField(
+            model_name='cable',
+            name='tenant',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='cables', to='tenancy.tenant'),
+        ),
     ]
     ]

+ 1 - 1
netbox/dcim/migrations/0136_device_airflow.py

@@ -4,7 +4,7 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [
     dependencies = [
-        ('dcim', '0135_location_tenant'),
+        ('dcim', '0135_tenancy_extensions'),
     ]
     ]
 
 
     operations = [
     operations = [

+ 7 - 0
netbox/dcim/models/cables.py

@@ -67,6 +67,13 @@ class Cable(PrimaryModel):
         choices=CableStatusChoices,
         choices=CableStatusChoices,
         default=CableStatusChoices.STATUS_CONNECTED
         default=CableStatusChoices.STATUS_CONNECTED
     )
     )
+    tenant = models.ForeignKey(
+        to='tenancy.Tenant',
+        on_delete=models.PROTECT,
+        related_name='cables',
+        blank=True,
+        null=True
+    )
     label = models.CharField(
     label = models.CharField(
         max_length=100,
         max_length=100,
         blank=True
         blank=True

+ 3 - 1
netbox/dcim/tables/cables.py

@@ -2,6 +2,7 @@ import django_tables2 as tables
 from django_tables2.utils import Accessor
 from django_tables2.utils import Accessor
 
 
 from dcim.models import Cable
 from dcim.models import Cable
+from tenancy.tables import TenantColumn
 from utilities.tables import BaseTable, ChoiceFieldColumn, ColorColumn, TagColumn, TemplateColumn, ToggleColumn
 from utilities.tables import BaseTable, ChoiceFieldColumn, ColorColumn, TagColumn, TemplateColumn, ToggleColumn
 from .template_code import CABLE_LENGTH, CABLE_TERMINATION_PARENT
 from .template_code import CABLE_LENGTH, CABLE_TERMINATION_PARENT
 
 
@@ -45,6 +46,7 @@ class CableTable(BaseTable):
         verbose_name='Termination B'
         verbose_name='Termination B'
     )
     )
     status = ChoiceFieldColumn()
     status = ChoiceFieldColumn()
+    tenant = TenantColumn()
     length = TemplateColumn(
     length = TemplateColumn(
         template_code=CABLE_LENGTH,
         template_code=CABLE_LENGTH,
         order_by='_abs_length'
         order_by='_abs_length'
@@ -58,7 +60,7 @@ class CableTable(BaseTable):
         model = Cable
         model = Cable
         fields = (
         fields = (
             'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',
             'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',
-            'status', 'type', 'color', 'length', 'tags',
+            'status', 'type', 'tenant', 'color', 'length', 'tags',
         )
         )
         default_columns = (
         default_columns = (
             'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',
             'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',

+ 12 - 11
netbox/dcim/tests/test_filtersets.py

@@ -2819,6 +2819,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
         tenants = (
         tenants = (
             Tenant(name='Tenant 1', slug='tenant-1'),
             Tenant(name='Tenant 1', slug='tenant-1'),
             Tenant(name='Tenant 2', slug='tenant-2'),
             Tenant(name='Tenant 2', slug='tenant-2'),
+            Tenant(name='Tenant 3', slug='tenant-3'),
         )
         )
         Tenant.objects.bulk_create(tenants)
         Tenant.objects.bulk_create(tenants)
 
 
@@ -2834,9 +2835,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
         device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
         device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
 
 
         devices = (
         devices = (
-            Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1, tenant=tenants[0]),
-            Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2, tenant=tenants[0]),
-            Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1, tenant=tenants[1]),
+            Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1),
+            Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2),
+            Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1),
             Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=2),
             Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=2),
             Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=1),
             Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=1),
             Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=2),
             Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=2),
@@ -2863,12 +2864,12 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
         console_server_port = ConsoleServerPort.objects.create(device=devices[0], name='Console Server Port 1')
         console_server_port = ConsoleServerPort.objects.create(device=devices[0], name='Console Server Port 1')
 
 
         # Cables
         # Cables
-        Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
-        Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
-        Cable(termination_a=interfaces[5], termination_b=interfaces[6], label='Cable 3', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_CONNECTED, color='f44336', length=30, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
-        Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
-        Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
-        Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
+        Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, tenant=tenants[0], status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
+        Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, tenant=tenants[0], status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
+        Cable(termination_a=interfaces[5], termination_b=interfaces[6], label='Cable 3', type=CableTypeChoices.TYPE_CAT5E, tenant=tenants[1], status=CableStatusChoices.STATUS_CONNECTED, color='f44336', length=30, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
+        Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, tenant=tenants[1], status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
+        Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, tenant=tenants[2], status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
+        Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, tenant=tenants[2], status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
         Cable(termination_a=console_port, termination_b=console_server_port, label='Cable 7').save()
         Cable(termination_a=console_port, termination_b=console_server_port, label='Cable 7').save()
 
 
     def test_label(self):
     def test_label(self):
@@ -2921,9 +2922,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
     def test_tenant(self):
     def test_tenant(self):
         tenant = Tenant.objects.all()[:2]
         tenant = Tenant.objects.all()[:2]
         params = {'tenant_id': [tenant[0].pk, tenant[1].pk]}
         params = {'tenant_id': [tenant[0].pk, tenant[1].pk]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
         params = {'tenant': [tenant[0].slug, tenant[1].slug]}
         params = {'tenant': [tenant[0].slug, tenant[1].slug]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_termination_types(self):
     def test_termination_types(self):
         params = {'termination_a_type': 'dcim.consoleport'}
         params = {'termination_a_type': 'dcim.consoleport'}

+ 13 - 0
netbox/templates/dcim/cable.html

@@ -23,6 +23,19 @@
                                 <span class="badge bg-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
                                 <span class="badge bg-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
                             </td>
                             </td>
                         </tr>
                         </tr>
+                        <tr>
+                            <th scope="row">Tenant</th>
+                            <td>
+                                {% if object.tenant %}
+                                    {% if object.tenant.group %}
+                                        <a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
+                                    {% endif %}
+                                    <a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
+                                {% else %}
+                                    <span class="text-muted">None</span>
+                                {% endif %}
+                            </td>
+                        </tr>
                         <tr>
                         <tr>
                             <th scope="row">Label</th>
                             <th scope="row">Label</th>
                             <td>{{ object.label|placeholder }}</td>
                             <td>{{ object.label|placeholder }}</td>

+ 3 - 1
netbox/templates/dcim/inc/cable_form.html

@@ -2,6 +2,8 @@
 
 
 {% render_field form.status %}
 {% render_field form.status %}
 {% render_field form.type %}
 {% render_field form.type %}
+{% render_field form.tenant_group %}
+{% render_field form.tenant %}
 {% render_field form.label %}
 {% render_field form.label %}
 {% render_field form.color %}
 {% render_field form.color %}
 <div class="row mb-3">
 <div class="row mb-3">
@@ -17,7 +19,7 @@
 {% render_field form.tags %}
 {% render_field form.tags %}
 {% if form.custom_fields %}
 {% if form.custom_fields %}
   <div class="field-group">
   <div class="field-group">
-    <div class="row mb-2">
+    <div class="row mb-3">
       <h5 class="offset-sm-3">Custom Fields</h5>
       <h5 class="offset-sm-3">Custom Fields</h5>
     </div>
     </div>
     {% render_custom_fields form %}
     {% render_custom_fields form %}