models.py 28 KB

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