circuits.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. from django.apps import apps
  2. from django.contrib.contenttypes.fields import GenericRelation
  3. from django.core.exceptions import ValidationError
  4. from django.db import models
  5. from django.urls import reverse
  6. from circuits.choices import *
  7. from dcim.models import CabledObjectModel
  8. from netbox.models import (
  9. ChangeLoggedModel, CustomFieldsMixin, CustomLinksMixin, OrganizationalModel, NetBoxModel, TagsMixin,
  10. )
  11. from netbox.models.features import WebhooksMixin
  12. __all__ = (
  13. 'Circuit',
  14. 'CircuitTermination',
  15. 'CircuitType',
  16. )
  17. class CircuitType(OrganizationalModel):
  18. """
  19. Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
  20. "Long Haul," "Metro," or "Out-of-Band".
  21. """
  22. name = models.CharField(
  23. max_length=100,
  24. unique=True
  25. )
  26. slug = models.SlugField(
  27. max_length=100,
  28. unique=True
  29. )
  30. description = models.CharField(
  31. max_length=200,
  32. blank=True,
  33. )
  34. class Meta:
  35. ordering = ['name']
  36. def __str__(self):
  37. return self.name
  38. def get_absolute_url(self):
  39. return reverse('circuits:circuittype', args=[self.pk])
  40. class Circuit(NetBoxModel):
  41. """
  42. A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
  43. circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
  44. in Kbps.
  45. """
  46. cid = models.CharField(
  47. max_length=100,
  48. verbose_name='Circuit ID'
  49. )
  50. provider = models.ForeignKey(
  51. to='circuits.Provider',
  52. on_delete=models.PROTECT,
  53. related_name='circuits'
  54. )
  55. type = models.ForeignKey(
  56. to='CircuitType',
  57. on_delete=models.PROTECT,
  58. related_name='circuits'
  59. )
  60. status = models.CharField(
  61. max_length=50,
  62. choices=CircuitStatusChoices,
  63. default=CircuitStatusChoices.STATUS_ACTIVE
  64. )
  65. tenant = models.ForeignKey(
  66. to='tenancy.Tenant',
  67. on_delete=models.PROTECT,
  68. related_name='circuits',
  69. blank=True,
  70. null=True
  71. )
  72. install_date = models.DateField(
  73. blank=True,
  74. null=True,
  75. verbose_name='Installed'
  76. )
  77. termination_date = models.DateField(
  78. blank=True,
  79. null=True,
  80. verbose_name='Terminates'
  81. )
  82. commit_rate = models.PositiveIntegerField(
  83. blank=True,
  84. null=True,
  85. verbose_name='Commit rate (Kbps)')
  86. description = models.CharField(
  87. max_length=200,
  88. blank=True
  89. )
  90. comments = models.TextField(
  91. blank=True
  92. )
  93. # Generic relations
  94. contacts = GenericRelation(
  95. to='tenancy.ContactAssignment'
  96. )
  97. images = GenericRelation(
  98. to='extras.ImageAttachment'
  99. )
  100. # Cache associated CircuitTerminations
  101. termination_a = models.ForeignKey(
  102. to='circuits.CircuitTermination',
  103. on_delete=models.SET_NULL,
  104. related_name='+',
  105. editable=False,
  106. blank=True,
  107. null=True
  108. )
  109. termination_z = models.ForeignKey(
  110. to='circuits.CircuitTermination',
  111. on_delete=models.SET_NULL,
  112. related_name='+',
  113. editable=False,
  114. blank=True,
  115. null=True
  116. )
  117. clone_fields = (
  118. 'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate', 'description',
  119. )
  120. class Meta:
  121. ordering = ['provider', 'cid']
  122. constraints = (
  123. models.UniqueConstraint(
  124. fields=('provider', 'cid'),
  125. name='%(app_label)s_%(class)s_unique_provider_cid'
  126. ),
  127. )
  128. def __str__(self):
  129. return self.cid
  130. @classmethod
  131. def get_prerequisite_models(cls):
  132. return [apps.get_model('circuits.Provider'), CircuitType]
  133. def get_absolute_url(self):
  134. return reverse('circuits:circuit', args=[self.pk])
  135. def get_status_color(self):
  136. return CircuitStatusChoices.colors.get(self.status)
  137. class CircuitTermination(
  138. CustomFieldsMixin,
  139. CustomLinksMixin,
  140. TagsMixin,
  141. WebhooksMixin,
  142. ChangeLoggedModel,
  143. CabledObjectModel
  144. ):
  145. circuit = models.ForeignKey(
  146. to='circuits.Circuit',
  147. on_delete=models.CASCADE,
  148. related_name='terminations'
  149. )
  150. term_side = models.CharField(
  151. max_length=1,
  152. choices=CircuitTerminationSideChoices,
  153. verbose_name='Termination'
  154. )
  155. site = models.ForeignKey(
  156. to='dcim.Site',
  157. on_delete=models.PROTECT,
  158. related_name='circuit_terminations',
  159. blank=True,
  160. null=True
  161. )
  162. provider_network = models.ForeignKey(
  163. to='circuits.ProviderNetwork',
  164. on_delete=models.PROTECT,
  165. related_name='circuit_terminations',
  166. blank=True,
  167. null=True
  168. )
  169. port_speed = models.PositiveIntegerField(
  170. verbose_name='Port speed (Kbps)',
  171. blank=True,
  172. null=True
  173. )
  174. upstream_speed = models.PositiveIntegerField(
  175. blank=True,
  176. null=True,
  177. verbose_name='Upstream speed (Kbps)',
  178. help_text='Upstream speed, if different from port speed'
  179. )
  180. xconnect_id = models.CharField(
  181. max_length=50,
  182. blank=True,
  183. verbose_name='Cross-connect ID'
  184. )
  185. pp_info = models.CharField(
  186. max_length=100,
  187. blank=True,
  188. verbose_name='Patch panel/port(s)'
  189. )
  190. description = models.CharField(
  191. max_length=200,
  192. blank=True
  193. )
  194. class Meta:
  195. ordering = ['circuit', 'term_side']
  196. constraints = (
  197. models.UniqueConstraint(
  198. fields=('circuit', 'term_side'),
  199. name='%(app_label)s_%(class)s_unique_circuit_term_side'
  200. ),
  201. )
  202. def __str__(self):
  203. return f'Termination {self.term_side}: {self.site or self.provider_network}'
  204. def get_absolute_url(self):
  205. if self.site:
  206. return self.site.get_absolute_url()
  207. return self.provider_network.get_absolute_url()
  208. def clean(self):
  209. super().clean()
  210. # Must define either site *or* provider network
  211. if self.site is None and self.provider_network is None:
  212. raise ValidationError("A circuit termination must attach to either a site or a provider network.")
  213. if self.site and self.provider_network:
  214. raise ValidationError("A circuit termination cannot attach to both a site and a provider network.")
  215. def to_objectchange(self, action):
  216. objectchange = super().to_objectchange(action)
  217. objectchange.related_object = self.circuit
  218. return objectchange
  219. @property
  220. def parent_object(self):
  221. return self.circuit
  222. def get_peer_termination(self):
  223. peer_side = 'Z' if self.term_side == 'A' else 'A'
  224. try:
  225. return CircuitTermination.objects.prefetch_related('site').get(
  226. circuit=self.circuit,
  227. term_side=peer_side
  228. )
  229. except CircuitTermination.DoesNotExist:
  230. return None