bulk_import.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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 as _
  5. from dcim.models import Device, Interface, Site
  6. from ipam.choices import *
  7. from ipam.constants import *
  8. from ipam.models import *
  9. from netbox.forms import NetBoxModelImportForm
  10. from tenancy.models import Tenant
  11. from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
  12. from virtualization.models import VirtualMachine, VMInterface
  13. __all__ = (
  14. 'AggregateImportForm',
  15. 'ASNImportForm',
  16. 'ASNRangeImportForm',
  17. 'FHRPGroupImportForm',
  18. 'IPAddressImportForm',
  19. 'IPRangeImportForm',
  20. 'L2VPNImportForm',
  21. 'L2VPNTerminationImportForm',
  22. 'PrefixImportForm',
  23. 'RIRImportForm',
  24. 'RoleImportForm',
  25. 'RouteTargetImportForm',
  26. 'ServiceImportForm',
  27. 'ServiceTemplateImportForm',
  28. 'VLANImportForm',
  29. 'VLANGroupImportForm',
  30. 'VRFImportForm',
  31. )
  32. class VRFImportForm(NetBoxModelImportForm):
  33. tenant = CSVModelChoiceField(
  34. queryset=Tenant.objects.all(),
  35. required=False,
  36. to_field_name='name',
  37. help_text=_('Assigned tenant')
  38. )
  39. class Meta:
  40. model = VRF
  41. fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments', 'tags')
  42. class RouteTargetImportForm(NetBoxModelImportForm):
  43. tenant = CSVModelChoiceField(
  44. queryset=Tenant.objects.all(),
  45. required=False,
  46. to_field_name='name',
  47. help_text=_('Assigned tenant')
  48. )
  49. class Meta:
  50. model = RouteTarget
  51. fields = ('name', 'tenant', 'description', 'comments', 'tags')
  52. class RIRImportForm(NetBoxModelImportForm):
  53. slug = SlugField()
  54. class Meta:
  55. model = RIR
  56. fields = ('name', 'slug', 'is_private', 'description', 'tags')
  57. class AggregateImportForm(NetBoxModelImportForm):
  58. rir = CSVModelChoiceField(
  59. queryset=RIR.objects.all(),
  60. to_field_name='name',
  61. help_text=_('Assigned RIR')
  62. )
  63. tenant = CSVModelChoiceField(
  64. queryset=Tenant.objects.all(),
  65. required=False,
  66. to_field_name='name',
  67. help_text=_('Assigned tenant')
  68. )
  69. class Meta:
  70. model = Aggregate
  71. fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments', 'tags')
  72. class ASNRangeImportForm(NetBoxModelImportForm):
  73. rir = CSVModelChoiceField(
  74. queryset=RIR.objects.all(),
  75. to_field_name='name',
  76. help_text=_('Assigned RIR')
  77. )
  78. tenant = CSVModelChoiceField(
  79. queryset=Tenant.objects.all(),
  80. required=False,
  81. to_field_name='name',
  82. help_text=_('Assigned tenant')
  83. )
  84. class Meta:
  85. model = ASNRange
  86. fields = ('name', 'slug', 'rir', 'start', 'end', 'tenant', 'description', 'tags')
  87. class ASNImportForm(NetBoxModelImportForm):
  88. rir = CSVModelChoiceField(
  89. queryset=RIR.objects.all(),
  90. to_field_name='name',
  91. help_text=_('Assigned RIR')
  92. )
  93. tenant = CSVModelChoiceField(
  94. queryset=Tenant.objects.all(),
  95. required=False,
  96. to_field_name='name',
  97. help_text=_('Assigned tenant')
  98. )
  99. class Meta:
  100. model = ASN
  101. fields = ('asn', 'rir', 'tenant', 'description', 'comments', 'tags')
  102. class RoleImportForm(NetBoxModelImportForm):
  103. slug = SlugField()
  104. class Meta:
  105. model = Role
  106. fields = ('name', 'slug', 'weight', 'description', 'tags')
  107. class PrefixImportForm(NetBoxModelImportForm):
  108. vrf = CSVModelChoiceField(
  109. queryset=VRF.objects.all(),
  110. to_field_name='name',
  111. required=False,
  112. help_text=_('Assigned VRF')
  113. )
  114. tenant = CSVModelChoiceField(
  115. queryset=Tenant.objects.all(),
  116. required=False,
  117. to_field_name='name',
  118. help_text=_('Assigned tenant')
  119. )
  120. site = CSVModelChoiceField(
  121. queryset=Site.objects.all(),
  122. required=False,
  123. to_field_name='name',
  124. help_text=_('Assigned site')
  125. )
  126. vlan_group = CSVModelChoiceField(
  127. queryset=VLANGroup.objects.all(),
  128. required=False,
  129. to_field_name='name',
  130. help_text=_("VLAN's group (if any)")
  131. )
  132. vlan = CSVModelChoiceField(
  133. queryset=VLAN.objects.all(),
  134. required=False,
  135. to_field_name='vid',
  136. help_text=_("Assigned VLAN")
  137. )
  138. status = CSVChoiceField(
  139. choices=PrefixStatusChoices,
  140. help_text=_('Operational status')
  141. )
  142. role = CSVModelChoiceField(
  143. queryset=Role.objects.all(),
  144. required=False,
  145. to_field_name='name',
  146. help_text=_('Functional role')
  147. )
  148. class Meta:
  149. model = Prefix
  150. fields = (
  151. 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized',
  152. 'description', 'comments', 'tags',
  153. )
  154. def __init__(self, data=None, *args, **kwargs):
  155. super().__init__(data, *args, **kwargs)
  156. if data:
  157. # Limit VLAN queryset by assigned site and/or group (if specified)
  158. params = {}
  159. if data.get('site'):
  160. params[f"site__{self.fields['site'].to_field_name}"] = data.get('site')
  161. if data.get('vlan_group'):
  162. params[f"group__{self.fields['vlan_group'].to_field_name}"] = data.get('vlan_group')
  163. if params:
  164. self.fields['vlan'].queryset = self.fields['vlan'].queryset.filter(**params)
  165. class IPRangeImportForm(NetBoxModelImportForm):
  166. vrf = CSVModelChoiceField(
  167. queryset=VRF.objects.all(),
  168. to_field_name='name',
  169. required=False,
  170. help_text=_('Assigned VRF')
  171. )
  172. tenant = CSVModelChoiceField(
  173. queryset=Tenant.objects.all(),
  174. required=False,
  175. to_field_name='name',
  176. help_text=_('Assigned tenant')
  177. )
  178. status = CSVChoiceField(
  179. choices=IPRangeStatusChoices,
  180. help_text=_('Operational status')
  181. )
  182. role = CSVModelChoiceField(
  183. queryset=Role.objects.all(),
  184. required=False,
  185. to_field_name='name',
  186. help_text=_('Functional role')
  187. )
  188. class Meta:
  189. model = IPRange
  190. fields = (
  191. 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'mark_utilized', 'description',
  192. 'comments', 'tags',
  193. )
  194. class IPAddressImportForm(NetBoxModelImportForm):
  195. vrf = CSVModelChoiceField(
  196. queryset=VRF.objects.all(),
  197. to_field_name='name',
  198. required=False,
  199. help_text=_('Assigned VRF')
  200. )
  201. tenant = CSVModelChoiceField(
  202. queryset=Tenant.objects.all(),
  203. to_field_name='name',
  204. required=False,
  205. help_text=_('Assigned tenant')
  206. )
  207. status = CSVChoiceField(
  208. choices=IPAddressStatusChoices,
  209. help_text=_('Operational status')
  210. )
  211. role = CSVChoiceField(
  212. choices=IPAddressRoleChoices,
  213. required=False,
  214. help_text=_('Functional role')
  215. )
  216. device = CSVModelChoiceField(
  217. queryset=Device.objects.all(),
  218. required=False,
  219. to_field_name='name',
  220. help_text=_('Parent device of assigned interface (if any)')
  221. )
  222. virtual_machine = CSVModelChoiceField(
  223. queryset=VirtualMachine.objects.all(),
  224. required=False,
  225. to_field_name='name',
  226. help_text=_('Parent VM of assigned interface (if any)')
  227. )
  228. interface = CSVModelChoiceField(
  229. queryset=Interface.objects.none(), # Can also refer to VMInterface
  230. required=False,
  231. to_field_name='name',
  232. help_text=_('Assigned interface')
  233. )
  234. is_primary = forms.BooleanField(
  235. help_text=_('Make this the primary IP for the assigned device'),
  236. required=False
  237. )
  238. class Meta:
  239. model = IPAddress
  240. fields = [
  241. 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
  242. 'dns_name', 'description', 'comments', 'tags',
  243. ]
  244. def __init__(self, data=None, *args, **kwargs):
  245. super().__init__(data, *args, **kwargs)
  246. if data:
  247. # Limit interface queryset by assigned device
  248. if data.get('device'):
  249. self.fields['interface'].queryset = Interface.objects.filter(
  250. **{f"device__{self.fields['device'].to_field_name}": data['device']}
  251. )
  252. # Limit interface queryset by assigned device
  253. elif data.get('virtual_machine'):
  254. self.fields['interface'].queryset = VMInterface.objects.filter(
  255. **{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']}
  256. )
  257. def clean(self):
  258. super().clean()
  259. device = self.cleaned_data.get('device')
  260. virtual_machine = self.cleaned_data.get('virtual_machine')
  261. interface = self.cleaned_data.get('interface')
  262. is_primary = self.cleaned_data.get('is_primary')
  263. # Validate is_primary
  264. if is_primary and not device and not virtual_machine:
  265. raise forms.ValidationError({
  266. "is_primary": "No device or virtual machine specified; cannot set as primary IP"
  267. })
  268. if is_primary and not interface:
  269. raise forms.ValidationError({
  270. "is_primary": "No interface specified; cannot set as primary IP"
  271. })
  272. def save(self, *args, **kwargs):
  273. # Set interface assignment
  274. if self.cleaned_data.get('interface'):
  275. self.instance.assigned_object = self.cleaned_data['interface']
  276. ipaddress = super().save(*args, **kwargs)
  277. # Set as primary for device/VM
  278. if self.cleaned_data.get('is_primary'):
  279. parent = self.cleaned_data['device'] or self.cleaned_data['virtual_machine']
  280. if self.instance.address.version == 4:
  281. parent.primary_ip4 = ipaddress
  282. elif self.instance.address.version == 6:
  283. parent.primary_ip6 = ipaddress
  284. parent.save()
  285. return ipaddress
  286. class FHRPGroupImportForm(NetBoxModelImportForm):
  287. protocol = CSVChoiceField(
  288. choices=FHRPGroupProtocolChoices
  289. )
  290. auth_type = CSVChoiceField(
  291. choices=FHRPGroupAuthTypeChoices,
  292. required=False
  293. )
  294. class Meta:
  295. model = FHRPGroup
  296. fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'comments', 'tags')
  297. class VLANGroupImportForm(NetBoxModelImportForm):
  298. slug = SlugField()
  299. scope_type = CSVContentTypeField(
  300. queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
  301. required=False,
  302. label=_('Scope type (app & model)')
  303. )
  304. min_vid = forms.IntegerField(
  305. min_value=VLAN_VID_MIN,
  306. max_value=VLAN_VID_MAX,
  307. required=False,
  308. label=f'Minimum child VLAN VID (default: {VLAN_VID_MIN})'
  309. )
  310. max_vid = forms.IntegerField(
  311. min_value=VLAN_VID_MIN,
  312. max_value=VLAN_VID_MAX,
  313. required=False,
  314. label=f'Maximum child VLAN VID (default: {VLAN_VID_MIN})'
  315. )
  316. class Meta:
  317. model = VLANGroup
  318. fields = ('name', 'slug', 'scope_type', 'scope_id', 'min_vid', 'max_vid', 'description', 'tags')
  319. labels = {
  320. 'scope_id': 'Scope ID',
  321. }
  322. class VLANImportForm(NetBoxModelImportForm):
  323. site = CSVModelChoiceField(
  324. queryset=Site.objects.all(),
  325. required=False,
  326. to_field_name='name',
  327. help_text=_('Assigned site')
  328. )
  329. group = CSVModelChoiceField(
  330. queryset=VLANGroup.objects.all(),
  331. required=False,
  332. to_field_name='name',
  333. help_text=_('Assigned VLAN group')
  334. )
  335. tenant = CSVModelChoiceField(
  336. queryset=Tenant.objects.all(),
  337. to_field_name='name',
  338. required=False,
  339. help_text=_('Assigned tenant')
  340. )
  341. status = CSVChoiceField(
  342. choices=VLANStatusChoices,
  343. help_text=_('Operational status')
  344. )
  345. role = CSVModelChoiceField(
  346. queryset=Role.objects.all(),
  347. required=False,
  348. to_field_name='name',
  349. help_text=_('Functional role')
  350. )
  351. class Meta:
  352. model = VLAN
  353. fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'comments', 'tags')
  354. class ServiceTemplateImportForm(NetBoxModelImportForm):
  355. protocol = CSVChoiceField(
  356. choices=ServiceProtocolChoices,
  357. help_text=_('IP protocol')
  358. )
  359. class Meta:
  360. model = ServiceTemplate
  361. fields = ('name', 'protocol', 'ports', 'description', 'comments', 'tags')
  362. class ServiceImportForm(NetBoxModelImportForm):
  363. device = CSVModelChoiceField(
  364. queryset=Device.objects.all(),
  365. required=False,
  366. to_field_name='name',
  367. help_text=_('Required if not assigned to a VM')
  368. )
  369. virtual_machine = CSVModelChoiceField(
  370. queryset=VirtualMachine.objects.all(),
  371. required=False,
  372. to_field_name='name',
  373. help_text=_('Required if not assigned to a device')
  374. )
  375. protocol = CSVChoiceField(
  376. choices=ServiceProtocolChoices,
  377. help_text=_('IP protocol')
  378. )
  379. class Meta:
  380. model = Service
  381. fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description', 'comments', 'tags')
  382. class L2VPNImportForm(NetBoxModelImportForm):
  383. tenant = CSVModelChoiceField(
  384. queryset=Tenant.objects.all(),
  385. required=False,
  386. to_field_name='name',
  387. )
  388. type = CSVChoiceField(
  389. choices=L2VPNTypeChoices,
  390. help_text=_('L2VPN type')
  391. )
  392. class Meta:
  393. model = L2VPN
  394. fields = ('identifier', 'name', 'slug', 'tenant', 'type', 'description',
  395. 'comments', 'tags')
  396. class L2VPNTerminationImportForm(NetBoxModelImportForm):
  397. l2vpn = CSVModelChoiceField(
  398. queryset=L2VPN.objects.all(),
  399. required=True,
  400. to_field_name='name',
  401. label=_('L2VPN'),
  402. )
  403. device = CSVModelChoiceField(
  404. queryset=Device.objects.all(),
  405. required=False,
  406. to_field_name='name',
  407. help_text=_('Parent device (for interface)')
  408. )
  409. virtual_machine = CSVModelChoiceField(
  410. queryset=VirtualMachine.objects.all(),
  411. required=False,
  412. to_field_name='name',
  413. help_text=_('Parent virtual machine (for interface)')
  414. )
  415. interface = CSVModelChoiceField(
  416. queryset=Interface.objects.none(), # Can also refer to VMInterface
  417. required=False,
  418. to_field_name='name',
  419. help_text=_('Assigned interface (device or VM)')
  420. )
  421. vlan = CSVModelChoiceField(
  422. queryset=VLAN.objects.all(),
  423. required=False,
  424. to_field_name='name',
  425. help_text=_('Assigned VLAN')
  426. )
  427. class Meta:
  428. model = L2VPNTermination
  429. fields = ('l2vpn', 'device', 'virtual_machine', 'interface', 'vlan', 'tags')
  430. def __init__(self, data=None, *args, **kwargs):
  431. super().__init__(data, *args, **kwargs)
  432. if data:
  433. # Limit interface queryset by device or VM
  434. if data.get('device'):
  435. self.fields['interface'].queryset = Interface.objects.filter(
  436. **{f"device__{self.fields['device'].to_field_name}": data['device']}
  437. )
  438. elif data.get('virtual_machine'):
  439. self.fields['interface'].queryset = VMInterface.objects.filter(
  440. **{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']}
  441. )
  442. def clean(self):
  443. super().clean()
  444. if self.cleaned_data.get('device') and self.cleaned_data.get('virtual_machine'):
  445. raise ValidationError('Cannot import device and VM interface terminations simultaneously.')
  446. if not (self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')):
  447. raise ValidationError('Each termination must specify either an interface or a VLAN.')
  448. if self.cleaned_data.get('interface') and self.cleaned_data.get('vlan'):
  449. raise ValidationError('Cannot assign both an interface and a VLAN.')
  450. self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')