diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index b829083e3..7eba6375f 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -9,13 +9,16 @@ from ipam.constants import * from ipam.models import * from netbox.forms import NetBoxModelImportForm from tenancy.models import Tenant -from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField, \ - CSVModelMultipleChoiceField +from utilities.forms.fields import ( + CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField, +) + from virtualization.models import VirtualMachine, VMInterface __all__ = ( 'AggregateImportForm', 'ASNImportForm', + 'ASNRangeImportForm', 'FHRPGroupImportForm', 'IPAddressImportForm', 'IPRangeImportForm', @@ -40,10 +43,25 @@ class VRFImportForm(NetBoxModelImportForm): to_field_name='name', help_text=_('Assigned tenant') ) + import_targets = CSVModelMultipleChoiceField( + queryset=RouteTarget.objects.all(), + required=False, + to_field_name='name', + help_text=_('Import route targets') + ) + export_targets = CSVModelMultipleChoiceField( + queryset=RouteTarget.objects.all(), + required=False, + to_field_name='name', + help_text=_('Export route targets') + ) class Meta: model = VRF - fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments', 'tags') + fields = ( + 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'comments', + 'tags', + ) class RouteTargetImportForm(NetBoxModelImportForm): @@ -65,9 +83,6 @@ class RIRImportForm(NetBoxModelImportForm): class Meta: model = RIR fields = ('name', 'slug', 'is_private', 'description', 'tags') - help_texts = { - 'name': _('RIR name'), - } class AggregateImportForm(NetBoxModelImportForm): @@ -88,6 +103,24 @@ class AggregateImportForm(NetBoxModelImportForm): fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments', 'tags') +class ASNRangeImportForm(NetBoxModelImportForm): + 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 = ASNRange + fields = ('name', 'slug', 'rir', 'start', 'end', 'tenant', 'description', 'tags') + + class ASNImportForm(NetBoxModelImportForm): rir = CSVModelChoiceField( queryset=RIR.objects.all(), @@ -166,16 +199,31 @@ class PrefixImportForm(NetBoxModelImportForm): def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) - if data: + if not data: + return - # 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) + site = data.get('site') + vlan_group = data.get('vlan_group') + + # Limit VLAN queryset by assigned site and/or group (if specified) + query = Q() + + if site: + query |= Q(**{ + f"site__{self.fields['site'].to_field_name}": site + }) + # Don't Forget to include VLANs without a site in the filter + query |= Q(**{ + f"site__{self.fields['site'].to_field_name}__isnull": True + }) + + if vlan_group: + query &= Q(**{ + f"group__{self.fields['vlan_group'].to_field_name}": vlan_group + }) + + queryset = self.fields['vlan'].queryset.filter(query) + self.fields['vlan'].queryset = queryset class IPRangeImportForm(NetBoxModelImportForm): @@ -205,7 +253,8 @@ class IPRangeImportForm(NetBoxModelImportForm): class Meta: model = IPRange fields = ( - 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description', 'comments', 'tags', + 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'mark_utilized', 'description', + 'comments', 'tags', ) @@ -391,10 +440,6 @@ class VLANImportForm(NetBoxModelImportForm): class Meta: model = VLAN fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'comments', 'tags') - help_texts = { - 'vid': 'Numeric VLAN ID (1-4094)', - 'name': 'VLAN name', - } class ServiceTemplateImportForm(NetBoxModelImportForm): @@ -435,7 +480,7 @@ class ServiceImportForm(NetBoxModelImportForm): class Meta: model = Service fields = ( - 'device', 'virtual_machine', 'ipaddresses', 'name', 'protocol', 'ports', 'description', 'comments', 'tags') + 'device', 'virtual_machine', 'ipaddresses', 'name', 'protocol', 'ports', 'description', 'comments', 'tags') class L2VPNImportForm(NetBoxModelImportForm): @@ -451,7 +496,8 @@ class L2VPNImportForm(NetBoxModelImportForm): class Meta: model = L2VPN - fields = ('identifier', 'name', 'slug', 'type', 'description', 'comments', 'tags') + fields = ('identifier', 'name', 'slug', 'tenant', 'type', 'description', + 'comments', 'tags') class L2VPNTerminationImportForm(NetBoxModelImportForm): @@ -510,9 +556,11 @@ class L2VPNTerminationImportForm(NetBoxModelImportForm): 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')): + if not self.instance and 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') + # if this is an update we might not have interface or vlan in the form data + if self.cleaned_data.get('interface') or self.cleaned_data.get('vlan'): + self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vlan')