mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
409 lines
17 KiB
Python
409 lines
17 KiB
Python
from netaddr import IPNetwork
|
|
|
|
from django import forms
|
|
from django.db.models import Count
|
|
|
|
from dcim.models import Site, Device, Interface
|
|
from utilities.forms import BootstrapMixin, ConfirmationForm, APISelect, Livesearch, CSVDataField, BulkImportForm
|
|
from .models import VRF, RIR, Aggregate, Prefix, IPAddress, VLAN, Status, Role
|
|
|
|
|
|
#
|
|
# VRFs
|
|
#
|
|
|
|
class VRFForm(forms.ModelForm, BootstrapMixin):
|
|
|
|
class Meta:
|
|
model = VRF
|
|
fields = ['name', 'rd', 'description']
|
|
help_texts = {
|
|
'rd': "Route distinguisher in any format",
|
|
}
|
|
|
|
|
|
class VRFFromCSVForm(forms.ModelForm):
|
|
|
|
class Meta:
|
|
model = VRF
|
|
fields = ['name', 'rd', 'description']
|
|
|
|
|
|
class VRFImportForm(BulkImportForm, BootstrapMixin):
|
|
csv = CSVDataField(csv_form=VRFFromCSVForm)
|
|
|
|
|
|
class VRFBulkEditForm(forms.Form, BootstrapMixin):
|
|
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
|
|
description = forms.CharField(max_length=100, required=False)
|
|
|
|
|
|
class VRFBulkDeleteForm(ConfirmationForm):
|
|
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
|
|
|
#
|
|
# Aggregates
|
|
#
|
|
|
|
class AggregateForm(forms.ModelForm, BootstrapMixin):
|
|
|
|
class Meta:
|
|
model = Aggregate
|
|
fields = ['prefix', 'rir', 'date_added', 'description']
|
|
help_texts = {
|
|
'prefix': "IPv4 or IPv6 network",
|
|
'rir': "Regional Internet Registry responsible for this prefix",
|
|
'date_added': "Format: YYYY-MM-DD",
|
|
}
|
|
|
|
|
|
class AggregateFromCSVForm(forms.ModelForm):
|
|
rir = forms.ModelChoiceField(queryset=RIR.objects.all(), to_field_name='name',
|
|
error_messages={'invalid_choice': 'RIR not found.'})
|
|
|
|
class Meta:
|
|
model = Aggregate
|
|
fields = ['prefix', 'rir', 'date_added', 'description']
|
|
|
|
|
|
class AggregateImportForm(BulkImportForm, BootstrapMixin):
|
|
csv = CSVDataField(csv_form=AggregateFromCSVForm)
|
|
|
|
|
|
class AggregateBulkEditForm(forms.Form, BootstrapMixin):
|
|
pk = forms.ModelMultipleChoiceField(queryset=Aggregate.objects.all(), widget=forms.MultipleHiddenInput)
|
|
rir = forms.ModelChoiceField(queryset=RIR.objects.all(), required=False, label='RIR')
|
|
date_added = forms.DateField(required=False)
|
|
description = forms.CharField(max_length=50, required=False)
|
|
|
|
|
|
class AggregateBulkDeleteForm(ConfirmationForm):
|
|
pk = forms.ModelMultipleChoiceField(queryset=Aggregate.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
|
|
|
def aggregate_rir_choices():
|
|
rir_choices = RIR.objects.annotate(aggregate_count=Count('aggregates'))
|
|
return [(r.slug, '{} ({})'.format(r.name, r.aggregate_count)) for r in rir_choices]
|
|
|
|
|
|
class AggregateFilterForm(forms.Form, BootstrapMixin):
|
|
rir = forms.MultipleChoiceField(required=False, choices=aggregate_rir_choices, label='RIR',
|
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
|
|
|
|
#
|
|
# Prefixes
|
|
#
|
|
|
|
class PrefixForm(forms.ModelForm, BootstrapMixin):
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
|
|
widget=forms.Select(attrs={'filter-for': 'vlan'}))
|
|
vlan = forms.ModelChoiceField(queryset=VLAN.objects.all(), required=False, label='VLAN',
|
|
widget=APISelect(api_url='/api/ipam/vlans/?site_id={{site}}'))
|
|
|
|
class Meta:
|
|
model = Prefix
|
|
fields = ['prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'description']
|
|
help_texts = {
|
|
'prefix': "IPv4 or IPv6 network",
|
|
'vrf': "VRF (if applicable)",
|
|
'site': "The site to which this prefix is assigned (if applicable)",
|
|
'vlan': "The VLAN to which this prefix is assigned (if applicable)",
|
|
'status': "Operational status of this prefix",
|
|
'role': "The primary function of this prefix",
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(PrefixForm, self).__init__(*args, **kwargs)
|
|
|
|
self.fields['vrf'].empty_label = 'Global'
|
|
|
|
# Initialize field without choices to avoid pulling all VLANs from the database
|
|
if self.is_bound and self.data.get('site'):
|
|
self.fields['vlan'].queryset = VLAN.objects.filter(site__pk=self.data['site'])
|
|
elif self.initial.get('site'):
|
|
self.fields['vlan'].queryset = VLAN.objects.filter(site=self.initial['site'])
|
|
else:
|
|
self.fields['vlan'].choices = []
|
|
|
|
def clean_prefix(self):
|
|
data = self.cleaned_data['prefix']
|
|
try:
|
|
prefix = IPNetwork(data)
|
|
except:
|
|
raise
|
|
if prefix.version == 4 and prefix.prefixlen == 32:
|
|
raise forms.ValidationError("Cannot create host addresses (/32) as prefixes. These should be IPv4 "
|
|
"addresses instead.")
|
|
elif prefix.version == 6 and prefix.prefixlen == 128:
|
|
raise forms.ValidationError("Cannot create host addresses (/128) as prefixes. These should be IPv6 "
|
|
"addresses instead.")
|
|
return data
|
|
|
|
|
|
class PrefixFromCSVForm(forms.ModelForm):
|
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
|
error_messages={'invalid_choice': 'VRF not found.'})
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, to_field_name='name',
|
|
error_messages={'invalid_choice': 'Site not found.'})
|
|
status = forms.ModelChoiceField(queryset=Status.objects.all(), to_field_name='name',
|
|
error_messages={'invalid_choice': 'Invalid status.'})
|
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False, to_field_name='name',
|
|
error_messages={'invalid_choice': 'Invalid role.'})
|
|
|
|
class Meta:
|
|
model = Prefix
|
|
fields = ['prefix', 'vrf', 'site', 'status', 'role', 'description']
|
|
|
|
|
|
class PrefixImportForm(BulkImportForm, BootstrapMixin):
|
|
csv = CSVDataField(csv_form=PrefixFromCSVForm)
|
|
|
|
|
|
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')
|
|
status = forms.ModelChoiceField(queryset=Status.objects.all(), required=False)
|
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
|
description = forms.CharField(max_length=50, required=False)
|
|
|
|
|
|
class PrefixBulkDeleteForm(ConfirmationForm):
|
|
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
|
|
|
def prefix_vrf_choices():
|
|
vrf_choices = [('', 'All'), (0, 'Global')]
|
|
vrf_choices += [(v.pk, v.name) for v in VRF.objects.all()]
|
|
return vrf_choices
|
|
|
|
|
|
def prefix_status_choices():
|
|
status_choices = Status.objects.annotate(prefix_count=Count('prefixes'))
|
|
return [(s.slug, '{} ({})'.format(s.name, s.prefix_count)) for s in status_choices]
|
|
|
|
|
|
def prefix_site_choices():
|
|
site_choices = Site.objects.annotate(prefix_count=Count('prefixes'))
|
|
return [(s.slug, '{} ({})'.format(s.name, s.prefix_count)) for s in site_choices]
|
|
|
|
|
|
def prefix_role_choices():
|
|
role_choices = Role.objects.annotate(prefix_count=Count('prefixes'))
|
|
return [(r.slug, '{} ({})'.format(r.name, r.prefix_count)) for r in 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)
|
|
site = forms.MultipleChoiceField(required=False, choices=prefix_site_choices,
|
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
role = forms.MultipleChoiceField(required=False, choices=prefix_role_choices,
|
|
widget=forms.SelectMultiple(attrs={'size': 8}))
|
|
expand = forms.BooleanField(required=False, label='Expand prefix hierarchy')
|
|
|
|
|
|
#
|
|
# IP addresses
|
|
#
|
|
|
|
class IPAddressForm(forms.ModelForm, BootstrapMixin):
|
|
nat_site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
|
|
widget=forms.Select(attrs={'filter-for': 'nat_device'}))
|
|
nat_device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
|
|
widget=APISelect(api_url='/api/dcim/devices/?site_id={{nat_site}}',
|
|
attrs={'filter-for': 'nat_inside'}))
|
|
livesearch = forms.CharField(required=False, label='IP Address', widget=Livesearch(
|
|
query_key='q', query_url='ipam-api:ipaddress_list', field_to_update='nat_inside', obj_label='address')
|
|
)
|
|
nat_inside = forms.ModelChoiceField(queryset=IPAddress.objects.all(), required=False, label='NAT (Inside)',
|
|
widget=APISelect(api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}',
|
|
display_field='address'))
|
|
|
|
class Meta:
|
|
model = IPAddress
|
|
fields = ['address', 'vrf', 'nat_device', 'nat_inside', 'description']
|
|
help_texts = {
|
|
'address': "IPv4 or IPv6 address and mask",
|
|
'vrf': "VRF (if applicable)",
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(IPAddressForm, self).__init__(*args, **kwargs)
|
|
|
|
self.fields['vrf'].empty_label = 'Global'
|
|
|
|
if self.instance.nat_inside:
|
|
|
|
nat_inside = self.instance.nat_inside
|
|
# If the IP is assigned to an interface, populate site/device fields accordingly
|
|
if self.instance.nat_inside.interface:
|
|
self.initial['nat_site'] = self.instance.nat_inside.interface.device.rack.site.pk
|
|
self.initial['nat_device'] = self.instance.nat_inside.interface.device.pk
|
|
self.fields['nat_device'].queryset = Device.objects.filter(rack__site=nat_inside.interface.device.rack.site)
|
|
self.fields['nat_inside'].queryset = IPAddress.objects.filter(interface__device=nat_inside.interface.device)
|
|
else:
|
|
self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk)
|
|
|
|
else:
|
|
|
|
# Initialize nat_device choices if nat_site is set
|
|
if self.is_bound and self.data.get('nat_site'):
|
|
self.fields['nat_device'].queryset = Device.objects.filter(rack__site__pk=self.data['nat_site'])
|
|
elif self.initial.get('nat_site'):
|
|
self.fields['nat_device'].queryset = Device.objects.filter(rack__site=self.initial['nat_site'])
|
|
else:
|
|
self.fields['nat_device'].choices = []
|
|
|
|
# Initialize nat_inside choices if nat_device is set
|
|
if self.is_bound and self.data.get('nat_device'):
|
|
self.fields['nat_inside'].queryset = IPAddress.objects.filter(interface__device__pk=self.data['nat_device'])
|
|
elif self.initial.get('nat_device'):
|
|
self.fields['nat_inside'].queryset = IPAddress.objects.filter(interface__device__pk=self.initial['nat_device'])
|
|
else:
|
|
self.fields['nat_inside'].choices = []
|
|
|
|
|
|
class IPAddressFromCSVForm(forms.ModelForm):
|
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
|
error_messages={'invalid_choice': 'Site 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)
|
|
is_primary = forms.BooleanField(required=False)
|
|
|
|
class Meta:
|
|
model = IPAddress
|
|
fields = ['address', 'vrf', 'device', 'interface_name', 'is_primary', 'description']
|
|
|
|
def clean(self):
|
|
|
|
device = self.cleaned_data.get('device')
|
|
interface_name = self.cleaned_data.get('interface_name')
|
|
is_primary = self.cleaned_data.get('is_primary')
|
|
|
|
# Validate interface
|
|
if device and interface_name:
|
|
try:
|
|
Interface.objects.get(device=device, name=interface_name)
|
|
except Interface.DoesNotExist:
|
|
self.add_error('interface_name', "Invalid interface ({}) for {}".format(interface_name, device))
|
|
elif device and not interface_name:
|
|
self.add_error('interface_name', "Device set ({}) but interface missing".format(device))
|
|
elif interface_name and not device:
|
|
self.add_error('device', "Interface set ({}) but device missing or invalid".format(interface_name))
|
|
|
|
# Validate is_primary
|
|
if is_primary and not device:
|
|
self.add_error('is_primary', "No device specified; cannot set as primary IP")
|
|
|
|
def save(self, commit=True):
|
|
|
|
# Set interface
|
|
if self.cleaned_data['device'] and self.cleaned_data['interface_name']:
|
|
self.instance.interface = Interface.objects.get(device=self.cleaned_data['device'],
|
|
name=self.cleaned_data['interface_name'])
|
|
# Set as primary for device
|
|
if self.cleaned_data['is_primary']:
|
|
self.instance.primary_for = self.cleaned_data['device']
|
|
|
|
return super(IPAddressFromCSVForm, self).save(commit=commit)
|
|
|
|
|
|
class IPAddressImportForm(BulkImportForm, BootstrapMixin):
|
|
csv = CSVDataField(csv_form=IPAddressFromCSVForm)
|
|
|
|
|
|
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')
|
|
description = forms.CharField(max_length=50, required=False)
|
|
|
|
|
|
class IPAddressBulkDeleteForm(ConfirmationForm):
|
|
pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
|
|
|
def ipaddress_family_choices():
|
|
return [('', 'All'), (4, 'IPv4'), (6, 'IPv6')]
|
|
|
|
|
|
class IPAddressFilterForm(forms.Form, BootstrapMixin):
|
|
family = forms.ChoiceField(required=False, choices=ipaddress_family_choices, label='Address Family')
|
|
|
|
|
|
#
|
|
# VLANs
|
|
#
|
|
|
|
class VLANForm(forms.ModelForm, BootstrapMixin):
|
|
|
|
class Meta:
|
|
model = VLAN
|
|
fields = ['site', 'vid', 'name', 'status', 'role']
|
|
help_texts = {
|
|
'site': "The site at which this VLAN exists",
|
|
'vid': "Configured VLAN ID",
|
|
'name': "Configured VLAN name",
|
|
'status': "Operational status of this VLAN",
|
|
'role': "The primary function of this VLAN",
|
|
}
|
|
|
|
|
|
class VLANFromCSVForm(forms.ModelForm):
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), to_field_name='name',
|
|
error_messages={'invalid_choice': 'Device not found.'})
|
|
status = forms.ModelChoiceField(queryset=Status.objects.all(), to_field_name='name',
|
|
error_messages={'invalid_choice': 'Invalid status.'})
|
|
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', 'vid', 'name', 'status', 'role']
|
|
|
|
|
|
class VLANImportForm(BulkImportForm, BootstrapMixin):
|
|
csv = CSVDataField(csv_form=VLANFromCSVForm)
|
|
|
|
|
|
class VLANBulkEditForm(forms.Form, BootstrapMixin):
|
|
pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
|
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
|
status = forms.ModelChoiceField(queryset=Status.objects.all(), required=False)
|
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
|
|
|
|
|
class VLANBulkDeleteForm(ConfirmationForm):
|
|
pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
|
|
|
|
|
|
def vlan_site_choices():
|
|
site_choices = Site.objects.annotate(vlan_count=Count('vlans'))
|
|
return [(s.slug, '{} ({})'.format(s.name, s.vlan_count)) for s in site_choices]
|
|
|
|
|
|
def vlan_status_choices():
|
|
status_choices = Status.objects.annotate(vlan_count=Count('vlans'))
|
|
return [(s.slug, '{} ({})'.format(s.name, s.vlan_count)) for s in status_choices]
|
|
|
|
|
|
def vlan_role_choices():
|
|
role_choices = Role.objects.annotate(vlan_count=Count('vlans'))
|
|
return [(r.slug, '{} ({})'.format(r.name, r.vlan_count)) for r in role_choices]
|
|
|
|
|
|
class VLANFilterForm(forms.Form, BootstrapMixin):
|
|
site = forms.MultipleChoiceField(required=False, choices=vlan_site_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}))
|