mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 18:08:38 -06:00
Extend Cable model to support multiple A/B terminations
This commit is contained in:
parent
6c290353c1
commit
4bb9b6ee26
@ -977,8 +977,8 @@ class CableSerializer(NetBoxModelSerializer):
|
|||||||
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_ids', 'termination_a', 'termination_b_type',
|
||||||
'termination_b_id', 'termination_b', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit',
|
'termination_b_ids', 'termination_b', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit',
|
||||||
'tags', 'custom_fields', 'created', 'last_updated',
|
'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -986,14 +986,12 @@ class CableSerializer(NetBoxModelSerializer):
|
|||||||
"""
|
"""
|
||||||
Serialize a nested representation of a termination.
|
Serialize a nested representation of a termination.
|
||||||
"""
|
"""
|
||||||
if side.lower() not in ['a', 'b']:
|
assert side.lower() in ('a', 'b')
|
||||||
raise ValueError("Termination side must be either A or B.")
|
termination_type = getattr(obj, f'termination_{side}_type').model_class()
|
||||||
termination = getattr(obj, 'termination_{}'.format(side.lower()))
|
termination = getattr(obj, f'termination_{side}')
|
||||||
if termination is None:
|
serializer = get_serializer_for_model(termination_type, prefix='Nested')
|
||||||
return None
|
|
||||||
serializer = get_serializer_for_model(termination, prefix='Nested')
|
|
||||||
context = {'request': self.context['request']}
|
context = {'request': self.context['request']}
|
||||||
data = serializer(termination, context=context).data
|
data = serializer(termination, context=context, many=True).data
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -647,9 +647,7 @@ class InventoryItemRoleViewSet(NetBoxModelViewSet):
|
|||||||
|
|
||||||
class CableViewSet(NetBoxModelViewSet):
|
class CableViewSet(NetBoxModelViewSet):
|
||||||
metadata_class = ContentTypeMetadata
|
metadata_class = ContentTypeMetadata
|
||||||
queryset = Cable.objects.prefetch_related(
|
queryset = Cable.objects.all()
|
||||||
'termination_a', 'termination_b'
|
|
||||||
)
|
|
||||||
serializer_class = serializers.CableSerializer
|
serializer_class = serializers.CableSerializer
|
||||||
filterset_class = filtersets.CableFilterSet
|
filterset_class = filtersets.CableFilterSet
|
||||||
|
|
||||||
|
@ -1499,9 +1499,9 @@ class VirtualChassisFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
||||||
termination_a_type = ContentTypeFilter()
|
termination_a_type = ContentTypeFilter()
|
||||||
termination_a_id = MultiValueNumberFilter()
|
termination_a_ids = MultiValueNumberFilter()
|
||||||
termination_b_type = ContentTypeFilter()
|
termination_b_type = ContentTypeFilter()
|
||||||
termination_b_id = MultiValueNumberFilter()
|
termination_b_ids = MultiValueNumberFilter()
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=CableTypeChoices
|
choices=CableTypeChoices
|
||||||
)
|
)
|
||||||
@ -1537,7 +1537,7 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
fields = ['id', 'label', 'length', 'length_unit', 'termination_a_id', 'termination_b_id']
|
fields = ['id', 'label', 'length', 'length_unit', 'termination_a_ids', 'termination_b_ids']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1546,8 +1546,8 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(**{'_termination_a_{}__in'.format(name): value}) |
|
Q(**{f'_termination_a_{name}__in': value}) |
|
||||||
Q(**{'_termination_b_{}__in'.format(name): value})
|
Q(**{f'_termination_b_{name}__in': value})
|
||||||
)
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ from circuits.models import Circuit, CircuitTermination, Provider
|
|||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from utilities.forms import DynamicModelChoiceField, StaticSelect
|
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConnectCableToCircuitTerminationForm',
|
'ConnectCableToCircuitTerminationForm',
|
||||||
@ -22,7 +22,7 @@ class ConnectCableToDeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
Base form for connecting a Cable to a Device component
|
Base form for connecting a Cable to a Device component
|
||||||
"""
|
"""
|
||||||
# Termination A
|
# Termination A
|
||||||
termination_a_id = DynamicModelChoiceField(
|
termination_a_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied'
|
disabled_indicator='_occupied'
|
||||||
@ -87,8 +87,8 @@ class ConnectCableToDeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
fields = [
|
fields = [
|
||||||
'termination_a_id', 'termination_b_region', 'termination_b_sitegroup', 'termination_b_site',
|
'termination_a_ids', 'termination_b_region', 'termination_b_sitegroup', 'termination_b_site',
|
||||||
'termination_b_rack', 'termination_b_device', 'termination_b_id', 'type', 'status', 'tenant_group',
|
'termination_b_rack', 'termination_b_device', 'termination_b_ids', 'type', 'status', 'tenant_group',
|
||||||
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
|
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
@ -97,17 +97,17 @@ class ConnectCableToDeviceForm(TenancyForm, NetBoxModelForm):
|
|||||||
'length_unit': StaticSelect,
|
'length_unit': StaticSelect,
|
||||||
}
|
}
|
||||||
|
|
||||||
def clean_termination_a_id(self):
|
def clean_termination_a_ids(self):
|
||||||
# Return the PK rather than the object
|
# Return the PK rather than the object
|
||||||
return getattr(self.cleaned_data['termination_a_id'], 'pk', None)
|
return [getattr(obj, 'pk') for obj in self.cleaned_data['termination_a_ids']]
|
||||||
|
|
||||||
def clean_termination_b_id(self):
|
def clean_termination_b_ids(self):
|
||||||
# Return the PK rather than the object
|
# Return the PK rather than the object
|
||||||
return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
|
return [getattr(obj, 'pk') for obj in self.cleaned_data['termination_b_ids']]
|
||||||
|
|
||||||
|
|
||||||
class ConnectCableToConsolePortForm(ConnectCableToDeviceForm):
|
class ConnectCableToConsolePortForm(ConnectCableToDeviceForm):
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=ConsolePort.objects.all(),
|
queryset=ConsolePort.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -118,7 +118,7 @@ class ConnectCableToConsolePortForm(ConnectCableToDeviceForm):
|
|||||||
|
|
||||||
|
|
||||||
class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm):
|
class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm):
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=ConsoleServerPort.objects.all(),
|
queryset=ConsoleServerPort.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -129,7 +129,7 @@ class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm):
|
|||||||
|
|
||||||
|
|
||||||
class ConnectCableToPowerPortForm(ConnectCableToDeviceForm):
|
class ConnectCableToPowerPortForm(ConnectCableToDeviceForm):
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=PowerPort.objects.all(),
|
queryset=PowerPort.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -140,7 +140,7 @@ class ConnectCableToPowerPortForm(ConnectCableToDeviceForm):
|
|||||||
|
|
||||||
|
|
||||||
class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm):
|
class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm):
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=PowerOutlet.objects.all(),
|
queryset=PowerOutlet.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -151,7 +151,7 @@ class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm):
|
|||||||
|
|
||||||
|
|
||||||
class ConnectCableToInterfaceForm(ConnectCableToDeviceForm):
|
class ConnectCableToInterfaceForm(ConnectCableToDeviceForm):
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -163,7 +163,7 @@ class ConnectCableToInterfaceForm(ConnectCableToDeviceForm):
|
|||||||
|
|
||||||
|
|
||||||
class ConnectCableToFrontPortForm(ConnectCableToDeviceForm):
|
class ConnectCableToFrontPortForm(ConnectCableToDeviceForm):
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=FrontPort.objects.all(),
|
queryset=FrontPort.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -174,7 +174,7 @@ class ConnectCableToFrontPortForm(ConnectCableToDeviceForm):
|
|||||||
|
|
||||||
|
|
||||||
class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
|
class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=RearPort.objects.all(),
|
queryset=RearPort.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -186,7 +186,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
|
|||||||
|
|
||||||
class ConnectCableToCircuitTerminationForm(TenancyForm, NetBoxModelForm):
|
class ConnectCableToCircuitTerminationForm(TenancyForm, NetBoxModelForm):
|
||||||
# Termination A
|
# Termination A
|
||||||
termination_a_id = DynamicModelChoiceField(
|
termination_a_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
label='Side',
|
label='Side',
|
||||||
disabled_indicator='_occupied'
|
disabled_indicator='_occupied'
|
||||||
@ -231,7 +231,7 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, NetBoxModelForm):
|
|||||||
'site_id': '$termination_b_site',
|
'site_id': '$termination_b_site',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=CircuitTermination.objects.all(),
|
queryset=CircuitTermination.objects.all(),
|
||||||
label='Side',
|
label='Side',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -242,8 +242,8 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
class Meta(ConnectCableToDeviceForm.Meta):
|
class Meta(ConnectCableToDeviceForm.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'termination_a_id', 'termination_b_provider', 'termination_b_region', 'termination_b_sitegroup',
|
'termination_a_ids', 'termination_b_provider', 'termination_b_region', 'termination_b_sitegroup',
|
||||||
'termination_b_site', 'termination_b_circuit', 'termination_b_id', 'type', 'status', 'tenant_group',
|
'termination_b_site', 'termination_b_circuit', 'termination_b_ids', 'type', 'status', 'tenant_group',
|
||||||
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
|
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -258,7 +258,7 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
class ConnectCableToPowerFeedForm(TenancyForm, NetBoxModelForm):
|
class ConnectCableToPowerFeedForm(TenancyForm, NetBoxModelForm):
|
||||||
# Termination A
|
# Termination A
|
||||||
termination_a_id = DynamicModelChoiceField(
|
termination_a_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied'
|
disabled_indicator='_occupied'
|
||||||
@ -307,7 +307,7 @@ class ConnectCableToPowerFeedForm(TenancyForm, NetBoxModelForm):
|
|||||||
'location_id': '$termination_b_location',
|
'location_id': '$termination_b_location',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
termination_b_id = DynamicModelChoiceField(
|
termination_b_ids = DynamicModelMultipleChoiceField(
|
||||||
queryset=PowerFeed.objects.all(),
|
queryset=PowerFeed.objects.all(),
|
||||||
label='Name',
|
label='Name',
|
||||||
disabled_indicator='_occupied',
|
disabled_indicator='_occupied',
|
||||||
@ -318,8 +318,8 @@ class ConnectCableToPowerFeedForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
class Meta(ConnectCableToDeviceForm.Meta):
|
class Meta(ConnectCableToDeviceForm.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'termination_a_id', 'termination_b_region', 'termination_b_sitegroup', 'termination_b_site',
|
'termination_a_ids', 'termination_b_region', 'termination_b_sitegroup', 'termination_b_site',
|
||||||
'termination_b_location', 'termination_b_powerpanel', 'termination_b_id', 'type', 'status', 'tenant_group',
|
'termination_b_location', 'termination_b_powerpanel', 'termination_b_ids', 'type', 'status', 'tenant_group',
|
||||||
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
|
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.0.4 on 2022-04-25 16:35
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0153_created_datetimefield'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cable',
|
||||||
|
name='termination_a_ids',
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(base_field=models.PositiveBigIntegerField(), null=True, size=None),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cable',
|
||||||
|
name='termination_b_ids',
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(base_field=models.PositiveBigIntegerField(), null=True, size=None),
|
||||||
|
),
|
||||||
|
]
|
36
netbox/dcim/migrations/0155_cable_copy_termination_ids.py
Normal file
36
netbox/dcim/migrations/0155_cable_copy_termination_ids.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.models import ExpressionWrapper, F
|
||||||
|
|
||||||
|
|
||||||
|
def copy_termination_ids(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Copy original A & B termination ID values to new array fields.
|
||||||
|
"""
|
||||||
|
Cable = apps.get_model('dcim', 'Cable')
|
||||||
|
|
||||||
|
# TODO: Optimize data migration using F expressions
|
||||||
|
# Cable.objects.update(
|
||||||
|
# termination_a_ids=ExpressionWrapper(F('termination_a_id'), output_field=ArrayField),
|
||||||
|
# termination_b_ids=ExpressionWrapper(F('termination_b_id'), output_field=ArrayField)
|
||||||
|
# )
|
||||||
|
|
||||||
|
for cable in Cable.objects.all():
|
||||||
|
Cable.objects.filter(pk=cable.pk).update(
|
||||||
|
termination_a_ids=[cable.termination_a_id],
|
||||||
|
termination_b_ids=[cable.termination_b_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0154_cable_add_termination_id_arrays'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
code=copy_termination_ids,
|
||||||
|
reverse_code=migrations.RunPython.noop
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 4.0.4 on 2022-04-25 20:45
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0155_cable_copy_termination_ids'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='cable',
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='cable',
|
||||||
|
name='termination_a_id',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='cable',
|
||||||
|
name='termination_b_id',
|
||||||
|
),
|
||||||
|
]
|
@ -2,6 +2,7 @@ from collections import defaultdict
|
|||||||
|
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
@ -38,10 +39,9 @@ class Cable(NetBoxModel):
|
|||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='+'
|
related_name='+'
|
||||||
)
|
)
|
||||||
termination_a_id = models.PositiveBigIntegerField()
|
termination_a_ids = ArrayField(
|
||||||
termination_a = GenericForeignKey(
|
base_field=models.PositiveBigIntegerField(),
|
||||||
ct_field='termination_a_type',
|
null=True
|
||||||
fk_field='termination_a_id'
|
|
||||||
)
|
)
|
||||||
termination_b_type = models.ForeignKey(
|
termination_b_type = models.ForeignKey(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
@ -49,10 +49,9 @@ class Cable(NetBoxModel):
|
|||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='+'
|
related_name='+'
|
||||||
)
|
)
|
||||||
termination_b_id = models.PositiveBigIntegerField()
|
termination_b_ids = ArrayField(
|
||||||
termination_b = GenericForeignKey(
|
base_field=models.PositiveBigIntegerField(),
|
||||||
ct_field='termination_b_type',
|
null=True
|
||||||
fk_field='termination_b_id'
|
|
||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
@ -115,10 +114,6 @@ class Cable(NetBoxModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['pk']
|
ordering = ['pk']
|
||||||
unique_together = (
|
|
||||||
('termination_a_type', 'termination_a_id'),
|
|
||||||
('termination_b_type', 'termination_b_id'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -137,9 +132,9 @@ class Cable(NetBoxModel):
|
|||||||
instance = super().from_db(db, field_names, values)
|
instance = super().from_db(db, field_names, values)
|
||||||
|
|
||||||
instance._orig_termination_a_type_id = instance.termination_a_type_id
|
instance._orig_termination_a_type_id = instance.termination_a_type_id
|
||||||
instance._orig_termination_a_id = instance.termination_a_id
|
instance._orig_termination_a_ids = instance.termination_a_ids
|
||||||
instance._orig_termination_b_type_id = instance.termination_b_type_id
|
instance._orig_termination_b_type_id = instance.termination_b_type_id
|
||||||
instance._orig_termination_b_id = instance.termination_b_id
|
instance._orig_termination_b_ids = instance.termination_b_ids
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
@ -150,6 +145,18 @@ class Cable(NetBoxModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:cable', args=[self.pk])
|
return reverse('dcim:cable', args=[self.pk])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def termination_a(self):
|
||||||
|
if not hasattr(self, 'termination_a_type') or not self.termination_a_ids:
|
||||||
|
return []
|
||||||
|
return list(self.termination_a_type.model_class().objects.filter(pk__in=self.termination_a_ids))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def termination_b(self):
|
||||||
|
if not hasattr(self, 'termination_b_type') or not self.termination_b_ids:
|
||||||
|
return []
|
||||||
|
return list(self.termination_b_type.model_class().objects.filter(pk__in=self.termination_b_ids))
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
from circuits.models import CircuitTermination
|
from circuits.models import CircuitTermination
|
||||||
|
|
||||||
@ -158,9 +165,8 @@ class Cable(NetBoxModel):
|
|||||||
# Validate that termination A exists
|
# Validate that termination A exists
|
||||||
if not hasattr(self, 'termination_a_type'):
|
if not hasattr(self, 'termination_a_type'):
|
||||||
raise ValidationError('Termination A type has not been specified')
|
raise ValidationError('Termination A type has not been specified')
|
||||||
try:
|
model = self.termination_a_type.model_class()
|
||||||
self.termination_a_type.model_class().objects.get(pk=self.termination_a_id)
|
if model.objects.filter(pk__in=self.termination_a_ids).count() != len(self.termination_a_ids):
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'termination_a': 'Invalid ID for type {}'.format(self.termination_a_type)
|
'termination_a': 'Invalid ID for type {}'.format(self.termination_a_type)
|
||||||
})
|
})
|
||||||
@ -168,9 +174,8 @@ class Cable(NetBoxModel):
|
|||||||
# Validate that termination B exists
|
# Validate that termination B exists
|
||||||
if not hasattr(self, 'termination_b_type'):
|
if not hasattr(self, 'termination_b_type'):
|
||||||
raise ValidationError('Termination B type has not been specified')
|
raise ValidationError('Termination B type has not been specified')
|
||||||
try:
|
model = self.termination_a_type.model_class()
|
||||||
self.termination_b_type.model_class().objects.get(pk=self.termination_b_id)
|
if model.objects.filter(pk__in=self.termination_b_ids).count() != len(self.termination_b_ids):
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'termination_b': 'Invalid ID for type {}'.format(self.termination_b_type)
|
'termination_b': 'Invalid ID for type {}'.format(self.termination_b_type)
|
||||||
})
|
})
|
||||||
@ -180,14 +185,14 @@ class Cable(NetBoxModel):
|
|||||||
err_msg = 'Cable termination points may not be modified. Delete and recreate the cable instead.'
|
err_msg = 'Cable termination points may not be modified. Delete and recreate the cable instead.'
|
||||||
if (
|
if (
|
||||||
self.termination_a_type_id != self._orig_termination_a_type_id or
|
self.termination_a_type_id != self._orig_termination_a_type_id or
|
||||||
self.termination_a_id != self._orig_termination_a_id
|
set(self.termination_a_ids) != set(self._orig_termination_a_ids)
|
||||||
):
|
):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'termination_a': err_msg
|
'termination_a': err_msg
|
||||||
})
|
})
|
||||||
if (
|
if (
|
||||||
self.termination_b_type_id != self._orig_termination_b_type_id or
|
self.termination_b_type_id != self._orig_termination_b_type_id or
|
||||||
self.termination_b_id != self._orig_termination_b_id
|
set(self.termination_b_ids) != set(self._orig_termination_b_ids)
|
||||||
):
|
):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'termination_b': err_msg
|
'termination_b': err_msg
|
||||||
@ -197,18 +202,18 @@ class Cable(NetBoxModel):
|
|||||||
type_b = self.termination_b_type.model
|
type_b = self.termination_b_type.model
|
||||||
|
|
||||||
# Validate interface types
|
# Validate interface types
|
||||||
if type_a == 'interface' and self.termination_a.type in NONCONNECTABLE_IFACE_TYPES:
|
if type_a == 'interface':
|
||||||
raise ValidationError({
|
for term in self.termination_a:
|
||||||
'termination_a_id': 'Cables cannot be terminated to {} interfaces'.format(
|
if term.type in NONCONNECTABLE_IFACE_TYPES:
|
||||||
self.termination_a.get_type_display()
|
raise ValidationError({
|
||||||
)
|
'termination_a_id': f'Cables cannot be terminated to {term.get_type_display()} interfaces'
|
||||||
})
|
})
|
||||||
if type_b == 'interface' and self.termination_b.type in NONCONNECTABLE_IFACE_TYPES:
|
if type_a == 'interface':
|
||||||
raise ValidationError({
|
for term in self.termination_b:
|
||||||
'termination_b_id': 'Cables cannot be terminated to {} interfaces'.format(
|
if term.type in NONCONNECTABLE_IFACE_TYPES:
|
||||||
self.termination_b.get_type_display()
|
raise ValidationError({
|
||||||
)
|
'termination_b_id': f'Cables cannot be terminated to {term.get_type_display()} interfaces'
|
||||||
})
|
})
|
||||||
|
|
||||||
# Check that termination types are compatible
|
# Check that termination types are compatible
|
||||||
if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
|
if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
|
||||||
@ -216,50 +221,48 @@ class Cable(NetBoxModel):
|
|||||||
f"Incompatible termination types: {self.termination_a_type} and {self.termination_b_type}"
|
f"Incompatible termination types: {self.termination_a_type} and {self.termination_b_type}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check that two connected RearPorts have the same number of positions (if both are >1)
|
# TODO: Is this validation still necessary?
|
||||||
if isinstance(self.termination_a, RearPort) and isinstance(self.termination_b, RearPort):
|
# # Check that two connected RearPorts have the same number of positions (if both are >1)
|
||||||
if self.termination_a.positions > 1 and self.termination_b.positions > 1:
|
# if isinstance(self.termination_a, RearPort) and isinstance(self.termination_b, RearPort):
|
||||||
if self.termination_a.positions != self.termination_b.positions:
|
# if self.termination_a.positions > 1 and self.termination_b.positions > 1:
|
||||||
raise ValidationError(
|
# if self.termination_a.positions != self.termination_b.positions:
|
||||||
f"{self.termination_a} has {self.termination_a.positions} position(s) but "
|
# raise ValidationError(
|
||||||
f"{self.termination_b} has {self.termination_b.positions}. "
|
# f"{self.termination_a} has {self.termination_a.positions} position(s) but "
|
||||||
f"Both terminations must have the same number of positions (if greater than one)."
|
# f"{self.termination_b} has {self.termination_b.positions}. "
|
||||||
)
|
# f"Both terminations must have the same number of positions (if greater than one)."
|
||||||
|
# )
|
||||||
|
|
||||||
# A termination point cannot be connected to itself
|
# A termination point cannot be connected to itself
|
||||||
if self.termination_a == self.termination_b:
|
if set(self.termination_a).intersection(self.termination_b):
|
||||||
raise ValidationError(f"Cannot connect {self.termination_a_type} to itself")
|
raise ValidationError(f"Cannot connect {self.termination_a_type} to itself")
|
||||||
|
|
||||||
# A front port cannot be connected to its corresponding rear port
|
# TODO
|
||||||
if (
|
# # A front port cannot be connected to its corresponding rear port
|
||||||
type_a in ['frontport', 'rearport'] and
|
# if (
|
||||||
type_b in ['frontport', 'rearport'] and
|
# type_a in ['frontport', 'rearport'] and
|
||||||
(
|
# type_b in ['frontport', 'rearport'] and
|
||||||
getattr(self.termination_a, 'rear_port', None) == self.termination_b or
|
# (
|
||||||
getattr(self.termination_b, 'rear_port', None) == self.termination_a
|
# getattr(self.termination_a, 'rear_port', None) == self.termination_b or
|
||||||
)
|
# getattr(self.termination_b, 'rear_port', None) == self.termination_a
|
||||||
):
|
# )
|
||||||
raise ValidationError("A front port cannot be connected to it corresponding rear port")
|
# ):
|
||||||
|
# raise ValidationError("A front port cannot be connected to it corresponding rear port")
|
||||||
|
|
||||||
# A CircuitTermination attached to a ProviderNetwork cannot have a Cable
|
# TODO
|
||||||
if isinstance(self.termination_a, CircuitTermination) and self.termination_a.provider_network is not None:
|
# # A CircuitTermination attached to a ProviderNetwork cannot have a Cable
|
||||||
raise ValidationError({
|
# if isinstance(self.termination_a, CircuitTermination) and self.termination_a.provider_network is not None:
|
||||||
'termination_a_id': "Circuit terminations attached to a provider network may not be cabled."
|
# raise ValidationError({
|
||||||
})
|
# 'termination_a_id': "Circuit terminations attached to a provider network may not be cabled."
|
||||||
if isinstance(self.termination_b, CircuitTermination) and self.termination_b.provider_network is not None:
|
# })
|
||||||
raise ValidationError({
|
# if isinstance(self.termination_b, CircuitTermination) and self.termination_b.provider_network is not None:
|
||||||
'termination_b_id': "Circuit terminations attached to a provider network may not be cabled."
|
# raise ValidationError({
|
||||||
})
|
# 'termination_b_id': "Circuit terminations attached to a provider network may not be cabled."
|
||||||
|
# })
|
||||||
|
|
||||||
# Check for an existing Cable connected to either termination object
|
# Check for an existing Cable connected to either termination object
|
||||||
if self.termination_a.cable not in (None, self):
|
for term in [*self.termination_a, *self.termination_b]:
|
||||||
raise ValidationError("{} already has a cable attached (#{})".format(
|
if term.cable not in (None, self):
|
||||||
self.termination_a, self.termination_a.cable_id
|
raise ValidationError(f'{term} already has a cable attached (#{term.cable_id})')
|
||||||
))
|
|
||||||
if self.termination_b.cable not in (None, self):
|
|
||||||
raise ValidationError("{} already has a cable attached (#{})".format(
|
|
||||||
self.termination_b, self.termination_b.cable_id
|
|
||||||
))
|
|
||||||
|
|
||||||
# Validate length and length_unit
|
# Validate length and length_unit
|
||||||
if self.length is not None and not self.length_unit:
|
if self.length is not None and not self.length_unit:
|
||||||
@ -276,10 +279,10 @@ class Cable(NetBoxModel):
|
|||||||
self._abs_length = None
|
self._abs_length = None
|
||||||
|
|
||||||
# Store the parent Device for the A and B terminations (if applicable) to enable filtering
|
# Store the parent Device for the A and B terminations (if applicable) to enable filtering
|
||||||
if hasattr(self.termination_a, 'device'):
|
if hasattr(self.termination_a[0], 'device'):
|
||||||
self._termination_a_device = self.termination_a.device
|
self._termination_a_device = self.termination_a[0].device
|
||||||
if hasattr(self.termination_b, 'device'):
|
if hasattr(self.termination_b[0], 'device'):
|
||||||
self._termination_b_device = self.termination_b.device
|
self._termination_b_device = self.termination_b[0].device
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@ -289,14 +292,6 @@ class Cable(NetBoxModel):
|
|||||||
def get_status_color(self):
|
def get_status_color(self):
|
||||||
return LinkStatusChoices.colors.get(self.status)
|
return LinkStatusChoices.colors.get(self.status)
|
||||||
|
|
||||||
def get_compatible_types(self):
|
|
||||||
"""
|
|
||||||
Return all termination types compatible with termination A.
|
|
||||||
"""
|
|
||||||
if self.termination_a is None:
|
|
||||||
return
|
|
||||||
return COMPATIBLE_TERMINATION_TYPES[self.termination_a._meta.model_name]
|
|
||||||
|
|
||||||
|
|
||||||
class CablePath(models.Model):
|
class CablePath(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -79,21 +79,24 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
|
|||||||
logger.debug(f"Skipping endpoint updates for imported cable {instance}")
|
logger.debug(f"Skipping endpoint updates for imported cable {instance}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Cache the Cable on its two termination points
|
# TODO: Update link peer fields
|
||||||
if instance.termination_a.cable != instance:
|
# Cache the Cable on its termination points
|
||||||
logger.debug(f"Updating termination A for cable {instance}")
|
for term in instance.termination_a:
|
||||||
instance.termination_a.cable = instance
|
if term.cable != instance:
|
||||||
instance.termination_a._link_peer = instance.termination_b
|
logger.debug(f"Updating termination A for cable {instance}: {term}")
|
||||||
instance.termination_a.save()
|
term.cable = instance
|
||||||
if instance.termination_b.cable != instance:
|
# term._link_peer = instance.termination_b
|
||||||
logger.debug(f"Updating termination B for cable {instance}")
|
term.save()
|
||||||
instance.termination_b.cable = instance
|
for term in instance.termination_b:
|
||||||
instance.termination_b._link_peer = instance.termination_a
|
if term.cable != instance:
|
||||||
instance.termination_b.save()
|
logger.debug(f"Updating termination B for cable {instance}")
|
||||||
|
term.cable = instance
|
||||||
|
# term._link_peer = instance.termination_a
|
||||||
|
term.save()
|
||||||
|
|
||||||
# Create/update cable paths
|
# Create/update cable paths
|
||||||
if created:
|
if created:
|
||||||
for termination in (instance.termination_a, instance.termination_b):
|
for termination in [*instance.termination_a, *instance.termination_b]:
|
||||||
if isinstance(termination, PathEndpoint):
|
if isinstance(termination, PathEndpoint):
|
||||||
create_cablepath(termination)
|
create_cablepath(termination)
|
||||||
else:
|
else:
|
||||||
@ -116,14 +119,14 @@ def nullify_connected_endpoints(instance, **kwargs):
|
|||||||
logger = logging.getLogger('netbox.dcim.cable')
|
logger = logging.getLogger('netbox.dcim.cable')
|
||||||
|
|
||||||
# Disassociate the Cable from its termination points
|
# Disassociate the Cable from its termination points
|
||||||
if instance.termination_a is not None:
|
if instance.termination_a:
|
||||||
logger.debug(f"Nullifying termination A for cable {instance}")
|
logger.debug(f"Nullifying termination A for cable {instance}")
|
||||||
model = instance.termination_a._meta.model
|
model = instance.termination_a_type.model_class()
|
||||||
model.objects.filter(pk=instance.termination_a.pk).update(_link_peer_type=None, _link_peer_id=None)
|
model.objects.filter(pk__in=instance.termination_a_ids).update(_link_peer_type=None, _link_peer_id=None)
|
||||||
if instance.termination_b is not None:
|
if instance.termination_b:
|
||||||
logger.debug(f"Nullifying termination B for cable {instance}")
|
logger.debug(f"Nullifying termination B for cable {instance}")
|
||||||
model = instance.termination_b._meta.model
|
model = instance.termination_b_type.model_class()
|
||||||
model.objects.filter(pk=instance.termination_b.pk).update(_link_peer_type=None, _link_peer_id=None)
|
model.objects.filter(pk__in=instance.termination_b_ids).update(_link_peer_type=None, _link_peer_id=None)
|
||||||
|
|
||||||
# Delete and retrace any dependent cable paths
|
# Delete and retrace any dependent cable paths
|
||||||
for cablepath in CablePath.objects.filter(path__contains=instance):
|
for cablepath in CablePath.objects.filter(path__contains=instance):
|
||||||
|
@ -4,7 +4,7 @@ from django_tables2.utils import Accessor
|
|||||||
from dcim.models import Cable
|
from dcim.models import Cable
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import NetBoxTable, columns
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from .template_code import CABLE_LENGTH, CABLE_TERMINATION_PARENT
|
from .template_code import CABLE_LENGTH, CABLE_TERMINATION, CABLE_TERMINATION_PARENT
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CableTable',
|
'CableTable',
|
||||||
@ -28,7 +28,8 @@ class CableTable(NetBoxTable):
|
|||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name='Rack A'
|
verbose_name='Rack A'
|
||||||
)
|
)
|
||||||
termination_a = tables.Column(
|
termination_a = tables.TemplateColumn(
|
||||||
|
template_code=CABLE_TERMINATION,
|
||||||
accessor=Accessor('termination_a'),
|
accessor=Accessor('termination_a'),
|
||||||
orderable=False,
|
orderable=False,
|
||||||
linkify=True,
|
linkify=True,
|
||||||
@ -46,7 +47,8 @@ class CableTable(NetBoxTable):
|
|||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name='Rack B'
|
verbose_name='Rack B'
|
||||||
)
|
)
|
||||||
termination_b = tables.Column(
|
termination_b = tables.TemplateColumn(
|
||||||
|
template_code=CABLE_TERMINATION,
|
||||||
accessor=Accessor('termination_b'),
|
accessor=Accessor('termination_b'),
|
||||||
orderable=False,
|
orderable=False,
|
||||||
linkify=True,
|
linkify=True,
|
||||||
|
@ -13,14 +13,20 @@ CABLE_LENGTH = """
|
|||||||
{% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %}
|
{% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
CABLE_TERMINATION = """
|
||||||
|
{{ value|join:", " }}
|
||||||
|
"""
|
||||||
|
|
||||||
CABLE_TERMINATION_PARENT = """
|
CABLE_TERMINATION_PARENT = """
|
||||||
{% if value.device %}
|
{% with value.0 as termination %}
|
||||||
<a href="{{ value.device.get_absolute_url }}">{{ value.device }}</a>
|
{% if termination.device %}
|
||||||
{% elif value.circuit %}
|
<a href="{{ termination.device.get_absolute_url }}">{{ termination.device }}</a>
|
||||||
<a href="{{ value.circuit.get_absolute_url }}">{{ value.circuit }}</a>
|
{% elif termination.circuit %}
|
||||||
{% elif value.power_panel %}
|
<a href="{{ termination.circuit.get_absolute_url }}">{{ termination.circuit }}</a>
|
||||||
<a href="{{ value.power_panel.get_absolute_url }}">{{ value.power_panel }}</a>
|
{% elif termination.power_panel %}
|
||||||
{% endif %}
|
<a href="{{ termination.power_panel.get_absolute_url }}">{{ termination.power_panel }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEVICE_LINK = """
|
DEVICE_LINK = """
|
||||||
|
@ -2831,12 +2831,13 @@ class CableCreateView(generic.ObjectEditView):
|
|||||||
|
|
||||||
def alter_object(self, obj, request, url_args, url_kwargs):
|
def alter_object(self, obj, request, url_args, url_kwargs):
|
||||||
termination_a_type = url_kwargs.get('termination_a_type')
|
termination_a_type = url_kwargs.get('termination_a_type')
|
||||||
termination_a_id = request.GET.get('termination_a_id')
|
termination_a_ids = request.GET.get('termination_a_ids', [])
|
||||||
app_label, model = request.GET.get('termination_b_type').split('.')
|
app_label, model = request.GET.get('termination_b_type').split('.')
|
||||||
self.termination_b_type = ContentType.objects.get(app_label=app_label, model=model)
|
self.termination_b_type = ContentType.objects.get(app_label=app_label, model=model)
|
||||||
|
|
||||||
# Initialize Cable termination attributes
|
# Initialize Cable termination attributes
|
||||||
obj.termination_a = termination_a_type.objects.get(pk=termination_a_id)
|
obj.termination_a_type = ContentType.objects.get_for_model(termination_a_type)
|
||||||
|
obj.termination_a_ids = termination_a_type.objects.filter(pk__in=termination_a_ids)
|
||||||
obj.termination_b_type = self.termination_b_type
|
obj.termination_b_type = self.termination_b_type
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
@ -2844,21 +2845,19 @@ class CableCreateView(generic.ObjectEditView):
|
|||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
obj = self.get_object(**kwargs)
|
obj = self.get_object(**kwargs)
|
||||||
obj = self.alter_object(obj, request, args, kwargs)
|
obj = self.alter_object(obj, request, args, kwargs)
|
||||||
|
initial_data = request.GET
|
||||||
|
|
||||||
# Parse initial data manually to avoid setting field values as lists
|
# TODO
|
||||||
initial_data = {k: request.GET[k] for k in request.GET}
|
# # Set initial site and rack based on side A termination (if not already set)
|
||||||
|
# termination_a_site = getattr(obj.termination_a.parent_object, 'site', None)
|
||||||
# Set initial site and rack based on side A termination (if not already set)
|
# if 'termination_b_site' not in initial_data:
|
||||||
termination_a_site = getattr(obj.termination_a.parent_object, 'site', None)
|
# initial_data['termination_b_site'] = termination_a_site
|
||||||
if 'termination_b_site' not in initial_data:
|
# if 'termination_b_rack' not in initial_data:
|
||||||
initial_data['termination_b_site'] = termination_a_site
|
# initial_data['termination_b_rack'] = getattr(obj.termination_a.parent_object, 'rack', None)
|
||||||
if 'termination_b_rack' not in initial_data:
|
|
||||||
initial_data['termination_b_rack'] = getattr(obj.termination_a.parent_object, 'rack', None)
|
|
||||||
|
|
||||||
form = self.form(instance=obj, initial=initial_data)
|
form = self.form(instance=obj, initial=initial_data)
|
||||||
|
|
||||||
# Set the queryset of termination A
|
# Set the queryset of termination A
|
||||||
form.fields['termination_a_id'].queryset = kwargs['termination_a_type'].objects.all()
|
form.fields['termination_a_ids'].queryset = kwargs['termination_a_type'].objects.all()
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'obj': obj,
|
||||||
|
@ -5,85 +5,79 @@
|
|||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">Cable</h5>
|
||||||
Cable
|
<div class="card-body">
|
||||||
</h5>
|
<table class="table table-hover attr-table">
|
||||||
<div class="card-body">
|
<tr>
|
||||||
<table class="table table-hover attr-table">
|
<th scope="row">Type</th>
|
||||||
<tr>
|
<td>{{ object.get_type_display|placeholder }}</td>
|
||||||
<th scope="row">Type</th>
|
</tr>
|
||||||
<td>{{ object.get_type_display|placeholder }}</td>
|
<tr>
|
||||||
</tr>
|
<th scope="row">Status</th>
|
||||||
<tr>
|
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||||
<th scope="row">Status</th>
|
</tr>
|
||||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
<tr>
|
||||||
</tr>
|
<th scope="row">Tenant</th>
|
||||||
<tr>
|
<td>
|
||||||
<th scope="row">Tenant</th>
|
{% if object.tenant.group %}
|
||||||
<td>
|
{{ object.tenant.group|linkify }} /
|
||||||
{% if object.tenant.group %}
|
{% endif %}
|
||||||
{{ object.tenant.group|linkify }} /
|
{{ object.tenant|linkify|placeholder }}
|
||||||
{% endif %}
|
</td>
|
||||||
{{ object.tenant|linkify|placeholder }}
|
</tr>
|
||||||
</td>
|
<tr>
|
||||||
</tr>
|
<th scope="row">Label</th>
|
||||||
<tr>
|
<td>{{ object.label|placeholder }}</td>
|
||||||
<th scope="row">Label</th>
|
</tr>
|
||||||
<td>{{ object.label|placeholder }}</td>
|
<tr>
|
||||||
</tr>
|
<th scope="row">Color</th>
|
||||||
<tr>
|
<td>
|
||||||
<th scope="row">Color</th>
|
{% if object.color %}
|
||||||
<td>
|
<span class="color-label" style="background-color: #{{ object.color }}"> </span>
|
||||||
{% if object.color %}
|
{% else %}
|
||||||
<span class="color-label" style="background-color: #{{ object.color }}"> </span>
|
<span class="text-muted">—</span>
|
||||||
{% else %}
|
{% endif %}
|
||||||
<span class="text-muted">—</span>
|
</td>
|
||||||
{% endif %}
|
</tr>
|
||||||
</td>
|
<tr>
|
||||||
</tr>
|
<th scope="row">Length</th>
|
||||||
<tr>
|
<td>
|
||||||
<th scope="row">Length</th>
|
{% if object.length %}
|
||||||
<td>
|
{{ object.length|floatformat }} {{ object.get_length_unit_display }}
|
||||||
{% if object.length %}
|
{% else %}
|
||||||
{{ object.length|floatformat }} {{ object.get_length_unit_display }}
|
<span class="text-muted">—</span>
|
||||||
{% else %}
|
{% endif %}
|
||||||
<span class="text-muted">—</span>
|
</td>
|
||||||
{% endif %}
|
</tr>
|
||||||
</td>
|
</table>
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
|
||||||
{% include 'inc/panels/tags.html' %}
|
|
||||||
{% plugin_left_page object %}
|
|
||||||
</div>
|
|
||||||
<div class="col col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">
|
|
||||||
Termination A
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
{% include 'dcim/inc/cable_termination.html' with termination=object.termination_a %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">
|
|
||||||
Termination B
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
{% include 'dcim/inc/cable_termination.html' with termination=object.termination_b %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% plugin_right_page object %}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="col col-md-6">
|
||||||
<div class="col col-md-12">
|
<div class="card">
|
||||||
{% plugin_full_width_page object %}
|
<h5 class="card-header">Termination A</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'dcim/inc/cable_termination.html' with termination=object.termination_a %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">Termination B</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'dcim/inc/cable_termination.html' with termination=object.termination_b %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load form_helpers %}
|
{% load form_helpers %}
|
||||||
|
|
||||||
{% block title %}Connect {{ form.instance.termination_a.device }} {{ form.instance.termination_a }} to {{ termination_b_type|bettertitle }}{% endblock %}
|
{% block title %}Connect Cable to {{ termination_b_type|bettertitle }}{% endblock %}
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs px-3">
|
<ul class="nav nav-tabs px-3">
|
||||||
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
{% with termination_a=form.instance.termination_a %}
|
{% with termination_a=form.instance.termination_a.0 %}
|
||||||
{% render_errors form %}
|
{% render_errors form %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -92,7 +92,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% render_field form.termination_a_id %}
|
{% render_field form.termination_a_ids %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -148,7 +148,7 @@
|
|||||||
<input class="form-control" value="{{ termination_b_type|capfirst }}" disabled />
|
<input class="form-control" value="{{ termination_b_type|capfirst }}" disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.termination_b_id %}
|
{% render_field form.termination_b_ids %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,42 +1,46 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
{% if termination.device %}
|
{% if termination.0.device %}
|
||||||
{# Device component #}
|
{# Device component #}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device</td>
|
<td>Device</td>
|
||||||
<td>{{ termination.device|linkify }}</td>
|
<td>{{ termination.0.device|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Site</td>
|
<td>Site</td>
|
||||||
<td>{{ termination.device.site|linkify }}</td>
|
<td>{{ termination.0.device.site|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if termination.device.rack %}
|
{% if termination.0.device.rack %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rack</td>
|
<td>Rack</td>
|
||||||
<td>{{ termination.device.rack|linkify }}</td>
|
<td>{{ termination.0.device.rack|linkify }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
|
||||||
<tr>
|
|
||||||
<td>Type</td>
|
|
||||||
<td>{{ termination|meta:"verbose_name"|capfirst }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Component</td>
|
|
||||||
<td>{{ termination|linkify }}</td>
|
|
||||||
</tr>
|
|
||||||
{% else %}
|
|
||||||
{# Circuit termination #}
|
|
||||||
<tr>
|
|
||||||
<td>Provider</td>
|
|
||||||
<td>{{ termination.circuit.provider|linkify }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Circuit</td>
|
|
||||||
<td>{{ termination.circuit|linkify }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Termination</td>
|
|
||||||
<td>{{ termination }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<td>Type</td>
|
||||||
|
<td>{{ termination.0|meta:"verbose_name"|capfirst }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Component(s)</td>
|
||||||
|
<td>
|
||||||
|
{% for term in termination %}
|
||||||
|
{{ term|linkify }}{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
{# Circuit termination #}
|
||||||
|
<tr>
|
||||||
|
<td>Provider</td>
|
||||||
|
<td>{{ termination.0.circuit.provider|linkify }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Circuit</td>
|
||||||
|
<td>
|
||||||
|
{% for term in termination %}
|
||||||
|
{{ term.circuit|linkify }} ({{ term }}){% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
|
@ -132,6 +132,7 @@ def serialize_object(obj, extra=None):
|
|||||||
implicitly excluded.
|
implicitly excluded.
|
||||||
"""
|
"""
|
||||||
json_str = serialize('json', [obj])
|
json_str = serialize('json', [obj])
|
||||||
|
print(json_str)
|
||||||
data = json.loads(json_str)[0]['fields']
|
data = json.loads(json_str)[0]['fields']
|
||||||
|
|
||||||
# Exclude any MPTTModel fields
|
# Exclude any MPTTModel fields
|
||||||
|
Loading…
Reference in New Issue
Block a user