forms.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. from django import forms
  2. from django.db.models import Count
  3. from dcim.models import Site, Device, Interface
  4. from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
  5. from tenancy.forms import bulkedit_tenant_choices
  6. from tenancy.models import Tenant
  7. from utilities.forms import BootstrapMixin, APISelect, Livesearch, CSVDataField, BulkImportForm, SlugField
  8. from .models import (
  9. Aggregate, IPAddress, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, VLAN, VLANGroup, VLAN_STATUS_CHOICES, VRF,
  10. )
  11. FORM_PREFIX_STATUS_CHOICES = (('', '---------'),) + PREFIX_STATUS_CHOICES
  12. FORM_VLAN_STATUS_CHOICES = (('', '---------'),) + VLAN_STATUS_CHOICES
  13. IP_FAMILY_CHOICES = [
  14. ('', 'All'),
  15. (4, 'IPv4'),
  16. (6, 'IPv6'),
  17. ]
  18. def bulkedit_vrf_choices():
  19. """
  20. Include an option to assign the object to the global table.
  21. """
  22. choices = [
  23. (None, '---------'),
  24. (0, 'Global'),
  25. ]
  26. choices += [(v.pk, v.name) for v in VRF.objects.all()]
  27. return choices
  28. #
  29. # VRFs
  30. #
  31. class VRFForm(BootstrapMixin, CustomFieldForm):
  32. class Meta:
  33. model = VRF
  34. fields = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
  35. labels = {
  36. 'rd': "RD",
  37. }
  38. help_texts = {
  39. 'rd': "Route distinguisher in any format",
  40. }
  41. class VRFFromCSVForm(forms.ModelForm):
  42. tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
  43. error_messages={'invalid_choice': 'Tenant not found.'})
  44. class Meta:
  45. model = VRF
  46. fields = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
  47. class VRFImportForm(BulkImportForm, BootstrapMixin):
  48. csv = CSVDataField(csv_form=VRFFromCSVForm)
  49. class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  50. pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
  51. tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
  52. description = forms.CharField(max_length=100, required=False)
  53. def vrf_tenant_choices():
  54. tenant_choices = Tenant.objects.annotate(vrf_count=Count('vrfs'))
  55. return [(t.slug, u'{} ({})'.format(t.name, t.vrf_count)) for t in tenant_choices]
  56. class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
  57. model = VRF
  58. tenant = forms.MultipleChoiceField(required=False, choices=vrf_tenant_choices,
  59. widget=forms.SelectMultiple(attrs={'size': 8}))
  60. #
  61. # RIRs
  62. #
  63. class RIRForm(forms.ModelForm, BootstrapMixin):
  64. slug = SlugField()
  65. class Meta:
  66. model = RIR
  67. fields = ['name', 'slug']
  68. #
  69. # Aggregates
  70. #
  71. class AggregateForm(BootstrapMixin, CustomFieldForm):
  72. class Meta:
  73. model = Aggregate
  74. fields = ['prefix', 'rir', 'date_added', 'description']
  75. help_texts = {
  76. 'prefix': "IPv4 or IPv6 network",
  77. 'rir': "Regional Internet Registry responsible for this prefix",
  78. 'date_added': "Format: YYYY-MM-DD",
  79. }
  80. class AggregateFromCSVForm(forms.ModelForm):
  81. rir = forms.ModelChoiceField(queryset=RIR.objects.all(), to_field_name='name',
  82. error_messages={'invalid_choice': 'RIR not found.'})
  83. class Meta:
  84. model = Aggregate
  85. fields = ['prefix', 'rir', 'date_added', 'description']
  86. class AggregateImportForm(BulkImportForm, BootstrapMixin):
  87. csv = CSVDataField(csv_form=AggregateFromCSVForm)
  88. class AggregateBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  89. pk = forms.ModelMultipleChoiceField(queryset=Aggregate.objects.all(), widget=forms.MultipleHiddenInput)
  90. rir = forms.ModelChoiceField(queryset=RIR.objects.all(), required=False, label='RIR')
  91. date_added = forms.DateField(required=False)
  92. description = forms.CharField(max_length=100, required=False)
  93. def aggregate_rir_choices():
  94. rir_choices = RIR.objects.annotate(aggregate_count=Count('aggregates'))
  95. return [(r.slug, u'{} ({})'.format(r.name, r.aggregate_count)) for r in rir_choices]
  96. class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
  97. model = Aggregate
  98. family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address Family')
  99. rir = forms.MultipleChoiceField(required=False, choices=aggregate_rir_choices, label='RIR',
  100. widget=forms.SelectMultiple(attrs={'size': 8}))
  101. #
  102. # Roles
  103. #
  104. class RoleForm(forms.ModelForm, BootstrapMixin):
  105. slug = SlugField()
  106. class Meta:
  107. model = Role
  108. fields = ['name', 'slug']
  109. #
  110. # Prefixes
  111. #
  112. class PrefixForm(BootstrapMixin, CustomFieldForm):
  113. site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
  114. widget=forms.Select(attrs={'filter-for': 'vlan'}))
  115. vlan = forms.ModelChoiceField(queryset=VLAN.objects.all(), required=False, label='VLAN',
  116. widget=APISelect(api_url='/api/ipam/vlans/?site_id={{site}}',
  117. display_field='display_name'))
  118. class Meta:
  119. model = Prefix
  120. fields = ['prefix', 'vrf', 'tenant', 'site', 'vlan', 'status', 'role', 'description']
  121. help_texts = {
  122. 'prefix': "IPv4 or IPv6 network",
  123. 'vrf': "VRF (if applicable)",
  124. 'site': "The site to which this prefix is assigned (if applicable)",
  125. 'vlan': "The VLAN to which this prefix is assigned (if applicable)",
  126. 'status': "Operational status of this prefix",
  127. 'role': "The primary function of this prefix",
  128. }
  129. def __init__(self, *args, **kwargs):
  130. super(PrefixForm, self).__init__(*args, **kwargs)
  131. self.fields['vrf'].empty_label = 'Global'
  132. # Initialize field without choices to avoid pulling all VLANs from the database
  133. if self.is_bound and self.data.get('site'):
  134. self.fields['vlan'].queryset = VLAN.objects.filter(site__pk=self.data['site'])
  135. elif self.initial.get('site'):
  136. self.fields['vlan'].queryset = VLAN.objects.filter(site=self.initial['site'])
  137. else:
  138. self.fields['vlan'].choices = []
  139. def clean_prefix(self):
  140. prefix = self.cleaned_data['prefix']
  141. if prefix.version == 4 and prefix.prefixlen == 32:
  142. raise forms.ValidationError("Cannot create host addresses (/32) as prefixes. These should be IPv4 "
  143. "addresses instead.")
  144. elif prefix.version == 6 and prefix.prefixlen == 128:
  145. raise forms.ValidationError("Cannot create host addresses (/128) as prefixes. These should be IPv6 "
  146. "addresses instead.")
  147. return prefix
  148. class PrefixFromCSVForm(forms.ModelForm):
  149. vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
  150. error_messages={'invalid_choice': 'VRF not found.'})
  151. tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
  152. error_messages={'invalid_choice': 'Tenant not found.'})
  153. site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, to_field_name='name',
  154. error_messages={'invalid_choice': 'Site not found.'})
  155. vlan_group_name = forms.CharField(required=False)
  156. vlan_vid = forms.IntegerField(required=False)
  157. status_name = forms.ChoiceField(choices=[(s[1], s[0]) for s in PREFIX_STATUS_CHOICES])
  158. role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False, to_field_name='name',
  159. error_messages={'invalid_choice': 'Invalid role.'})
  160. class Meta:
  161. model = Prefix
  162. fields = ['prefix', 'vrf', 'tenant', 'site', 'vlan_group_name', 'vlan_vid', 'status_name', 'role',
  163. 'description']
  164. def clean(self):
  165. super(PrefixFromCSVForm, self).clean()
  166. site = self.cleaned_data.get('site')
  167. vlan_group_name = self.cleaned_data.get('vlan_group_name')
  168. vlan_vid = self.cleaned_data.get('vlan_vid')
  169. # Validate VLAN
  170. vlan_group = None
  171. if vlan_group_name:
  172. try:
  173. vlan_group = VLANGroup.objects.get(site=site, name=vlan_group_name)
  174. except VLANGroup.DoesNotExist:
  175. self.add_error('vlan_group_name', "Invalid VLAN group ({} - {}).".format(site, vlan_group_name))
  176. if vlan_vid and vlan_group:
  177. try:
  178. self.instance.vlan = VLAN.objects.get(group=vlan_group, vid=vlan_vid)
  179. except VLAN.DoesNotExist:
  180. self.add_error('vlan_vid', "Invalid VLAN ID ({} - {}).".format(vlan_group, vlan_vid))
  181. elif vlan_vid and site:
  182. try:
  183. self.instance.vlan = VLAN.objects.get(site=site, vid=vlan_vid)
  184. except VLAN.MultipleObjectsReturned:
  185. self.add_error('vlan_vid', "Multiple VLANs found ({} - VID {})".format(site, vlan_vid))
  186. elif vlan_vid:
  187. self.add_error('vlan_vid', "Must specify site and/or VLAN group when assigning a VLAN.")
  188. def save(self, *args, **kwargs):
  189. m = super(PrefixFromCSVForm, self).save(commit=False)
  190. # Assign Prefix status by name
  191. m.status = dict(self.fields['status_name'].choices)[self.cleaned_data['status_name']]
  192. if kwargs.get('commit'):
  193. m.save()
  194. return m
  195. class PrefixImportForm(BulkImportForm, BootstrapMixin):
  196. csv = CSVDataField(csv_form=PrefixFromCSVForm)
  197. class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  198. pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
  199. site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
  200. vrf = forms.TypedChoiceField(choices=bulkedit_vrf_choices, coerce=int, required=False, label='VRF')
  201. tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
  202. status = forms.ChoiceField(choices=FORM_PREFIX_STATUS_CHOICES, required=False)
  203. role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
  204. description = forms.CharField(max_length=100, required=False)
  205. def prefix_vrf_choices():
  206. vrf_choices = VRF.objects.annotate(prefix_count=Count('prefixes'))
  207. return [(v.pk, u'{} ({})'.format(v.name, v.prefix_count)) for v in vrf_choices]
  208. def tenant_choices():
  209. tenant_choices = Tenant.objects.all()
  210. return [(t.slug, t.name) for t in tenant_choices]
  211. def prefix_site_choices():
  212. site_choices = Site.objects.annotate(prefix_count=Count('prefixes'))
  213. return [(s.slug, u'{} ({})'.format(s.name, s.prefix_count)) for s in site_choices]
  214. def prefix_status_choices():
  215. status_counts = {}
  216. for status in Prefix.objects.values('status').annotate(count=Count('status')).order_by('status'):
  217. status_counts[status['status']] = status['count']
  218. return [(s[0], u'{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in PREFIX_STATUS_CHOICES]
  219. def prefix_role_choices():
  220. role_choices = Role.objects.annotate(prefix_count=Count('prefixes'))
  221. return [(r.slug, u'{} ({})'.format(r.name, r.prefix_count)) for r in role_choices]
  222. class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
  223. model = Prefix
  224. parent = forms.CharField(required=False, label='Search Within', widget=forms.TextInput(attrs={
  225. 'placeholder': 'Network',
  226. }))
  227. family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address Family')
  228. vrf = forms.MultipleChoiceField(required=False, choices=prefix_vrf_choices, label='VRF',
  229. widget=forms.SelectMultiple(attrs={'size': 6}))
  230. tenant = forms.MultipleChoiceField(required=False, choices=tenant_choices, label='Tenant',
  231. widget=forms.SelectMultiple(attrs={'size': 6}))
  232. status = forms.MultipleChoiceField(required=False, choices=prefix_status_choices,
  233. widget=forms.SelectMultiple(attrs={'size': 6}))
  234. site = forms.MultipleChoiceField(required=False, choices=prefix_site_choices,
  235. widget=forms.SelectMultiple(attrs={'size': 6}))
  236. role = forms.MultipleChoiceField(required=False, choices=prefix_role_choices,
  237. widget=forms.SelectMultiple(attrs={'size': 6}))
  238. expand = forms.BooleanField(required=False, label='Expand prefix hierarchy')
  239. #
  240. # IP addresses
  241. #
  242. class IPAddressForm(BootstrapMixin, CustomFieldForm):
  243. nat_site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
  244. widget=forms.Select(attrs={'filter-for': 'nat_device'}))
  245. nat_device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
  246. widget=APISelect(api_url='/api/dcim/devices/?site_id={{nat_site}}',
  247. attrs={'filter-for': 'nat_inside'}))
  248. livesearch = forms.CharField(required=False, label='IP Address', widget=Livesearch(
  249. query_key='q', query_url='ipam-api:ipaddress_list', field_to_update='nat_inside', obj_label='address')
  250. )
  251. nat_inside = forms.ModelChoiceField(queryset=IPAddress.objects.all(), required=False, label='NAT (Inside)',
  252. widget=APISelect(api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}',
  253. display_field='address'))
  254. class Meta:
  255. model = IPAddress
  256. fields = ['address', 'vrf', 'tenant', 'nat_device', 'nat_inside', 'description']
  257. help_texts = {
  258. 'address': "IPv4 or IPv6 address and mask",
  259. 'vrf': "VRF (if applicable)",
  260. }
  261. def __init__(self, *args, **kwargs):
  262. super(IPAddressForm, self).__init__(*args, **kwargs)
  263. self.fields['vrf'].empty_label = 'Global'
  264. if self.instance.nat_inside:
  265. nat_inside = self.instance.nat_inside
  266. # If the IP is assigned to an interface, populate site/device fields accordingly
  267. if self.instance.nat_inside.interface:
  268. self.initial['nat_site'] = self.instance.nat_inside.interface.device.rack.site.pk
  269. self.initial['nat_device'] = self.instance.nat_inside.interface.device.pk
  270. self.fields['nat_device'].queryset = Device.objects.filter(
  271. rack__site=nat_inside.interface.device.rack.site)
  272. self.fields['nat_inside'].queryset = IPAddress.objects.filter(
  273. interface__device=nat_inside.interface.device)
  274. else:
  275. self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk)
  276. else:
  277. # Initialize nat_device choices if nat_site is set
  278. if self.is_bound and self.data.get('nat_site'):
  279. self.fields['nat_device'].queryset = Device.objects.filter(rack__site__pk=self.data['nat_site'])
  280. elif self.initial.get('nat_site'):
  281. self.fields['nat_device'].queryset = Device.objects.filter(rack__site=self.initial['nat_site'])
  282. else:
  283. self.fields['nat_device'].choices = []
  284. # Initialize nat_inside choices if nat_device is set
  285. if self.is_bound and self.data.get('nat_device'):
  286. self.fields['nat_inside'].queryset = IPAddress.objects.filter(
  287. interface__device__pk=self.data['nat_device'])
  288. elif self.initial.get('nat_device'):
  289. self.fields['nat_inside'].queryset = IPAddress.objects.filter(
  290. interface__device__pk=self.initial['nat_device'])
  291. else:
  292. self.fields['nat_inside'].choices = []
  293. class IPAddressFromCSVForm(forms.ModelForm):
  294. vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
  295. error_messages={'invalid_choice': 'VRF not found.'})
  296. tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
  297. error_messages={'invalid_choice': 'Tenant not found.'})
  298. device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name',
  299. error_messages={'invalid_choice': 'Device not found.'})
  300. interface_name = forms.CharField(required=False)
  301. is_primary = forms.BooleanField(required=False)
  302. class Meta:
  303. model = IPAddress
  304. fields = ['address', 'vrf', 'tenant', 'device', 'interface_name', 'is_primary', 'description']
  305. def clean(self):
  306. device = self.cleaned_data.get('device')
  307. interface_name = self.cleaned_data.get('interface_name')
  308. is_primary = self.cleaned_data.get('is_primary')
  309. # Validate interface
  310. if device and interface_name:
  311. try:
  312. Interface.objects.get(device=device, name=interface_name)
  313. except Interface.DoesNotExist:
  314. self.add_error('interface_name', "Invalid interface ({}) for {}".format(interface_name, device))
  315. elif device and not interface_name:
  316. self.add_error('interface_name', "Device set ({}) but interface missing".format(device))
  317. elif interface_name and not device:
  318. self.add_error('device', "Interface set ({}) but device missing or invalid".format(interface_name))
  319. # Validate is_primary
  320. if is_primary and not device:
  321. self.add_error('is_primary', "No device specified; cannot set as primary IP")
  322. def save(self, commit=True):
  323. # Set interface
  324. if self.cleaned_data['device'] and self.cleaned_data['interface_name']:
  325. self.instance.interface = Interface.objects.get(device=self.cleaned_data['device'],
  326. name=self.cleaned_data['interface_name'])
  327. # Set as primary for device
  328. if self.cleaned_data['is_primary']:
  329. if self.instance.address.version == 4:
  330. self.instance.primary_ip4_for = self.cleaned_data['device']
  331. elif self.instance.address.version == 6:
  332. self.instance.primary_ip6_for = self.cleaned_data['device']
  333. return super(IPAddressFromCSVForm, self).save(commit=commit)
  334. class IPAddressImportForm(BulkImportForm, BootstrapMixin):
  335. csv = CSVDataField(csv_form=IPAddressFromCSVForm)
  336. class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  337. pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
  338. vrf = forms.TypedChoiceField(choices=bulkedit_vrf_choices, coerce=int, required=False, label='VRF')
  339. tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
  340. description = forms.CharField(max_length=100, required=False)
  341. def ipaddress_vrf_choices():
  342. vrf_choices = VRF.objects.annotate(ipaddress_count=Count('ip_addresses'))
  343. return [(v.pk, u'{} ({})'.format(v.name, v.ipaddress_count)) for v in vrf_choices]
  344. class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
  345. model = IPAddress
  346. parent = forms.CharField(required=False, label='Search Within', widget=forms.TextInput(attrs={
  347. 'placeholder': 'Prefix',
  348. }))
  349. family = forms.ChoiceField(required=False, choices=IP_FAMILY_CHOICES, label='Address Family')
  350. vrf = forms.MultipleChoiceField(required=False, choices=ipaddress_vrf_choices, label='VRF',
  351. widget=forms.SelectMultiple(attrs={'size': 6}))
  352. tenant = forms.MultipleChoiceField(required=False, choices=tenant_choices, label='Tenant',
  353. widget=forms.SelectMultiple(attrs={'size': 6}))
  354. #
  355. # VLAN groups
  356. #
  357. class VLANGroupForm(forms.ModelForm, BootstrapMixin):
  358. slug = SlugField()
  359. class Meta:
  360. model = VLANGroup
  361. fields = ['site', 'name', 'slug']
  362. def vlangroup_site_choices():
  363. site_choices = Site.objects.annotate(vlangroup_count=Count('vlan_groups'))
  364. return [(s.slug, u'{} ({})'.format(s.name, s.vlangroup_count)) for s in site_choices]
  365. class VLANGroupFilterForm(forms.Form, BootstrapMixin):
  366. site = forms.MultipleChoiceField(required=False, choices=vlangroup_site_choices,
  367. widget=forms.SelectMultiple(attrs={'size': 8}))
  368. #
  369. # VLANs
  370. #
  371. class VLANForm(BootstrapMixin, CustomFieldForm):
  372. group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False, label='Group', widget=APISelect(
  373. api_url='/api/ipam/vlan-groups/?site_id={{site}}',
  374. ))
  375. class Meta:
  376. model = VLAN
  377. fields = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description']
  378. help_texts = {
  379. 'site': "The site at which this VLAN exists",
  380. 'group': "VLAN group (optional)",
  381. 'vid': "Configured VLAN ID",
  382. 'name': "Configured VLAN name",
  383. 'status': "Operational status of this VLAN",
  384. 'role': "The primary function of this VLAN",
  385. }
  386. widgets = {
  387. 'site': forms.Select(attrs={'filter-for': 'group'}),
  388. }
  389. def __init__(self, *args, **kwargs):
  390. super(VLANForm, self).__init__(*args, **kwargs)
  391. # Limit VLAN group choices
  392. if self.is_bound and self.data.get('site'):
  393. self.fields['group'].queryset = VLANGroup.objects.filter(site__pk=self.data['site'])
  394. elif self.initial.get('site'):
  395. self.fields['group'].queryset = VLANGroup.objects.filter(site=self.initial['site'])
  396. else:
  397. self.fields['group'].choices = []
  398. class VLANFromCSVForm(forms.ModelForm):
  399. site = forms.ModelChoiceField(queryset=Site.objects.all(), to_field_name='name',
  400. error_messages={'invalid_choice': 'Device not found.'})
  401. group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False, to_field_name='name',
  402. error_messages={'invalid_choice': 'VLAN group not found.'})
  403. tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
  404. error_messages={'invalid_choice': 'Tenant not found.'})
  405. status_name = forms.ChoiceField(choices=[(s[1], s[0]) for s in VLAN_STATUS_CHOICES])
  406. role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False, to_field_name='name',
  407. error_messages={'invalid_choice': 'Invalid role.'})
  408. class Meta:
  409. model = VLAN
  410. fields = ['site', 'group', 'vid', 'name', 'tenant', 'status_name', 'role', 'description']
  411. def save(self, *args, **kwargs):
  412. m = super(VLANFromCSVForm, self).save(commit=False)
  413. # Assign VLAN status by name
  414. m.status = dict(self.fields['status_name'].choices)[self.cleaned_data['status_name']]
  415. if kwargs.get('commit'):
  416. m.save()
  417. return m
  418. class VLANImportForm(BulkImportForm, BootstrapMixin):
  419. csv = CSVDataField(csv_form=VLANFromCSVForm)
  420. class VLANBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
  421. pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
  422. site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
  423. group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
  424. tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
  425. status = forms.ChoiceField(choices=FORM_VLAN_STATUS_CHOICES, required=False)
  426. role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
  427. description = forms.CharField(max_length=100, required=False)
  428. def vlan_site_choices():
  429. site_choices = Site.objects.annotate(vlan_count=Count('vlans'))
  430. return [(s.slug, u'{} ({})'.format(s.name, s.vlan_count)) for s in site_choices]
  431. def vlan_group_choices():
  432. group_choices = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans'))
  433. return [(g.pk, u'{} ({})'.format(g, g.vlan_count)) for g in group_choices]
  434. def vlan_tenant_choices():
  435. tenant_choices = Tenant.objects.annotate(vrf_count=Count('vlans'))
  436. return [(t.slug, u'{} ({})'.format(t.name, t.vrf_count)) for t in tenant_choices]
  437. def vlan_status_choices():
  438. status_counts = {}
  439. for status in VLAN.objects.values('status').annotate(count=Count('status')).order_by('status'):
  440. status_counts[status['status']] = status['count']
  441. return [(s[0], u'{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in VLAN_STATUS_CHOICES]
  442. def vlan_role_choices():
  443. role_choices = Role.objects.annotate(vlan_count=Count('vlans'))
  444. return [(r.slug, u'{} ({})'.format(r.name, r.vlan_count)) for r in role_choices]
  445. class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
  446. model = VLAN
  447. site = forms.MultipleChoiceField(required=False, choices=vlan_site_choices,
  448. widget=forms.SelectMultiple(attrs={'size': 8}))
  449. group_id = forms.MultipleChoiceField(required=False, choices=vlan_group_choices, label='VLAN Group',
  450. widget=forms.SelectMultiple(attrs={'size': 8}))
  451. tenant = forms.MultipleChoiceField(required=False, choices=vlan_tenant_choices,
  452. widget=forms.SelectMultiple(attrs={'size': 8}))
  453. status = forms.MultipleChoiceField(required=False, choices=vlan_status_choices)
  454. role = forms.MultipleChoiceField(required=False, choices=vlan_role_choices,
  455. widget=forms.SelectMultiple(attrs={'size': 8}))