models.py 45 KB

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