bulk_edit.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. from django import forms
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.core.exceptions import ObjectDoesNotExist
  4. from django.utils.translation import gettext_lazy as _
  5. from dcim.forms.mixins import ScopedBulkEditForm
  6. from dcim.models import Region, Site, SiteGroup
  7. from ipam.choices import *
  8. from ipam.constants import *
  9. from ipam.models import *
  10. from ipam.models import ASN
  11. from netbox.forms import NetBoxModelBulkEditForm, OrganizationalModelBulkEditForm, PrimaryModelBulkEditForm
  12. from tenancy.models import Tenant
  13. from utilities.forms import add_blank_choice, get_field_value
  14. from utilities.forms.fields import (
  15. ContentTypeChoiceField,
  16. DynamicModelChoiceField,
  17. DynamicModelMultipleChoiceField,
  18. NumericArrayField,
  19. NumericRangeArrayField,
  20. )
  21. from utilities.forms.rendering import FieldSet
  22. from utilities.forms.widgets import BulkEditNullBooleanSelect, HTMXSelect
  23. from utilities.templatetags.builtins.filters import bettertitle
  24. __all__ = (
  25. 'ASNBulkEditForm',
  26. 'ASNRangeBulkEditForm',
  27. 'AggregateBulkEditForm',
  28. 'FHRPGroupBulkEditForm',
  29. 'IPAddressBulkEditForm',
  30. 'IPRangeBulkEditForm',
  31. 'PrefixBulkEditForm',
  32. 'RIRBulkEditForm',
  33. 'RoleBulkEditForm',
  34. 'RouteTargetBulkEditForm',
  35. 'ServiceBulkEditForm',
  36. 'ServiceTemplateBulkEditForm',
  37. 'VLANBulkEditForm',
  38. 'VLANGroupBulkEditForm',
  39. 'VLANTranslationPolicyBulkEditForm',
  40. 'VLANTranslationRuleBulkEditForm',
  41. 'VRFBulkEditForm',
  42. )
  43. class VRFBulkEditForm(PrimaryModelBulkEditForm):
  44. tenant = DynamicModelChoiceField(
  45. label=_('Tenant'),
  46. queryset=Tenant.objects.all(),
  47. required=False
  48. )
  49. enforce_unique = forms.NullBooleanField(
  50. required=False,
  51. widget=BulkEditNullBooleanSelect(),
  52. label=_('Enforce unique space')
  53. )
  54. model = VRF
  55. fieldsets = (
  56. FieldSet('tenant', 'enforce_unique', 'description'),
  57. )
  58. nullable_fields = ('tenant', 'description', 'comments')
  59. class RouteTargetBulkEditForm(PrimaryModelBulkEditForm):
  60. tenant = DynamicModelChoiceField(
  61. label=_('Tenant'),
  62. queryset=Tenant.objects.all(),
  63. required=False
  64. )
  65. model = RouteTarget
  66. fieldsets = (
  67. FieldSet('tenant', 'description'),
  68. )
  69. nullable_fields = ('tenant', 'description', 'comments')
  70. class RIRBulkEditForm(OrganizationalModelBulkEditForm):
  71. is_private = forms.NullBooleanField(
  72. label=_('Is private'),
  73. required=False,
  74. widget=BulkEditNullBooleanSelect
  75. )
  76. model = RIR
  77. fieldsets = (
  78. FieldSet('is_private', 'description'),
  79. )
  80. nullable_fields = ('is_private', 'description', 'comments')
  81. class ASNRangeBulkEditForm(OrganizationalModelBulkEditForm):
  82. rir = DynamicModelChoiceField(
  83. queryset=RIR.objects.all(),
  84. required=False,
  85. label=_('RIR')
  86. )
  87. tenant = DynamicModelChoiceField(
  88. label=_('Tenant'),
  89. queryset=Tenant.objects.all(),
  90. required=False
  91. )
  92. model = ASNRange
  93. fieldsets = (
  94. FieldSet('rir', 'tenant', 'description'),
  95. )
  96. nullable_fields = ('description', 'comments')
  97. class ASNBulkEditForm(PrimaryModelBulkEditForm):
  98. sites = DynamicModelMultipleChoiceField(
  99. label=_('Sites'),
  100. queryset=Site.objects.all(),
  101. required=False
  102. )
  103. rir = DynamicModelChoiceField(
  104. queryset=RIR.objects.all(),
  105. required=False,
  106. label=_('RIR')
  107. )
  108. role = DynamicModelChoiceField(
  109. queryset=Role.objects.all(),
  110. required=False,
  111. label=_('Role')
  112. )
  113. tenant = DynamicModelChoiceField(
  114. label=_('Tenant'),
  115. queryset=Tenant.objects.all(),
  116. required=False
  117. )
  118. model = ASN
  119. fieldsets = (
  120. FieldSet('sites', 'rir', 'role', 'tenant', 'description'),
  121. )
  122. nullable_fields = ('role', 'tenant', 'description', 'comments')
  123. class AggregateBulkEditForm(PrimaryModelBulkEditForm):
  124. rir = DynamicModelChoiceField(
  125. queryset=RIR.objects.all(),
  126. required=False,
  127. label=_('RIR')
  128. )
  129. tenant = DynamicModelChoiceField(
  130. label=_('Tenant'),
  131. queryset=Tenant.objects.all(),
  132. required=False
  133. )
  134. date_added = forms.DateField(
  135. label=_('Date added'),
  136. required=False
  137. )
  138. model = Aggregate
  139. fieldsets = (
  140. FieldSet('rir', 'tenant', 'date_added', 'description'),
  141. )
  142. nullable_fields = ('date_added', 'description', 'comments')
  143. class RoleBulkEditForm(OrganizationalModelBulkEditForm):
  144. weight = forms.IntegerField(
  145. label=_('Weight'),
  146. required=False
  147. )
  148. model = Role
  149. fieldsets = (
  150. FieldSet('weight', 'description'),
  151. )
  152. nullable_fields = ('description', 'comments')
  153. class PrefixBulkEditForm(ScopedBulkEditForm, PrimaryModelBulkEditForm):
  154. vlan_group = DynamicModelChoiceField(
  155. queryset=VLANGroup.objects.all(),
  156. required=False,
  157. label=_('VLAN Group')
  158. )
  159. vlan = DynamicModelChoiceField(
  160. queryset=VLAN.objects.all(),
  161. required=False,
  162. label=_('VLAN'),
  163. query_params={
  164. 'group_id': '$vlan_group',
  165. }
  166. )
  167. vrf = DynamicModelChoiceField(
  168. queryset=VRF.objects.all(),
  169. required=False,
  170. label=_('VRF')
  171. )
  172. prefix_length = forms.IntegerField(
  173. label=_('Prefix length'),
  174. min_value=PREFIX_LENGTH_MIN,
  175. max_value=PREFIX_LENGTH_MAX,
  176. required=False
  177. )
  178. tenant = DynamicModelChoiceField(
  179. label=_('Tenant'),
  180. queryset=Tenant.objects.all(),
  181. required=False
  182. )
  183. status = forms.ChoiceField(
  184. label=_('Status'),
  185. choices=add_blank_choice(PrefixStatusChoices),
  186. required=False
  187. )
  188. role = DynamicModelChoiceField(
  189. label=_('Role'),
  190. queryset=Role.objects.all(),
  191. required=False
  192. )
  193. is_pool = forms.NullBooleanField(
  194. required=False,
  195. widget=BulkEditNullBooleanSelect(),
  196. label=_('Is a pool')
  197. )
  198. mark_utilized = forms.NullBooleanField(
  199. required=False,
  200. widget=BulkEditNullBooleanSelect(),
  201. label=_('Treat as fully utilized')
  202. )
  203. model = Prefix
  204. fieldsets = (
  205. FieldSet('tenant', 'status', 'role', 'description'),
  206. FieldSet('vrf', 'prefix_length', 'is_pool', 'mark_utilized', name=_('Addressing')),
  207. FieldSet('scope_type', 'scope', name=_('Scope')),
  208. FieldSet('vlan_group', 'vlan', name=_('VLAN Assignment')),
  209. )
  210. nullable_fields = (
  211. 'vlan', 'vrf', 'tenant', 'role', 'scope', 'description', 'comments',
  212. )
  213. class IPRangeBulkEditForm(PrimaryModelBulkEditForm):
  214. vrf = DynamicModelChoiceField(
  215. queryset=VRF.objects.all(),
  216. required=False,
  217. label=_('VRF')
  218. )
  219. tenant = DynamicModelChoiceField(
  220. label=_('Tenant'),
  221. queryset=Tenant.objects.all(),
  222. required=False
  223. )
  224. status = forms.ChoiceField(
  225. label=_('Status'),
  226. choices=add_blank_choice(IPRangeStatusChoices),
  227. required=False
  228. )
  229. role = DynamicModelChoiceField(
  230. label=_('Role'),
  231. queryset=Role.objects.all(),
  232. required=False
  233. )
  234. mark_populated = forms.NullBooleanField(
  235. required=False,
  236. widget=BulkEditNullBooleanSelect(),
  237. label=_('Treat as populated')
  238. )
  239. mark_utilized = forms.NullBooleanField(
  240. required=False,
  241. widget=BulkEditNullBooleanSelect(),
  242. label=_('Treat as fully utilized')
  243. )
  244. model = IPRange
  245. fieldsets = (
  246. FieldSet('status', 'role', 'vrf', 'tenant', 'mark_utilized', 'description'),
  247. )
  248. nullable_fields = (
  249. 'vrf', 'tenant', 'role', 'description', 'comments',
  250. )
  251. class IPAddressBulkEditForm(PrimaryModelBulkEditForm):
  252. vrf = DynamicModelChoiceField(
  253. queryset=VRF.objects.all(),
  254. required=False,
  255. label=_('VRF')
  256. )
  257. mask_length = forms.IntegerField(
  258. label=_('Mask length'),
  259. min_value=IPADDRESS_MASK_LENGTH_MIN,
  260. max_value=IPADDRESS_MASK_LENGTH_MAX,
  261. required=False
  262. )
  263. tenant = DynamicModelChoiceField(
  264. label=_('Tenant'),
  265. queryset=Tenant.objects.all(),
  266. required=False
  267. )
  268. status = forms.ChoiceField(
  269. label=_('Status'),
  270. choices=add_blank_choice(IPAddressStatusChoices),
  271. required=False
  272. )
  273. role = forms.ChoiceField(
  274. label=_('Role'),
  275. choices=add_blank_choice(IPAddressRoleChoices),
  276. required=False
  277. )
  278. dns_name = forms.CharField(
  279. max_length=255,
  280. required=False,
  281. label=_('DNS name')
  282. )
  283. model = IPAddress
  284. fieldsets = (
  285. FieldSet('status', 'role', 'tenant', 'description'),
  286. FieldSet('vrf', 'mask_length', 'dns_name', name=_('Addressing')),
  287. )
  288. nullable_fields = (
  289. 'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
  290. )
  291. class FHRPGroupBulkEditForm(PrimaryModelBulkEditForm):
  292. protocol = forms.ChoiceField(
  293. label=_('Protocol'),
  294. choices=add_blank_choice(FHRPGroupProtocolChoices),
  295. required=False
  296. )
  297. group_id = forms.IntegerField(
  298. min_value=0,
  299. required=False,
  300. label=_('Group ID')
  301. )
  302. auth_type = forms.ChoiceField(
  303. choices=add_blank_choice(FHRPGroupAuthTypeChoices),
  304. required=False,
  305. label=_('Authentication type')
  306. )
  307. auth_key = forms.CharField(
  308. max_length=255,
  309. required=False,
  310. label=_('Authentication key')
  311. )
  312. name = forms.CharField(
  313. label=_('Name'),
  314. max_length=100,
  315. required=False
  316. )
  317. model = FHRPGroup
  318. fieldsets = (
  319. FieldSet('protocol', 'group_id', 'name', 'description'),
  320. FieldSet('auth_type', 'auth_key', name=_('Authentication')),
  321. )
  322. nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments')
  323. class VLANGroupBulkEditForm(OrganizationalModelBulkEditForm):
  324. scope_type = ContentTypeChoiceField(
  325. queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
  326. widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
  327. required=False,
  328. label=_('Scope type')
  329. )
  330. scope = DynamicModelChoiceField(
  331. label=_('Scope'),
  332. queryset=Site.objects.none(), # Initial queryset
  333. required=False,
  334. disabled=True,
  335. selector=True
  336. )
  337. vid_ranges = NumericRangeArrayField(
  338. label=_('VLAN ID ranges'),
  339. required=False
  340. )
  341. tenant = DynamicModelChoiceField(
  342. label=_('Tenant'),
  343. queryset=Tenant.objects.all(),
  344. required=False
  345. )
  346. model = VLANGroup
  347. fieldsets = (
  348. FieldSet('site', 'vid_ranges', 'description'),
  349. FieldSet('scope_type', 'scope', name=_('Scope')),
  350. FieldSet('tenant', name=_('Tenancy')),
  351. )
  352. nullable_fields = ('description', 'scope', 'comments')
  353. def __init__(self, *args, **kwargs):
  354. super().__init__(*args, **kwargs)
  355. if scope_type_id := get_field_value(self, 'scope_type'):
  356. try:
  357. scope_type = ContentType.objects.get(pk=scope_type_id)
  358. model = scope_type.model_class()
  359. self.fields['scope'].queryset = model.objects.all()
  360. self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower
  361. self.fields['scope'].disabled = False
  362. self.fields['scope'].label = _(bettertitle(model._meta.verbose_name))
  363. except ObjectDoesNotExist:
  364. pass
  365. class VLANBulkEditForm(PrimaryModelBulkEditForm):
  366. region = DynamicModelChoiceField(
  367. label=_('Region'),
  368. queryset=Region.objects.all(),
  369. required=False
  370. )
  371. site_group = DynamicModelChoiceField(
  372. label=_('Site group'),
  373. queryset=SiteGroup.objects.all(),
  374. required=False
  375. )
  376. site = DynamicModelChoiceField(
  377. label=_('Site'),
  378. queryset=Site.objects.all(),
  379. required=False,
  380. query_params={
  381. 'region_id': '$region',
  382. 'group_id': '$site_group',
  383. }
  384. )
  385. group = DynamicModelChoiceField(
  386. label=_('Group'),
  387. queryset=VLANGroup.objects.all(),
  388. required=False,
  389. query_params={
  390. 'site_id': '$site'
  391. }
  392. )
  393. tenant = DynamicModelChoiceField(
  394. label=_('Tenant'),
  395. queryset=Tenant.objects.all(),
  396. required=False
  397. )
  398. status = forms.ChoiceField(
  399. label=_('Status'),
  400. choices=add_blank_choice(VLANStatusChoices),
  401. required=False
  402. )
  403. role = DynamicModelChoiceField(
  404. label=_('Role'),
  405. queryset=Role.objects.all(),
  406. required=False
  407. )
  408. qinq_role = forms.ChoiceField(
  409. label=_('Q-in-Q role'),
  410. choices=add_blank_choice(VLANQinQRoleChoices),
  411. required=False
  412. )
  413. qinq_svlan = DynamicModelChoiceField(
  414. label=_('Q-in-Q SVLAN'),
  415. queryset=VLAN.objects.all(),
  416. required=False,
  417. query_params={
  418. 'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE,
  419. }
  420. )
  421. model = VLAN
  422. fieldsets = (
  423. FieldSet('status', 'role', 'tenant', 'description'),
  424. FieldSet('qinq_role', 'qinq_svlan', name=_('Q-in-Q')),
  425. FieldSet('region', 'site_group', 'site', 'group', name=_('Site & Group')),
  426. )
  427. nullable_fields = (
  428. 'site', 'group', 'tenant', 'role', 'description', 'qinq_role', 'qinq_svlan', 'comments',
  429. )
  430. class VLANTranslationPolicyBulkEditForm(PrimaryModelBulkEditForm):
  431. model = VLANTranslationPolicy
  432. fieldsets = (
  433. FieldSet('description'),
  434. )
  435. nullable_fields = ('description',)
  436. class VLANTranslationRuleBulkEditForm(NetBoxModelBulkEditForm):
  437. policy = DynamicModelChoiceField(
  438. label=_('Policy'),
  439. queryset=VLANTranslationPolicy.objects.all(),
  440. selector=True
  441. )
  442. local_vid = forms.IntegerField(required=False)
  443. remote_vid = forms.IntegerField(required=False)
  444. model = VLANTranslationRule
  445. fieldsets = (
  446. FieldSet('policy', 'local_vid', 'remote_vid'),
  447. )
  448. fields = ('policy', 'local_vid', 'remote_vid')
  449. class ServiceTemplateBulkEditForm(PrimaryModelBulkEditForm):
  450. protocol = forms.ChoiceField(
  451. label=_('Protocol'),
  452. choices=add_blank_choice(ServiceProtocolChoices),
  453. required=False
  454. )
  455. ports = NumericArrayField(
  456. label=_('Ports'),
  457. base_field=forms.IntegerField(
  458. min_value=SERVICE_PORT_MIN,
  459. max_value=SERVICE_PORT_MAX
  460. ),
  461. required=False
  462. )
  463. model = ServiceTemplate
  464. fieldsets = (
  465. FieldSet('protocol', 'ports', 'description'),
  466. )
  467. nullable_fields = ('description', 'comments')
  468. class ServiceBulkEditForm(ServiceTemplateBulkEditForm):
  469. model = Service