models.py 7.6 KB

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