Reference WirelessLink on both attached Interfaces

This commit is contained in:
jeremystretch 2021-10-13 11:48:15 -04:00
parent 90e9f34494
commit 445e16f668
7 changed files with 116 additions and 11 deletions

View 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'),
),
]

View File

@ -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):

View File

@ -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 %}

View File

@ -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 %}

View File

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

View File

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

View 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
)