models.py 9.0 KB

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