device_component_templates.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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 extras.models import ObjectChange
  7. from utilities.fields import NaturalOrderingField
  8. from utilities.ordering import naturalize_interface
  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. _name = NaturalOrderingField(
  52. target_field='name',
  53. max_length=100,
  54. blank=True
  55. )
  56. type = models.CharField(
  57. max_length=50,
  58. choices=ConsolePortTypeChoices,
  59. blank=True
  60. )
  61. class Meta:
  62. ordering = ('device_type', '_name')
  63. unique_together = ('device_type', 'name')
  64. def __str__(self):
  65. return self.name
  66. def instantiate(self, device):
  67. return ConsolePort(
  68. device=device,
  69. name=self.name,
  70. type=self.type
  71. )
  72. class ConsoleServerPortTemplate(ComponentTemplateModel):
  73. """
  74. A template for a ConsoleServerPort to be created for a new Device.
  75. """
  76. device_type = models.ForeignKey(
  77. to='dcim.DeviceType',
  78. on_delete=models.CASCADE,
  79. related_name='consoleserverport_templates'
  80. )
  81. name = models.CharField(
  82. max_length=50
  83. )
  84. _name = NaturalOrderingField(
  85. target_field='name',
  86. max_length=100,
  87. blank=True
  88. )
  89. type = models.CharField(
  90. max_length=50,
  91. choices=ConsolePortTypeChoices,
  92. blank=True
  93. )
  94. class Meta:
  95. ordering = ('device_type', '_name')
  96. unique_together = ('device_type', 'name')
  97. def __str__(self):
  98. return self.name
  99. def instantiate(self, device):
  100. return ConsoleServerPort(
  101. device=device,
  102. name=self.name,
  103. type=self.type
  104. )
  105. class PowerPortTemplate(ComponentTemplateModel):
  106. """
  107. A template for a PowerPort to be created for a new Device.
  108. """
  109. device_type = models.ForeignKey(
  110. to='dcim.DeviceType',
  111. on_delete=models.CASCADE,
  112. related_name='powerport_templates'
  113. )
  114. name = models.CharField(
  115. max_length=50
  116. )
  117. _name = NaturalOrderingField(
  118. target_field='name',
  119. max_length=100,
  120. blank=True
  121. )
  122. type = models.CharField(
  123. max_length=50,
  124. choices=PowerPortTypeChoices,
  125. blank=True
  126. )
  127. maximum_draw = models.PositiveSmallIntegerField(
  128. blank=True,
  129. null=True,
  130. validators=[MinValueValidator(1)],
  131. help_text="Maximum power draw (watts)"
  132. )
  133. allocated_draw = models.PositiveSmallIntegerField(
  134. blank=True,
  135. null=True,
  136. validators=[MinValueValidator(1)],
  137. help_text="Allocated power draw (watts)"
  138. )
  139. class Meta:
  140. ordering = ('device_type', '_name')
  141. unique_together = ('device_type', 'name')
  142. def __str__(self):
  143. return self.name
  144. def instantiate(self, device):
  145. return PowerPort(
  146. device=device,
  147. name=self.name,
  148. type=self.type,
  149. maximum_draw=self.maximum_draw,
  150. allocated_draw=self.allocated_draw
  151. )
  152. class PowerOutletTemplate(ComponentTemplateModel):
  153. """
  154. A template for a PowerOutlet to be created for a new Device.
  155. """
  156. device_type = models.ForeignKey(
  157. to='dcim.DeviceType',
  158. on_delete=models.CASCADE,
  159. related_name='poweroutlet_templates'
  160. )
  161. name = models.CharField(
  162. max_length=50
  163. )
  164. _name = NaturalOrderingField(
  165. target_field='name',
  166. max_length=100,
  167. blank=True
  168. )
  169. type = models.CharField(
  170. max_length=50,
  171. choices=PowerOutletTypeChoices,
  172. blank=True
  173. )
  174. power_port = models.ForeignKey(
  175. to='dcim.PowerPortTemplate',
  176. on_delete=models.SET_NULL,
  177. blank=True,
  178. null=True,
  179. related_name='poweroutlet_templates'
  180. )
  181. feed_leg = models.CharField(
  182. max_length=50,
  183. choices=PowerOutletFeedLegChoices,
  184. blank=True,
  185. help_text="Phase (for three-phase feeds)"
  186. )
  187. class Meta:
  188. ordering = ('device_type', '_name')
  189. unique_together = ('device_type', 'name')
  190. def __str__(self):
  191. return self.name
  192. def clean(self):
  193. # Validate power port assignment
  194. if self.power_port and self.power_port.device_type != self.device_type:
  195. raise ValidationError(
  196. "Parent power port ({}) must belong to the same device type".format(self.power_port)
  197. )
  198. def instantiate(self, device):
  199. if self.power_port:
  200. power_port = PowerPort.objects.get(device=device, name=self.power_port.name)
  201. else:
  202. power_port = None
  203. return PowerOutlet(
  204. device=device,
  205. name=self.name,
  206. type=self.type,
  207. power_port=power_port,
  208. feed_leg=self.feed_leg
  209. )
  210. class InterfaceTemplate(ComponentTemplateModel):
  211. """
  212. A template for a physical data interface on a new Device.
  213. """
  214. device_type = models.ForeignKey(
  215. to='dcim.DeviceType',
  216. on_delete=models.CASCADE,
  217. related_name='interface_templates'
  218. )
  219. name = models.CharField(
  220. max_length=64
  221. )
  222. _name = NaturalOrderingField(
  223. target_field='name',
  224. naturalize_function=naturalize_interface,
  225. max_length=100,
  226. blank=True
  227. )
  228. type = models.CharField(
  229. max_length=50,
  230. choices=InterfaceTypeChoices
  231. )
  232. mgmt_only = models.BooleanField(
  233. default=False,
  234. verbose_name='Management only'
  235. )
  236. class Meta:
  237. ordering = ('device_type', '_name')
  238. unique_together = ('device_type', 'name')
  239. def __str__(self):
  240. return self.name
  241. def instantiate(self, device):
  242. return Interface(
  243. device=device,
  244. name=self.name,
  245. type=self.type,
  246. mgmt_only=self.mgmt_only
  247. )
  248. class FrontPortTemplate(ComponentTemplateModel):
  249. """
  250. Template for a pass-through port on the front of a new Device.
  251. """
  252. device_type = models.ForeignKey(
  253. to='dcim.DeviceType',
  254. on_delete=models.CASCADE,
  255. related_name='frontport_templates'
  256. )
  257. name = models.CharField(
  258. max_length=64
  259. )
  260. _name = NaturalOrderingField(
  261. target_field='name',
  262. max_length=100,
  263. blank=True
  264. )
  265. type = models.CharField(
  266. max_length=50,
  267. choices=PortTypeChoices
  268. )
  269. rear_port = models.ForeignKey(
  270. to='dcim.RearPortTemplate',
  271. on_delete=models.CASCADE,
  272. related_name='frontport_templates'
  273. )
  274. rear_port_position = models.PositiveSmallIntegerField(
  275. default=1,
  276. validators=[MinValueValidator(1), MaxValueValidator(64)]
  277. )
  278. class Meta:
  279. ordering = ('device_type', '_name')
  280. unique_together = (
  281. ('device_type', 'name'),
  282. ('rear_port', 'rear_port_position'),
  283. )
  284. def __str__(self):
  285. return self.name
  286. def clean(self):
  287. # Validate rear port assignment
  288. if self.rear_port.device_type != self.device_type:
  289. raise ValidationError(
  290. "Rear port ({}) must belong to the same device type".format(self.rear_port)
  291. )
  292. # Validate rear port position assignment
  293. if self.rear_port_position > self.rear_port.positions:
  294. raise ValidationError(
  295. "Invalid rear port position ({}); rear port {} has only {} positions".format(
  296. self.rear_port_position, self.rear_port.name, self.rear_port.positions
  297. )
  298. )
  299. def instantiate(self, device):
  300. if self.rear_port:
  301. rear_port = RearPort.objects.get(device=device, name=self.rear_port.name)
  302. else:
  303. rear_port = None
  304. return FrontPort(
  305. device=device,
  306. name=self.name,
  307. type=self.type,
  308. rear_port=rear_port,
  309. rear_port_position=self.rear_port_position
  310. )
  311. class RearPortTemplate(ComponentTemplateModel):
  312. """
  313. Template for a pass-through port on the rear of a new Device.
  314. """
  315. device_type = models.ForeignKey(
  316. to='dcim.DeviceType',
  317. on_delete=models.CASCADE,
  318. related_name='rearport_templates'
  319. )
  320. name = models.CharField(
  321. max_length=64
  322. )
  323. _name = NaturalOrderingField(
  324. target_field='name',
  325. max_length=100,
  326. blank=True
  327. )
  328. type = models.CharField(
  329. max_length=50,
  330. choices=PortTypeChoices
  331. )
  332. positions = models.PositiveSmallIntegerField(
  333. default=1,
  334. validators=[MinValueValidator(1), MaxValueValidator(64)]
  335. )
  336. class Meta:
  337. ordering = ('device_type', '_name')
  338. unique_together = ('device_type', 'name')
  339. def __str__(self):
  340. return self.name
  341. def instantiate(self, device):
  342. return RearPort(
  343. device=device,
  344. name=self.name,
  345. type=self.type,
  346. positions=self.positions
  347. )
  348. class DeviceBayTemplate(ComponentTemplateModel):
  349. """
  350. A template for a DeviceBay to be created for a new parent Device.
  351. """
  352. device_type = models.ForeignKey(
  353. to='dcim.DeviceType',
  354. on_delete=models.CASCADE,
  355. related_name='device_bay_templates'
  356. )
  357. name = models.CharField(
  358. max_length=50
  359. )
  360. _name = NaturalOrderingField(
  361. target_field='name',
  362. max_length=100,
  363. blank=True
  364. )
  365. class Meta:
  366. ordering = ('device_type', '_name')
  367. unique_together = ('device_type', 'name')
  368. def __str__(self):
  369. return self.name
  370. def instantiate(self, device):
  371. return DeviceBay(
  372. device=device,
  373. name=self.name
  374. )