models.py 24 KB

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