from django import forms from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from dcim.models import Device, Interface, Site from ipam.choices import * from ipam.constants import * from ipam.models import * from netbox.forms import NetBoxModelCSVForm from tenancy.models import Tenant from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField from virtualization.models import VirtualMachine, VMInterface __all__ = ( 'AggregateCSVForm', 'ASNCSVForm', 'FHRPGroupCSVForm', 'IPAddressCSVForm', 'IPRangeCSVForm', 'L2VPNCSVForm', 'L2VPNTerminationCSVForm', 'PrefixCSVForm', 'RIRCSVForm', 'RoleCSVForm', 'RouteTargetCSVForm', 'ServiceCSVForm', 'ServiceTemplateCSVForm', 'VLANCSVForm', 'VLANGroupCSVForm', 'VRFCSVForm', ) class VRFCSVForm(NetBoxModelCSVForm): tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', help_text='Assigned tenant' ) class Meta: model = VRF fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description') class RouteTargetCSVForm(NetBoxModelCSVForm): tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', help_text='Assigned tenant' ) class Meta: model = RouteTarget fields = ('name', 'description', 'tenant') class RIRCSVForm(NetBoxModelCSVForm): slug = SlugField() class Meta: model = RIR fields = ('name', 'slug', 'is_private', 'description') help_texts = { 'name': 'RIR name', } class AggregateCSVForm(NetBoxModelCSVForm): rir = CSVModelChoiceField( queryset=RIR.objects.all(), to_field_name='name', help_text='Assigned RIR' ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', help_text='Assigned tenant' ) class Meta: model = Aggregate fields = ('prefix', 'rir', 'tenant', 'date_added', 'description') class ASNCSVForm(NetBoxModelCSVForm): rir = CSVModelChoiceField( queryset=RIR.objects.all(), to_field_name='name', help_text='Assigned RIR' ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', help_text='Assigned tenant' ) class Meta: model = ASN fields = ('asn', 'rir', 'tenant', 'description') help_texts = {} class RoleCSVForm(NetBoxModelCSVForm): slug = SlugField() class Meta: model = Role fields = ('name', 'slug', 'weight', 'description') class PrefixCSVForm(NetBoxModelCSVForm): vrf = CSVModelChoiceField( queryset=VRF.objects.all(), to_field_name='name', required=False, help_text='Assigned VRF' ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', help_text='Assigned tenant' ) site = CSVModelChoiceField( queryset=Site.objects.all(), required=False, to_field_name='name', help_text='Assigned site' ) vlan_group = CSVModelChoiceField( queryset=VLANGroup.objects.all(), required=False, to_field_name='name', help_text="VLAN's group (if any)" ) vlan = CSVModelChoiceField( queryset=VLAN.objects.all(), required=False, to_field_name='vid', help_text="Assigned VLAN" ) status = CSVChoiceField( choices=PrefixStatusChoices, help_text='Operational status' ) role = CSVModelChoiceField( queryset=Role.objects.all(), required=False, to_field_name='name', help_text='Functional role' ) class Meta: model = Prefix fields = ( 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description', ) def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) if data: # Limit VLAN queryset by assigned site and/or group (if specified) params = {} if data.get('site'): params[f"site__{self.fields['site'].to_field_name}"] = data.get('site') if data.get('vlan_group'): params[f"group__{self.fields['vlan_group'].to_field_name}"] = data.get('vlan_group') if params: self.fields['vlan'].queryset = self.fields['vlan'].queryset.filter(**params) class IPRangeCSVForm(NetBoxModelCSVForm): vrf = CSVModelChoiceField( queryset=VRF.objects.all(), to_field_name='name', required=False, help_text='Assigned VRF' ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', help_text='Assigned tenant' ) status = CSVChoiceField( choices=IPRangeStatusChoices, help_text='Operational status' ) role = CSVModelChoiceField( queryset=Role.objects.all(), required=False, to_field_name='name', help_text='Functional role' ) class Meta: model = IPRange fields = ( 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description', ) class IPAddressCSVForm(NetBoxModelCSVForm): vrf = CSVModelChoiceField( queryset=VRF.objects.all(), to_field_name='name', required=False, help_text='Assigned VRF' ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), to_field_name='name', required=False, help_text='Assigned tenant' ) status = CSVChoiceField( choices=IPAddressStatusChoices, help_text='Operational status' ) role = CSVChoiceField( choices=IPAddressRoleChoices, required=False, help_text='Functional role' ) device = CSVModelChoiceField( queryset=Device.objects.all(), required=False, to_field_name='name', help_text='Parent device of assigned interface (if any)' ) virtual_machine = CSVModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, to_field_name='name', help_text='Parent VM of assigned interface (if any)' ) interface = CSVModelChoiceField( queryset=Interface.objects.none(), # Can also refer to VMInterface required=False, to_field_name='name', help_text='Assigned interface' ) is_primary = forms.BooleanField( help_text='Make this the primary IP for the assigned device', required=False ) class Meta: model = IPAddress fields = [ 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary', 'dns_name', 'description', ] def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) if data: # Limit interface queryset by assigned device if data.get('device'): self.fields['interface'].queryset = Interface.objects.filter( **{f"device__{self.fields['device'].to_field_name}": data['device']} ) # Limit interface queryset by assigned device elif data.get('virtual_machine'): self.fields['interface'].queryset = VMInterface.objects.filter( **{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']} ) def clean(self): super().clean() device = self.cleaned_data.get('device') virtual_machine = self.cleaned_data.get('virtual_machine') interface = self.cleaned_data.get('interface') is_primary = self.cleaned_data.get('is_primary') # Validate is_primary if is_primary and not device and not virtual_machine: raise forms.ValidationError({ "is_primary": "No device or virtual machine specified; cannot set as primary IP" }) if is_primary and not interface: raise forms.ValidationError({ "is_primary": "No interface specified; cannot set as primary IP" }) def save(self, *args, **kwargs): # Set interface assignment if self.cleaned_data['interface']: self.instance.assigned_object = self.cleaned_data['interface'] ipaddress = super().save(*args, **kwargs) # Set as primary for device/VM if self.cleaned_data['is_primary']: parent = self.cleaned_data['device'] or self.cleaned_data['virtual_machine'] if self.instance.address.version == 4: parent.primary_ip4 = ipaddress elif self.instance.address.version == 6: parent.primary_ip6 = ipaddress parent.save() return ipaddress class FHRPGroupCSVForm(NetBoxModelCSVForm): protocol = CSVChoiceField( choices=FHRPGroupProtocolChoices ) auth_type = CSVChoiceField( choices=FHRPGroupAuthTypeChoices, required=False ) class Meta: model = FHRPGroup fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'description') class VLANGroupCSVForm(NetBoxModelCSVForm): slug = SlugField() scope_type = CSVContentTypeField( queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES), required=False, label='Scope type (app & model)' ) min_vid = forms.IntegerField( min_value=VLAN_VID_MIN, max_value=VLAN_VID_MAX, required=False, label=f'Minimum child VLAN VID (default: {VLAN_VID_MIN})' ) max_vid = forms.IntegerField( min_value=VLAN_VID_MIN, max_value=VLAN_VID_MAX, required=False, label=f'Maximum child VLAN VID (default: {VLAN_VID_MIN})' ) class Meta: model = VLANGroup fields = ('name', 'slug', 'scope_type', 'scope_id', 'min_vid', 'max_vid', 'description') labels = { 'scope_id': 'Scope ID', } class VLANCSVForm(NetBoxModelCSVForm): site = CSVModelChoiceField( queryset=Site.objects.all(), required=False, to_field_name='name', help_text='Assigned site' ) group = CSVModelChoiceField( queryset=VLANGroup.objects.all(), required=False, to_field_name='name', help_text='Assigned VLAN group' ) tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), to_field_name='name', required=False, help_text='Assigned tenant' ) status = CSVChoiceField( choices=VLANStatusChoices, help_text='Operational status' ) role = CSVModelChoiceField( queryset=Role.objects.all(), required=False, to_field_name='name', help_text='Functional role' ) class Meta: model = VLAN fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description') help_texts = { 'vid': 'Numeric VLAN ID (1-4094)', 'name': 'VLAN name', } class ServiceTemplateCSVForm(NetBoxModelCSVForm): protocol = CSVChoiceField( choices=ServiceProtocolChoices, help_text='IP protocol' ) class Meta: model = ServiceTemplate fields = ('name', 'protocol', 'ports', 'description') class ServiceCSVForm(NetBoxModelCSVForm): device = CSVModelChoiceField( queryset=Device.objects.all(), required=False, to_field_name='name', help_text='Required if not assigned to a VM' ) virtual_machine = CSVModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, to_field_name='name', help_text='Required if not assigned to a device' ) protocol = CSVChoiceField( choices=ServiceProtocolChoices, help_text='IP protocol' ) class Meta: model = Service fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description') class L2VPNCSVForm(NetBoxModelCSVForm): tenant = CSVModelChoiceField( queryset=Tenant.objects.all(), required=False, to_field_name='name', ) type = CSVChoiceField( choices=L2VPNTypeChoices, help_text='L2VPN type' ) class Meta: model = L2VPN fields = ('identifier', 'name', 'slug', 'type', 'description') class L2VPNTerminationCSVForm(NetBoxModelCSVForm): l2vpn = CSVModelChoiceField( queryset=L2VPN.objects.all(), required=True, to_field_name='name', label='L2VPN', ) device = CSVModelChoiceField( queryset=Device.objects.all(), required=False, to_field_name='name', help_text='Parent device (for interface)' ) virtual_machine = CSVModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, to_field_name='name', help_text='Parent virtual machine (for interface)' ) interface = CSVModelChoiceField( queryset=Interface.objects.none(), # Can also refer to VMInterface required=False, to_field_name='name', help_text='Assigned interface (device or VM)' ) vlan = CSVModelChoiceField( queryset=VLAN.objects.all(), required=False, to_field_name='name', help_text='Assigned VLAN' ) class Meta: model = L2VPNTermination fields = ('l2vpn', 'device', 'virtual_machine', 'interface', 'vlan') def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) if data: # Limit interface queryset by device or VM if data.get('device'): self.fields['interface'].queryset = Interface.objects.filter( **{f"device__{self.fields['device'].to_field_name}": data['device']} ) elif data.get('virtual_machine'): self.fields['interface'].queryset = VMInterface.objects.filter( **{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']} ) def clean(self): super().clean() if self.cleaned_data.get('device') and self.cleaned_data.get('virtual_machine'): raise ValidationError('Cannot import device and VM interface terminations simultaneously.') if not (self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')): raise ValidationError('Each termination must specify either an interface or a VLAN.') if self.cleaned_data.get('interface') and self.cleaned_data.get('vlan'): raise ValidationError('Cannot assign both an interface and a VLAN.') self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')