model_forms.py 24 KB

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