models.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. from django import forms
  2. from django.contrib.contenttypes.models import ContentType
  3. from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
  4. from extras.forms import CustomFieldModelForm
  5. from extras.models import Tag
  6. from ipam.choices import *
  7. from ipam.constants import *
  8. from ipam.formfields import IPNetworkFormField
  9. from ipam.models import *
  10. from ipam.models import ASN
  11. from tenancy.forms import TenancyForm
  12. from utilities.exceptions import PermissionsViolation
  13. from utilities.forms import (
  14. add_blank_choice, BootstrapMixin, ContentTypeChoiceField, DatePicker, DynamicModelChoiceField,
  15. DynamicModelMultipleChoiceField, NumericArrayField, SlugField, StaticSelect, StaticSelectMultiple,
  16. )
  17. from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
  18. __all__ = (
  19. 'AggregateForm',
  20. 'ASNForm',
  21. 'FHRPGroupForm',
  22. 'FHRPGroupAssignmentForm',
  23. 'IPAddressAssignForm',
  24. 'IPAddressBulkAddForm',
  25. 'IPAddressForm',
  26. 'IPRangeForm',
  27. 'PrefixForm',
  28. 'RIRForm',
  29. 'RoleForm',
  30. 'RouteTargetForm',
  31. 'ServiceForm',
  32. 'VLANForm',
  33. 'VLANGroupForm',
  34. 'VRFForm',
  35. )
  36. class VRFForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  37. import_targets = DynamicModelMultipleChoiceField(
  38. queryset=RouteTarget.objects.all(),
  39. required=False
  40. )
  41. export_targets = DynamicModelMultipleChoiceField(
  42. queryset=RouteTarget.objects.all(),
  43. required=False
  44. )
  45. tags = DynamicModelMultipleChoiceField(
  46. queryset=Tag.objects.all(),
  47. required=False
  48. )
  49. class Meta:
  50. model = VRF
  51. fields = [
  52. 'name', 'rd', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tenant_group', 'tenant',
  53. 'tags',
  54. ]
  55. fieldsets = (
  56. ('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
  57. ('Route Targets', ('import_targets', 'export_targets')),
  58. ('Tenancy', ('tenant_group', 'tenant')),
  59. )
  60. labels = {
  61. 'rd': "RD",
  62. }
  63. help_texts = {
  64. 'rd': "Route distinguisher in any format",
  65. }
  66. class RouteTargetForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  67. tags = DynamicModelMultipleChoiceField(
  68. queryset=Tag.objects.all(),
  69. required=False
  70. )
  71. class Meta:
  72. model = RouteTarget
  73. fields = [
  74. 'name', 'description', 'tenant_group', 'tenant', 'tags',
  75. ]
  76. fieldsets = (
  77. ('Route Target', ('name', 'description', 'tags')),
  78. ('Tenancy', ('tenant_group', 'tenant')),
  79. )
  80. class RIRForm(BootstrapMixin, CustomFieldModelForm):
  81. slug = SlugField()
  82. tags = DynamicModelMultipleChoiceField(
  83. queryset=Tag.objects.all(),
  84. required=False
  85. )
  86. class Meta:
  87. model = RIR
  88. fields = [
  89. 'name', 'slug', 'is_private', 'description', 'tags',
  90. ]
  91. class AggregateForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  92. rir = DynamicModelChoiceField(
  93. queryset=RIR.objects.all(),
  94. label='RIR'
  95. )
  96. tags = DynamicModelMultipleChoiceField(
  97. queryset=Tag.objects.all(),
  98. required=False
  99. )
  100. class Meta:
  101. model = Aggregate
  102. fields = [
  103. 'prefix', 'rir', 'date_added', 'description', 'tenant_group', 'tenant', 'tags',
  104. ]
  105. fieldsets = (
  106. ('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
  107. ('Tenancy', ('tenant_group', 'tenant')),
  108. )
  109. help_texts = {
  110. 'prefix': "IPv4 or IPv6 network",
  111. 'rir': "Regional Internet Registry responsible for this prefix",
  112. }
  113. widgets = {
  114. 'date_added': DatePicker(),
  115. }
  116. class ASNForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  117. rir = DynamicModelChoiceField(
  118. queryset=RIR.objects.all(),
  119. label='RIR',
  120. )
  121. sites = DynamicModelMultipleChoiceField(
  122. queryset=Site.objects.all(),
  123. label='Sites',
  124. required=False
  125. )
  126. tags = DynamicModelMultipleChoiceField(
  127. queryset=Tag.objects.all(),
  128. required=False
  129. )
  130. class Meta:
  131. model = ASN
  132. fields = [
  133. 'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'tags'
  134. ]
  135. fieldsets = (
  136. ('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
  137. ('Tenancy', ('tenant_group', 'tenant')),
  138. )
  139. help_texts = {
  140. 'asn': "AS number",
  141. 'rir': "Regional Internet Registry responsible for this prefix",
  142. }
  143. widgets = {
  144. 'date_added': DatePicker(),
  145. }
  146. def __init__(self, data=None, instance=None, *args, **kwargs):
  147. super().__init__(data=data, instance=instance, *args, **kwargs)
  148. if self.instance and self.instance.pk is not None:
  149. self.fields['sites'].initial = self.instance.sites.all().values_list('id', flat=True)
  150. def save(self, *args, **kwargs):
  151. instance = super().save(*args, **kwargs)
  152. instance.sites.set(self.cleaned_data['sites'])
  153. return instance
  154. class RoleForm(BootstrapMixin, CustomFieldModelForm):
  155. slug = SlugField()
  156. tags = DynamicModelMultipleChoiceField(
  157. queryset=Tag.objects.all(),
  158. required=False
  159. )
  160. class Meta:
  161. model = Role
  162. fields = [
  163. 'name', 'slug', 'weight', 'description', 'tags',
  164. ]
  165. class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  166. vrf = DynamicModelChoiceField(
  167. queryset=VRF.objects.all(),
  168. required=False,
  169. label='VRF'
  170. )
  171. region = DynamicModelChoiceField(
  172. queryset=Region.objects.all(),
  173. required=False,
  174. initial_params={
  175. 'sites': '$site'
  176. }
  177. )
  178. site_group = DynamicModelChoiceField(
  179. queryset=SiteGroup.objects.all(),
  180. required=False,
  181. initial_params={
  182. 'sites': '$site'
  183. }
  184. )
  185. site = DynamicModelChoiceField(
  186. queryset=Site.objects.all(),
  187. required=False,
  188. null_option='None',
  189. query_params={
  190. 'region_id': '$region',
  191. 'group_id': '$site_group',
  192. }
  193. )
  194. vlan_group = DynamicModelChoiceField(
  195. queryset=VLANGroup.objects.all(),
  196. required=False,
  197. label='VLAN group',
  198. null_option='None',
  199. query_params={
  200. 'site_id': '$site'
  201. },
  202. initial_params={
  203. 'vlans': '$vlan'
  204. }
  205. )
  206. vlan = DynamicModelChoiceField(
  207. queryset=VLAN.objects.all(),
  208. required=False,
  209. label='VLAN',
  210. query_params={
  211. 'site_id': '$site',
  212. 'group_id': '$vlan_group',
  213. }
  214. )
  215. role = DynamicModelChoiceField(
  216. queryset=Role.objects.all(),
  217. required=False
  218. )
  219. tags = DynamicModelMultipleChoiceField(
  220. queryset=Tag.objects.all(),
  221. required=False
  222. )
  223. class Meta:
  224. model = Prefix
  225. fields = [
  226. 'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
  227. 'tenant_group', 'tenant', 'tags',
  228. ]
  229. fieldsets = (
  230. ('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
  231. ('Site/VLAN Assignment', ('region', 'site_group', 'site', 'vlan_group', 'vlan')),
  232. ('Tenancy', ('tenant_group', 'tenant')),
  233. )
  234. widgets = {
  235. 'status': StaticSelect(),
  236. }
  237. class IPRangeForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  238. vrf = DynamicModelChoiceField(
  239. queryset=VRF.objects.all(),
  240. required=False,
  241. label='VRF'
  242. )
  243. role = DynamicModelChoiceField(
  244. queryset=Role.objects.all(),
  245. required=False
  246. )
  247. tags = DynamicModelMultipleChoiceField(
  248. queryset=Tag.objects.all(),
  249. required=False
  250. )
  251. class Meta:
  252. model = IPRange
  253. fields = [
  254. 'vrf', 'start_address', 'end_address', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
  255. ]
  256. fieldsets = (
  257. ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
  258. ('Tenancy', ('tenant_group', 'tenant')),
  259. )
  260. widgets = {
  261. 'status': StaticSelect(),
  262. }
  263. class IPAddressForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  264. device = DynamicModelChoiceField(
  265. queryset=Device.objects.all(),
  266. required=False,
  267. initial_params={
  268. 'interfaces': '$interface'
  269. }
  270. )
  271. interface = DynamicModelChoiceField(
  272. queryset=Interface.objects.all(),
  273. required=False,
  274. query_params={
  275. 'device_id': '$device'
  276. }
  277. )
  278. virtual_machine = DynamicModelChoiceField(
  279. queryset=VirtualMachine.objects.all(),
  280. required=False,
  281. initial_params={
  282. 'interfaces': '$vminterface'
  283. }
  284. )
  285. vminterface = DynamicModelChoiceField(
  286. queryset=VMInterface.objects.all(),
  287. required=False,
  288. label='Interface',
  289. query_params={
  290. 'virtual_machine_id': '$virtual_machine'
  291. }
  292. )
  293. vrf = DynamicModelChoiceField(
  294. queryset=VRF.objects.all(),
  295. required=False,
  296. label='VRF'
  297. )
  298. nat_region = DynamicModelChoiceField(
  299. queryset=Region.objects.all(),
  300. required=False,
  301. label='Region',
  302. initial_params={
  303. 'sites': '$nat_site'
  304. }
  305. )
  306. nat_site_group = DynamicModelChoiceField(
  307. queryset=SiteGroup.objects.all(),
  308. required=False,
  309. label='Site group',
  310. initial_params={
  311. 'sites': '$nat_site'
  312. }
  313. )
  314. nat_site = DynamicModelChoiceField(
  315. queryset=Site.objects.all(),
  316. required=False,
  317. label='Site',
  318. query_params={
  319. 'region_id': '$nat_region',
  320. 'group_id': '$nat_site_group',
  321. }
  322. )
  323. nat_rack = DynamicModelChoiceField(
  324. queryset=Rack.objects.all(),
  325. required=False,
  326. label='Rack',
  327. null_option='None',
  328. query_params={
  329. 'site_id': '$site'
  330. }
  331. )
  332. nat_device = DynamicModelChoiceField(
  333. queryset=Device.objects.all(),
  334. required=False,
  335. label='Device',
  336. query_params={
  337. 'site_id': '$site',
  338. 'rack_id': '$nat_rack',
  339. }
  340. )
  341. nat_cluster = DynamicModelChoiceField(
  342. queryset=Cluster.objects.all(),
  343. required=False,
  344. label='Cluster'
  345. )
  346. nat_virtual_machine = DynamicModelChoiceField(
  347. queryset=VirtualMachine.objects.all(),
  348. required=False,
  349. label='Virtual Machine',
  350. query_params={
  351. 'cluster_id': '$nat_cluster',
  352. }
  353. )
  354. nat_vrf = DynamicModelChoiceField(
  355. queryset=VRF.objects.all(),
  356. required=False,
  357. label='VRF'
  358. )
  359. nat_inside = DynamicModelChoiceField(
  360. queryset=IPAddress.objects.all(),
  361. required=False,
  362. label='IP Address',
  363. query_params={
  364. 'device_id': '$nat_device',
  365. 'virtual_machine_id': '$nat_virtual_machine',
  366. 'vrf_id': '$nat_vrf',
  367. }
  368. )
  369. primary_for_parent = forms.BooleanField(
  370. required=False,
  371. label='Make this the primary IP for the device/VM'
  372. )
  373. tags = DynamicModelMultipleChoiceField(
  374. queryset=Tag.objects.all(),
  375. required=False
  376. )
  377. class Meta:
  378. model = IPAddress
  379. fields = [
  380. 'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'primary_for_parent', 'nat_site', 'nat_rack',
  381. 'nat_device', 'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant',
  382. 'tags',
  383. ]
  384. widgets = {
  385. 'status': StaticSelect(),
  386. 'role': StaticSelect(),
  387. }
  388. def __init__(self, *args, **kwargs):
  389. # Initialize helper selectors
  390. instance = kwargs.get('instance')
  391. initial = kwargs.get('initial', {}).copy()
  392. if instance:
  393. if type(instance.assigned_object) is Interface:
  394. initial['interface'] = instance.assigned_object
  395. elif type(instance.assigned_object) is VMInterface:
  396. initial['vminterface'] = instance.assigned_object
  397. if instance.nat_inside:
  398. nat_inside_parent = instance.nat_inside.assigned_object
  399. if type(nat_inside_parent) is Interface:
  400. initial['nat_site'] = nat_inside_parent.device.site.pk
  401. if nat_inside_parent.device.rack:
  402. initial['nat_rack'] = nat_inside_parent.device.rack.pk
  403. initial['nat_device'] = nat_inside_parent.device.pk
  404. elif type(nat_inside_parent) is VMInterface:
  405. initial['nat_cluster'] = nat_inside_parent.virtual_machine.cluster.pk
  406. initial['nat_virtual_machine'] = nat_inside_parent.virtual_machine.pk
  407. kwargs['initial'] = initial
  408. super().__init__(*args, **kwargs)
  409. # Initialize primary_for_parent if IP address is already assigned
  410. if self.instance.pk and self.instance.assigned_object:
  411. parent = getattr(self.instance.assigned_object, 'parent_object', None)
  412. if parent and (
  413. self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or
  414. self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk
  415. ):
  416. self.initial['primary_for_parent'] = True
  417. def clean(self):
  418. super().clean()
  419. # Cannot select both a device interface and a VM interface
  420. if self.cleaned_data.get('interface') and self.cleaned_data.get('vminterface'):
  421. raise forms.ValidationError("Cannot select both a device interface and a virtual machine interface")
  422. self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
  423. # Primary IP assignment is only available if an interface has been assigned.
  424. interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
  425. if self.cleaned_data.get('primary_for_parent') and not interface:
  426. self.add_error(
  427. 'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs."
  428. )
  429. def save(self, *args, **kwargs):
  430. ipaddress = super().save(*args, **kwargs)
  431. # Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
  432. interface = self.instance.assigned_object
  433. if interface:
  434. parent = interface.parent_object
  435. if self.cleaned_data['primary_for_parent']:
  436. if ipaddress.address.version == 4:
  437. parent.primary_ip4 = ipaddress
  438. else:
  439. parent.primary_ip6 = ipaddress
  440. parent.save()
  441. elif ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress:
  442. parent.primary_ip4 = None
  443. parent.save()
  444. elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress:
  445. parent.primary_ip6 = None
  446. parent.save()
  447. return ipaddress
  448. class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  449. vrf = DynamicModelChoiceField(
  450. queryset=VRF.objects.all(),
  451. required=False,
  452. label='VRF'
  453. )
  454. tags = DynamicModelMultipleChoiceField(
  455. queryset=Tag.objects.all(),
  456. required=False
  457. )
  458. class Meta:
  459. model = IPAddress
  460. fields = [
  461. 'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'tenant_group', 'tenant', 'tags',
  462. ]
  463. widgets = {
  464. 'status': StaticSelect(),
  465. 'role': StaticSelect(),
  466. }
  467. class IPAddressAssignForm(BootstrapMixin, forms.Form):
  468. vrf_id = DynamicModelChoiceField(
  469. queryset=VRF.objects.all(),
  470. required=False,
  471. label='VRF'
  472. )
  473. q = forms.CharField(
  474. required=False,
  475. label='Search',
  476. )
  477. class FHRPGroupForm(BootstrapMixin, CustomFieldModelForm):
  478. tags = DynamicModelMultipleChoiceField(
  479. queryset=Tag.objects.all(),
  480. required=False
  481. )
  482. # Optionally create a new IPAddress along with the FHRPGroup
  483. ip_vrf = DynamicModelChoiceField(
  484. queryset=VRF.objects.all(),
  485. required=False,
  486. label='VRF'
  487. )
  488. ip_address = IPNetworkFormField(
  489. required=False,
  490. label='Address'
  491. )
  492. ip_status = forms.ChoiceField(
  493. choices=add_blank_choice(IPAddressStatusChoices),
  494. required=False,
  495. label='Status'
  496. )
  497. class Meta:
  498. model = FHRPGroup
  499. fields = (
  500. 'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_vrf', 'ip_address', 'ip_status', 'tags',
  501. )
  502. fieldsets = (
  503. ('FHRP Group', ('protocol', 'group_id', 'description', 'tags')),
  504. ('Authentication', ('auth_type', 'auth_key')),
  505. ('Virtual IP Address', ('ip_vrf', 'ip_address', 'ip_status'))
  506. )
  507. def save(self, *args, **kwargs):
  508. instance = super().save(*args, **kwargs)
  509. # Check if we need to create a new IPAddress for the group
  510. if self.cleaned_data.get('ip_address'):
  511. ipaddress = IPAddress(
  512. vrf=self.cleaned_data['ip_vrf'],
  513. address=self.cleaned_data['ip_address'],
  514. status=self.cleaned_data['ip_status'],
  515. assigned_object=instance
  516. )
  517. ipaddress.role = FHRP_PROTOCOL_ROLE_MAPPINGS[self.cleaned_data['protocol']]
  518. ipaddress.save()
  519. # Check that the new IPAddress conforms with any assigned object-level permissions
  520. if not IPAddress.objects.filter(pk=ipaddress.pk).first():
  521. raise PermissionsViolation()
  522. return instance
  523. class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
  524. group = DynamicModelChoiceField(
  525. queryset=FHRPGroup.objects.all()
  526. )
  527. class Meta:
  528. model = FHRPGroupAssignment
  529. fields = ('group', 'priority')
  530. def __init__(self, *args, **kwargs):
  531. super().__init__(*args, **kwargs)
  532. ipaddresses = self.instance.interface.ip_addresses.all()
  533. for ipaddress in ipaddresses:
  534. self.fields['group'].widget.add_query_param('related_ip', ipaddress.pk)
  535. class VLANGroupForm(BootstrapMixin, CustomFieldModelForm):
  536. scope_type = ContentTypeChoiceField(
  537. queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
  538. required=False,
  539. widget=StaticSelect
  540. )
  541. region = DynamicModelChoiceField(
  542. queryset=Region.objects.all(),
  543. required=False,
  544. initial_params={
  545. 'sites': '$site'
  546. }
  547. )
  548. sitegroup = DynamicModelChoiceField(
  549. queryset=SiteGroup.objects.all(),
  550. required=False,
  551. initial_params={
  552. 'sites': '$site'
  553. },
  554. label='Site group'
  555. )
  556. site = DynamicModelChoiceField(
  557. queryset=Site.objects.all(),
  558. required=False,
  559. initial_params={
  560. 'locations': '$location'
  561. },
  562. query_params={
  563. 'region_id': '$region',
  564. 'group_id': '$sitegroup',
  565. }
  566. )
  567. location = DynamicModelChoiceField(
  568. queryset=Location.objects.all(),
  569. required=False,
  570. initial_params={
  571. 'racks': '$rack'
  572. },
  573. query_params={
  574. 'site_id': '$site',
  575. }
  576. )
  577. rack = DynamicModelChoiceField(
  578. queryset=Rack.objects.all(),
  579. required=False,
  580. query_params={
  581. 'site_id': '$site',
  582. 'location_id': '$location',
  583. }
  584. )
  585. clustergroup = DynamicModelChoiceField(
  586. queryset=ClusterGroup.objects.all(),
  587. required=False,
  588. initial_params={
  589. 'clusters': '$cluster'
  590. },
  591. label='Cluster group'
  592. )
  593. cluster = DynamicModelChoiceField(
  594. queryset=Cluster.objects.all(),
  595. required=False,
  596. query_params={
  597. 'group_id': '$clustergroup',
  598. }
  599. )
  600. slug = SlugField()
  601. tags = DynamicModelMultipleChoiceField(
  602. queryset=Tag.objects.all(),
  603. required=False
  604. )
  605. class Meta:
  606. model = VLANGroup
  607. fields = [
  608. 'name', 'slug', 'description', 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack',
  609. 'clustergroup', 'cluster', 'tags',
  610. ]
  611. fieldsets = (
  612. ('VLAN Group', ('name', 'slug', 'description', 'tags')),
  613. ('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
  614. )
  615. widgets = {
  616. 'scope_type': StaticSelect,
  617. }
  618. def __init__(self, *args, **kwargs):
  619. instance = kwargs.get('instance')
  620. initial = kwargs.get('initial', {})
  621. if instance is not None and instance.scope:
  622. initial[instance.scope_type.model] = instance.scope
  623. kwargs['initial'] = initial
  624. super().__init__(*args, **kwargs)
  625. def clean(self):
  626. super().clean()
  627. # Assign scope based on scope_type
  628. if self.cleaned_data.get('scope_type'):
  629. scope_field = self.cleaned_data['scope_type'].model
  630. self.instance.scope = self.cleaned_data.get(scope_field)
  631. else:
  632. self.instance.scope_id = None
  633. class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  634. # VLANGroup assignment fields
  635. scope_type = forms.ChoiceField(
  636. choices=(
  637. ('', ''),
  638. ('dcim.region', 'Region'),
  639. ('dcim.sitegroup', 'Site group'),
  640. ('dcim.site', 'Site'),
  641. ('dcim.location', 'Location'),
  642. ('dcim.rack', 'Rack'),
  643. ('virtualization.clustergroup', 'Cluster group'),
  644. ('virtualization.cluster', 'Cluster'),
  645. ),
  646. required=False,
  647. widget=StaticSelect,
  648. label='Group scope'
  649. )
  650. group = DynamicModelChoiceField(
  651. queryset=VLANGroup.objects.all(),
  652. required=False,
  653. query_params={
  654. 'scope_type': '$scope_type',
  655. },
  656. label='VLAN Group'
  657. )
  658. # Site assignment fields
  659. region = DynamicModelChoiceField(
  660. queryset=Region.objects.all(),
  661. required=False,
  662. initial_params={
  663. 'sites': '$site'
  664. },
  665. label='Region'
  666. )
  667. sitegroup = DynamicModelChoiceField(
  668. queryset=SiteGroup.objects.all(),
  669. required=False,
  670. initial_params={
  671. 'sites': '$site'
  672. },
  673. label='Site group'
  674. )
  675. site = DynamicModelChoiceField(
  676. queryset=Site.objects.all(),
  677. required=False,
  678. null_option='None',
  679. query_params={
  680. 'region_id': '$region',
  681. 'group_id': '$sitegroup',
  682. }
  683. )
  684. # Other fields
  685. role = DynamicModelChoiceField(
  686. queryset=Role.objects.all(),
  687. required=False
  688. )
  689. tags = DynamicModelMultipleChoiceField(
  690. queryset=Tag.objects.all(),
  691. required=False
  692. )
  693. class Meta:
  694. model = VLAN
  695. fields = [
  696. 'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
  697. ]
  698. help_texts = {
  699. 'site': "Leave blank if this VLAN spans multiple sites",
  700. 'group': "VLAN group (optional)",
  701. 'vid': "Configured VLAN ID",
  702. 'name': "Configured VLAN name",
  703. 'status': "Operational status of this VLAN",
  704. 'role': "The primary function of this VLAN",
  705. }
  706. widgets = {
  707. 'status': StaticSelect(),
  708. }
  709. class ServiceForm(BootstrapMixin, CustomFieldModelForm):
  710. ports = NumericArrayField(
  711. base_field=forms.IntegerField(
  712. min_value=SERVICE_PORT_MIN,
  713. max_value=SERVICE_PORT_MAX
  714. ),
  715. help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
  716. )
  717. tags = DynamicModelMultipleChoiceField(
  718. queryset=Tag.objects.all(),
  719. required=False
  720. )
  721. class Meta:
  722. model = Service
  723. fields = [
  724. 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'tags',
  725. ]
  726. help_texts = {
  727. 'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
  728. "reachable via all IPs assigned to the device.",
  729. }
  730. widgets = {
  731. 'protocol': StaticSelect(),
  732. 'ipaddresses': StaticSelectMultiple(),
  733. }
  734. def __init__(self, *args, **kwargs):
  735. super().__init__(*args, **kwargs)
  736. # Limit IP address choices to those assigned to interfaces of the parent device/VM
  737. if self.instance.device:
  738. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  739. interface__in=self.instance.device.vc_interfaces().values_list('id', flat=True)
  740. )
  741. elif self.instance.virtual_machine:
  742. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  743. vminterface__in=self.instance.virtual_machine.interfaces.values_list('id', flat=True)
  744. )
  745. else:
  746. self.fields['ipaddresses'].choices = []