models.py 26 KB

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