mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 17:08:41 -06:00
Reference WirelessLink on both attached Interfaces
This commit is contained in:
parent
90e9f34494
commit
445e16f668
20
netbox/dcim/migrations/0138_interface_wireless_link.py
Normal file
20
netbox/dcim/migrations/0138_interface_wireless_link.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 3.2.8 on 2021-10-13 15:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wireless', '0001_wireless'),
|
||||||
|
('dcim', '0137_wireless'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='wireless_link',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wireless.wirelesslink'),
|
||||||
|
),
|
||||||
|
]
|
@ -529,6 +529,13 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
|
|||||||
null=True,
|
null=True,
|
||||||
verbose_name='Channel width'
|
verbose_name='Channel width'
|
||||||
)
|
)
|
||||||
|
wireless_link = models.ForeignKey(
|
||||||
|
to='wireless.WirelessLink',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
wireless_lans = models.ManyToManyField(
|
wireless_lans = models.ManyToManyField(
|
||||||
to='wireless.WirelessLAN',
|
to='wireless.WirelessLAN',
|
||||||
related_name='interfaces',
|
related_name='interfaces',
|
||||||
@ -568,14 +575,14 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Virtual interfaces cannot be connected
|
# Virtual Interfaces cannot have a Cable attached
|
||||||
if not self.is_connectable and self.cable:
|
if self.is_virtual and self.cable:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'type': f"{self.get_type_display()} interfaces cannot have a cable attached."
|
'type': f"{self.get_type_display()} interfaces cannot have a cable attached."
|
||||||
})
|
})
|
||||||
|
|
||||||
# Non-connectable interfaces cannot be marked as connected
|
# Virtual Interfaces cannot be marked as connected
|
||||||
if not self.is_connectable and self.mark_connected:
|
if self.is_virtual and self.mark_connected:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'mark_connected': f"{self.get_type_display()} interfaces cannot be marked as connected."
|
'mark_connected': f"{self.get_type_display()} interfaces cannot be marked as connected."
|
||||||
})
|
})
|
||||||
@ -635,8 +642,8 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
|
|||||||
})
|
})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_connectable(self):
|
def is_wired(self):
|
||||||
return self.type not in NONCONNECTABLE_IFACE_TYPES
|
return not self.is_virtual and not self.is_wireless
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_virtual(self):
|
def is_virtual(self):
|
||||||
|
@ -203,7 +203,7 @@ INTERFACE_BUTTONS = """
|
|||||||
<i class="mdi mdi-ethernet-cable-off" aria-hidden="true"></i>
|
<i class="mdi mdi-ethernet-cable-off" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif record.is_connectable and perms.dcim.add_cable %}
|
{% elif record.is_wired and perms.dcim.add_cable %}
|
||||||
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i></a>
|
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i></a>
|
||||||
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-lan-connect" aria-hidden="true"></i></a>
|
<a href="#" class="btn btn-outline-dark btn-sm disabled"><i class="mdi mdi-lan-connect" aria-hidden="true"></i></a>
|
||||||
{% if not record.mark_connected %}
|
{% if not record.mark_connected %}
|
||||||
|
@ -117,7 +117,7 @@
|
|||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
{% if object.is_connectable %}
|
{% if not object.is_virtual %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Connection
|
Connection
|
||||||
@ -221,10 +221,19 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
{% elif object.wireless_link %}
|
||||||
|
<table class="table table-hover">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Wireless Link</th>
|
||||||
|
<td>
|
||||||
|
<a href="{{ object.wireless_link.get_absolute_url }}">{{ object.wireless_link }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Not Connected
|
Not Connected
|
||||||
{% if perms.dcim.add_cable %}
|
{% if object.is_wired and perms.dcim.add_cable %}
|
||||||
<div class="dropdown float-end">
|
<div class="dropdown float-end">
|
||||||
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
|
||||||
@ -252,6 +261,12 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{% elif object.is_wireless and perms.wireless.add_wirelesslink %}
|
||||||
|
<div class="dropdown float-end">
|
||||||
|
<a href="{% url 'wireless:wirelesslink_add' %}?interface_a={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm">
|
||||||
|
<span class="mdi mdi-wifi-plus" aria-hidden="true"></span> Connect
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -3,3 +3,6 @@ from django.apps import AppConfig
|
|||||||
|
|
||||||
class WirelessConfig(AppConfig):
|
class WirelessConfig(AppConfig):
|
||||||
name = 'wireless'
|
name = 'wireless'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import wireless.signals
|
||||||
|
@ -37,13 +37,15 @@ class WirelessLinkForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
query_params={
|
query_params={
|
||||||
'kind': 'wireless'
|
'kind': 'wireless'
|
||||||
}
|
},
|
||||||
|
label='Interface A'
|
||||||
)
|
)
|
||||||
interface_b = DynamicModelChoiceField(
|
interface_b = DynamicModelChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
query_params={
|
query_params={
|
||||||
'kind': 'wireless'
|
'kind': 'wireless'
|
||||||
}
|
},
|
||||||
|
label='Interface B'
|
||||||
)
|
)
|
||||||
tags = DynamicModelMultipleChoiceField(
|
tags = DynamicModelMultipleChoiceField(
|
||||||
queryset=Tag.objects.all(),
|
queryset=Tag.objects.all(),
|
||||||
|
58
netbox/wireless/signals.py
Normal file
58
netbox/wireless/signals.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from django.db.models.signals import post_save, post_delete
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from dcim.models import Interface
|
||||||
|
from .models import WirelessLink
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Wireless links
|
||||||
|
#
|
||||||
|
|
||||||
|
@receiver(post_save, sender=WirelessLink)
|
||||||
|
def update_connected_interfaces(instance, raw=False, **kwargs):
|
||||||
|
"""
|
||||||
|
When a WirelessLink is saved, save a reference to it on each connected interface.
|
||||||
|
"""
|
||||||
|
print('update_connected_interfaces')
|
||||||
|
logger = logging.getLogger('netbox.wireless.wirelesslink')
|
||||||
|
if raw:
|
||||||
|
logger.debug(f"Skipping endpoint updates for imported wireless link {instance}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if instance.interface_a.wireless_link != instance:
|
||||||
|
logger.debug(f"Updating interface A for wireless link {instance}")
|
||||||
|
instance.interface_a.wireless_link = instance
|
||||||
|
# instance.interface_a._cable_peer = instance.interface_b # TODO: Rename _cable_peer field
|
||||||
|
instance.interface_a.save()
|
||||||
|
if instance.interface_b.cable != instance:
|
||||||
|
logger.debug(f"Updating interface B for wireless link {instance}")
|
||||||
|
instance.interface_b.wireless_link = instance
|
||||||
|
# instance.interface_b._cable_peer = instance.interface_a
|
||||||
|
instance.interface_b.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=WirelessLink)
|
||||||
|
def nullify_connected_interfaces(instance, **kwargs):
|
||||||
|
"""
|
||||||
|
When a WirelessLink is deleted, update its two connected Interfaces
|
||||||
|
"""
|
||||||
|
print('nullify_connected_interfaces')
|
||||||
|
logger = logging.getLogger('netbox.wireless.wirelesslink')
|
||||||
|
|
||||||
|
if instance.interface_a is not None:
|
||||||
|
logger.debug(f"Nullifying interface A for wireless link {instance}")
|
||||||
|
Interface.objects.filter(pk=instance.interface_a.pk).update(
|
||||||
|
wireless_link=None,
|
||||||
|
_cable_peer_type=None,
|
||||||
|
_cable_peer_id=None
|
||||||
|
)
|
||||||
|
if instance.interface_b is not None:
|
||||||
|
logger.debug(f"Nullifying interface B for wireless link {instance}")
|
||||||
|
Interface.objects.filter(pk=instance.interface_b.pk).update(
|
||||||
|
wireless_link=None,
|
||||||
|
_cable_peer_type=None,
|
||||||
|
_cable_peer_id=None
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user