models.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. from django.db import models
  2. from django.urls import reverse
  3. from dcim.fields import ASNField
  4. from dcim.models import CableTermination, PathEndpoint
  5. from extras.models import ObjectChange
  6. from extras.utils import extras_features
  7. from netbox.models import BigIDModel, ChangeLoggedModel, OrganizationalModel, PrimaryModel
  8. from utilities.querysets import RestrictedQuerySet
  9. from .choices import *
  10. from .querysets import CircuitQuerySet
  11. __all__ = (
  12. 'Circuit',
  13. 'CircuitTermination',
  14. 'CircuitType',
  15. 'Provider',
  16. )
  17. @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
  18. class Provider(PrimaryModel):
  19. """
  20. Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
  21. stores information pertinent to the user's relationship with the Provider.
  22. """
  23. name = models.CharField(
  24. max_length=100,
  25. unique=True
  26. )
  27. slug = models.SlugField(
  28. max_length=100,
  29. unique=True
  30. )
  31. asn = ASNField(
  32. blank=True,
  33. null=True,
  34. verbose_name='ASN',
  35. help_text='32-bit autonomous system number'
  36. )
  37. account = models.CharField(
  38. max_length=30,
  39. blank=True,
  40. verbose_name='Account number'
  41. )
  42. portal_url = models.URLField(
  43. blank=True,
  44. verbose_name='Portal URL'
  45. )
  46. noc_contact = models.TextField(
  47. blank=True,
  48. verbose_name='NOC contact'
  49. )
  50. admin_contact = models.TextField(
  51. blank=True,
  52. verbose_name='Admin contact'
  53. )
  54. comments = models.TextField(
  55. blank=True
  56. )
  57. objects = RestrictedQuerySet.as_manager()
  58. csv_headers = [
  59. 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
  60. ]
  61. clone_fields = [
  62. 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact',
  63. ]
  64. class Meta:
  65. ordering = ['name']
  66. def __str__(self):
  67. return self.name
  68. def get_absolute_url(self):
  69. return reverse('circuits:provider', args=[self.pk])
  70. def to_csv(self):
  71. return (
  72. self.name,
  73. self.slug,
  74. self.asn,
  75. self.account,
  76. self.portal_url,
  77. self.noc_contact,
  78. self.admin_contact,
  79. self.comments,
  80. )
  81. @extras_features('custom_fields', 'export_templates', 'webhooks')
  82. class CircuitType(OrganizationalModel):
  83. """
  84. Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
  85. "Long Haul," "Metro," or "Out-of-Band".
  86. """
  87. name = models.CharField(
  88. max_length=100,
  89. unique=True
  90. )
  91. slug = models.SlugField(
  92. max_length=100,
  93. unique=True
  94. )
  95. description = models.CharField(
  96. max_length=200,
  97. blank=True,
  98. )
  99. objects = RestrictedQuerySet.as_manager()
  100. csv_headers = ['name', 'slug', 'description']
  101. class Meta:
  102. ordering = ['name']
  103. def __str__(self):
  104. return self.name
  105. def get_absolute_url(self):
  106. return "{}?type={}".format(reverse('circuits:circuit_list'), self.slug)
  107. def to_csv(self):
  108. return (
  109. self.name,
  110. self.slug,
  111. self.description,
  112. )
  113. @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
  114. class Circuit(PrimaryModel):
  115. """
  116. A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
  117. circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
  118. in Kbps.
  119. """
  120. cid = models.CharField(
  121. max_length=100,
  122. verbose_name='Circuit ID'
  123. )
  124. provider = models.ForeignKey(
  125. to='circuits.Provider',
  126. on_delete=models.PROTECT,
  127. related_name='circuits'
  128. )
  129. type = models.ForeignKey(
  130. to='CircuitType',
  131. on_delete=models.PROTECT,
  132. related_name='circuits'
  133. )
  134. status = models.CharField(
  135. max_length=50,
  136. choices=CircuitStatusChoices,
  137. default=CircuitStatusChoices.STATUS_ACTIVE
  138. )
  139. tenant = models.ForeignKey(
  140. to='tenancy.Tenant',
  141. on_delete=models.PROTECT,
  142. related_name='circuits',
  143. blank=True,
  144. null=True
  145. )
  146. install_date = models.DateField(
  147. blank=True,
  148. null=True,
  149. verbose_name='Date installed'
  150. )
  151. commit_rate = models.PositiveIntegerField(
  152. blank=True,
  153. null=True,
  154. verbose_name='Commit rate (Kbps)')
  155. description = models.CharField(
  156. max_length=200,
  157. blank=True
  158. )
  159. comments = models.TextField(
  160. blank=True
  161. )
  162. objects = CircuitQuerySet.as_manager()
  163. csv_headers = [
  164. 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
  165. ]
  166. clone_fields = [
  167. 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
  168. ]
  169. class Meta:
  170. ordering = ['provider', 'cid']
  171. unique_together = ['provider', 'cid']
  172. def __str__(self):
  173. return self.cid
  174. def get_absolute_url(self):
  175. return reverse('circuits:circuit', args=[self.pk])
  176. def to_csv(self):
  177. return (
  178. self.cid,
  179. self.provider.name,
  180. self.type.name,
  181. self.get_status_display(),
  182. self.tenant.name if self.tenant else None,
  183. self.install_date,
  184. self.commit_rate,
  185. self.description,
  186. self.comments,
  187. )
  188. def get_status_class(self):
  189. return CircuitStatusChoices.CSS_CLASSES.get(self.status)
  190. def _get_termination(self, side):
  191. for ct in self.terminations.all():
  192. if ct.term_side == side:
  193. return ct
  194. return None
  195. @property
  196. def termination_a(self):
  197. return self._get_termination('A')
  198. @property
  199. def termination_z(self):
  200. return self._get_termination('Z')
  201. @extras_features('webhooks')
  202. class CircuitTermination(ChangeLoggedModel, PathEndpoint, CableTermination):
  203. circuit = models.ForeignKey(
  204. to='circuits.Circuit',
  205. on_delete=models.CASCADE,
  206. related_name='terminations'
  207. )
  208. term_side = models.CharField(
  209. max_length=1,
  210. choices=CircuitTerminationSideChoices,
  211. verbose_name='Termination'
  212. )
  213. site = models.ForeignKey(
  214. to='dcim.Site',
  215. on_delete=models.PROTECT,
  216. related_name='circuit_terminations'
  217. )
  218. port_speed = models.PositiveIntegerField(
  219. verbose_name='Port speed (Kbps)',
  220. blank=True,
  221. null=True
  222. )
  223. upstream_speed = models.PositiveIntegerField(
  224. blank=True,
  225. null=True,
  226. verbose_name='Upstream speed (Kbps)',
  227. help_text='Upstream speed, if different from port speed'
  228. )
  229. xconnect_id = models.CharField(
  230. max_length=50,
  231. blank=True,
  232. verbose_name='Cross-connect ID'
  233. )
  234. pp_info = models.CharField(
  235. max_length=100,
  236. blank=True,
  237. verbose_name='Patch panel/port(s)'
  238. )
  239. description = models.CharField(
  240. max_length=200,
  241. blank=True
  242. )
  243. objects = RestrictedQuerySet.as_manager()
  244. class Meta:
  245. ordering = ['circuit', 'term_side']
  246. unique_together = ['circuit', 'term_side']
  247. def __str__(self):
  248. return 'Side {}'.format(self.get_term_side_display())
  249. def to_objectchange(self, action):
  250. # Annotate the parent Circuit
  251. try:
  252. circuit = self.circuit
  253. except Circuit.DoesNotExist:
  254. # Parent circuit has been deleted
  255. circuit = None
  256. return super().to_objectchange(action, related_object=circuit)
  257. @property
  258. def parent_object(self):
  259. return self.circuit
  260. def get_peer_termination(self):
  261. peer_side = 'Z' if self.term_side == 'A' else 'A'
  262. try:
  263. return CircuitTermination.objects.prefetch_related('site').get(
  264. circuit=self.circuit,
  265. term_side=peer_side
  266. )
  267. except CircuitTermination.DoesNotExist:
  268. return None