2
0

models.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. from django import forms
  2. from django.contrib.contenttypes.models import ContentType
  3. from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
  4. from extras.forms import CustomFieldModelForm
  5. from extras.models import Tag
  6. from ipam.constants import *
  7. from ipam.models import *
  8. from ipam.models import ASN
  9. from tenancy.forms import TenancyForm
  10. from utilities.forms import (
  11. BootstrapMixin, ContentTypeChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
  12. NumericArrayField, SlugField, StaticSelect, StaticSelectMultiple,
  13. )
  14. from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
  15. __all__ = (
  16. 'AggregateForm',
  17. 'ASNForm',
  18. 'IPAddressAssignForm',
  19. 'IPAddressBulkAddForm',
  20. 'IPAddressForm',
  21. 'IPRangeForm',
  22. 'PrefixForm',
  23. 'RIRForm',
  24. 'RoleForm',
  25. 'RouteTargetForm',
  26. 'ServiceForm',
  27. 'VLANForm',
  28. 'VLANGroupForm',
  29. 'VRFForm',
  30. )
  31. class VRFForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  32. import_targets = DynamicModelMultipleChoiceField(
  33. queryset=RouteTarget.objects.all(),
  34. required=False
  35. )
  36. export_targets = DynamicModelMultipleChoiceField(
  37. queryset=RouteTarget.objects.all(),
  38. required=False
  39. )
  40. tags = DynamicModelMultipleChoiceField(
  41. queryset=Tag.objects.all(),
  42. required=False
  43. )
  44. class Meta:
  45. model = VRF
  46. fields = [
  47. 'name', 'rd', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tenant_group', 'tenant',
  48. 'tags',
  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. labels = {
  56. 'rd': "RD",
  57. }
  58. help_texts = {
  59. 'rd': "Route distinguisher in any format",
  60. }
  61. class RouteTargetForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  62. tags = DynamicModelMultipleChoiceField(
  63. queryset=Tag.objects.all(),
  64. required=False
  65. )
  66. class Meta:
  67. model = RouteTarget
  68. fields = [
  69. 'name', 'description', 'tenant_group', 'tenant', 'tags',
  70. ]
  71. fieldsets = (
  72. ('Route Target', ('name', 'description', 'tags')),
  73. ('Tenancy', ('tenant_group', 'tenant')),
  74. )
  75. class RIRForm(BootstrapMixin, CustomFieldModelForm):
  76. slug = SlugField()
  77. tags = DynamicModelMultipleChoiceField(
  78. queryset=Tag.objects.all(),
  79. required=False
  80. )
  81. class Meta:
  82. model = RIR
  83. fields = [
  84. 'name', 'slug', 'is_private', 'description', 'tags',
  85. ]
  86. class AggregateForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  87. rir = DynamicModelChoiceField(
  88. queryset=RIR.objects.all(),
  89. label='RIR'
  90. )
  91. tags = DynamicModelMultipleChoiceField(
  92. queryset=Tag.objects.all(),
  93. required=False
  94. )
  95. class Meta:
  96. model = Aggregate
  97. fields = [
  98. 'prefix', 'rir', 'date_added', 'description', 'tenant_group', 'tenant', 'tags',
  99. ]
  100. fieldsets = (
  101. ('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
  102. ('Tenancy', ('tenant_group', 'tenant')),
  103. )
  104. help_texts = {
  105. 'prefix': "IPv4 or IPv6 network",
  106. 'rir': "Regional Internet Registry responsible for this prefix",
  107. }
  108. widgets = {
  109. 'date_added': DatePicker(),
  110. }
  111. class ASNForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  112. rir = DynamicModelChoiceField(
  113. queryset=RIR.objects.all(),
  114. label='RIR',
  115. )
  116. class Meta:
  117. model = ASN
  118. fields = [
  119. 'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description'
  120. ]
  121. fieldsets = (
  122. ('ASN', ('asn', 'rir', 'sites', 'description')),
  123. ('Tenancy', ('tenant_group', 'tenant')),
  124. )
  125. help_texts = {
  126. 'asn': "AS number",
  127. 'rir': "Regional Internet Registry responsible for this prefix",
  128. }
  129. widgets = {
  130. 'date_added': DatePicker(),
  131. }
  132. class RoleForm(BootstrapMixin, CustomFieldModelForm):
  133. slug = SlugField()
  134. tags = DynamicModelMultipleChoiceField(
  135. queryset=Tag.objects.all(),
  136. required=False
  137. )
  138. class Meta:
  139. model = Role
  140. fields = [
  141. 'name', 'slug', 'weight', 'description', 'tags',
  142. ]
  143. class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  144. vrf = DynamicModelChoiceField(
  145. queryset=VRF.objects.all(),
  146. required=False,
  147. label='VRF'
  148. )
  149. region = DynamicModelChoiceField(
  150. queryset=Region.objects.all(),
  151. required=False,
  152. initial_params={
  153. 'sites': '$site'
  154. }
  155. )
  156. site_group = DynamicModelChoiceField(
  157. queryset=SiteGroup.objects.all(),
  158. required=False,
  159. initial_params={
  160. 'sites': '$site'
  161. }
  162. )
  163. site = DynamicModelChoiceField(
  164. queryset=Site.objects.all(),
  165. required=False,
  166. null_option='None',
  167. query_params={
  168. 'region_id': '$region',
  169. 'group_id': '$site_group',
  170. }
  171. )
  172. vlan_group = DynamicModelChoiceField(
  173. queryset=VLANGroup.objects.all(),
  174. required=False,
  175. label='VLAN group',
  176. null_option='None',
  177. query_params={
  178. 'site_id': '$site'
  179. },
  180. initial_params={
  181. 'vlans': '$vlan'
  182. }
  183. )
  184. vlan = DynamicModelChoiceField(
  185. queryset=VLAN.objects.all(),
  186. required=False,
  187. label='VLAN',
  188. query_params={
  189. 'site_id': '$site',
  190. 'group_id': '$vlan_group',
  191. }
  192. )
  193. role = DynamicModelChoiceField(
  194. queryset=Role.objects.all(),
  195. required=False
  196. )
  197. tags = DynamicModelMultipleChoiceField(
  198. queryset=Tag.objects.all(),
  199. required=False
  200. )
  201. class Meta:
  202. model = Prefix
  203. fields = [
  204. 'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
  205. 'tenant_group', 'tenant', 'tags',
  206. ]
  207. fieldsets = (
  208. ('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
  209. ('Site/VLAN Assignment', ('region', 'site_group', 'site', 'vlan_group', 'vlan')),
  210. ('Tenancy', ('tenant_group', 'tenant')),
  211. )
  212. widgets = {
  213. 'status': StaticSelect(),
  214. }
  215. class IPRangeForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  216. vrf = DynamicModelChoiceField(
  217. queryset=VRF.objects.all(),
  218. required=False,
  219. label='VRF'
  220. )
  221. role = DynamicModelChoiceField(
  222. queryset=Role.objects.all(),
  223. required=False
  224. )
  225. tags = DynamicModelMultipleChoiceField(
  226. queryset=Tag.objects.all(),
  227. required=False
  228. )
  229. class Meta:
  230. model = IPRange
  231. fields = [
  232. 'vrf', 'start_address', 'end_address', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
  233. ]
  234. fieldsets = (
  235. ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
  236. ('Tenancy', ('tenant_group', 'tenant')),
  237. )
  238. widgets = {
  239. 'status': StaticSelect(),
  240. }
  241. class IPAddressForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  242. device = DynamicModelChoiceField(
  243. queryset=Device.objects.all(),
  244. required=False,
  245. initial_params={
  246. 'interfaces': '$interface'
  247. }
  248. )
  249. interface = DynamicModelChoiceField(
  250. queryset=Interface.objects.all(),
  251. required=False,
  252. query_params={
  253. 'device_id': '$device'
  254. }
  255. )
  256. virtual_machine = DynamicModelChoiceField(
  257. queryset=VirtualMachine.objects.all(),
  258. required=False,
  259. initial_params={
  260. 'interfaces': '$vminterface'
  261. }
  262. )
  263. vminterface = DynamicModelChoiceField(
  264. queryset=VMInterface.objects.all(),
  265. required=False,
  266. label='Interface',
  267. query_params={
  268. 'virtual_machine_id': '$virtual_machine'
  269. }
  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. tags = DynamicModelMultipleChoiceField(
  352. queryset=Tag.objects.all(),
  353. required=False
  354. )
  355. class Meta:
  356. model = IPAddress
  357. fields = [
  358. 'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'primary_for_parent', 'nat_site', 'nat_rack',
  359. 'nat_device', 'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant',
  360. 'tags',
  361. ]
  362. widgets = {
  363. 'status': StaticSelect(),
  364. 'role': StaticSelect(),
  365. }
  366. def __init__(self, *args, **kwargs):
  367. # Initialize helper selectors
  368. instance = kwargs.get('instance')
  369. initial = kwargs.get('initial', {}).copy()
  370. if instance:
  371. if type(instance.assigned_object) is Interface:
  372. initial['interface'] = instance.assigned_object
  373. elif type(instance.assigned_object) is VMInterface:
  374. initial['vminterface'] = 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 = self.instance.assigned_object.parent_object
  390. if (
  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. # Cannot select both a device interface and a VM interface
  398. if self.cleaned_data.get('interface') and self.cleaned_data.get('vminterface'):
  399. raise forms.ValidationError("Cannot select both a device interface and a virtual machine interface")
  400. self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
  401. # Primary IP assignment is only available if an interface has been assigned.
  402. interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
  403. if self.cleaned_data.get('primary_for_parent') and not interface:
  404. self.add_error(
  405. 'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs."
  406. )
  407. def save(self, *args, **kwargs):
  408. ipaddress = super().save(*args, **kwargs)
  409. # Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
  410. interface = self.instance.assigned_object
  411. if interface:
  412. parent = interface.parent_object
  413. if self.cleaned_data['primary_for_parent']:
  414. if ipaddress.address.version == 4:
  415. parent.primary_ip4 = ipaddress
  416. else:
  417. parent.primary_ip6 = ipaddress
  418. parent.save()
  419. elif ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress:
  420. parent.primary_ip4 = None
  421. parent.save()
  422. elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress:
  423. parent.primary_ip6 = None
  424. parent.save()
  425. return ipaddress
  426. class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  427. vrf = DynamicModelChoiceField(
  428. queryset=VRF.objects.all(),
  429. required=False,
  430. label='VRF'
  431. )
  432. tags = DynamicModelMultipleChoiceField(
  433. queryset=Tag.objects.all(),
  434. required=False
  435. )
  436. class Meta:
  437. model = IPAddress
  438. fields = [
  439. 'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'tenant_group', 'tenant', 'tags',
  440. ]
  441. widgets = {
  442. 'status': StaticSelect(),
  443. 'role': StaticSelect(),
  444. }
  445. class IPAddressAssignForm(BootstrapMixin, forms.Form):
  446. vrf_id = DynamicModelChoiceField(
  447. queryset=VRF.objects.all(),
  448. required=False,
  449. label='VRF'
  450. )
  451. q = forms.CharField(
  452. required=False,
  453. label='Search',
  454. )
  455. class VLANGroupForm(BootstrapMixin, CustomFieldModelForm):
  456. scope_type = ContentTypeChoiceField(
  457. queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
  458. required=False,
  459. widget=StaticSelect
  460. )
  461. region = DynamicModelChoiceField(
  462. queryset=Region.objects.all(),
  463. required=False,
  464. initial_params={
  465. 'sites': '$site'
  466. }
  467. )
  468. sitegroup = DynamicModelChoiceField(
  469. queryset=SiteGroup.objects.all(),
  470. required=False,
  471. initial_params={
  472. 'sites': '$site'
  473. },
  474. label='Site group'
  475. )
  476. site = DynamicModelChoiceField(
  477. queryset=Site.objects.all(),
  478. required=False,
  479. initial_params={
  480. 'locations': '$location'
  481. },
  482. query_params={
  483. 'region_id': '$region',
  484. 'group_id': '$sitegroup',
  485. }
  486. )
  487. location = DynamicModelChoiceField(
  488. queryset=Location.objects.all(),
  489. required=False,
  490. initial_params={
  491. 'racks': '$rack'
  492. },
  493. query_params={
  494. 'site_id': '$site',
  495. }
  496. )
  497. rack = DynamicModelChoiceField(
  498. queryset=Rack.objects.all(),
  499. required=False,
  500. query_params={
  501. 'site_id': '$site',
  502. 'location_id': '$location',
  503. }
  504. )
  505. clustergroup = DynamicModelChoiceField(
  506. queryset=ClusterGroup.objects.all(),
  507. required=False,
  508. initial_params={
  509. 'clusters': '$cluster'
  510. },
  511. label='Cluster group'
  512. )
  513. cluster = DynamicModelChoiceField(
  514. queryset=Cluster.objects.all(),
  515. required=False,
  516. query_params={
  517. 'group_id': '$clustergroup',
  518. }
  519. )
  520. slug = SlugField()
  521. tags = DynamicModelMultipleChoiceField(
  522. queryset=Tag.objects.all(),
  523. required=False
  524. )
  525. class Meta:
  526. model = VLANGroup
  527. fields = [
  528. 'name', 'slug', 'description', 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack',
  529. 'clustergroup', 'cluster', 'tags',
  530. ]
  531. fieldsets = (
  532. ('VLAN Group', ('name', 'slug', 'description', 'tags')),
  533. ('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
  534. )
  535. widgets = {
  536. 'scope_type': StaticSelect,
  537. }
  538. def __init__(self, *args, **kwargs):
  539. instance = kwargs.get('instance')
  540. initial = kwargs.get('initial', {})
  541. if instance is not None and instance.scope:
  542. initial[instance.scope_type.model] = instance.scope
  543. kwargs['initial'] = initial
  544. super().__init__(*args, **kwargs)
  545. def clean(self):
  546. super().clean()
  547. # Assign scope based on scope_type
  548. if self.cleaned_data.get('scope_type'):
  549. scope_field = self.cleaned_data['scope_type'].model
  550. self.instance.scope = self.cleaned_data.get(scope_field)
  551. else:
  552. self.instance.scope_id = None
  553. class VLANForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
  554. # VLANGroup assignment fields
  555. scope_type = forms.ChoiceField(
  556. choices=(
  557. ('', ''),
  558. ('dcim.region', 'Region'),
  559. ('dcim.sitegroup', 'Site group'),
  560. ('dcim.site', 'Site'),
  561. ('dcim.location', 'Location'),
  562. ('dcim.rack', 'Rack'),
  563. ('virtualization.clustergroup', 'Cluster group'),
  564. ('virtualization.cluster', 'Cluster'),
  565. ),
  566. required=False,
  567. widget=StaticSelect,
  568. label='Group scope'
  569. )
  570. group = DynamicModelChoiceField(
  571. queryset=VLANGroup.objects.all(),
  572. required=False,
  573. query_params={
  574. 'scope_type': '$scope_type',
  575. },
  576. label='VLAN Group'
  577. )
  578. # Site assignment fields
  579. region = DynamicModelChoiceField(
  580. queryset=Region.objects.all(),
  581. required=False,
  582. initial_params={
  583. 'sites': '$site'
  584. },
  585. label='Region'
  586. )
  587. sitegroup = DynamicModelChoiceField(
  588. queryset=SiteGroup.objects.all(),
  589. required=False,
  590. initial_params={
  591. 'sites': '$site'
  592. },
  593. label='Site group'
  594. )
  595. site = DynamicModelChoiceField(
  596. queryset=Site.objects.all(),
  597. required=False,
  598. null_option='None',
  599. query_params={
  600. 'region_id': '$region',
  601. 'group_id': '$sitegroup',
  602. }
  603. )
  604. # Other fields
  605. role = DynamicModelChoiceField(
  606. queryset=Role.objects.all(),
  607. required=False
  608. )
  609. tags = DynamicModelMultipleChoiceField(
  610. queryset=Tag.objects.all(),
  611. required=False
  612. )
  613. class Meta:
  614. model = VLAN
  615. fields = [
  616. 'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
  617. ]
  618. help_texts = {
  619. 'site': "Leave blank if this VLAN spans multiple sites",
  620. 'group': "VLAN group (optional)",
  621. 'vid': "Configured VLAN ID",
  622. 'name': "Configured VLAN name",
  623. 'status': "Operational status of this VLAN",
  624. 'role': "The primary function of this VLAN",
  625. }
  626. widgets = {
  627. 'status': StaticSelect(),
  628. }
  629. class ServiceForm(BootstrapMixin, CustomFieldModelForm):
  630. ports = NumericArrayField(
  631. base_field=forms.IntegerField(
  632. min_value=SERVICE_PORT_MIN,
  633. max_value=SERVICE_PORT_MAX
  634. ),
  635. help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
  636. )
  637. tags = DynamicModelMultipleChoiceField(
  638. queryset=Tag.objects.all(),
  639. required=False
  640. )
  641. class Meta:
  642. model = Service
  643. fields = [
  644. 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'tags',
  645. ]
  646. help_texts = {
  647. 'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
  648. "reachable via all IPs assigned to the device.",
  649. }
  650. widgets = {
  651. 'protocol': StaticSelect(),
  652. 'ipaddresses': StaticSelectMultiple(),
  653. }
  654. def __init__(self, *args, **kwargs):
  655. super().__init__(*args, **kwargs)
  656. # Limit IP address choices to those assigned to interfaces of the parent device/VM
  657. if self.instance.device:
  658. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  659. interface__in=self.instance.device.vc_interfaces().values_list('id', flat=True)
  660. )
  661. elif self.instance.virtual_machine:
  662. self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
  663. vminterface__in=self.instance.virtual_machine.interfaces.values_list('id', flat=True)
  664. )
  665. else:
  666. self.fields['ipaddresses'].choices = []