2
0

models.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  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(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(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(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(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(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(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(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(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(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. fhrpgroup = DynamicModelChoiceField(
  294. queryset=FHRPGroup.objects.all(),
  295. required=False,
  296. label='FHRP Group'
  297. )
  298. vrf = DynamicModelChoiceField(
  299. queryset=VRF.objects.all(),
  300. required=False,
  301. label='VRF'
  302. )
  303. nat_region = DynamicModelChoiceField(
  304. queryset=Region.objects.all(),
  305. required=False,
  306. label='Region',
  307. initial_params={
  308. 'sites': '$nat_site'
  309. }
  310. )
  311. nat_site_group = DynamicModelChoiceField(
  312. queryset=SiteGroup.objects.all(),
  313. required=False,
  314. label='Site group',
  315. initial_params={
  316. 'sites': '$nat_site'
  317. }
  318. )
  319. nat_site = DynamicModelChoiceField(
  320. queryset=Site.objects.all(),
  321. required=False,
  322. label='Site',
  323. query_params={
  324. 'region_id': '$nat_region',
  325. 'group_id': '$nat_site_group',
  326. }
  327. )
  328. nat_rack = DynamicModelChoiceField(
  329. queryset=Rack.objects.all(),
  330. required=False,
  331. label='Rack',
  332. null_option='None',
  333. query_params={
  334. 'site_id': '$site'
  335. }
  336. )
  337. nat_device = DynamicModelChoiceField(
  338. queryset=Device.objects.all(),
  339. required=False,
  340. label='Device',
  341. query_params={
  342. 'site_id': '$site',
  343. 'rack_id': '$nat_rack',
  344. }
  345. )
  346. nat_cluster = DynamicModelChoiceField(
  347. queryset=Cluster.objects.all(),
  348. required=False,
  349. label='Cluster'
  350. )
  351. nat_virtual_machine = DynamicModelChoiceField(
  352. queryset=VirtualMachine.objects.all(),
  353. required=False,
  354. label='Virtual Machine',
  355. query_params={
  356. 'cluster_id': '$nat_cluster',
  357. }
  358. )
  359. nat_vrf = DynamicModelChoiceField(
  360. queryset=VRF.objects.all(),
  361. required=False,
  362. label='VRF'
  363. )
  364. nat_inside = DynamicModelChoiceField(
  365. queryset=IPAddress.objects.all(),
  366. required=False,
  367. label='IP Address',
  368. query_params={
  369. 'device_id': '$nat_device',
  370. 'virtual_machine_id': '$nat_virtual_machine',
  371. 'vrf_id': '$nat_vrf',
  372. }
  373. )
  374. primary_for_parent = forms.BooleanField(
  375. required=False,
  376. label='Make this the primary IP for the device/VM'
  377. )
  378. tags = DynamicModelMultipleChoiceField(
  379. queryset=Tag.objects.all(),
  380. required=False
  381. )
  382. class Meta:
  383. model = IPAddress
  384. fields = [
  385. 'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'primary_for_parent', 'nat_site', 'nat_rack',
  386. 'nat_device', 'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant',
  387. 'tags',
  388. ]
  389. widgets = {
  390. 'status': StaticSelect(),
  391. 'role': StaticSelect(),
  392. }
  393. def __init__(self, *args, **kwargs):
  394. # Initialize helper selectors
  395. instance = kwargs.get('instance')
  396. initial = kwargs.get('initial', {}).copy()
  397. if instance:
  398. if type(instance.assigned_object) is Interface:
  399. initial['interface'] = instance.assigned_object
  400. elif type(instance.assigned_object) is VMInterface:
  401. initial['vminterface'] = instance.assigned_object
  402. elif type(instance.assigned_object) is FHRPGroup:
  403. initial['fhrpgroup'] = instance.assigned_object
  404. if instance.nat_inside:
  405. nat_inside_parent = instance.nat_inside.assigned_object
  406. if type(nat_inside_parent) is Interface:
  407. initial['nat_site'] = nat_inside_parent.device.site.pk
  408. if nat_inside_parent.device.rack:
  409. initial['nat_rack'] = nat_inside_parent.device.rack.pk
  410. initial['nat_device'] = nat_inside_parent.device.pk
  411. elif type(nat_inside_parent) is VMInterface:
  412. initial['nat_cluster'] = nat_inside_parent.virtual_machine.cluster.pk
  413. initial['nat_virtual_machine'] = nat_inside_parent.virtual_machine.pk
  414. kwargs['initial'] = initial
  415. super().__init__(*args, **kwargs)
  416. # Initialize primary_for_parent if IP address is already assigned
  417. if self.instance.pk and self.instance.assigned_object:
  418. parent = getattr(self.instance.assigned_object, 'parent_object', None)
  419. if parent and (
  420. self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or
  421. self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk
  422. ):
  423. self.initial['primary_for_parent'] = True
  424. def clean(self):
  425. super().clean()
  426. # Handle object assignment
  427. if self.cleaned_data['interface']:
  428. self.instance.assigned_object = self.cleaned_data['interface']
  429. elif self.cleaned_data['vminterface']:
  430. self.instance.assigned_object = self.cleaned_data['vminterface']
  431. elif self.cleaned_data['fhrpgroup']:
  432. self.instance.assigned_object = self.cleaned_data['fhrpgroup']
  433. # Primary IP assignment is only available if an interface has been assigned.
  434. interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
  435. if self.cleaned_data.get('primary_for_parent') and not interface:
  436. self.add_error(
  437. 'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs."
  438. )
  439. def save(self, *args, **kwargs):
  440. ipaddress = super().save(*args, **kwargs)
  441. # Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
  442. interface = self.instance.assigned_object
  443. if type(interface) in (Interface, VMInterface):
  444. parent = interface.parent_object
  445. if self.cleaned_data['primary_for_parent']:
  446. if ipaddress.address.version == 4:
  447. parent.primary_ip4 = ipaddress
  448. else:
  449. parent.primary_ip6 = ipaddress
  450. parent.save()
  451. elif ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress:
  452. parent.primary_ip4 = None
  453. parent.save()
  454. elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress:
  455. parent.primary_ip6 = None
  456. parent.save()
  457. return ipaddress
  458. class IPAddressBulkAddForm(TenancyForm, CustomFieldModelForm):
  459. vrf = DynamicModelChoiceField(
  460. queryset=VRF.objects.all(),
  461. required=False,
  462. label='VRF'
  463. )
  464. tags = DynamicModelMultipleChoiceField(
  465. queryset=Tag.objects.all(),
  466. required=False
  467. )
  468. class Meta:
  469. model = IPAddress
  470. fields = [
  471. 'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'tenant_group', 'tenant', 'tags',
  472. ]
  473. widgets = {
  474. 'status': StaticSelect(),
  475. 'role': StaticSelect(),
  476. }
  477. class IPAddressAssignForm(BootstrapMixin, forms.Form):
  478. vrf_id = DynamicModelChoiceField(
  479. queryset=VRF.objects.all(),
  480. required=False,
  481. label='VRF'
  482. )
  483. q = forms.CharField(
  484. required=False,
  485. label='Search',
  486. )
  487. class FHRPGroupForm(CustomFieldModelForm):
  488. tags = DynamicModelMultipleChoiceField(
  489. queryset=Tag.objects.all(),
  490. required=False
  491. )
  492. # Optionally create a new IPAddress along with the FHRPGroup
  493. ip_vrf = DynamicModelChoiceField(
  494. queryset=VRF.objects.all(),
  495. required=False,
  496. label='VRF'
  497. )
  498. ip_address = IPNetworkFormField(
  499. required=False,
  500. label='Address'
  501. )
  502. ip_status = forms.ChoiceField(
  503. choices=add_blank_choice(IPAddressStatusChoices),
  504. required=False,
  505. label='Status'
  506. )
  507. class Meta:
  508. model = FHRPGroup
  509. fields = (
  510. 'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_vrf', 'ip_address', 'ip_status', 'tags',
  511. )
  512. fieldsets = (
  513. ('FHRP Group', ('protocol', 'group_id', 'description', 'tags')),
  514. ('Authentication', ('auth_type', 'auth_key')),
  515. ('Virtual IP Address', ('ip_vrf', 'ip_address', 'ip_status'))
  516. )
  517. def save(self, *args, **kwargs):
  518. instance = super().save(*args, **kwargs)
  519. # Check if we need to create a new IPAddress for the group
  520. if self.cleaned_data.get('ip_address'):
  521. ipaddress = IPAddress(
  522. vrf=self.cleaned_data['ip_vrf'],
  523. address=self.cleaned_data['ip_address'],
  524. status=self.cleaned_data['ip_status'],
  525. role=FHRP_PROTOCOL_ROLE_MAPPINGS[self.cleaned_data['protocol']],
  526. assigned_object=instance
  527. )
  528. ipaddress.save()
  529. # Check that the new IPAddress conforms with any assigned object-level permissions
  530. if not IPAddress.objects.filter(pk=ipaddress.pk).first():
  531. raise PermissionsViolation()
  532. return instance
  533. def clean(self):
  534. ip_vrf = self.cleaned_data.get('ip_vrf')
  535. ip_address = self.cleaned_data.get('ip_address')
  536. ip_status = self.cleaned_data.get('ip_status')
  537. if ip_address:
  538. ip_form = IPAddressForm({
  539. 'address': ip_address,
  540. 'vrf': ip_vrf,
  541. 'status': ip_status,
  542. })
  543. if not ip_form.is_valid():
  544. self.errors.update({
  545. f'ip_{field}': error for field, error in ip_form.errors.items()
  546. })
  547. class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
  548. group = DynamicModelChoiceField(
  549. queryset=FHRPGroup.objects.all()
  550. )
  551. class Meta:
  552. model = FHRPGroupAssignment
  553. fields = ('group', 'priority')
  554. def __init__(self, *args, **kwargs):
  555. super().__init__(*args, **kwargs)
  556. ipaddresses = self.instance.interface.ip_addresses.all()
  557. for ipaddress in ipaddresses:
  558. self.fields['group'].widget.add_query_param('related_ip', ipaddress.pk)
  559. class VLANGroupForm(CustomFieldModelForm):
  560. scope_type = ContentTypeChoiceField(
  561. queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
  562. required=False,
  563. widget=StaticSelect
  564. )
  565. region = DynamicModelChoiceField(
  566. queryset=Region.objects.all(),
  567. required=False,
  568. initial_params={
  569. 'sites': '$site'
  570. }
  571. )
  572. sitegroup = DynamicModelChoiceField(
  573. queryset=SiteGroup.objects.all(),
  574. required=False,
  575. initial_params={
  576. 'sites': '$site'
  577. },
  578. label='Site group'
  579. )
  580. site = DynamicModelChoiceField(
  581. queryset=Site.objects.all(),
  582. required=False,
  583. initial_params={
  584. 'locations': '$location'
  585. },
  586. query_params={
  587. 'region_id': '$region',
  588. 'group_id': '$sitegroup',
  589. }
  590. )
  591. location = DynamicModelChoiceField(
  592. queryset=Location.objects.all(),
  593. required=False,
  594. initial_params={
  595. 'racks': '$rack'
  596. },
  597. query_params={
  598. 'site_id': '$site',
  599. }
  600. )
  601. rack = DynamicModelChoiceField(
  602. queryset=Rack.objects.all(),
  603. required=False,
  604. query_params={
  605. 'site_id': '$site',
  606. 'location_id': '$location',
  607. }
  608. )
  609. clustergroup = DynamicModelChoiceField(
  610. queryset=ClusterGroup.objects.all(),
  611. required=False,
  612. initial_params={
  613. 'clusters': '$cluster'
  614. },
  615. label='Cluster group'
  616. )
  617. cluster = DynamicModelChoiceField(
  618. queryset=Cluster.objects.all(),
  619. required=False,
  620. query_params={
  621. 'group_id': '$clustergroup',
  622. }
  623. )
  624. slug = SlugField()
  625. tags = DynamicModelMultipleChoiceField(
  626. queryset=Tag.objects.all(),
  627. required=False
  628. )
  629. class Meta:
  630. model = VLANGroup
  631. fields = [
  632. 'name', 'slug', 'description', 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack',
  633. 'clustergroup', 'cluster', 'tags',
  634. ]
  635. fieldsets = (
  636. ('VLAN Group', ('name', 'slug', 'description', 'tags')),
  637. ('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
  638. )
  639. widgets = {
  640. 'scope_type': StaticSelect,
  641. }
  642. def __init__(self, *args, **kwargs):
  643. instance = kwargs.get('instance')
  644. initial = kwargs.get('initial', {})
  645. if instance is not None and instance.scope:
  646. initial[instance.scope_type.model] = instance.scope
  647. kwargs['initial'] = initial
  648. super().__init__(*args, **kwargs)
  649. def clean(self):
  650. super().clean()
  651. # Assign scope based on scope_type
  652. if self.cleaned_data.get('scope_type'):
  653. scope_field = self.cleaned_data['scope_type'].model
  654. self.instance.scope = self.cleaned_data.get(scope_field)
  655. else:
  656. self.instance.scope_id = None
  657. class VLANForm(TenancyForm, CustomFieldModelForm):
  658. # VLANGroup assignment fields
  659. scope_type = forms.ChoiceField(
  660. choices=(
  661. ('', ''),
  662. ('dcim.region', 'Region'),
  663. ('dcim.sitegroup', 'Site group'),
  664. ('dcim.site', 'Site'),
  665. ('dcim.location', 'Location'),
  666. ('dcim.rack', 'Rack'),
  667. ('virtualization.clustergroup', 'Cluster group'),
  668. ('virtualization.cluster', 'Cluster'),
  669. ),
  670. required=False,
  671. widget=StaticSelect,
  672. label='Group scope'
  673. )
  674. group = DynamicModelChoiceField(
  675. queryset=VLANGroup.objects.all(),
  676. required=False,
  677. query_params={
  678. 'scope_type': '$scope_type',
  679. },
  680. label='VLAN Group'
  681. )
  682. # Site assignment fields
  683. region = DynamicModelChoiceField(
  684. queryset=Region.objects.all(),
  685. required=False,
  686. initial_params={
  687. 'sites': '$site'
  688. },
  689. label='Region'
  690. )
  691. sitegroup = DynamicModelChoiceField(
  692. queryset=SiteGroup.objects.all(),
  693. required=False,
  694. initial_params={
  695. 'sites': '$site'
  696. },
  697. label='Site group'
  698. )
  699. site = DynamicModelChoiceField(
  700. queryset=Site.objects.all(),
  701. required=False,
  702. null_option='None',
  703. query_params={
  704. 'region_id': '$region',
  705. 'group_id': '$sitegroup',
  706. }
  707. )
  708. # Other fields
  709. role = DynamicModelChoiceField(
  710. queryset=Role.objects.all(),
  711. required=False
  712. )
  713. tags = DynamicModelMultipleChoiceField(
  714. queryset=Tag.objects.all(),
  715. required=False
  716. )
  717. class Meta:
  718. model = VLAN
  719. fields = [
  720. 'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
  721. ]
  722. help_texts = {
  723. 'site': "Leave blank if this VLAN spans multiple sites",
  724. 'group': "VLAN group (optional)",
  725. 'vid': "Configured VLAN ID",
  726. 'name': "Configured VLAN name",
  727. 'status': "Operational status of this VLAN",
  728. 'role': "The primary function of this VLAN",
  729. }
  730. widgets = {
  731. 'status': StaticSelect(),
  732. }
  733. class ServiceForm(CustomFieldModelForm):
  734. ports = NumericArrayField(
  735. base_field=forms.IntegerField(
  736. min_value=SERVICE_PORT_MIN,
  737. max_value=SERVICE_PORT_MAX
  738. ),
  739. help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
  740. )
  741. tags = DynamicModelMultipleChoiceField(
  742. queryset=Tag.objects.all(),
  743. required=False
  744. )
  745. class Meta:
  746. model = Service
  747. fields = [
  748. 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'tags',
  749. ]
  750. help_texts = {
  751. 'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
  752. "reachable via all IPs assigned to the device.",
  753. }
  754. widgets = {
  755. 'protocol': StaticSelect(),
  756. 'ipaddresses': StaticSelectMultiple(),
  757. }
  758. def __init__(self, *args, **kwargs):
  759. super().__init__(*args, **kwargs)
  760. # Limit IP address choices to those assigned to interfaces of the parent device/VM
  761. if self.instance.device:
  762. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  763. interface__in=self.instance.device.vc_interfaces().values_list('id', flat=True)
  764. )
  765. elif self.instance.virtual_machine:
  766. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  767. vminterface__in=self.instance.virtual_machine.interfaces.values_list('id', flat=True)
  768. )
  769. else:
  770. self.fields['ipaddresses'].choices = []