model_forms.py 48 KB

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