|
|
@@ -4,6 +4,8 @@ from django import forms
|
|
|
from django.db.models import Count
|
|
|
|
|
|
from dcim.models import Site, Device, Interface
|
|
|
+from tenancy.forms import bulkedit_tenant_choices
|
|
|
+from tenancy.models import Tenant
|
|
|
from utilities.forms import BootstrapMixin, APISelect, Livesearch, CSVDataField, BulkImportForm, SlugField
|
|
|
|
|
|
from .models import (
|
|
|
@@ -15,6 +17,18 @@ FORM_PREFIX_STATUS_CHOICES = (('', '---------'),) + PREFIX_STATUS_CHOICES
|
|
|
FORM_VLAN_STATUS_CHOICES = (('', '---------'),) + VLAN_STATUS_CHOICES
|
|
|
|
|
|
|
|
|
+def bulkedit_vrf_choices():
|
|
|
+ """
|
|
|
+ Include an option to assign the object to the global table.
|
|
|
+ """
|
|
|
+ choices = [
|
|
|
+ (None, '---------'),
|
|
|
+ (0, 'Global'),
|
|
|
+ ]
|
|
|
+ choices += [(v.pk, v.name) for v in VRF.objects.all()]
|
|
|
+ return choices
|
|
|
+
|
|
|
+
|
|
|
#
|
|
|
# VRFs
|
|
|
#
|
|
|
@@ -23,7 +37,7 @@ class VRFForm(forms.ModelForm, BootstrapMixin):
|
|
|
|
|
|
class Meta:
|
|
|
model = VRF
|
|
|
- fields = ['name', 'rd', 'enforce_unique', 'description']
|
|
|
+ fields = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
|
|
|
labels = {
|
|
|
'rd': "RD",
|
|
|
}
|
|
|
@@ -33,10 +47,12 @@ class VRFForm(forms.ModelForm, BootstrapMixin):
|
|
|
|
|
|
|
|
|
class VRFFromCSVForm(forms.ModelForm):
|
|
|
+ tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
|
|
+ error_messages={'invalid_choice': 'Tenant not found.'})
|
|
|
|
|
|
class Meta:
|
|
|
model = VRF
|
|
|
- fields = ['name', 'rd', 'enforce_unique', 'description']
|
|
|
+ fields = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
|
|
|
|
|
|
|
|
|
class VRFImportForm(BulkImportForm, BootstrapMixin):
|
|
|
@@ -45,9 +61,20 @@ class VRFImportForm(BulkImportForm, BootstrapMixin):
|
|
|
|
|
|
class VRFBulkEditForm(forms.Form, BootstrapMixin):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
+ tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
|
|
description = forms.CharField(max_length=100, required=False)
|
|
|
|
|
|
|
|
|
+def vrf_tenant_choices():
|
|
|
+ tenant_choices = Tenant.objects.annotate(vrf_count=Count('vrfs'))
|
|
|
+ return [(t.slug, u'{} ({})'.format(t.name, t.vrf_count)) for t in tenant_choices]
|
|
|
+
|
|
|
+
|
|
|
+class VRFFilterForm(forms.Form, BootstrapMixin):
|
|
|
+ tenant = forms.MultipleChoiceField(required=False, choices=vrf_tenant_choices,
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
|
+
|
|
|
+
|
|
|
#
|
|
|
# RIRs
|
|
|
#
|
|
|
@@ -131,7 +158,7 @@ class PrefixForm(forms.ModelForm, BootstrapMixin):
|
|
|
|
|
|
class Meta:
|
|
|
model = Prefix
|
|
|
- fields = ['prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'description']
|
|
|
+ fields = ['prefix', 'vrf', 'tenant', 'site', 'vlan', 'status', 'role', 'description']
|
|
|
help_texts = {
|
|
|
'prefix': "IPv4 or IPv6 network",
|
|
|
'vrf': "VRF (if applicable)",
|
|
|
@@ -172,6 +199,8 @@ class PrefixForm(forms.ModelForm, BootstrapMixin):
|
|
|
class PrefixFromCSVForm(forms.ModelForm):
|
|
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
|
|
error_messages={'invalid_choice': 'VRF not found.'})
|
|
|
+ tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
|
|
+ error_messages={'invalid_choice': 'Tenant not found.'})
|
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, to_field_name='name',
|
|
|
error_messages={'invalid_choice': 'Site not found.'})
|
|
|
vlan_group_name = forms.CharField(required=False)
|
|
|
@@ -182,7 +211,8 @@ class PrefixFromCSVForm(forms.ModelForm):
|
|
|
|
|
|
class Meta:
|
|
|
model = Prefix
|
|
|
- fields = ['prefix', 'vrf', 'site', 'vlan_group_name', 'vlan_vid', 'status_name', 'role', 'description']
|
|
|
+ fields = ['prefix', 'vrf', 'tenant', 'site', 'vlan_group_name', 'vlan_vid', 'status_name', 'role',
|
|
|
+ 'description']
|
|
|
|
|
|
def clean(self):
|
|
|
|
|
|
@@ -228,18 +258,16 @@ class PrefixImportForm(BulkImportForm, BootstrapMixin):
|
|
|
class PrefixBulkEditForm(forms.Form, BootstrapMixin):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
|
|
- vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF',
|
|
|
- help_text="Select the VRF to assign, or check below to remove VRF assignment")
|
|
|
- vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
|
|
+ vrf = forms.TypedChoiceField(choices=bulkedit_vrf_choices, coerce=int, required=False, label='VRF')
|
|
|
+ tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
|
|
status = forms.ChoiceField(choices=FORM_PREFIX_STATUS_CHOICES, required=False)
|
|
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
|
|
description = forms.CharField(max_length=100, required=False)
|
|
|
|
|
|
|
|
|
def prefix_vrf_choices():
|
|
|
- vrf_choices = [('', 'All'), (0, 'Global')]
|
|
|
- vrf_choices += [(v.pk, v.name) for v in VRF.objects.all()]
|
|
|
- return vrf_choices
|
|
|
+ vrf_choices = VRF.objects.annotate(prefix_count=Count('prefixes'))
|
|
|
+ return [(v.pk, u'{} ({})'.format(v.name, v.prefix_count)) for v in vrf_choices]
|
|
|
|
|
|
|
|
|
def prefix_site_choices():
|
|
|
@@ -261,12 +289,16 @@ def prefix_role_choices():
|
|
|
|
|
|
class PrefixFilterForm(forms.Form, BootstrapMixin):
|
|
|
parent = forms.CharField(required=False, label='Search Within')
|
|
|
- vrf = forms.ChoiceField(required=False, choices=prefix_vrf_choices, label='VRF')
|
|
|
- status = forms.MultipleChoiceField(required=False, choices=prefix_status_choices)
|
|
|
+ vrf = forms.MultipleChoiceField(required=False, choices=prefix_vrf_choices, label='VRF',
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 6}))
|
|
|
+ tenant = forms.ModelMultipleChoiceField(queryset=Tenant.objects.all(), required=False,
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 6}))
|
|
|
+ status = forms.MultipleChoiceField(required=False, choices=prefix_status_choices,
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 6}))
|
|
|
site = forms.MultipleChoiceField(required=False, choices=prefix_site_choices,
|
|
|
- widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 6}))
|
|
|
role = forms.MultipleChoiceField(required=False, choices=prefix_role_choices,
|
|
|
- widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 6}))
|
|
|
expand = forms.BooleanField(required=False, label='Expand prefix hierarchy')
|
|
|
|
|
|
|
|
|
@@ -289,7 +321,7 @@ class IPAddressForm(forms.ModelForm, BootstrapMixin):
|
|
|
|
|
|
class Meta:
|
|
|
model = IPAddress
|
|
|
- fields = ['address', 'vrf', 'nat_device', 'nat_inside', 'description']
|
|
|
+ fields = ['address', 'vrf', 'tenant', 'nat_device', 'nat_inside', 'description']
|
|
|
help_texts = {
|
|
|
'address': "IPv4 or IPv6 address and mask",
|
|
|
'vrf': "VRF (if applicable)",
|
|
|
@@ -338,6 +370,8 @@ class IPAddressForm(forms.ModelForm, BootstrapMixin):
|
|
|
class IPAddressFromCSVForm(forms.ModelForm):
|
|
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
|
|
error_messages={'invalid_choice': 'VRF not found.'})
|
|
|
+ tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
|
|
+ error_messages={'invalid_choice': 'Tenant not found.'})
|
|
|
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name',
|
|
|
error_messages={'invalid_choice': 'Device not found.'})
|
|
|
interface_name = forms.CharField(required=False)
|
|
|
@@ -345,7 +379,7 @@ class IPAddressFromCSVForm(forms.ModelForm):
|
|
|
|
|
|
class Meta:
|
|
|
model = IPAddress
|
|
|
- fields = ['address', 'vrf', 'device', 'interface_name', 'is_primary', 'description']
|
|
|
+ fields = ['address', 'vrf', 'tenant', 'device', 'interface_name', 'is_primary', 'description']
|
|
|
|
|
|
def clean(self):
|
|
|
|
|
|
@@ -390,9 +424,8 @@ class IPAddressImportForm(BulkImportForm, BootstrapMixin):
|
|
|
|
|
|
class IPAddressBulkEditForm(forms.Form, BootstrapMixin):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
- vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF',
|
|
|
- help_text="Select the VRF to assign, or check below to remove VRF assignment")
|
|
|
- vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
|
|
+ vrf = forms.TypedChoiceField(choices=bulkedit_vrf_choices, coerce=int, required=False, label='VRF')
|
|
|
+ tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
|
|
description = forms.CharField(max_length=100, required=False)
|
|
|
|
|
|
|
|
|
@@ -401,14 +434,16 @@ def ipaddress_family_choices():
|
|
|
|
|
|
|
|
|
def ipaddress_vrf_choices():
|
|
|
- vrf_choices = [('', 'All'), (0, 'Global')]
|
|
|
- vrf_choices += [(v.pk, v.name) for v in VRF.objects.all()]
|
|
|
- return vrf_choices
|
|
|
+ vrf_choices = VRF.objects.annotate(ipaddress_count=Count('ip_addresses'))
|
|
|
+ return [(v.pk, u'{} ({})'.format(v.name, v.ipaddress_count)) for v in vrf_choices]
|
|
|
|
|
|
|
|
|
class IPAddressFilterForm(forms.Form, BootstrapMixin):
|
|
|
family = forms.ChoiceField(required=False, choices=ipaddress_family_choices, label='Address Family')
|
|
|
- vrf = forms.ChoiceField(required=False, choices=ipaddress_vrf_choices, label='VRF')
|
|
|
+ vrf = forms.MultipleChoiceField(required=False, choices=ipaddress_vrf_choices, label='VRF',
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 6}))
|
|
|
+ tenant = forms.ModelMultipleChoiceField(queryset=Tenant.objects.all(), required=False,
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 6}))
|
|
|
|
|
|
|
|
|
#
|
|
|
@@ -444,7 +479,7 @@ class VLANForm(forms.ModelForm, BootstrapMixin):
|
|
|
|
|
|
class Meta:
|
|
|
model = VLAN
|
|
|
- fields = ['site', 'group', 'vid', 'name', 'description', 'status', 'role']
|
|
|
+ fields = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description']
|
|
|
help_texts = {
|
|
|
'site': "The site at which this VLAN exists",
|
|
|
'group': "VLAN group (optional)",
|
|
|
@@ -475,13 +510,15 @@ class VLANFromCSVForm(forms.ModelForm):
|
|
|
error_messages={'invalid_choice': 'Device not found.'})
|
|
|
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False, to_field_name='name',
|
|
|
error_messages={'invalid_choice': 'VLAN group not found.'})
|
|
|
+ tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
|
|
+ error_messages={'invalid_choice': 'Tenant not found.'})
|
|
|
status_name = forms.ChoiceField(choices=[(s[1], s[0]) for s in VLAN_STATUS_CHOICES])
|
|
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False, to_field_name='name',
|
|
|
error_messages={'invalid_choice': 'Invalid role.'})
|
|
|
|
|
|
class Meta:
|
|
|
model = VLAN
|
|
|
- fields = ['site', 'group', 'vid', 'name', 'status_name', 'role', 'description']
|
|
|
+ fields = ['site', 'group', 'vid', 'name', 'tenant', 'status_name', 'role', 'description']
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
m = super(VLANFromCSVForm, self).save(commit=False)
|
|
|
@@ -500,6 +537,7 @@ class VLANBulkEditForm(forms.Form, BootstrapMixin):
|
|
|
pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
|
|
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
|
|
|
+ tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
|
|
status = forms.ChoiceField(choices=FORM_VLAN_STATUS_CHOICES, required=False)
|
|
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
|
|
description = forms.CharField(max_length=100, required=False)
|
|
|
@@ -515,6 +553,11 @@ def vlan_group_choices():
|
|
|
return [(g.pk, u'{} ({})'.format(g, g.vlan_count)) for g in group_choices]
|
|
|
|
|
|
|
|
|
+def vlan_tenant_choices():
|
|
|
+ tenant_choices = Tenant.objects.annotate(vrf_count=Count('vlans'))
|
|
|
+ return [(t.slug, u'{} ({})'.format(t.name, t.vrf_count)) for t in tenant_choices]
|
|
|
+
|
|
|
+
|
|
|
def vlan_status_choices():
|
|
|
status_counts = {}
|
|
|
for status in VLAN.objects.values('status').annotate(count=Count('status')).order_by('status'):
|
|
|
@@ -532,6 +575,8 @@ class VLANFilterForm(forms.Form, BootstrapMixin):
|
|
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
|
group_id = forms.MultipleChoiceField(required=False, choices=vlan_group_choices, label='VLAN Group',
|
|
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
|
+ tenant = forms.MultipleChoiceField(required=False, choices=vlan_tenant_choices,
|
|
|
+ widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
|
status = forms.MultipleChoiceField(required=False, choices=vlan_status_choices)
|
|
|
role = forms.MultipleChoiceField(required=False, choices=vlan_role_choices,
|
|
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|