mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-21 19:47:20 -06:00
Preliminary work on Cables
This commit is contained in:
parent
3eddeeadc5
commit
ea5121ffe1
@ -282,3 +282,20 @@ CONNECTION_STATUS_CHOICES = [
|
||||
[CONNECTION_STATUS_PLANNED, 'Planned'],
|
||||
[CONNECTION_STATUS_CONNECTED, 'Connected'],
|
||||
]
|
||||
|
||||
# Cable endpoint types
|
||||
CABLE_ENDPOINT_TYPES = (
|
||||
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport',
|
||||
)
|
||||
CABLE_CONNECTION_TYPES = CABLE_ENDPOINT_TYPES + (
|
||||
'frontpanelport', 'rearpanelport',
|
||||
)
|
||||
|
||||
# Cable types
|
||||
# TODO: Add more types
|
||||
CABLE_TYPE_COPPER = 1000
|
||||
CABLE_TYPE_FIBER = 2000
|
||||
CABLE_TYPE_CHOICES = (
|
||||
(CABLE_TYPE_COPPER, 'Copper'),
|
||||
(CABLE_TYPE_FIBER, 'Fiber'),
|
||||
)
|
||||
|
178
netbox/dcim/migrations/0066_cables.py
Normal file
178
netbox/dcim/migrations/0066_cables.py
Normal file
@ -0,0 +1,178 @@
|
||||
# Generated by Django 2.0.8 on 2018-10-18 19:41
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import utilities.fields
|
||||
|
||||
|
||||
def console_connections_to_cables(apps, schema_editor):
|
||||
"""
|
||||
Copy all existing console connections as Cables
|
||||
"""
|
||||
ConsolePort = apps.get_model('dcim', 'ConsolePort')
|
||||
Cable = apps.get_model('dcim', 'Cable')
|
||||
|
||||
# Load content types
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
consoleport_type = ContentType.objects.get(app_label='dcim', model='consoleport')
|
||||
consoleserverport_type = ContentType.objects.get(app_label='dcim', model='consoleserverport')
|
||||
|
||||
# Create a new Cable instance from each console connection
|
||||
for consoleport in ConsolePort.objects.filter(cs_port__isnull=False):
|
||||
c = Cable()
|
||||
# We have to assign GFK fields manually because we're inside a migration.
|
||||
c.endpoint_a_type = consoleport_type
|
||||
c.endpoint_a_id = consoleport.id
|
||||
c.endpoint_b_type = consoleserverport_type
|
||||
c.endpoint_b_id = consoleport.cs_port_id
|
||||
c.connection_status = consoleport.connection_status
|
||||
c.save()
|
||||
|
||||
|
||||
def power_connections_to_cables(apps, schema_editor):
|
||||
"""
|
||||
Copy all existing power connections as Cables
|
||||
"""
|
||||
PowerPort = apps.get_model('dcim', 'PowerPort')
|
||||
Cable = apps.get_model('dcim', 'Cable')
|
||||
|
||||
# Load content types
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
powerport_type = ContentType.objects.get(app_label='dcim', model='powerport')
|
||||
poweroutlet_type = ContentType.objects.get(app_label='dcim', model='poweroutlet')
|
||||
|
||||
# Create a new Cable instance from each power connection
|
||||
for powerport in PowerPort.objects.filter(power_outlet__isnull=False):
|
||||
c = Cable()
|
||||
# We have to assign GFK fields manually because we're inside a migration.
|
||||
c.endpoint_a_type = powerport_type
|
||||
c.endpoint_a_id = powerport.id
|
||||
c.endpoint_b_type = poweroutlet_type
|
||||
c.endpoint_b_id = powerport.power_outlet_id
|
||||
c.connection_status = powerport.connection_status
|
||||
c.save()
|
||||
|
||||
|
||||
def interface_connections_to_cables(apps, schema_editor):
|
||||
"""
|
||||
Copy all InterfaceConnections as Cables
|
||||
"""
|
||||
InterfaceConnection = apps.get_model('dcim', 'InterfaceConnection')
|
||||
Cable = apps.get_model('dcim', 'Cable')
|
||||
|
||||
# Load content types
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
interface_type = ContentType.objects.get(app_label='dcim', model='interface')
|
||||
|
||||
# Create a new Cable instance from each InterfaceConnection
|
||||
for conn in InterfaceConnection.objects.all():
|
||||
c = Cable()
|
||||
# We have to assign GFK fields manually because we're inside a migration.
|
||||
c.endpoint_a_type = interface_type
|
||||
c.endpoint_a_id = conn.interface_a_id
|
||||
c.endpoint_b_type = interface_type
|
||||
c.endpoint_b_id = conn.interface_b_id
|
||||
c.connection_status = conn.connection_status
|
||||
c.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('dcim', '0065_patch_panel_ports'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Cable',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('endpoint_a_id', models.PositiveIntegerField()),
|
||||
('endpoint_b_id', models.PositiveIntegerField()),
|
||||
('type', models.PositiveSmallIntegerField(blank=True, null=True)),
|
||||
('status', models.BooleanField(default=True)),
|
||||
('label', models.CharField(blank=True, max_length=100)),
|
||||
('color', utilities.fields.ColorField(blank=True, max_length=6)),
|
||||
('endpoint_a_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')),
|
||||
('endpoint_b_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='connected_endpoint_id',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='connected_endpoint_type',
|
||||
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='connected_endpoint_id',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='connected_endpoint_type',
|
||||
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='connection_status',
|
||||
field=models.NullBooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='connected_endpoint_id',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='connected_endpoint_type',
|
||||
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='connection_status',
|
||||
field=models.NullBooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='connected_endpoint_id',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='connected_endpoint_type',
|
||||
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='connection_status',
|
||||
field=models.NullBooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='connected_endpoint_id',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='connected_endpoint_type',
|
||||
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='cable',
|
||||
unique_together={('endpoint_b_type', 'endpoint_b_id'), ('endpoint_a_type', 'endpoint_a_id')},
|
||||
),
|
||||
|
||||
# Copy console/power/interface connections as Cables
|
||||
migrations.RunPython(console_connections_to_cables),
|
||||
migrations.RunPython(power_connections_to_cables),
|
||||
migrations.RunPython(interface_connections_to_cables),
|
||||
|
||||
]
|
@ -3,7 +3,8 @@ from itertools import count, groupby
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.postgres.fields import ArrayField, JSONField
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
@ -65,6 +66,32 @@ class ComponentModel(models.Model):
|
||||
).save()
|
||||
|
||||
|
||||
class ConnectableModel(models.Model):
|
||||
connected_endpoint_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
limit_choices_to={'model__in': CABLE_ENDPOINT_TYPES},
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
connected_endpoint_id = models.PositiveIntegerField(
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
connected_endpoint = GenericForeignKey(
|
||||
ct_field='connected_endpoint_type',
|
||||
fk_field='connected_endpoint_id'
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
default=CONNECTION_STATUS_CONNECTED
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
#
|
||||
# Regions
|
||||
#
|
||||
@ -1616,7 +1643,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
||||
# Console ports
|
||||
#
|
||||
|
||||
class ConsolePort(ComponentModel):
|
||||
class ConsolePort(ConnectableModel, ComponentModel):
|
||||
"""
|
||||
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
||||
"""
|
||||
@ -1679,7 +1706,7 @@ class ConsoleServerPortManager(models.Manager):
|
||||
}).order_by('device', 'name_padded')
|
||||
|
||||
|
||||
class ConsoleServerPort(ComponentModel):
|
||||
class ConsoleServerPort(ConnectableModel, ComponentModel):
|
||||
"""
|
||||
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
||||
"""
|
||||
@ -1720,7 +1747,7 @@ class ConsoleServerPort(ComponentModel):
|
||||
# Power ports
|
||||
#
|
||||
|
||||
class PowerPort(ComponentModel):
|
||||
class PowerPort(ConnectableModel, ComponentModel):
|
||||
"""
|
||||
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
||||
"""
|
||||
@ -1782,7 +1809,7 @@ class PowerOutletManager(models.Manager):
|
||||
}).order_by('device', 'name_padded')
|
||||
|
||||
|
||||
class PowerOutlet(ComponentModel):
|
||||
class PowerOutlet(ConnectableModel, ComponentModel):
|
||||
"""
|
||||
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
||||
"""
|
||||
@ -1823,7 +1850,7 @@ class PowerOutlet(ComponentModel):
|
||||
# Interfaces
|
||||
#
|
||||
|
||||
class Interface(ComponentModel):
|
||||
class Interface(ConnectableModel, ComponentModel):
|
||||
"""
|
||||
A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other
|
||||
Interface via the creation of an InterfaceConnection.
|
||||
@ -2423,3 +2450,64 @@ class VirtualChassis(ChangeLoggedModel):
|
||||
self.master,
|
||||
self.domain,
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Cables
|
||||
#
|
||||
|
||||
class Cable(ChangeLoggedModel):
|
||||
"""
|
||||
A physical connection between two endpoints.
|
||||
"""
|
||||
endpoint_a_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
limit_choices_to={'model__in': CABLE_CONNECTION_TYPES},
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+'
|
||||
)
|
||||
endpoint_a_id = models.PositiveIntegerField()
|
||||
endpoint_a = GenericForeignKey(
|
||||
ct_field='endpoint_a_type',
|
||||
fk_field='endpoint_a_id'
|
||||
)
|
||||
endpoint_b_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
limit_choices_to={'model__in': CABLE_CONNECTION_TYPES},
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+'
|
||||
)
|
||||
endpoint_b_id = models.PositiveIntegerField()
|
||||
endpoint_b = GenericForeignKey(
|
||||
ct_field='endpoint_b_type',
|
||||
fk_field='endpoint_b_id'
|
||||
)
|
||||
type = models.PositiveSmallIntegerField(
|
||||
choices=CABLE_TYPE_CHOICES,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
status = models.BooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
default=CONNECTION_STATUS_CONNECTED
|
||||
)
|
||||
label = models.CharField(
|
||||
max_length=100,
|
||||
blank=True
|
||||
)
|
||||
color = ColorField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (
|
||||
('endpoint_a_type', 'endpoint_a_id'),
|
||||
('endpoint_b_type', 'endpoint_b_id'),
|
||||
)
|
||||
|
||||
# TODO: This should follow all cables in a path
|
||||
def get_path_endpoints(self):
|
||||
"""
|
||||
Return the endpoints connected by this cable path.
|
||||
"""
|
||||
return (self.endpoint_a, self.endpoint_b)
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.db.models.signals import post_save, post_delete, pre_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from .models import Device, VirtualChassis
|
||||
from .models import Cable, Device, VirtualChassis
|
||||
|
||||
|
||||
@receiver(post_save, sender=VirtualChassis)
|
||||
@ -19,3 +19,27 @@ def clear_virtualchassis_members(instance, **kwargs):
|
||||
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
|
||||
"""
|
||||
Device.objects.filter(virtual_chassis=instance.pk).update(vc_position=None, vc_priority=None)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Cable)
|
||||
def update_connected_endpoints(instance, **kwargs):
|
||||
"""
|
||||
When a Cable is saved, update its connected endpoints.
|
||||
"""
|
||||
endpoint_a, endpoint_b = instance.get_path_endpoints()
|
||||
endpoint_a.connected_endpoint = endpoint_b
|
||||
endpoint_a.save()
|
||||
endpoint_b.connected_endpoint = endpoint_a
|
||||
endpoint_b.save()
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Cable)
|
||||
def nullify_connected_endpoints(instance, **kwargs):
|
||||
"""
|
||||
When a Cable is deleted, nullify its connected endpoints.
|
||||
"""
|
||||
endpoint_a, endpoint_b = instance.get_path_endpoints()
|
||||
endpoint_a.connected_endpoint = None
|
||||
endpoint_a.save()
|
||||
endpoint_b.connected_endpoint = None
|
||||
endpoint_b.save()
|
||||
|
Loading…
Reference in New Issue
Block a user