| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- from django.core.exceptions import ValidationError
- from django.db import models
- from django.utils.translation import gettext_lazy as _
- from dcim.choices import LinkStatusChoices
- from dcim.constants import WIRELESS_IFACE_TYPES
- from dcim.models.mixins import CachedScopeMixin
- from netbox.models import NestedGroupModel, PrimaryModel
- from netbox.models.mixins import DistanceMixin
- from .choices import *
- from .constants import *
- __all__ = (
- 'WirelessLAN',
- 'WirelessLANGroup',
- 'WirelessLink',
- )
- class WirelessAuthenticationBase(models.Model):
- """
- Abstract model for attaching attributes related to wireless authentication.
- """
- auth_type = models.CharField(
- max_length=50,
- choices=WirelessAuthTypeChoices,
- blank=True,
- null=True,
- verbose_name=_("authentication type"),
- )
- auth_cipher = models.CharField(
- verbose_name=_('authentication cipher'),
- max_length=50,
- choices=WirelessAuthCipherChoices,
- blank=True,
- null=True
- )
- auth_psk = models.CharField(
- max_length=PSK_MAX_LENGTH,
- blank=True,
- verbose_name=_('pre-shared key')
- )
- class Meta:
- abstract = True
- class WirelessLANGroup(NestedGroupModel):
- """
- A nested grouping of WirelessLANs
- """
- name = models.CharField(
- verbose_name=_('name'),
- max_length=100,
- unique=True,
- db_collation="natural_sort"
- )
- slug = models.SlugField(
- verbose_name=_('slug'),
- max_length=100,
- unique=True
- )
- class Meta:
- ordering = ('name', 'pk')
- constraints = (
- models.UniqueConstraint(
- fields=('parent', 'name'),
- name='%(app_label)s_%(class)s_unique_parent_name'
- ),
- )
- verbose_name = _('wireless LAN group')
- verbose_name_plural = _('wireless LAN groups')
- class WirelessLAN(WirelessAuthenticationBase, CachedScopeMixin, PrimaryModel):
- """
- A wireless network formed among an arbitrary number of access point and clients.
- """
- ssid = models.CharField(
- max_length=SSID_MAX_LENGTH,
- verbose_name=_('SSID')
- )
- group = models.ForeignKey(
- to='wireless.WirelessLANGroup',
- on_delete=models.SET_NULL,
- related_name='wireless_lans',
- blank=True,
- null=True
- )
- status = models.CharField(
- max_length=50,
- choices=WirelessLANStatusChoices,
- default=WirelessLANStatusChoices.STATUS_ACTIVE,
- verbose_name=_('status')
- )
- vlan = models.ForeignKey(
- to='ipam.VLAN',
- on_delete=models.PROTECT,
- blank=True,
- null=True,
- verbose_name=_('VLAN')
- )
- tenant = models.ForeignKey(
- to='tenancy.Tenant',
- on_delete=models.PROTECT,
- related_name='wireless_lans',
- blank=True,
- null=True
- )
- clone_fields = ('ssid', 'group', 'scope_type', 'scope_id', 'tenant', 'description')
- class Meta:
- ordering = ('ssid', 'pk')
- indexes = (
- models.Index(fields=('scope_type', 'scope_id')),
- )
- verbose_name = _('wireless LAN')
- verbose_name_plural = _('wireless LANs')
- def __str__(self):
- return self.ssid
- def get_status_color(self):
- return WirelessLANStatusChoices.colors.get(self.status)
- class WirelessLink(WirelessAuthenticationBase, DistanceMixin, PrimaryModel):
- """
- A point-to-point connection between two wireless Interfaces.
- """
- interface_a = models.ForeignKey(
- to='dcim.Interface',
- on_delete=models.PROTECT,
- related_name='+',
- verbose_name=_('interface A'),
- )
- interface_b = models.ForeignKey(
- to='dcim.Interface',
- on_delete=models.PROTECT,
- related_name='+',
- verbose_name=_('interface B'),
- )
- ssid = models.CharField(
- max_length=SSID_MAX_LENGTH,
- blank=True,
- verbose_name=_('SSID')
- )
- status = models.CharField(
- verbose_name=_('status'),
- max_length=50,
- choices=LinkStatusChoices,
- default=LinkStatusChoices.STATUS_CONNECTED
- )
- tenant = models.ForeignKey(
- to='tenancy.Tenant',
- on_delete=models.PROTECT,
- related_name='wireless_links',
- blank=True,
- null=True
- )
- # Cache the associated device for the A and B interfaces. This enables filtering of WirelessLinks by their
- # associated Devices.
- _interface_a_device = models.ForeignKey(
- to='dcim.Device',
- on_delete=models.CASCADE,
- related_name='+',
- blank=True,
- null=True
- )
- _interface_b_device = models.ForeignKey(
- to='dcim.Device',
- on_delete=models.CASCADE,
- related_name='+',
- blank=True,
- null=True
- )
- clone_fields = ('ssid', 'status')
- class Meta:
- ordering = ['pk']
- constraints = (
- models.UniqueConstraint(
- fields=('interface_a', 'interface_b'),
- name='%(app_label)s_%(class)s_unique_interfaces'
- ),
- )
- verbose_name = _('wireless link')
- verbose_name_plural = _('wireless links')
- def __str__(self):
- return self.ssid or f'#{self.pk}'
- def get_status_color(self):
- return LinkStatusChoices.colors.get(self.status)
- def clean(self):
- super().clean()
- # Validate interface types
- if hasattr(self, "interface_a") and self.interface_a.type not in WIRELESS_IFACE_TYPES:
- raise ValidationError({
- 'interface_a': _(
- "{type} is not a wireless interface."
- ).format(type=self.interface_a.get_type_display())
- })
- if hasattr(self, "interface_b") and self.interface_b.type not in WIRELESS_IFACE_TYPES:
- raise ValidationError({
- 'interface_b': _(
- "{type} is not a wireless interface."
- ).format(type=self.interface_b.get_type_display())
- })
- def save(self, *args, **kwargs):
- # Store the parent Device for the A and B interfaces
- self._interface_a_device = self.interface_a.device
- self._interface_b_device = self.interface_b.device
- super().save(*args, **kwargs)
|