2
0

models.py 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505
  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. class Meta:
  479. model = Device
  480. fields = [
  481. 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'rack',
  482. 'location', 'position', 'face', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6',
  483. 'cluster_group', 'cluster', 'tenant_group', 'tenant', 'comments', 'tags', 'local_context_data'
  484. ]
  485. help_texts = {
  486. 'device_role': "The function this device serves",
  487. 'serial': "Chassis serial number",
  488. 'local_context_data': "Local config context data overwrites all source contexts in the final rendered "
  489. "config context",
  490. }
  491. widgets = {
  492. 'face': StaticSelect(),
  493. 'status': StaticSelect(),
  494. 'airflow': StaticSelect(),
  495. 'primary_ip4': StaticSelect(),
  496. 'primary_ip6': StaticSelect(),
  497. }
  498. def __init__(self, *args, **kwargs):
  499. super().__init__(*args, **kwargs)
  500. if self.instance.pk:
  501. # Compile list of choices for primary IPv4 and IPv6 addresses
  502. for family in [4, 6]:
  503. ip_choices = [(None, '---------')]
  504. # Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member
  505. interface_ids = self.instance.vc_interfaces(if_master=False).values_list('pk', flat=True)
  506. # Collect interface IPs
  507. interface_ips = IPAddress.objects.filter(
  508. address__family=family,
  509. assigned_object_type=ContentType.objects.get_for_model(Interface),
  510. assigned_object_id__in=interface_ids
  511. ).prefetch_related('assigned_object')
  512. if interface_ips:
  513. ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
  514. ip_choices.append(('Interface IPs', ip_list))
  515. # Collect NAT IPs
  516. nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
  517. address__family=family,
  518. nat_inside__assigned_object_type=ContentType.objects.get_for_model(Interface),
  519. nat_inside__assigned_object_id__in=interface_ids
  520. ).prefetch_related('assigned_object')
  521. if nat_ips:
  522. ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
  523. ip_choices.append(('NAT IPs', ip_list))
  524. self.fields['primary_ip{}'.format(family)].choices = ip_choices
  525. # If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device
  526. # can be flipped from one face to another.
  527. self.fields['position'].widget.add_query_param('exclude', self.instance.pk)
  528. # Disable rack assignment if this is a child device installed in a parent device
  529. if self.instance.device_type.is_child_device and hasattr(self.instance, 'parent_bay'):
  530. self.fields['site'].disabled = True
  531. self.fields['rack'].disabled = True
  532. self.initial['site'] = self.instance.parent_bay.device.site_id
  533. self.initial['rack'] = self.instance.parent_bay.device.rack_id
  534. else:
  535. # An object that doesn't exist yet can't have any IPs assigned to it
  536. self.fields['primary_ip4'].choices = []
  537. self.fields['primary_ip4'].widget.attrs['readonly'] = True
  538. self.fields['primary_ip6'].choices = []
  539. self.fields['primary_ip6'].widget.attrs['readonly'] = True
  540. # Rack position
  541. position = self.data.get('position') or self.initial.get('position')
  542. if position:
  543. self.fields['position'].widget.choices = [(position, f'U{position}')]
  544. class ModuleForm(NetBoxModelForm):
  545. device = DynamicModelChoiceField(
  546. queryset=Device.objects.all(),
  547. initial_params={
  548. 'modulebays': '$module_bay'
  549. }
  550. )
  551. module_bay = DynamicModelChoiceField(
  552. queryset=ModuleBay.objects.all(),
  553. query_params={
  554. 'device_id': '$device'
  555. }
  556. )
  557. manufacturer = DynamicModelChoiceField(
  558. queryset=Manufacturer.objects.all(),
  559. required=False,
  560. initial_params={
  561. 'module_types': '$module_type'
  562. }
  563. )
  564. module_type = DynamicModelChoiceField(
  565. queryset=ModuleType.objects.all(),
  566. query_params={
  567. 'manufacturer_id': '$manufacturer'
  568. }
  569. )
  570. comments = CommentField()
  571. replicate_components = forms.BooleanField(
  572. required=False,
  573. initial=True,
  574. help_text="Automatically populate components associated with this module type"
  575. )
  576. adopt_components = forms.BooleanField(
  577. required=False,
  578. initial=False,
  579. help_text="Adopt already existing components"
  580. )
  581. fieldsets = (
  582. ('Module', (
  583. 'device', 'module_bay', 'manufacturer', 'module_type', 'tags',
  584. )),
  585. ('Hardware', (
  586. 'serial', 'asset_tag', 'replicate_components', 'adopt_components',
  587. )),
  588. )
  589. class Meta:
  590. model = Module
  591. fields = [
  592. 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'tags',
  593. 'replicate_components', 'adopt_components', 'comments',
  594. ]
  595. def __init__(self, *args, **kwargs):
  596. super().__init__(*args, **kwargs)
  597. if self.instance.pk:
  598. self.fields['replicate_components'].initial = False
  599. self.fields['replicate_components'].disabled = True
  600. self.fields['adopt_components'].initial = False
  601. self.fields['adopt_components'].disabled = True
  602. def save(self, *args, **kwargs):
  603. # If replicate_components is False, disable automatic component replication on the instance
  604. if self.instance.pk or not self.cleaned_data['replicate_components']:
  605. self.instance._disable_replication = True
  606. if self.cleaned_data['adopt_components']:
  607. self.instance._adopt_components = True
  608. return super().save(*args, **kwargs)
  609. def clean(self):
  610. super().clean()
  611. replicate_components = self.cleaned_data.get("replicate_components")
  612. adopt_components = self.cleaned_data.get("adopt_components")
  613. device = self.cleaned_data['device']
  614. module_type = self.cleaned_data['module_type']
  615. module_bay = self.cleaned_data['module_bay']
  616. # Bail out if we are not installing a new module or if we are not replicating components
  617. if self.instance.pk or not replicate_components:
  618. return
  619. for templates, component_attribute in [
  620. ("consoleporttemplates", "consoleports"),
  621. ("consoleserverporttemplates", "consoleserverports"),
  622. ("interfacetemplates", "interfaces"),
  623. ("powerporttemplates", "powerports"),
  624. ("poweroutlettemplates", "poweroutlets"),
  625. ("rearporttemplates", "rearports"),
  626. ("frontporttemplates", "frontports")
  627. ]:
  628. # Prefetch installed components
  629. installed_components = {
  630. component.name: component for component in getattr(device, component_attribute).all()
  631. }
  632. # Get the templates for the module type.
  633. for template in getattr(module_type, templates).all():
  634. # Installing modules with placeholders require that the bay has a position value
  635. if MODULE_TOKEN in template.name and not module_bay.position:
  636. raise forms.ValidationError(
  637. "Cannot install module with placeholder values in a module bay with no position defined"
  638. )
  639. resolved_name = template.name.replace(MODULE_TOKEN, module_bay.position)
  640. existing_item = installed_components.get(resolved_name)
  641. # It is not possible to adopt components already belonging to a module
  642. if adopt_components and existing_item and existing_item.module:
  643. raise forms.ValidationError(
  644. f"Cannot adopt {template.component_model.__name__} '{resolved_name}' as it already belongs "
  645. f"to a module"
  646. )
  647. # If we are not adopting components we error if the component exists
  648. if not adopt_components and resolved_name in installed_components:
  649. raise forms.ValidationError(
  650. f"{template.component_model.__name__} - {resolved_name} already exists"
  651. )
  652. class CableForm(TenancyForm, NetBoxModelForm):
  653. class Meta:
  654. model = Cable
  655. fields = [
  656. 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
  657. ]
  658. widgets = {
  659. 'status': StaticSelect,
  660. 'type': StaticSelect,
  661. 'length_unit': StaticSelect,
  662. }
  663. error_messages = {
  664. 'length': {
  665. 'max_value': 'Maximum length is 32767 (any unit)'
  666. }
  667. }
  668. class PowerPanelForm(NetBoxModelForm):
  669. region = DynamicModelChoiceField(
  670. queryset=Region.objects.all(),
  671. required=False,
  672. initial_params={
  673. 'sites': '$site'
  674. }
  675. )
  676. site_group = DynamicModelChoiceField(
  677. queryset=SiteGroup.objects.all(),
  678. required=False,
  679. initial_params={
  680. 'sites': '$site'
  681. }
  682. )
  683. site = DynamicModelChoiceField(
  684. queryset=Site.objects.all(),
  685. query_params={
  686. 'region_id': '$region',
  687. 'group_id': '$site_group',
  688. }
  689. )
  690. location = DynamicModelChoiceField(
  691. queryset=Location.objects.all(),
  692. required=False,
  693. query_params={
  694. 'site_id': '$site'
  695. }
  696. )
  697. fieldsets = (
  698. ('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'tags')),
  699. )
  700. class Meta:
  701. model = PowerPanel
  702. fields = [
  703. 'region', 'site_group', 'site', 'location', 'name', 'tags',
  704. ]
  705. class PowerFeedForm(NetBoxModelForm):
  706. region = DynamicModelChoiceField(
  707. queryset=Region.objects.all(),
  708. required=False,
  709. initial_params={
  710. 'sites__powerpanel': '$power_panel'
  711. }
  712. )
  713. site_group = DynamicModelChoiceField(
  714. queryset=SiteGroup.objects.all(),
  715. required=False,
  716. initial_params={
  717. 'sites': '$site'
  718. }
  719. )
  720. site = DynamicModelChoiceField(
  721. queryset=Site.objects.all(),
  722. required=False,
  723. initial_params={
  724. 'powerpanel': '$power_panel'
  725. },
  726. query_params={
  727. 'region_id': '$region',
  728. 'group_id': '$site_group',
  729. }
  730. )
  731. power_panel = DynamicModelChoiceField(
  732. queryset=PowerPanel.objects.all(),
  733. query_params={
  734. 'site_id': '$site'
  735. }
  736. )
  737. rack = DynamicModelChoiceField(
  738. queryset=Rack.objects.all(),
  739. required=False,
  740. query_params={
  741. 'site_id': '$site'
  742. }
  743. )
  744. comments = CommentField()
  745. fieldsets = (
  746. ('Power Panel', ('region', 'site', 'power_panel')),
  747. ('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')),
  748. ('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
  749. )
  750. class Meta:
  751. model = PowerFeed
  752. fields = [
  753. 'region', 'site_group', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply',
  754. 'phase', 'voltage', 'amperage', 'max_utilization', 'comments', 'tags',
  755. ]
  756. widgets = {
  757. 'status': StaticSelect(),
  758. 'type': StaticSelect(),
  759. 'supply': StaticSelect(),
  760. 'phase': StaticSelect(),
  761. }
  762. #
  763. # Virtual chassis
  764. #
  765. class VirtualChassisForm(NetBoxModelForm):
  766. master = forms.ModelChoiceField(
  767. queryset=Device.objects.all(),
  768. required=False,
  769. )
  770. class Meta:
  771. model = VirtualChassis
  772. fields = [
  773. 'name', 'domain', 'master', 'tags',
  774. ]
  775. widgets = {
  776. 'master': SelectWithPK(),
  777. }
  778. def __init__(self, *args, **kwargs):
  779. super().__init__(*args, **kwargs)
  780. self.fields['master'].queryset = Device.objects.filter(virtual_chassis=self.instance)
  781. class DeviceVCMembershipForm(forms.ModelForm):
  782. class Meta:
  783. model = Device
  784. fields = [
  785. 'vc_position', 'vc_priority',
  786. ]
  787. labels = {
  788. 'vc_position': 'Position',
  789. 'vc_priority': 'Priority',
  790. }
  791. def __init__(self, validate_vc_position=False, *args, **kwargs):
  792. super().__init__(*args, **kwargs)
  793. # Require VC position (only required when the Device is a VirtualChassis member)
  794. self.fields['vc_position'].required = True
  795. # Add bootstrap classes to form elements.
  796. self.fields['vc_position'].widget.attrs = {'class': 'form-control'}
  797. self.fields['vc_priority'].widget.attrs = {'class': 'form-control'}
  798. # Validation of vc_position is optional. This is only required when adding a new member to an existing
  799. # VirtualChassis. Otherwise, vc_position validation is handled by BaseVCMemberFormSet.
  800. self.validate_vc_position = validate_vc_position
  801. def clean_vc_position(self):
  802. vc_position = self.cleaned_data['vc_position']
  803. if self.validate_vc_position:
  804. conflicting_members = Device.objects.filter(
  805. virtual_chassis=self.instance.virtual_chassis,
  806. vc_position=vc_position
  807. )
  808. if conflicting_members.exists():
  809. raise forms.ValidationError(
  810. 'A virtual chassis member already exists in position {}.'.format(vc_position)
  811. )
  812. return vc_position
  813. class VCMemberSelectForm(BootstrapMixin, forms.Form):
  814. region = DynamicModelChoiceField(
  815. queryset=Region.objects.all(),
  816. required=False,
  817. initial_params={
  818. 'sites': '$site'
  819. }
  820. )
  821. site_group = DynamicModelChoiceField(
  822. queryset=SiteGroup.objects.all(),
  823. required=False,
  824. initial_params={
  825. 'sites': '$site'
  826. }
  827. )
  828. site = DynamicModelChoiceField(
  829. queryset=Site.objects.all(),
  830. required=False,
  831. query_params={
  832. 'region_id': '$region',
  833. 'group_id': '$site_group',
  834. }
  835. )
  836. rack = DynamicModelChoiceField(
  837. queryset=Rack.objects.all(),
  838. required=False,
  839. null_option='None',
  840. query_params={
  841. 'site_id': '$site'
  842. }
  843. )
  844. device = DynamicModelChoiceField(
  845. queryset=Device.objects.all(),
  846. query_params={
  847. 'site_id': '$site',
  848. 'rack_id': '$rack',
  849. 'virtual_chassis_id': 'null',
  850. }
  851. )
  852. def clean_device(self):
  853. device = self.cleaned_data['device']
  854. if device.virtual_chassis is not None:
  855. raise forms.ValidationError(
  856. f"Device {device} is already assigned to a virtual chassis."
  857. )
  858. return device
  859. #
  860. # Device component templates
  861. #
  862. class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
  863. class Meta:
  864. model = ConsolePortTemplate
  865. fields = [
  866. 'device_type', 'module_type', 'name', 'label', 'type', 'description',
  867. ]
  868. widgets = {
  869. 'device_type': forms.HiddenInput(),
  870. 'module_type': forms.HiddenInput(),
  871. 'type': StaticSelect,
  872. }
  873. class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
  874. class Meta:
  875. model = ConsoleServerPortTemplate
  876. fields = [
  877. 'device_type', 'module_type', 'name', 'label', 'type', 'description',
  878. ]
  879. widgets = {
  880. 'device_type': forms.HiddenInput(),
  881. 'module_type': forms.HiddenInput(),
  882. 'type': StaticSelect,
  883. }
  884. class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
  885. class Meta:
  886. model = PowerPortTemplate
  887. fields = [
  888. 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
  889. ]
  890. widgets = {
  891. 'device_type': forms.HiddenInput(),
  892. 'module_type': forms.HiddenInput(),
  893. 'type': StaticSelect(),
  894. }
  895. class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
  896. power_port = DynamicModelChoiceField(
  897. queryset=PowerPortTemplate.objects.all(),
  898. required=False,
  899. query_params={
  900. 'devicetype_id': '$device_type',
  901. }
  902. )
  903. class Meta:
  904. model = PowerOutletTemplate
  905. fields = [
  906. 'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
  907. ]
  908. widgets = {
  909. 'device_type': forms.HiddenInput(),
  910. 'module_type': forms.HiddenInput(),
  911. 'type': StaticSelect(),
  912. 'feed_leg': StaticSelect(),
  913. }
  914. class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
  915. class Meta:
  916. model = InterfaceTemplate
  917. fields = [
  918. 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description',
  919. ]
  920. widgets = {
  921. 'device_type': forms.HiddenInput(),
  922. 'module_type': forms.HiddenInput(),
  923. 'type': StaticSelect(),
  924. }
  925. class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
  926. rear_port = DynamicModelChoiceField(
  927. queryset=RearPortTemplate.objects.all(),
  928. required=False,
  929. query_params={
  930. 'devicetype_id': '$device_type',
  931. }
  932. )
  933. class Meta:
  934. model = FrontPortTemplate
  935. fields = [
  936. 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
  937. 'description',
  938. ]
  939. widgets = {
  940. 'device_type': forms.HiddenInput(),
  941. 'module_type': forms.HiddenInput(),
  942. 'type': StaticSelect(),
  943. }
  944. class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
  945. class Meta:
  946. model = RearPortTemplate
  947. fields = [
  948. 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
  949. ]
  950. widgets = {
  951. 'device_type': forms.HiddenInput(),
  952. 'module_type': forms.HiddenInput(),
  953. 'type': StaticSelect(),
  954. }
  955. class ModuleBayTemplateForm(BootstrapMixin, forms.ModelForm):
  956. class Meta:
  957. model = ModuleBayTemplate
  958. fields = [
  959. 'device_type', 'name', 'label', 'position', 'description',
  960. ]
  961. widgets = {
  962. 'device_type': forms.HiddenInput(),
  963. }
  964. class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
  965. class Meta:
  966. model = DeviceBayTemplate
  967. fields = [
  968. 'device_type', 'name', 'label', 'description',
  969. ]
  970. widgets = {
  971. 'device_type': forms.HiddenInput(),
  972. }
  973. class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
  974. parent = DynamicModelChoiceField(
  975. queryset=InventoryItemTemplate.objects.all(),
  976. required=False,
  977. query_params={
  978. 'devicetype_id': '$device_type'
  979. }
  980. )
  981. role = DynamicModelChoiceField(
  982. queryset=InventoryItemRole.objects.all(),
  983. required=False
  984. )
  985. manufacturer = DynamicModelChoiceField(
  986. queryset=Manufacturer.objects.all(),
  987. required=False
  988. )
  989. component_type = ContentTypeChoiceField(
  990. queryset=ContentType.objects.all(),
  991. limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS,
  992. required=False,
  993. widget=forms.HiddenInput
  994. )
  995. component_id = forms.IntegerField(
  996. required=False,
  997. widget=forms.HiddenInput
  998. )
  999. class Meta:
  1000. model = InventoryItemTemplate
  1001. fields = [
  1002. 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
  1003. 'component_type', 'component_id',
  1004. ]
  1005. widgets = {
  1006. 'device_type': forms.HiddenInput(),
  1007. }
  1008. #
  1009. # Device components
  1010. #
  1011. class ConsolePortForm(NetBoxModelForm):
  1012. module = DynamicModelChoiceField(
  1013. queryset=Module.objects.all(),
  1014. required=False,
  1015. query_params={
  1016. 'device_id': '$device',
  1017. }
  1018. )
  1019. class Meta:
  1020. model = ConsolePort
  1021. fields = [
  1022. 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
  1023. ]
  1024. widgets = {
  1025. 'device': forms.HiddenInput(),
  1026. 'type': StaticSelect(),
  1027. 'speed': StaticSelect(),
  1028. }
  1029. class ConsoleServerPortForm(NetBoxModelForm):
  1030. module = DynamicModelChoiceField(
  1031. queryset=Module.objects.all(),
  1032. required=False,
  1033. query_params={
  1034. 'device_id': '$device',
  1035. }
  1036. )
  1037. class Meta:
  1038. model = ConsoleServerPort
  1039. fields = [
  1040. 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
  1041. ]
  1042. widgets = {
  1043. 'device': forms.HiddenInput(),
  1044. 'type': StaticSelect(),
  1045. 'speed': StaticSelect(),
  1046. }
  1047. class PowerPortForm(NetBoxModelForm):
  1048. module = DynamicModelChoiceField(
  1049. queryset=Module.objects.all(),
  1050. required=False,
  1051. query_params={
  1052. 'device_id': '$device',
  1053. }
  1054. )
  1055. class Meta:
  1056. model = PowerPort
  1057. fields = [
  1058. 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
  1059. 'description',
  1060. 'tags',
  1061. ]
  1062. widgets = {
  1063. 'device': forms.HiddenInput(),
  1064. 'type': StaticSelect(),
  1065. }
  1066. class PowerOutletForm(NetBoxModelForm):
  1067. module = DynamicModelChoiceField(
  1068. queryset=Module.objects.all(),
  1069. required=False,
  1070. query_params={
  1071. 'device_id': '$device',
  1072. }
  1073. )
  1074. power_port = DynamicModelChoiceField(
  1075. queryset=PowerPort.objects.all(),
  1076. required=False,
  1077. query_params={
  1078. 'device_id': '$device',
  1079. }
  1080. )
  1081. class Meta:
  1082. model = PowerOutlet
  1083. fields = [
  1084. 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
  1085. 'tags',
  1086. ]
  1087. widgets = {
  1088. 'device': forms.HiddenInput(),
  1089. 'type': StaticSelect(),
  1090. 'feed_leg': StaticSelect(),
  1091. }
  1092. class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
  1093. module = DynamicModelChoiceField(
  1094. queryset=Module.objects.all(),
  1095. required=False,
  1096. query_params={
  1097. 'device_id': '$device',
  1098. }
  1099. )
  1100. parent = DynamicModelChoiceField(
  1101. queryset=Interface.objects.all(),
  1102. required=False,
  1103. label='Parent interface',
  1104. query_params={
  1105. 'device_id': '$device',
  1106. }
  1107. )
  1108. bridge = DynamicModelChoiceField(
  1109. queryset=Interface.objects.all(),
  1110. required=False,
  1111. label='Bridged interface',
  1112. query_params={
  1113. 'device_id': '$device',
  1114. }
  1115. )
  1116. lag = DynamicModelChoiceField(
  1117. queryset=Interface.objects.all(),
  1118. required=False,
  1119. label='LAG interface',
  1120. query_params={
  1121. 'device_id': '$device',
  1122. 'type': 'lag',
  1123. }
  1124. )
  1125. wireless_lan_group = DynamicModelChoiceField(
  1126. queryset=WirelessLANGroup.objects.all(),
  1127. required=False,
  1128. label='Wireless LAN group'
  1129. )
  1130. wireless_lans = DynamicModelMultipleChoiceField(
  1131. queryset=WirelessLAN.objects.all(),
  1132. required=False,
  1133. label='Wireless LANs',
  1134. query_params={
  1135. 'group_id': '$wireless_lan_group',
  1136. }
  1137. )
  1138. vlan_group = DynamicModelChoiceField(
  1139. queryset=VLANGroup.objects.all(),
  1140. required=False,
  1141. label='VLAN group'
  1142. )
  1143. untagged_vlan = DynamicModelChoiceField(
  1144. queryset=VLAN.objects.all(),
  1145. required=False,
  1146. label='Untagged VLAN',
  1147. query_params={
  1148. 'group_id': '$vlan_group',
  1149. 'available_on_device': '$device',
  1150. }
  1151. )
  1152. tagged_vlans = DynamicModelMultipleChoiceField(
  1153. queryset=VLAN.objects.all(),
  1154. required=False,
  1155. label='Tagged VLANs',
  1156. query_params={
  1157. 'group_id': '$vlan_group',
  1158. 'available_on_device': '$device',
  1159. }
  1160. )
  1161. vrf = DynamicModelChoiceField(
  1162. queryset=VRF.objects.all(),
  1163. required=False,
  1164. label='VRF'
  1165. )
  1166. fieldsets = (
  1167. ('Interface', ('device', 'module', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
  1168. ('Addressing', ('vrf', 'mac_address', 'wwn')),
  1169. ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
  1170. ('Related Interfaces', ('parent', 'bridge', 'lag')),
  1171. ('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
  1172. ('Wireless', (
  1173. 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
  1174. )),
  1175. )
  1176. class Meta:
  1177. model = Interface
  1178. fields = [
  1179. 'device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag',
  1180. 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel',
  1181. 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans',
  1182. 'vrf', 'tags',
  1183. ]
  1184. widgets = {
  1185. 'device': forms.HiddenInput(),
  1186. 'type': StaticSelect(),
  1187. 'speed': SelectSpeedWidget(),
  1188. 'duplex': StaticSelect(),
  1189. 'mode': StaticSelect(),
  1190. 'rf_role': StaticSelect(),
  1191. 'rf_channel': StaticSelect(),
  1192. }
  1193. labels = {
  1194. 'mode': '802.1Q Mode',
  1195. }
  1196. help_texts = {
  1197. 'mode': INTERFACE_MODE_HELP_TEXT,
  1198. 'rf_channel_frequency': "Populated by selected channel (if set)",
  1199. 'rf_channel_width': "Populated by selected channel (if set)",
  1200. }
  1201. def __init__(self, *args, **kwargs):
  1202. super().__init__(*args, **kwargs)
  1203. # Restrict LAG/bridge interface assignment by device/VC
  1204. device_id = self.data['device'] if self.is_bound else self.initial.get('device')
  1205. device = Device.objects.filter(pk=device_id).first()
  1206. if device and device.virtual_chassis and device.virtual_chassis.master:
  1207. self.fields['lag'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
  1208. self.fields['bridge'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
  1209. class FrontPortForm(NetBoxModelForm):
  1210. module = DynamicModelChoiceField(
  1211. queryset=Module.objects.all(),
  1212. required=False,
  1213. query_params={
  1214. 'device_id': '$device',
  1215. }
  1216. )
  1217. rear_port = DynamicModelChoiceField(
  1218. queryset=RearPort.objects.all(),
  1219. query_params={
  1220. 'device_id': '$device',
  1221. }
  1222. )
  1223. class Meta:
  1224. model = FrontPort
  1225. fields = [
  1226. 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
  1227. 'description', 'tags',
  1228. ]
  1229. widgets = {
  1230. 'device': forms.HiddenInput(),
  1231. 'type': StaticSelect(),
  1232. }
  1233. class RearPortForm(NetBoxModelForm):
  1234. module = DynamicModelChoiceField(
  1235. queryset=Module.objects.all(),
  1236. required=False,
  1237. query_params={
  1238. 'device_id': '$device',
  1239. }
  1240. )
  1241. class Meta:
  1242. model = RearPort
  1243. fields = [
  1244. 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
  1245. ]
  1246. widgets = {
  1247. 'device': forms.HiddenInput(),
  1248. 'type': StaticSelect(),
  1249. }
  1250. class ModuleBayForm(NetBoxModelForm):
  1251. class Meta:
  1252. model = ModuleBay
  1253. fields = [
  1254. 'device', 'name', 'label', 'position', 'description', 'tags',
  1255. ]
  1256. widgets = {
  1257. 'device': forms.HiddenInput(),
  1258. }
  1259. class DeviceBayForm(NetBoxModelForm):
  1260. class Meta:
  1261. model = DeviceBay
  1262. fields = [
  1263. 'device', 'name', 'label', 'description', 'tags',
  1264. ]
  1265. widgets = {
  1266. 'device': forms.HiddenInput(),
  1267. }
  1268. class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
  1269. installed_device = forms.ModelChoiceField(
  1270. queryset=Device.objects.all(),
  1271. label='Child Device',
  1272. help_text="Child devices must first be created and assigned to the site/rack of the parent device.",
  1273. widget=StaticSelect(),
  1274. )
  1275. def __init__(self, device_bay, *args, **kwargs):
  1276. super().__init__(*args, **kwargs)
  1277. self.fields['installed_device'].queryset = Device.objects.filter(
  1278. site=device_bay.device.site,
  1279. rack=device_bay.device.rack,
  1280. parent_bay__isnull=True,
  1281. device_type__u_height=0,
  1282. device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
  1283. ).exclude(pk=device_bay.device.pk)
  1284. class InventoryItemForm(NetBoxModelForm):
  1285. device = DynamicModelChoiceField(
  1286. queryset=Device.objects.all()
  1287. )
  1288. parent = DynamicModelChoiceField(
  1289. queryset=InventoryItem.objects.all(),
  1290. required=False,
  1291. query_params={
  1292. 'device_id': '$device'
  1293. }
  1294. )
  1295. role = DynamicModelChoiceField(
  1296. queryset=InventoryItemRole.objects.all(),
  1297. required=False
  1298. )
  1299. manufacturer = DynamicModelChoiceField(
  1300. queryset=Manufacturer.objects.all(),
  1301. required=False
  1302. )
  1303. component_type = ContentTypeChoiceField(
  1304. queryset=ContentType.objects.all(),
  1305. limit_choices_to=MODULAR_COMPONENT_MODELS,
  1306. required=False,
  1307. widget=forms.HiddenInput
  1308. )
  1309. component_id = forms.IntegerField(
  1310. required=False,
  1311. widget=forms.HiddenInput
  1312. )
  1313. fieldsets = (
  1314. ('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
  1315. ('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
  1316. )
  1317. class Meta:
  1318. model = InventoryItem
  1319. fields = [
  1320. 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
  1321. 'description', 'component_type', 'component_id', 'tags',
  1322. ]
  1323. #
  1324. # Device component roles
  1325. #
  1326. class InventoryItemRoleForm(NetBoxModelForm):
  1327. slug = SlugField()
  1328. class Meta:
  1329. model = InventoryItemRole
  1330. fields = [
  1331. 'name', 'slug', 'color', 'description', 'tags',
  1332. ]