device_component_templates.py 11 KB

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