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 %}
Connection @@ -221,10 +221,19 @@ + {% elif object.wireless_link %} + + + + + +
Wireless Link + {{ object.wireless_link }} +
{% 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 + )