models.py 8.3 KB

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