models.py 5.8 KB

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