diff --git a/netbox/dcim/migrations/0138_interface_wireless_link.py b/netbox/dcim/migrations/0138_interface_wireless_link.py
new file mode 100644
index 000000000..42b7a1042
--- /dev/null
+++ b/netbox/dcim/migrations/0138_interface_wireless_link.py
@@ -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'),
+ ),
+ ]
diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py
index 60eb4c368..dc8246990 100644
--- a/netbox/dcim/models/device_components.py
+++ b/netbox/dcim/models/device_components.py
@@ -529,6 +529,13 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
null=True,
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(
to='wireless.WirelessLAN',
related_name='interfaces',
@@ -568,14 +575,14 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
def clean(self):
super().clean()
- # Virtual interfaces cannot be connected
- if not self.is_connectable and self.cable:
+ # Virtual Interfaces cannot have a Cable attached
+ if self.is_virtual and self.cable:
raise ValidationError({
'type': f"{self.get_type_display()} interfaces cannot have a cable attached."
})
- # Non-connectable interfaces cannot be marked as connected
- if not self.is_connectable and self.mark_connected:
+ # Virtual Interfaces cannot be marked as connected
+ if self.is_virtual and self.mark_connected:
raise ValidationError({
'mark_connected': f"{self.get_type_display()} interfaces cannot be marked as connected."
})
@@ -635,8 +642,8 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint):
})
@property
- def is_connectable(self):
- return self.type not in NONCONNECTABLE_IFACE_TYPES
+ def is_wired(self):
+ return not self.is_virtual and not self.is_wireless
@property
def is_virtual(self):
diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py
index 2f359e1b9..9e2dc519b 100644
--- a/netbox/dcim/tables/template_code.py
+++ b/netbox/dcim/tables/template_code.py
@@ -203,7 +203,7 @@ INTERFACE_BUTTONS = """
{% endif %}
-{% elif record.is_connectable and perms.dcim.add_cable %}
+{% elif record.is_wired and perms.dcim.add_cable %}
{% if not record.mark_connected %}
diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html
index 33eaa95db..bf24a89ef 100644
--- a/netbox/templates/dcim/interface.html
+++ b/netbox/templates/dcim/interface.html
@@ -117,7 +117,7 @@
{% plugin_left_page object %}
- {% if object.is_connectable %}
+ {% if not object.is_virtual %}
{% else %}
Not Connected
- {% if perms.dcim.add_cable %}
+ {% if object.is_wired and perms.dcim.add_cable %}
+ {% elif object.is_wireless and perms.wireless.add_wirelesslink %}
+
{% endif %}
{% endif %}
diff --git a/netbox/wireless/apps.py b/netbox/wireless/apps.py
index 1f6deff22..59e47aba5 100644
--- a/netbox/wireless/apps.py
+++ b/netbox/wireless/apps.py
@@ -3,3 +3,6 @@ from django.apps import AppConfig
class WirelessConfig(AppConfig):
name = 'wireless'
+
+ def ready(self):
+ import wireless.signals
diff --git a/netbox/wireless/forms/models.py b/netbox/wireless/forms/models.py
index 08e864340..c494fb5a2 100644
--- a/netbox/wireless/forms/models.py
+++ b/netbox/wireless/forms/models.py
@@ -37,13 +37,15 @@ class WirelessLinkForm(BootstrapMixin, CustomFieldModelForm):
queryset=Interface.objects.all(),
query_params={
'kind': 'wireless'
- }
+ },
+ label='Interface A'
)
interface_b = DynamicModelChoiceField(
queryset=Interface.objects.all(),
query_params={
'kind': 'wireless'
- }
+ },
+ label='Interface B'
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
diff --git a/netbox/wireless/signals.py b/netbox/wireless/signals.py
new file mode 100644
index 000000000..a42566a00
--- /dev/null
+++ b/netbox/wireless/signals.py
@@ -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
+ )