models.py 29 KB

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