models.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from django.core.exceptions import ValidationError
  2. from django.db import models
  3. from django.urls import reverse
  4. from mptt.models import MPTTModel, TreeForeignKey
  5. from dcim.choices import LinkStatusChoices
  6. from dcim.constants import WIRELESS_IFACE_TYPES
  7. from netbox.models import NestedGroupModel, NetBoxModel
  8. from .choices import *
  9. from .constants import *
  10. __all__ = (
  11. 'WirelessLAN',
  12. 'WirelessLANGroup',
  13. 'WirelessLink',
  14. )
  15. class WirelessAuthenticationBase(models.Model):
  16. """
  17. Abstract model for attaching attributes related to wireless authentication.
  18. """
  19. auth_type = models.CharField(
  20. max_length=50,
  21. choices=WirelessAuthTypeChoices,
  22. blank=True
  23. )
  24. auth_cipher = models.CharField(
  25. max_length=50,
  26. choices=WirelessAuthCipherChoices,
  27. blank=True
  28. )
  29. auth_psk = models.CharField(
  30. max_length=PSK_MAX_LENGTH,
  31. blank=True,
  32. verbose_name='Pre-shared key'
  33. )
  34. class Meta:
  35. abstract = True
  36. class WirelessLANGroup(NestedGroupModel):
  37. """
  38. A nested grouping of WirelessLANs
  39. """
  40. name = models.CharField(
  41. max_length=100,
  42. unique=True
  43. )
  44. slug = models.SlugField(
  45. max_length=100,
  46. unique=True
  47. )
  48. parent = TreeForeignKey(
  49. to='self',
  50. on_delete=models.CASCADE,
  51. related_name='children',
  52. blank=True,
  53. null=True,
  54. db_index=True
  55. )
  56. description = models.CharField(
  57. max_length=200,
  58. blank=True
  59. )
  60. class Meta:
  61. ordering = ('name', 'pk')
  62. unique_together = (
  63. ('parent', 'name')
  64. )
  65. verbose_name = 'Wireless LAN Group'
  66. def __str__(self):
  67. return self.name
  68. def get_absolute_url(self):
  69. return reverse('wireless:wirelesslangroup', args=[self.pk])
  70. class WirelessLAN(WirelessAuthenticationBase, NetBoxModel):
  71. """
  72. A wireless network formed among an arbitrary number of access point and clients.
  73. """
  74. ssid = models.CharField(
  75. max_length=SSID_MAX_LENGTH,
  76. verbose_name='SSID'
  77. )
  78. group = models.ForeignKey(
  79. to='wireless.WirelessLANGroup',
  80. on_delete=models.SET_NULL,
  81. related_name='wireless_lans',
  82. blank=True,
  83. null=True
  84. )
  85. vlan = models.ForeignKey(
  86. to='ipam.VLAN',
  87. on_delete=models.PROTECT,
  88. blank=True,
  89. null=True,
  90. verbose_name='VLAN'
  91. )
  92. description = models.CharField(
  93. max_length=200,
  94. blank=True
  95. )
  96. class Meta:
  97. ordering = ('ssid', 'pk')
  98. verbose_name = 'Wireless LAN'
  99. def __str__(self):
  100. return self.ssid
  101. def get_absolute_url(self):
  102. return reverse('wireless:wirelesslan', args=[self.pk])
  103. class WirelessLink(WirelessAuthenticationBase, NetBoxModel):
  104. """
  105. A point-to-point connection between two wireless Interfaces.
  106. """
  107. interface_a = models.ForeignKey(
  108. to='dcim.Interface',
  109. limit_choices_to={'type__in': WIRELESS_IFACE_TYPES},
  110. on_delete=models.PROTECT,
  111. related_name='+'
  112. )
  113. interface_b = models.ForeignKey(
  114. to='dcim.Interface',
  115. limit_choices_to={'type__in': WIRELESS_IFACE_TYPES},
  116. on_delete=models.PROTECT,
  117. related_name='+'
  118. )
  119. ssid = models.CharField(
  120. max_length=SSID_MAX_LENGTH,
  121. blank=True,
  122. verbose_name='SSID'
  123. )
  124. status = models.CharField(
  125. max_length=50,
  126. choices=LinkStatusChoices,
  127. default=LinkStatusChoices.STATUS_CONNECTED
  128. )
  129. description = models.CharField(
  130. max_length=200,
  131. blank=True
  132. )
  133. # Cache the associated device for the A and B interfaces. This enables filtering of WirelessLinks by their
  134. # associated Devices.
  135. _interface_a_device = models.ForeignKey(
  136. to='dcim.Device',
  137. on_delete=models.CASCADE,
  138. related_name='+',
  139. blank=True,
  140. null=True
  141. )
  142. _interface_b_device = models.ForeignKey(
  143. to='dcim.Device',
  144. on_delete=models.CASCADE,
  145. related_name='+',
  146. blank=True,
  147. null=True
  148. )
  149. clone_fields = ('ssid', 'status')
  150. class Meta:
  151. ordering = ['pk']
  152. unique_together = ('interface_a', 'interface_b')
  153. def __str__(self):
  154. return f'#{self.pk}'
  155. def get_absolute_url(self):
  156. return reverse('wireless:wirelesslink', args=[self.pk])
  157. def get_status_class(self):
  158. return LinkStatusChoices.colors.get(self.status)
  159. def clean(self):
  160. # Validate interface types
  161. if self.interface_a.type not in WIRELESS_IFACE_TYPES:
  162. raise ValidationError({
  163. 'interface_a': f"{self.interface_a.get_type_display()} is not a wireless interface."
  164. })
  165. if self.interface_b.type not in WIRELESS_IFACE_TYPES:
  166. raise ValidationError({
  167. 'interface_a': f"{self.interface_b.get_type_display()} is not a wireless interface."
  168. })
  169. def save(self, *args, **kwargs):
  170. # Store the parent Device for the A and B interfaces
  171. self._interface_a_device = self.interface_a.device
  172. self._interface_b_device = self.interface_b.device
  173. super().save(*args, **kwargs)