device_component_templates.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. from django.core.exceptions import ValidationError
  2. from django.core.validators import MaxValueValidator, MinValueValidator
  3. from django.db import models
  4. from dcim.choices import *
  5. from dcim.constants import *
  6. from dcim.managers import InterfaceManager
  7. from extras.models import ObjectChange
  8. from utilities.managers import NaturalOrderingManager
  9. from utilities.utils import serialize_object
  10. from .device_components import (
  11. ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, PowerOutlet, PowerPort, RearPort,
  12. )
  13. __all__ = (
  14. 'ConsolePortTemplate',
  15. 'ConsoleServerPortTemplate',
  16. 'DeviceBayTemplate',
  17. 'FrontPortTemplate',
  18. 'InterfaceTemplate',
  19. 'PowerOutletTemplate',
  20. 'PowerPortTemplate',
  21. 'RearPortTemplate',
  22. )
  23. class ComponentTemplateModel(models.Model):
  24. class Meta:
  25. abstract = True
  26. def instantiate(self, device):
  27. """
  28. Instantiate a new component on the specified Device.
  29. """
  30. raise NotImplementedError()
  31. def to_objectchange(self, action):
  32. return ObjectChange(
  33. changed_object=self,
  34. object_repr=str(self),
  35. action=action,
  36. related_object=self.device_type,
  37. object_data=serialize_object(self)
  38. )
  39. class ConsolePortTemplate(ComponentTemplateModel):
  40. """
  41. A template for a ConsolePort to be created for a new Device.
  42. """
  43. device_type = models.ForeignKey(
  44. to='dcim.DeviceType',
  45. on_delete=models.CASCADE,
  46. related_name='consoleport_templates'
  47. )
  48. name = models.CharField(
  49. max_length=50
  50. )
  51. type = models.CharField(
  52. max_length=50,
  53. choices=ConsolePortTypeChoices,
  54. blank=True
  55. )
  56. objects = NaturalOrderingManager()
  57. class Meta:
  58. ordering = ['device_type', 'name']
  59. unique_together = ['device_type', 'name']
  60. def __str__(self):
  61. return self.name
  62. def instantiate(self, device):
  63. return ConsolePort(
  64. device=device,
  65. name=self.name,
  66. type=self.type
  67. )
  68. class ConsoleServerPortTemplate(ComponentTemplateModel):
  69. """
  70. A template for a ConsoleServerPort to be created for a new Device.
  71. """
  72. device_type = models.ForeignKey(
  73. to='dcim.DeviceType',
  74. on_delete=models.CASCADE,
  75. related_name='consoleserverport_templates'
  76. )
  77. name = models.CharField(
  78. max_length=50
  79. )
  80. type = models.CharField(
  81. max_length=50,
  82. choices=ConsolePortTypeChoices,
  83. blank=True
  84. )
  85. objects = NaturalOrderingManager()
  86. class Meta:
  87. ordering = ['device_type', 'name']
  88. unique_together = ['device_type', 'name']
  89. def __str__(self):
  90. return self.name
  91. def instantiate(self, device):
  92. return ConsoleServerPort(
  93. device=device,
  94. name=self.name,
  95. type=self.type
  96. )
  97. class PowerPortTemplate(ComponentTemplateModel):
  98. """
  99. A template for a PowerPort to be created for a new Device.
  100. """
  101. device_type = models.ForeignKey(
  102. to='dcim.DeviceType',
  103. on_delete=models.CASCADE,
  104. related_name='powerport_templates'
  105. )
  106. name = models.CharField(
  107. max_length=50
  108. )
  109. type = models.CharField(
  110. max_length=50,
  111. choices=PowerPortTypeChoices,
  112. blank=True
  113. )
  114. maximum_draw = models.PositiveSmallIntegerField(
  115. blank=True,
  116. null=True,
  117. validators=[MinValueValidator(1)],
  118. help_text="Maximum power draw (watts)"
  119. )
  120. allocated_draw = models.PositiveSmallIntegerField(
  121. blank=True,
  122. null=True,
  123. validators=[MinValueValidator(1)],
  124. help_text="Allocated power draw (watts)"
  125. )
  126. objects = NaturalOrderingManager()
  127. class Meta:
  128. ordering = ['device_type', 'name']
  129. unique_together = ['device_type', 'name']
  130. def __str__(self):
  131. return self.name
  132. def instantiate(self, device):
  133. return PowerPort(
  134. device=device,
  135. name=self.name,
  136. maximum_draw=self.maximum_draw,
  137. allocated_draw=self.allocated_draw
  138. )
  139. class PowerOutletTemplate(ComponentTemplateModel):
  140. """
  141. A template for a PowerOutlet to be created for a new Device.
  142. """
  143. device_type = models.ForeignKey(
  144. to='dcim.DeviceType',
  145. on_delete=models.CASCADE,
  146. related_name='poweroutlet_templates'
  147. )
  148. name = models.CharField(
  149. max_length=50
  150. )
  151. type = models.CharField(
  152. max_length=50,
  153. choices=PowerOutletTypeChoices,
  154. blank=True
  155. )
  156. power_port = models.ForeignKey(
  157. to='dcim.PowerPortTemplate',
  158. on_delete=models.SET_NULL,
  159. blank=True,
  160. null=True,
  161. related_name='poweroutlet_templates'
  162. )
  163. feed_leg = models.CharField(
  164. max_length=50,
  165. choices=PowerOutletFeedLegChoices,
  166. blank=True,
  167. help_text="Phase (for three-phase feeds)"
  168. )
  169. objects = NaturalOrderingManager()
  170. class Meta:
  171. ordering = ['device_type', 'name']
  172. unique_together = ['device_type', 'name']
  173. def __str__(self):
  174. return self.name
  175. def clean(self):
  176. # Validate power port assignment
  177. if self.power_port and self.power_port.device_type != self.device_type:
  178. raise ValidationError(
  179. "Parent power port ({}) must belong to the same device type".format(self.power_port)
  180. )
  181. def instantiate(self, device):
  182. if self.power_port:
  183. power_port = PowerPort.objects.get(device=device, name=self.power_port.name)
  184. else:
  185. power_port = None
  186. return PowerOutlet(
  187. device=device,
  188. name=self.name,
  189. power_port=power_port,
  190. feed_leg=self.feed_leg
  191. )
  192. class InterfaceTemplate(ComponentTemplateModel):
  193. """
  194. A template for a physical data interface on a new Device.
  195. """
  196. device_type = models.ForeignKey(
  197. to='dcim.DeviceType',
  198. on_delete=models.CASCADE,
  199. related_name='interface_templates'
  200. )
  201. name = models.CharField(
  202. max_length=64
  203. )
  204. type = models.CharField(
  205. max_length=50,
  206. choices=InterfaceTypeChoices
  207. )
  208. mgmt_only = models.BooleanField(
  209. default=False,
  210. verbose_name='Management only'
  211. )
  212. objects = InterfaceManager()
  213. class Meta:
  214. ordering = ['device_type', 'name']
  215. unique_together = ['device_type', 'name']
  216. def __str__(self):
  217. return self.name
  218. def instantiate(self, device):
  219. return Interface(
  220. device=device,
  221. name=self.name,
  222. type=self.type,
  223. mgmt_only=self.mgmt_only
  224. )
  225. class FrontPortTemplate(ComponentTemplateModel):
  226. """
  227. Template for a pass-through port on the front of a new Device.
  228. """
  229. device_type = models.ForeignKey(
  230. to='dcim.DeviceType',
  231. on_delete=models.CASCADE,
  232. related_name='frontport_templates'
  233. )
  234. name = models.CharField(
  235. max_length=64
  236. )
  237. type = models.CharField(
  238. max_length=50,
  239. choices=PortTypeChoices
  240. )
  241. rear_port = models.ForeignKey(
  242. to='dcim.RearPortTemplate',
  243. on_delete=models.CASCADE,
  244. related_name='frontport_templates'
  245. )
  246. rear_port_position = models.PositiveSmallIntegerField(
  247. default=1,
  248. validators=[MinValueValidator(1), MaxValueValidator(64)]
  249. )
  250. objects = NaturalOrderingManager()
  251. class Meta:
  252. ordering = ['device_type', 'name']
  253. unique_together = [
  254. ['device_type', 'name'],
  255. ['rear_port', 'rear_port_position'],
  256. ]
  257. def __str__(self):
  258. return self.name
  259. def clean(self):
  260. # Validate rear port assignment
  261. if self.rear_port.device_type != self.device_type:
  262. raise ValidationError(
  263. "Rear port ({}) must belong to the same device type".format(self.rear_port)
  264. )
  265. # Validate rear port position assignment
  266. if self.rear_port_position > self.rear_port.positions:
  267. raise ValidationError(
  268. "Invalid rear port position ({}); rear port {} has only {} positions".format(
  269. self.rear_port_position, self.rear_port.name, self.rear_port.positions
  270. )
  271. )
  272. def instantiate(self, device):
  273. if self.rear_port:
  274. rear_port = RearPort.objects.get(device=device, name=self.rear_port.name)
  275. else:
  276. rear_port = None
  277. return FrontPort(
  278. device=device,
  279. name=self.name,
  280. type=self.type,
  281. rear_port=rear_port,
  282. rear_port_position=self.rear_port_position
  283. )
  284. class RearPortTemplate(ComponentTemplateModel):
  285. """
  286. Template for a pass-through port on the rear of a new Device.
  287. """
  288. device_type = models.ForeignKey(
  289. to='dcim.DeviceType',
  290. on_delete=models.CASCADE,
  291. related_name='rearport_templates'
  292. )
  293. name = models.CharField(
  294. max_length=64
  295. )
  296. type = models.CharField(
  297. max_length=50,
  298. choices=PortTypeChoices
  299. )
  300. positions = models.PositiveSmallIntegerField(
  301. default=1,
  302. validators=[MinValueValidator(1), MaxValueValidator(64)]
  303. )
  304. objects = NaturalOrderingManager()
  305. class Meta:
  306. ordering = ['device_type', 'name']
  307. unique_together = ['device_type', 'name']
  308. def __str__(self):
  309. return self.name
  310. def instantiate(self, device):
  311. return RearPort(
  312. device=device,
  313. name=self.name,
  314. type=self.type,
  315. positions=self.positions
  316. )
  317. class DeviceBayTemplate(ComponentTemplateModel):
  318. """
  319. A template for a DeviceBay to be created for a new parent Device.
  320. """
  321. device_type = models.ForeignKey(
  322. to='dcim.DeviceType',
  323. on_delete=models.CASCADE,
  324. related_name='device_bay_templates'
  325. )
  326. name = models.CharField(
  327. max_length=50
  328. )
  329. objects = NaturalOrderingManager()
  330. class Meta:
  331. ordering = ['device_type', 'name']
  332. unique_together = ['device_type', 'name']
  333. def __str__(self):
  334. return self.name
  335. def instantiate(self, device):
  336. return DeviceBay(
  337. device=device,
  338. name=self.name
  339. )