model_forms.py 26 KB

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