models.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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. tenant = models.ForeignKey(
  93. to='tenancy.Tenant',
  94. on_delete=models.PROTECT,
  95. related_name='wireless_lans',
  96. blank=True,
  97. null=True
  98. )
  99. description = models.CharField(
  100. max_length=200,
  101. blank=True
  102. )
  103. class Meta:
  104. ordering = ('ssid', 'pk')
  105. verbose_name = 'Wireless LAN'
  106. def __str__(self):
  107. return self.ssid
  108. def get_absolute_url(self):
  109. return reverse('wireless:wirelesslan', args=[self.pk])
  110. class WirelessLink(WirelessAuthenticationBase, NetBoxModel):
  111. """
  112. A point-to-point connection between two wireless Interfaces.
  113. """
  114. interface_a = models.ForeignKey(
  115. to='dcim.Interface',
  116. limit_choices_to={'type__in': WIRELESS_IFACE_TYPES},
  117. on_delete=models.PROTECT,
  118. related_name='+'
  119. )
  120. interface_b = models.ForeignKey(
  121. to='dcim.Interface',
  122. limit_choices_to={'type__in': WIRELESS_IFACE_TYPES},
  123. on_delete=models.PROTECT,
  124. related_name='+'
  125. )
  126. ssid = models.CharField(
  127. max_length=SSID_MAX_LENGTH,
  128. blank=True,
  129. verbose_name='SSID'
  130. )
  131. status = models.CharField(
  132. max_length=50,
  133. choices=LinkStatusChoices,
  134. default=LinkStatusChoices.STATUS_CONNECTED
  135. )
  136. tenant = models.ForeignKey(
  137. to='tenancy.Tenant',
  138. on_delete=models.PROTECT,
  139. related_name='wireless_links',
  140. blank=True,
  141. null=True
  142. )
  143. description = models.CharField(
  144. max_length=200,
  145. blank=True
  146. )
  147. # Cache the associated device for the A and B interfaces. This enables filtering of WirelessLinks by their
  148. # associated Devices.
  149. _interface_a_device = models.ForeignKey(
  150. to='dcim.Device',
  151. on_delete=models.CASCADE,
  152. related_name='+',
  153. blank=True,
  154. null=True
  155. )
  156. _interface_b_device = models.ForeignKey(
  157. to='dcim.Device',
  158. on_delete=models.CASCADE,
  159. related_name='+',
  160. blank=True,
  161. null=True
  162. )
  163. clone_fields = ('ssid', 'status')
  164. class Meta:
  165. ordering = ['pk']
  166. unique_together = ('interface_a', 'interface_b')
  167. def __str__(self):
  168. return f'#{self.pk}'
  169. def get_absolute_url(self):
  170. return reverse('wireless:wirelesslink', args=[self.pk])
  171. def get_status_color(self):
  172. return LinkStatusChoices.colors.get(self.status)
  173. def clean(self):
  174. # Validate interface types
  175. if self.interface_a.type not in WIRELESS_IFACE_TYPES:
  176. raise ValidationError({
  177. 'interface_a': f"{self.interface_a.get_type_display()} is not a wireless interface."
  178. })
  179. if self.interface_b.type not in WIRELESS_IFACE_TYPES:
  180. raise ValidationError({
  181. 'interface_a': f"{self.interface_b.get_type_display()} is not a wireless interface."
  182. })
  183. def save(self, *args, **kwargs):
  184. # Store the parent Device for the A and B interfaces
  185. self._interface_a_device = self.interface_a.device
  186. self._interface_b_device = self.interface_b.device
  187. super().save(*args, **kwargs)