circuits.py 6.5 KB

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