models.py 29 KB

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