models.py 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473
  1. from django import forms
  2. from django.utils.translation import gettext as _
  3. from django.contrib.auth.models import User
  4. from django.contrib.contenttypes.models import ContentType
  5. from timezone_field import TimeZoneFormField
  6. from dcim.choices import *
  7. from dcim.constants import *
  8. from dcim.models import *
  9. from extras.models import Tag
  10. from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
  11. from netbox.forms import NetBoxModelForm
  12. from tenancy.forms import TenancyForm
  13. from utilities.forms import (
  14. APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
  15. DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SelectWithPK, SmallTextarea,
  16. SlugField, StaticSelect, SelectSpeedWidget,
  17. )
  18. from virtualization.models import Cluster, ClusterGroup
  19. from wireless.models import WirelessLAN, WirelessLANGroup
  20. from .common import InterfaceCommonForm
  21. __all__ = (
  22. 'CableForm',
  23. 'ConsolePortForm',
  24. 'ConsolePortTemplateForm',
  25. 'ConsoleServerPortForm',
  26. 'ConsoleServerPortTemplateForm',
  27. 'DeviceBayForm',
  28. 'DeviceBayTemplateForm',
  29. 'DeviceForm',
  30. 'DeviceRoleForm',
  31. 'DeviceTypeForm',
  32. 'DeviceVCMembershipForm',
  33. 'FrontPortForm',
  34. 'FrontPortTemplateForm',
  35. 'InterfaceForm',
  36. 'InterfaceTemplateForm',
  37. 'InventoryItemForm',
  38. 'InventoryItemRoleForm',
  39. 'InventoryItemTemplateForm',
  40. 'LocationForm',
  41. 'ManufacturerForm',
  42. 'ModuleForm',
  43. 'ModuleBayForm',
  44. 'ModuleBayTemplateForm',
  45. 'ModuleTypeForm',
  46. 'PlatformForm',
  47. 'PopulateDeviceBayForm',
  48. 'PowerFeedForm',
  49. 'PowerOutletForm',
  50. 'PowerOutletTemplateForm',
  51. 'PowerPanelForm',
  52. 'PowerPortForm',
  53. 'PowerPortTemplateForm',
  54. 'RackForm',
  55. 'RackReservationForm',
  56. 'RackRoleForm',
  57. 'RearPortForm',
  58. 'RearPortTemplateForm',
  59. 'RegionForm',
  60. 'SiteForm',
  61. 'SiteGroupForm',
  62. 'VCMemberSelectForm',
  63. 'VirtualChassisForm',
  64. )
  65. INTERFACE_MODE_HELP_TEXT = """
  66. Access: One untagged VLAN<br />
  67. Tagged: One untagged VLAN and/or one or more tagged VLANs<br />
  68. Tagged (All): Implies all VLANs are available (w/optional untagged VLAN)
  69. """
  70. class RegionForm(NetBoxModelForm):
  71. parent = DynamicModelChoiceField(
  72. queryset=Region.objects.all(),
  73. required=False
  74. )
  75. slug = SlugField()
  76. tags = DynamicModelMultipleChoiceField(
  77. queryset=Tag.objects.all(),
  78. required=False
  79. )
  80. class Meta:
  81. model = Region
  82. fields = (
  83. 'parent', 'name', 'slug', 'description', 'tags',
  84. )
  85. class SiteGroupForm(NetBoxModelForm):
  86. parent = DynamicModelChoiceField(
  87. queryset=SiteGroup.objects.all(),
  88. required=False
  89. )
  90. slug = SlugField()
  91. tags = DynamicModelMultipleChoiceField(
  92. queryset=Tag.objects.all(),
  93. required=False
  94. )
  95. class Meta:
  96. model = SiteGroup
  97. fields = (
  98. 'parent', 'name', 'slug', 'description', 'tags',
  99. )
  100. class SiteForm(TenancyForm, NetBoxModelForm):
  101. region = DynamicModelChoiceField(
  102. queryset=Region.objects.all(),
  103. required=False
  104. )
  105. group = DynamicModelChoiceField(
  106. queryset=SiteGroup.objects.all(),
  107. required=False
  108. )
  109. asns = DynamicModelMultipleChoiceField(
  110. queryset=ASN.objects.all(),
  111. label=_('ASNs'),
  112. required=False
  113. )
  114. slug = SlugField()
  115. time_zone = TimeZoneFormField(
  116. choices=add_blank_choice(TimeZoneFormField().choices),
  117. required=False,
  118. widget=StaticSelect()
  119. )
  120. comments = CommentField()
  121. tags = DynamicModelMultipleChoiceField(
  122. queryset=Tag.objects.all(),
  123. required=False
  124. )
  125. fieldsets = (
  126. ('Site', (
  127. 'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
  128. )),
  129. ('Tenancy', ('tenant_group', 'tenant')),
  130. ('Contact Info', ('physical_address', 'shipping_address', 'latitude', 'longitude')),
  131. )
  132. class Meta:
  133. model = Site
  134. fields = (
  135. 'name', 'slug', 'status', 'region', 'group', 'tenant_group', 'tenant', 'facility', 'asns', 'time_zone',
  136. 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
  137. )
  138. widgets = {
  139. 'physical_address': SmallTextarea(
  140. attrs={
  141. 'rows': 3,
  142. }
  143. ),
  144. 'shipping_address': SmallTextarea(
  145. attrs={
  146. 'rows': 3,
  147. }
  148. ),
  149. 'status': StaticSelect(),
  150. 'time_zone': StaticSelect(),
  151. }
  152. help_texts = {
  153. 'name': "Full name of the site",
  154. 'facility': "Data center provider and facility (e.g. Equinix NY7)",
  155. 'time_zone': "Local time zone",
  156. 'description': "Short description (will appear in sites list)",
  157. 'physical_address': "Physical location of the building (e.g. for GPS)",
  158. 'shipping_address': "If different from the physical address",
  159. 'latitude': "Latitude in decimal format (xx.yyyyyy)",
  160. 'longitude': "Longitude in decimal format (xx.yyyyyy)"
  161. }
  162. class LocationForm(TenancyForm, NetBoxModelForm):
  163. region = DynamicModelChoiceField(
  164. queryset=Region.objects.all(),
  165. required=False,
  166. initial_params={
  167. 'sites': '$site'
  168. }
  169. )
  170. site_group = DynamicModelChoiceField(
  171. queryset=SiteGroup.objects.all(),
  172. required=False,
  173. initial_params={
  174. 'sites': '$site'
  175. }
  176. )
  177. site = DynamicModelChoiceField(
  178. queryset=Site.objects.all(),
  179. query_params={
  180. 'region_id': '$region',
  181. 'group_id': '$site_group',
  182. }
  183. )
  184. parent = DynamicModelChoiceField(
  185. queryset=Location.objects.all(),
  186. required=False,
  187. query_params={
  188. 'site_id': '$site'
  189. }
  190. )
  191. slug = SlugField()
  192. tags = DynamicModelMultipleChoiceField(
  193. queryset=Tag.objects.all(),
  194. required=False
  195. )
  196. fieldsets = (
  197. ('Location', (
  198. 'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tags',
  199. )),
  200. ('Tenancy', ('tenant_group', 'tenant')),
  201. )
  202. class Meta:
  203. model = Location
  204. fields = (
  205. 'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tenant_group', 'tenant', 'tags',
  206. )
  207. class RackRoleForm(NetBoxModelForm):
  208. slug = SlugField()
  209. tags = DynamicModelMultipleChoiceField(
  210. queryset=Tag.objects.all(),
  211. required=False
  212. )
  213. class Meta:
  214. model = RackRole
  215. fields = [
  216. 'name', 'slug', 'color', 'description', 'tags',
  217. ]
  218. class RackForm(TenancyForm, NetBoxModelForm):
  219. region = DynamicModelChoiceField(
  220. queryset=Region.objects.all(),
  221. required=False,
  222. initial_params={
  223. 'sites': '$site'
  224. }
  225. )
  226. site_group = DynamicModelChoiceField(
  227. queryset=SiteGroup.objects.all(),
  228. required=False,
  229. initial_params={
  230. 'sites': '$site'
  231. }
  232. )
  233. site = DynamicModelChoiceField(
  234. queryset=Site.objects.all(),
  235. query_params={
  236. 'region_id': '$region',
  237. 'group_id': '$site_group',
  238. }
  239. )
  240. location = DynamicModelChoiceField(
  241. queryset=Location.objects.all(),
  242. required=False,
  243. query_params={
  244. 'site_id': '$site'
  245. }
  246. )
  247. role = DynamicModelChoiceField(
  248. queryset=RackRole.objects.all(),
  249. required=False
  250. )
  251. comments = CommentField()
  252. tags = DynamicModelMultipleChoiceField(
  253. queryset=Tag.objects.all(),
  254. required=False
  255. )
  256. class Meta:
  257. model = Rack
  258. fields = [
  259. 'region', 'site_group', 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status',
  260. 'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
  261. 'outer_unit', 'comments', 'tags',
  262. ]
  263. help_texts = {
  264. 'site': "The site at which the rack exists",
  265. 'name': "Organizational rack name",
  266. 'facility_id': "The unique rack ID assigned by the facility",
  267. 'u_height': "Height in rack units",
  268. }
  269. widgets = {
  270. 'status': StaticSelect(),
  271. 'type': StaticSelect(),
  272. 'width': StaticSelect(),
  273. 'outer_unit': StaticSelect(),
  274. }
  275. class RackReservationForm(TenancyForm, NetBoxModelForm):
  276. region = DynamicModelChoiceField(
  277. queryset=Region.objects.all(),
  278. required=False,
  279. initial_params={
  280. 'sites': '$site'
  281. }
  282. )
  283. site_group = DynamicModelChoiceField(
  284. queryset=SiteGroup.objects.all(),
  285. required=False,
  286. initial_params={
  287. 'sites': '$site'
  288. }
  289. )
  290. site = DynamicModelChoiceField(
  291. queryset=Site.objects.all(),
  292. required=False,
  293. query_params={
  294. 'region_id': '$region',
  295. 'group_id': '$site_group',
  296. }
  297. )
  298. location = DynamicModelChoiceField(
  299. queryset=Location.objects.all(),
  300. required=False,
  301. query_params={
  302. 'site_id': '$site'
  303. }
  304. )
  305. rack = DynamicModelChoiceField(
  306. queryset=Rack.objects.all(),
  307. query_params={
  308. 'site_id': '$site',
  309. 'location_id': '$location',
  310. }
  311. )
  312. units = NumericArrayField(
  313. base_field=forms.IntegerField(),
  314. help_text="Comma-separated list of numeric unit IDs. A range may be specified using a hyphen."
  315. )
  316. user = forms.ModelChoiceField(
  317. queryset=User.objects.order_by(
  318. 'username'
  319. ),
  320. widget=StaticSelect()
  321. )
  322. tags = DynamicModelMultipleChoiceField(
  323. queryset=Tag.objects.all(),
  324. required=False
  325. )
  326. fieldsets = (
  327. ('Reservation', ('region', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
  328. ('Tenancy', ('tenant_group', 'tenant')),
  329. )
  330. class Meta:
  331. model = RackReservation
  332. fields = [
  333. 'region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'tenant_group', 'tenant',
  334. 'description', 'tags',
  335. ]
  336. class ManufacturerForm(NetBoxModelForm):
  337. slug = SlugField()
  338. tags = DynamicModelMultipleChoiceField(
  339. queryset=Tag.objects.all(),
  340. required=False
  341. )
  342. class Meta:
  343. model = Manufacturer
  344. fields = [
  345. 'name', 'slug', 'description', 'tags',
  346. ]
  347. class DeviceTypeForm(NetBoxModelForm):
  348. manufacturer = DynamicModelChoiceField(
  349. queryset=Manufacturer.objects.all()
  350. )
  351. slug = SlugField(
  352. slug_source='model'
  353. )
  354. comments = CommentField()
  355. tags = DynamicModelMultipleChoiceField(
  356. queryset=Tag.objects.all(),
  357. required=False
  358. )
  359. fieldsets = (
  360. ('Device Type', (
  361. 'manufacturer', 'model', 'slug', 'part_number', 'tags',
  362. )),
  363. ('Chassis', (
  364. 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
  365. )),
  366. ('Images', ('front_image', 'rear_image')),
  367. )
  368. class Meta:
  369. model = DeviceType
  370. fields = [
  371. 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
  372. 'front_image', 'rear_image', 'comments', 'tags',
  373. ]
  374. widgets = {
  375. 'subdevice_role': StaticSelect(),
  376. 'front_image': ClearableFileInput(attrs={
  377. 'accept': DEVICETYPE_IMAGE_FORMATS
  378. }),
  379. 'rear_image': ClearableFileInput(attrs={
  380. 'accept': DEVICETYPE_IMAGE_FORMATS
  381. })
  382. }
  383. class ModuleTypeForm(NetBoxModelForm):
  384. manufacturer = DynamicModelChoiceField(
  385. queryset=Manufacturer.objects.all()
  386. )
  387. comments = CommentField()
  388. tags = DynamicModelMultipleChoiceField(
  389. queryset=Tag.objects.all(),
  390. required=False
  391. )
  392. class Meta:
  393. model = ModuleType
  394. fields = [
  395. 'manufacturer', 'model', 'part_number', 'comments', 'tags',
  396. ]
  397. class DeviceRoleForm(NetBoxModelForm):
  398. slug = SlugField()
  399. tags = DynamicModelMultipleChoiceField(
  400. queryset=Tag.objects.all(),
  401. required=False
  402. )
  403. class Meta:
  404. model = DeviceRole
  405. fields = [
  406. 'name', 'slug', 'color', 'vm_role', 'description', 'tags',
  407. ]
  408. class PlatformForm(NetBoxModelForm):
  409. manufacturer = DynamicModelChoiceField(
  410. queryset=Manufacturer.objects.all(),
  411. required=False
  412. )
  413. slug = SlugField(
  414. max_length=64
  415. )
  416. tags = DynamicModelMultipleChoiceField(
  417. queryset=Tag.objects.all(),
  418. required=False
  419. )
  420. class Meta:
  421. model = Platform
  422. fields = [
  423. 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description', 'tags',
  424. ]
  425. widgets = {
  426. 'napalm_args': SmallTextarea(),
  427. }
  428. class DeviceForm(TenancyForm, NetBoxModelForm):
  429. region = DynamicModelChoiceField(
  430. queryset=Region.objects.all(),
  431. required=False,
  432. initial_params={
  433. 'sites': '$site'
  434. }
  435. )
  436. site_group = DynamicModelChoiceField(
  437. queryset=SiteGroup.objects.all(),
  438. required=False,
  439. initial_params={
  440. 'sites': '$site'
  441. }
  442. )
  443. site = DynamicModelChoiceField(
  444. queryset=Site.objects.all(),
  445. query_params={
  446. 'region_id': '$region',
  447. 'group_id': '$site_group',
  448. }
  449. )
  450. location = DynamicModelChoiceField(
  451. queryset=Location.objects.all(),
  452. required=False,
  453. query_params={
  454. 'site_id': '$site'
  455. },
  456. initial_params={
  457. 'racks': '$rack'
  458. }
  459. )
  460. rack = DynamicModelChoiceField(
  461. queryset=Rack.objects.all(),
  462. required=False,
  463. query_params={
  464. 'site_id': '$site',
  465. 'location_id': '$location',
  466. }
  467. )
  468. position = forms.IntegerField(
  469. required=False,
  470. help_text="The lowest-numbered unit occupied by the device",
  471. widget=APISelect(
  472. api_url='/api/dcim/racks/{{rack}}/elevation/',
  473. attrs={
  474. 'disabled-indicator': 'device',
  475. 'data-dynamic-params': '[{"fieldName":"face","queryParam":"face"}]'
  476. }
  477. )
  478. )
  479. manufacturer = DynamicModelChoiceField(
  480. queryset=Manufacturer.objects.all(),
  481. required=False,
  482. initial_params={
  483. 'device_types': '$device_type'
  484. }
  485. )
  486. device_type = DynamicModelChoiceField(
  487. queryset=DeviceType.objects.all(),
  488. query_params={
  489. 'manufacturer_id': '$manufacturer'
  490. }
  491. )
  492. device_role = DynamicModelChoiceField(
  493. queryset=DeviceRole.objects.all()
  494. )
  495. platform = DynamicModelChoiceField(
  496. queryset=Platform.objects.all(),
  497. required=False,
  498. query_params={
  499. 'manufacturer_id': ['$manufacturer', 'null']
  500. }
  501. )
  502. cluster_group = DynamicModelChoiceField(
  503. queryset=ClusterGroup.objects.all(),
  504. required=False,
  505. null_option='None',
  506. initial_params={
  507. 'clusters': '$cluster'
  508. }
  509. )
  510. cluster = DynamicModelChoiceField(
  511. queryset=Cluster.objects.all(),
  512. required=False,
  513. query_params={
  514. 'group_id': '$cluster_group'
  515. }
  516. )
  517. comments = CommentField()
  518. local_context_data = JSONField(
  519. required=False,
  520. label=''
  521. )
  522. tags = DynamicModelMultipleChoiceField(
  523. queryset=Tag.objects.all(),
  524. required=False
  525. )
  526. class Meta:
  527. model = Device
  528. fields = [
  529. 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'rack',
  530. 'location', 'position', 'face', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6',
  531. 'cluster_group', 'cluster', 'tenant_group', 'tenant', 'comments', 'tags', 'local_context_data'
  532. ]
  533. help_texts = {
  534. 'device_role': "The function this device serves",
  535. 'serial': "Chassis serial number",
  536. 'local_context_data': "Local config context data overwrites all source contexts in the final rendered "
  537. "config context",
  538. }
  539. widgets = {
  540. 'face': StaticSelect(),
  541. 'status': StaticSelect(),
  542. 'airflow': StaticSelect(),
  543. 'primary_ip4': StaticSelect(),
  544. 'primary_ip6': StaticSelect(),
  545. }
  546. def __init__(self, *args, **kwargs):
  547. super().__init__(*args, **kwargs)
  548. if self.instance.pk:
  549. # Compile list of choices for primary IPv4 and IPv6 addresses
  550. for family in [4, 6]:
  551. ip_choices = [(None, '---------')]
  552. # Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member
  553. interface_ids = self.instance.vc_interfaces(if_master=False).values_list('pk', flat=True)
  554. # Collect interface IPs
  555. interface_ips = IPAddress.objects.filter(
  556. address__family=family,
  557. assigned_object_type=ContentType.objects.get_for_model(Interface),
  558. assigned_object_id__in=interface_ids
  559. ).prefetch_related('assigned_object')
  560. if interface_ips:
  561. ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
  562. ip_choices.append(('Interface IPs', ip_list))
  563. # Collect NAT IPs
  564. nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
  565. address__family=family,
  566. nat_inside__assigned_object_type=ContentType.objects.get_for_model(Interface),
  567. nat_inside__assigned_object_id__in=interface_ids
  568. ).prefetch_related('assigned_object')
  569. if nat_ips:
  570. ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
  571. ip_choices.append(('NAT IPs', ip_list))
  572. self.fields['primary_ip{}'.format(family)].choices = ip_choices
  573. # If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device
  574. # can be flipped from one face to another.
  575. self.fields['position'].widget.add_query_param('exclude', self.instance.pk)
  576. # Limit platform by manufacturer
  577. self.fields['platform'].queryset = Platform.objects.filter(
  578. Q(manufacturer__isnull=True) | Q(manufacturer=self.instance.device_type.manufacturer)
  579. )
  580. # Disable rack assignment if this is a child device installed in a parent device
  581. if self.instance.device_type.is_child_device and hasattr(self.instance, 'parent_bay'):
  582. self.fields['site'].disabled = True
  583. self.fields['rack'].disabled = True
  584. self.initial['site'] = self.instance.parent_bay.device.site_id
  585. self.initial['rack'] = self.instance.parent_bay.device.rack_id
  586. else:
  587. # An object that doesn't exist yet can't have any IPs assigned to it
  588. self.fields['primary_ip4'].choices = []
  589. self.fields['primary_ip4'].widget.attrs['readonly'] = True
  590. self.fields['primary_ip6'].choices = []
  591. self.fields['primary_ip6'].widget.attrs['readonly'] = True
  592. # Rack position
  593. position = self.data.get('position') or self.initial.get('position')
  594. if position:
  595. self.fields['position'].widget.choices = [(position, f'U{position}')]
  596. class ModuleForm(NetBoxModelForm):
  597. device = DynamicModelChoiceField(
  598. queryset=Device.objects.all(),
  599. required=False,
  600. initial_params={
  601. 'modulebays': '$module_bay'
  602. }
  603. )
  604. module_bay = DynamicModelChoiceField(
  605. queryset=ModuleBay.objects.all(),
  606. query_params={
  607. 'device_id': '$device'
  608. }
  609. )
  610. manufacturer = DynamicModelChoiceField(
  611. queryset=Manufacturer.objects.all(),
  612. required=False,
  613. initial_params={
  614. 'device_types': '$device_type'
  615. }
  616. )
  617. module_type = DynamicModelChoiceField(
  618. queryset=ModuleType.objects.all(),
  619. query_params={
  620. 'manufacturer_id': '$manufacturer'
  621. }
  622. )
  623. comments = CommentField()
  624. tags = DynamicModelMultipleChoiceField(
  625. queryset=Tag.objects.all(),
  626. required=False
  627. )
  628. class Meta:
  629. model = Module
  630. fields = [
  631. 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'tags', 'comments',
  632. ]
  633. class CableForm(TenancyForm, NetBoxModelForm):
  634. tags = DynamicModelMultipleChoiceField(
  635. queryset=Tag.objects.all(),
  636. required=False
  637. )
  638. class Meta:
  639. model = Cable
  640. fields = [
  641. 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
  642. ]
  643. widgets = {
  644. 'status': StaticSelect,
  645. 'type': StaticSelect,
  646. 'length_unit': StaticSelect,
  647. }
  648. error_messages = {
  649. 'length': {
  650. 'max_value': 'Maximum length is 32767 (any unit)'
  651. }
  652. }
  653. class PowerPanelForm(NetBoxModelForm):
  654. region = DynamicModelChoiceField(
  655. queryset=Region.objects.all(),
  656. required=False,
  657. initial_params={
  658. 'sites': '$site'
  659. }
  660. )
  661. site_group = DynamicModelChoiceField(
  662. queryset=SiteGroup.objects.all(),
  663. required=False,
  664. initial_params={
  665. 'sites': '$site'
  666. }
  667. )
  668. site = DynamicModelChoiceField(
  669. queryset=Site.objects.all(),
  670. query_params={
  671. 'region_id': '$region',
  672. 'group_id': '$site_group',
  673. }
  674. )
  675. location = DynamicModelChoiceField(
  676. queryset=Location.objects.all(),
  677. required=False,
  678. query_params={
  679. 'site_id': '$site'
  680. }
  681. )
  682. tags = DynamicModelMultipleChoiceField(
  683. queryset=Tag.objects.all(),
  684. required=False
  685. )
  686. fieldsets = (
  687. ('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'tags')),
  688. )
  689. class Meta:
  690. model = PowerPanel
  691. fields = [
  692. 'region', 'site_group', 'site', 'location', 'name', 'tags',
  693. ]
  694. class PowerFeedForm(NetBoxModelForm):
  695. region = DynamicModelChoiceField(
  696. queryset=Region.objects.all(),
  697. required=False,
  698. initial_params={
  699. 'sites__powerpanel': '$power_panel'
  700. }
  701. )
  702. site_group = DynamicModelChoiceField(
  703. queryset=SiteGroup.objects.all(),
  704. required=False,
  705. initial_params={
  706. 'sites': '$site'
  707. }
  708. )
  709. site = DynamicModelChoiceField(
  710. queryset=Site.objects.all(),
  711. required=False,
  712. initial_params={
  713. 'powerpanel': '$power_panel'
  714. },
  715. query_params={
  716. 'region_id': '$region',
  717. 'group_id': '$site_group',
  718. }
  719. )
  720. power_panel = DynamicModelChoiceField(
  721. queryset=PowerPanel.objects.all(),
  722. query_params={
  723. 'site_id': '$site'
  724. }
  725. )
  726. rack = DynamicModelChoiceField(
  727. queryset=Rack.objects.all(),
  728. required=False,
  729. query_params={
  730. 'site_id': '$site'
  731. }
  732. )
  733. comments = CommentField()
  734. tags = DynamicModelMultipleChoiceField(
  735. queryset=Tag.objects.all(),
  736. required=False
  737. )
  738. fieldsets = (
  739. ('Power Panel', ('region', 'site', 'power_panel')),
  740. ('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')),
  741. ('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
  742. )
  743. class Meta:
  744. model = PowerFeed
  745. fields = [
  746. 'region', 'site_group', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply',
  747. 'phase', 'voltage', 'amperage', 'max_utilization', 'comments', 'tags',
  748. ]
  749. widgets = {
  750. 'status': StaticSelect(),
  751. 'type': StaticSelect(),
  752. 'supply': StaticSelect(),
  753. 'phase': StaticSelect(),
  754. }
  755. #
  756. # Virtual chassis
  757. #
  758. class VirtualChassisForm(NetBoxModelForm):
  759. master = forms.ModelChoiceField(
  760. queryset=Device.objects.all(),
  761. required=False,
  762. )
  763. tags = DynamicModelMultipleChoiceField(
  764. queryset=Tag.objects.all(),
  765. required=False
  766. )
  767. class Meta:
  768. model = VirtualChassis
  769. fields = [
  770. 'name', 'domain', 'master', 'tags',
  771. ]
  772. widgets = {
  773. 'master': SelectWithPK(),
  774. }
  775. def __init__(self, *args, **kwargs):
  776. super().__init__(*args, **kwargs)
  777. self.fields['master'].queryset = Device.objects.filter(virtual_chassis=self.instance)
  778. class DeviceVCMembershipForm(forms.ModelForm):
  779. class Meta:
  780. model = Device
  781. fields = [
  782. 'vc_position', 'vc_priority',
  783. ]
  784. labels = {
  785. 'vc_position': 'Position',
  786. 'vc_priority': 'Priority',
  787. }
  788. def __init__(self, validate_vc_position=False, *args, **kwargs):
  789. super().__init__(*args, **kwargs)
  790. # Require VC position (only required when the Device is a VirtualChassis member)
  791. self.fields['vc_position'].required = True
  792. # Add bootstrap classes to form elements.
  793. self.fields['vc_position'].widget.attrs = {'class': 'form-control'}
  794. self.fields['vc_priority'].widget.attrs = {'class': 'form-control'}
  795. # Validation of vc_position is optional. This is only required when adding a new member to an existing
  796. # VirtualChassis. Otherwise, vc_position validation is handled by BaseVCMemberFormSet.
  797. self.validate_vc_position = validate_vc_position
  798. def clean_vc_position(self):
  799. vc_position = self.cleaned_data['vc_position']
  800. if self.validate_vc_position:
  801. conflicting_members = Device.objects.filter(
  802. virtual_chassis=self.instance.virtual_chassis,
  803. vc_position=vc_position
  804. )
  805. if conflicting_members.exists():
  806. raise forms.ValidationError(
  807. 'A virtual chassis member already exists in position {}.'.format(vc_position)
  808. )
  809. return vc_position
  810. class VCMemberSelectForm(BootstrapMixin, forms.Form):
  811. region = DynamicModelChoiceField(
  812. queryset=Region.objects.all(),
  813. required=False,
  814. initial_params={
  815. 'sites': '$site'
  816. }
  817. )
  818. site_group = DynamicModelChoiceField(
  819. queryset=SiteGroup.objects.all(),
  820. required=False,
  821. initial_params={
  822. 'sites': '$site'
  823. }
  824. )
  825. site = DynamicModelChoiceField(
  826. queryset=Site.objects.all(),
  827. required=False,
  828. query_params={
  829. 'region_id': '$region',
  830. 'group_id': '$site_group',
  831. }
  832. )
  833. rack = DynamicModelChoiceField(
  834. queryset=Rack.objects.all(),
  835. required=False,
  836. null_option='None',
  837. query_params={
  838. 'site_id': '$site'
  839. }
  840. )
  841. device = DynamicModelChoiceField(
  842. queryset=Device.objects.all(),
  843. query_params={
  844. 'site_id': '$site',
  845. 'rack_id': '$rack',
  846. 'virtual_chassis_id': 'null',
  847. }
  848. )
  849. def clean_device(self):
  850. device = self.cleaned_data['device']
  851. if device.virtual_chassis is not None:
  852. raise forms.ValidationError(
  853. f"Device {device} is already assigned to a virtual chassis."
  854. )
  855. return device
  856. #
  857. # Device component templates
  858. #
  859. class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
  860. class Meta:
  861. model = ConsolePortTemplate
  862. fields = [
  863. 'device_type', 'module_type', 'name', 'label', 'type', 'description',
  864. ]
  865. widgets = {
  866. 'device_type': forms.HiddenInput(),
  867. 'module_type': forms.HiddenInput(),
  868. 'type': StaticSelect,
  869. }
  870. class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
  871. class Meta:
  872. model = ConsoleServerPortTemplate
  873. fields = [
  874. 'device_type', 'module_type', 'name', 'label', 'type', 'description',
  875. ]
  876. widgets = {
  877. 'device_type': forms.HiddenInput(),
  878. 'module_type': forms.HiddenInput(),
  879. 'type': StaticSelect,
  880. }
  881. class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
  882. class Meta:
  883. model = PowerPortTemplate
  884. fields = [
  885. 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
  886. ]
  887. widgets = {
  888. 'device_type': forms.HiddenInput(),
  889. 'module_type': forms.HiddenInput(),
  890. 'type': StaticSelect(),
  891. }
  892. class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
  893. power_port = DynamicModelChoiceField(
  894. queryset=PowerPortTemplate.objects.all(),
  895. required=False,
  896. query_params={
  897. 'devicetype_id': '$device_type',
  898. }
  899. )
  900. class Meta:
  901. model = PowerOutletTemplate
  902. fields = [
  903. 'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
  904. ]
  905. widgets = {
  906. 'device_type': forms.HiddenInput(),
  907. 'module_type': forms.HiddenInput(),
  908. 'type': StaticSelect(),
  909. 'feed_leg': StaticSelect(),
  910. }
  911. class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
  912. class Meta:
  913. model = InterfaceTemplate
  914. fields = [
  915. 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description',
  916. ]
  917. widgets = {
  918. 'device_type': forms.HiddenInput(),
  919. 'module_type': forms.HiddenInput(),
  920. 'type': StaticSelect(),
  921. }
  922. class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
  923. rear_port = DynamicModelChoiceField(
  924. queryset=RearPortTemplate.objects.all(),
  925. required=False,
  926. query_params={
  927. 'devicetype_id': '$device_type',
  928. }
  929. )
  930. class Meta:
  931. model = FrontPortTemplate
  932. fields = [
  933. 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
  934. 'description',
  935. ]
  936. widgets = {
  937. 'device_type': forms.HiddenInput(),
  938. 'module_type': forms.HiddenInput(),
  939. 'type': StaticSelect(),
  940. }
  941. class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
  942. class Meta:
  943. model = RearPortTemplate
  944. fields = [
  945. 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
  946. ]
  947. widgets = {
  948. 'device_type': forms.HiddenInput(),
  949. 'module_type': forms.HiddenInput(),
  950. 'type': StaticSelect(),
  951. }
  952. class ModuleBayTemplateForm(BootstrapMixin, forms.ModelForm):
  953. class Meta:
  954. model = ModuleBayTemplate
  955. fields = [
  956. 'device_type', 'name', 'label', 'position', 'description',
  957. ]
  958. widgets = {
  959. 'device_type': forms.HiddenInput(),
  960. }
  961. class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
  962. class Meta:
  963. model = DeviceBayTemplate
  964. fields = [
  965. 'device_type', 'name', 'label', 'description',
  966. ]
  967. widgets = {
  968. 'device_type': forms.HiddenInput(),
  969. }
  970. class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
  971. parent = DynamicModelChoiceField(
  972. queryset=InventoryItem.objects.all(),
  973. required=False,
  974. query_params={
  975. 'device_id': '$device'
  976. }
  977. )
  978. role = DynamicModelChoiceField(
  979. queryset=InventoryItemRole.objects.all(),
  980. required=False
  981. )
  982. manufacturer = DynamicModelChoiceField(
  983. queryset=Manufacturer.objects.all(),
  984. required=False
  985. )
  986. component_type = ContentTypeChoiceField(
  987. queryset=ContentType.objects.all(),
  988. limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS,
  989. required=False,
  990. widget=forms.HiddenInput
  991. )
  992. component_id = forms.IntegerField(
  993. required=False,
  994. widget=forms.HiddenInput
  995. )
  996. fieldsets = (
  997. ('Inventory Item', ('device_type', 'parent', 'name', 'label', 'role', 'description')),
  998. ('Hardware', ('manufacturer', 'part_id')),
  999. )
  1000. class Meta:
  1001. model = InventoryItemTemplate
  1002. fields = [
  1003. 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
  1004. 'component_type', 'component_id',
  1005. ]
  1006. widgets = {
  1007. 'device_type': forms.HiddenInput(),
  1008. }
  1009. #
  1010. # Device components
  1011. #
  1012. class ConsolePortForm(NetBoxModelForm):
  1013. tags = DynamicModelMultipleChoiceField(
  1014. queryset=Tag.objects.all(),
  1015. required=False
  1016. )
  1017. class Meta:
  1018. model = ConsolePort
  1019. fields = [
  1020. 'device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
  1021. ]
  1022. widgets = {
  1023. 'device': forms.HiddenInput(),
  1024. 'type': StaticSelect(),
  1025. 'speed': StaticSelect(),
  1026. }
  1027. class ConsoleServerPortForm(NetBoxModelForm):
  1028. tags = DynamicModelMultipleChoiceField(
  1029. queryset=Tag.objects.all(),
  1030. required=False
  1031. )
  1032. class Meta:
  1033. model = ConsoleServerPort
  1034. fields = [
  1035. 'device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
  1036. ]
  1037. widgets = {
  1038. 'device': forms.HiddenInput(),
  1039. 'type': StaticSelect(),
  1040. 'speed': StaticSelect(),
  1041. }
  1042. class PowerPortForm(NetBoxModelForm):
  1043. tags = DynamicModelMultipleChoiceField(
  1044. queryset=Tag.objects.all(),
  1045. required=False
  1046. )
  1047. class Meta:
  1048. model = PowerPort
  1049. fields = [
  1050. 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description',
  1051. 'tags',
  1052. ]
  1053. widgets = {
  1054. 'device': forms.HiddenInput(),
  1055. 'type': StaticSelect(),
  1056. }
  1057. class PowerOutletForm(NetBoxModelForm):
  1058. power_port = DynamicModelChoiceField(
  1059. queryset=PowerPort.objects.all(),
  1060. required=False,
  1061. query_params={
  1062. 'device_id': '$device',
  1063. }
  1064. )
  1065. tags = DynamicModelMultipleChoiceField(
  1066. queryset=Tag.objects.all(),
  1067. required=False
  1068. )
  1069. class Meta:
  1070. model = PowerOutlet
  1071. fields = [
  1072. 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description', 'tags',
  1073. ]
  1074. widgets = {
  1075. 'device': forms.HiddenInput(),
  1076. 'type': StaticSelect(),
  1077. 'feed_leg': StaticSelect(),
  1078. }
  1079. class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
  1080. parent = DynamicModelChoiceField(
  1081. queryset=Interface.objects.all(),
  1082. required=False,
  1083. label='Parent interface',
  1084. query_params={
  1085. 'device_id': '$device',
  1086. }
  1087. )
  1088. bridge = DynamicModelChoiceField(
  1089. queryset=Interface.objects.all(),
  1090. required=False,
  1091. label='Bridged interface',
  1092. query_params={
  1093. 'device_id': '$device',
  1094. }
  1095. )
  1096. lag = DynamicModelChoiceField(
  1097. queryset=Interface.objects.all(),
  1098. required=False,
  1099. label='LAG interface',
  1100. query_params={
  1101. 'device_id': '$device',
  1102. 'type': 'lag',
  1103. }
  1104. )
  1105. wireless_lan_group = DynamicModelChoiceField(
  1106. queryset=WirelessLANGroup.objects.all(),
  1107. required=False,
  1108. label='Wireless LAN group'
  1109. )
  1110. wireless_lans = DynamicModelMultipleChoiceField(
  1111. queryset=WirelessLAN.objects.all(),
  1112. required=False,
  1113. label='Wireless LANs',
  1114. query_params={
  1115. 'group_id': '$wireless_lan_group',
  1116. }
  1117. )
  1118. vlan_group = DynamicModelChoiceField(
  1119. queryset=VLANGroup.objects.all(),
  1120. required=False,
  1121. label='VLAN group'
  1122. )
  1123. untagged_vlan = DynamicModelChoiceField(
  1124. queryset=VLAN.objects.all(),
  1125. required=False,
  1126. label='Untagged VLAN',
  1127. query_params={
  1128. 'group_id': '$vlan_group',
  1129. 'available_on_device': '$device',
  1130. }
  1131. )
  1132. tagged_vlans = DynamicModelMultipleChoiceField(
  1133. queryset=VLAN.objects.all(),
  1134. required=False,
  1135. label='Tagged VLANs',
  1136. query_params={
  1137. 'group_id': '$vlan_group',
  1138. 'available_on_device': '$device',
  1139. }
  1140. )
  1141. vrf = DynamicModelChoiceField(
  1142. queryset=VRF.objects.all(),
  1143. required=False,
  1144. label='VRF'
  1145. )
  1146. tags = DynamicModelMultipleChoiceField(
  1147. queryset=Tag.objects.all(),
  1148. required=False
  1149. )
  1150. fieldsets = (
  1151. ('Interface', ('device', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
  1152. ('Addressing', ('vrf', 'mac_address', 'wwn')),
  1153. ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
  1154. ('Related Interfaces', ('parent', 'bridge', 'lag')),
  1155. ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
  1156. ('Wireless', (
  1157. 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
  1158. )),
  1159. )
  1160. class Meta:
  1161. model = Interface
  1162. fields = [
  1163. 'device', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu',
  1164. 'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
  1165. 'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
  1166. ]
  1167. widgets = {
  1168. 'device': forms.HiddenInput(),
  1169. 'type': StaticSelect(),
  1170. 'speed': SelectSpeedWidget(),
  1171. 'duplex': StaticSelect(),
  1172. 'mode': StaticSelect(),
  1173. 'rf_role': StaticSelect(),
  1174. 'rf_channel': StaticSelect(),
  1175. }
  1176. labels = {
  1177. 'mode': '802.1Q Mode',
  1178. }
  1179. help_texts = {
  1180. 'mode': INTERFACE_MODE_HELP_TEXT,
  1181. 'rf_channel_frequency': "Populated by selected channel (if set)",
  1182. 'rf_channel_width': "Populated by selected channel (if set)",
  1183. }
  1184. class FrontPortForm(NetBoxModelForm):
  1185. rear_port = DynamicModelChoiceField(
  1186. queryset=RearPort.objects.all(),
  1187. query_params={
  1188. 'device_id': '$device',
  1189. }
  1190. )
  1191. tags = DynamicModelMultipleChoiceField(
  1192. queryset=Tag.objects.all(),
  1193. required=False
  1194. )
  1195. class Meta:
  1196. model = FrontPort
  1197. fields = [
  1198. 'device', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
  1199. 'description', 'tags',
  1200. ]
  1201. widgets = {
  1202. 'device': forms.HiddenInput(),
  1203. 'type': StaticSelect(),
  1204. }
  1205. class RearPortForm(NetBoxModelForm):
  1206. tags = DynamicModelMultipleChoiceField(
  1207. queryset=Tag.objects.all(),
  1208. required=False
  1209. )
  1210. class Meta:
  1211. model = RearPort
  1212. fields = [
  1213. 'device', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
  1214. ]
  1215. widgets = {
  1216. 'device': forms.HiddenInput(),
  1217. 'type': StaticSelect(),
  1218. }
  1219. class ModuleBayForm(NetBoxModelForm):
  1220. tags = DynamicModelMultipleChoiceField(
  1221. queryset=Tag.objects.all(),
  1222. required=False
  1223. )
  1224. class Meta:
  1225. model = ModuleBay
  1226. fields = [
  1227. 'device', 'name', 'label', 'position', 'description', 'tags',
  1228. ]
  1229. widgets = {
  1230. 'device': forms.HiddenInput(),
  1231. }
  1232. class DeviceBayForm(NetBoxModelForm):
  1233. tags = DynamicModelMultipleChoiceField(
  1234. queryset=Tag.objects.all(),
  1235. required=False
  1236. )
  1237. class Meta:
  1238. model = DeviceBay
  1239. fields = [
  1240. 'device', 'name', 'label', 'description', 'tags',
  1241. ]
  1242. widgets = {
  1243. 'device': forms.HiddenInput(),
  1244. }
  1245. class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
  1246. installed_device = forms.ModelChoiceField(
  1247. queryset=Device.objects.all(),
  1248. label='Child Device',
  1249. help_text="Child devices must first be created and assigned to the site/rack of the parent device.",
  1250. widget=StaticSelect(),
  1251. )
  1252. def __init__(self, device_bay, *args, **kwargs):
  1253. super().__init__(*args, **kwargs)
  1254. self.fields['installed_device'].queryset = Device.objects.filter(
  1255. site=device_bay.device.site,
  1256. rack=device_bay.device.rack,
  1257. parent_bay__isnull=True,
  1258. device_type__u_height=0,
  1259. device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
  1260. ).exclude(pk=device_bay.device.pk)
  1261. class InventoryItemForm(NetBoxModelForm):
  1262. parent = DynamicModelChoiceField(
  1263. queryset=InventoryItem.objects.all(),
  1264. required=False,
  1265. query_params={
  1266. 'device_id': '$device'
  1267. }
  1268. )
  1269. role = DynamicModelChoiceField(
  1270. queryset=InventoryItemRole.objects.all(),
  1271. required=False
  1272. )
  1273. manufacturer = DynamicModelChoiceField(
  1274. queryset=Manufacturer.objects.all(),
  1275. required=False
  1276. )
  1277. component_type = ContentTypeChoiceField(
  1278. queryset=ContentType.objects.all(),
  1279. limit_choices_to=MODULAR_COMPONENT_MODELS,
  1280. required=False,
  1281. widget=forms.HiddenInput
  1282. )
  1283. component_id = forms.IntegerField(
  1284. required=False,
  1285. widget=forms.HiddenInput
  1286. )
  1287. tags = DynamicModelMultipleChoiceField(
  1288. queryset=Tag.objects.all(),
  1289. required=False
  1290. )
  1291. fieldsets = (
  1292. ('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
  1293. ('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
  1294. )
  1295. class Meta:
  1296. model = InventoryItem
  1297. fields = [
  1298. 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
  1299. 'description', 'component_type', 'component_id', 'tags',
  1300. ]
  1301. widgets = {
  1302. 'device': forms.HiddenInput(),
  1303. }
  1304. #
  1305. # Device component roles
  1306. #
  1307. class InventoryItemRoleForm(NetBoxModelForm):
  1308. slug = SlugField()
  1309. tags = DynamicModelMultipleChoiceField(
  1310. queryset=Tag.objects.all(),
  1311. required=False
  1312. )
  1313. class Meta:
  1314. model = InventoryItemRole
  1315. fields = [
  1316. 'name', 'slug', 'color', 'description', 'tags',
  1317. ]