diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 3bc8124ea..896ca1d87 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -9,8 +9,8 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi from tenancy.forms import TenancyForm from tenancy.models import Tenant from utilities.forms import ( - APISelect, BootstrapMixin, BulkEditNullBooleanSelect, BulkImportForm, ChainedModelChoiceField, CSVDataField, - ExpandableIPAddressField, FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice, + APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, ExpandableIPAddressField, + FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice, ) from .models import ( Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN, @@ -48,19 +48,22 @@ class VRFForm(BootstrapMixin, TenancyForm, CustomFieldForm): } -class VRFFromCSVForm(forms.ModelForm): - tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False, - error_messages={'invalid_choice': 'Tenant not found.'}) +class VRFCSVForm(forms.ModelForm): + tenant = forms.ModelChoiceField( + queryset=Tenant.objects.all(), + required=False, + to_field_name='name', + help_text='Name of assigned tenant', + error_messages={ + 'invalid_choice': 'Tenant not found.', + } + ) class Meta: model = VRF fields = ['name', 'rd', 'tenant', 'enforce_unique', 'description'] -class VRFImportForm(BootstrapMixin, BulkImportForm): - csv = CSVDataField(csv_form=VRFFromCSVForm) - - class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput) tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) @@ -116,19 +119,21 @@ class AggregateForm(BootstrapMixin, CustomFieldForm): } -class AggregateFromCSVForm(forms.ModelForm): - rir = forms.ModelChoiceField(queryset=RIR.objects.all(), to_field_name='name', - error_messages={'invalid_choice': 'RIR not found.'}) +class AggregateCSVForm(forms.ModelForm): + rir = forms.ModelChoiceField( + queryset=RIR.objects.all(), + to_field_name='name', + help_text='Name of parent RIR', + error_messages={ + 'invalid_choice': 'RIR not found.', + } + ) class Meta: model = Aggregate fields = ['prefix', 'rir', 'date_added', 'description'] -class AggregateImportForm(BootstrapMixin, BulkImportForm): - csv = CSVDataField(csv_form=AggregateFromCSVForm) - - class AggregateBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=Aggregate.objects.all(), widget=forms.MultipleHiddenInput) rir = forms.ModelChoiceField(queryset=RIR.objects.all(), required=False, label='RIR') @@ -197,18 +202,54 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm): self.fields['vrf'].empty_label = 'Global' -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) - vlan_vid = forms.IntegerField(required=False) - status = forms.CharField() - role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False, to_field_name='name', - error_messages={'invalid_choice': 'Invalid role.'}) +class PrefixCSVForm(forms.ModelForm): + vrf = forms.ModelChoiceField( + queryset=VRF.objects.all(), + required=False, + to_field_name='rd', + help_text='Route distinguisher of parent VRF', + error_messages={ + 'invalid_choice': 'VRF not found.', + } + ) + tenant = forms.ModelChoiceField( + queryset=Tenant.objects.all(), + required=False, + to_field_name='name', + help_text='Name of assigned tenant', + error_messages={ + 'invalid_choice': 'Tenant not found.', + } + ) + site = forms.ModelChoiceField( + queryset=Site.objects.all(), + required=False, + to_field_name='name', + help_text='Name of parent site', + error_messages={ + 'invalid_choice': 'Site not found.', + } + ) + vlan_group_name = forms.CharField( + help_text='Group name of assigned VLAN', + required=False + ) + vlan_vid = forms.IntegerField( + help_text='Numeric ID of assigned VLAN', + required=False + ) + status = forms.CharField( + help_text='Status name' + ) + role = forms.ModelChoiceField( + queryset=Role.objects.all(), + required=False, + to_field_name='name', + help_text='Role name', + error_messages={ + 'invalid_choice': 'Invalid role.', + } + ) class Meta: model = Prefix @@ -219,8 +260,6 @@ class PrefixFromCSVForm(forms.ModelForm): def clean(self): - super(PrefixFromCSVForm, self).clean() - site = self.cleaned_data.get('site') vlan_group_name = self.cleaned_data.get('vlan_group_name') vlan_vid = self.cleaned_data.get('vlan_vid') @@ -258,10 +297,6 @@ class PrefixFromCSVForm(forms.ModelForm): raise ValidationError("Invalid status: {}".format(self.cleaned_data['status'])) -class PrefixImportForm(BootstrapMixin, BulkImportForm): - csv = CSVDataField(csv_form=PrefixFromCSVForm) - - class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput) site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False) @@ -513,16 +548,45 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm): self.fields['vrf'].empty_label = 'Global' -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.'}) - status = forms.CharField() - 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 IPAddressCSVForm(forms.ModelForm): + vrf = forms.ModelChoiceField( + queryset=VRF.objects.all(), + required=False, + to_field_name='rd', + help_text='Route distinguisher of the assigned VRF', + error_messages={ + 'invalid_choice': 'VRF not found.', + } + ) + tenant = forms.ModelChoiceField( + queryset=Tenant.objects.all(), + to_field_name='name', + required=False, + help_text='Name of the assigned tenant', + error_messages={ + 'invalid_choice': 'Tenant not found.', + } + ) + status = forms.CharField( + help_text='Status name' + ) + device = forms.ModelChoiceField( + queryset=Device.objects.all(), + required=False, + to_field_name='name', + help_text='Name of assigned Device', + error_messages={ + 'invalid_choice': 'Device not found.', + } + ) + interface_name = forms.CharField( + help_text='Name of assigned interface', + required=False + ) + is_primary = forms.BooleanField( + help_text='This is the primary IP for the assigned device', + required=False + ) class Meta: model = IPAddress @@ -569,11 +633,7 @@ class IPAddressFromCSVForm(forms.ModelForm): elif self.instance.address.version == 6: self.instance.primary_ip6_for = self.cleaned_data['device'] - return super(IPAddressFromCSVForm, self).save(*args, **kwargs) - - -class IPAddressImportForm(BootstrapMixin, BulkImportForm): - csv = CSVDataField(csv_form=IPAddressFromCSVForm) + return super(IPAddressCSVForm, self).save(*args, **kwargs) class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): @@ -673,20 +733,40 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm): } -class VLANFromCSVForm(forms.ModelForm): +class VLANCSVForm(forms.ModelForm): site = forms.ModelChoiceField( - queryset=Site.objects.all(), required=False, to_field_name='name', - error_messages={'invalid_choice': 'Site not found.'} + queryset=Site.objects.all(), + required=False, + to_field_name='name', + help_text='Name of parent site', + error_messages={ + 'invalid_choice': 'Site not found.', + } + ) + group_name = forms.CharField( + help_text='Name of parent VLAN group', + required=False ) - group_name = forms.CharField(required=False) tenant = forms.ModelChoiceField( - Tenant.objects.all(), to_field_name='name', required=False, - error_messages={'invalid_choice': 'Tenant not found.'} + queryset=Tenant.objects.all(), + to_field_name='name', + required=False, + help_text='Name of assigned tenant', + error_messages={ + 'invalid_choice': 'Tenant not found.', + } + ) + status = forms.CharField( + help_text='Status name' ) - status = forms.CharField() role = forms.ModelChoiceField( - queryset=Role.objects.all(), required=False, to_field_name='name', - error_messages={'invalid_choice': 'Invalid role.'} + queryset=Role.objects.all(), + required=False, + to_field_name='name', + help_text='Name of assigned role', + error_messages={ + 'invalid_choice': 'Invalid role.', + } ) class Meta: @@ -695,8 +775,6 @@ class VLANFromCSVForm(forms.ModelForm): def clean(self): - super(VLANFromCSVForm, self).clean() - # Validate VLANGroup group_name = self.cleaned_data.get('group_name') if group_name: @@ -714,7 +792,7 @@ class VLANFromCSVForm(forms.ModelForm): def save(self, *args, **kwargs): - vlan = super(VLANFromCSVForm, self).save(commit=False) + vlan = super(VLANCSVForm, self).save(commit=False) # Assign VLANGroup by site and name if self.cleaned_data['group_name']: @@ -725,10 +803,6 @@ class VLANFromCSVForm(forms.ModelForm): return vlan -class VLANImportForm(BootstrapMixin, BulkImportForm): - csv = CSVDataField(csv_form=VLANFromCSVForm) - - class VLANBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput) site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index a51f47b6e..871bae6e1 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -13,7 +13,8 @@ from django.views.generic import View from dcim.models import Device from utilities.paginator import EnhancedPaginator from utilities.views import ( - BulkAddView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView, + BulkAddView, BulkDeleteView, BulkEditView, BulkImportView, BulkImportView2, ObjectDeleteView, ObjectEditView, + ObjectListView, ) from . import filters, forms, tables from .models import ( @@ -128,11 +129,10 @@ class VRFDeleteView(PermissionRequiredMixin, ObjectDeleteView): default_return_url = 'ipam:vrf_list' -class VRFBulkImportView(PermissionRequiredMixin, BulkImportView): +class VRFBulkImportView(PermissionRequiredMixin, BulkImportView2): permission_required = 'ipam.add_vrf' - form = forms.VRFImportForm + model_form = forms.VRFCSVForm table = tables.VRFTable - template_name = 'ipam/vrf_import.html' default_return_url = 'ipam:vrf_list' @@ -339,11 +339,10 @@ class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView): default_return_url = 'ipam:aggregate_list' -class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView): +class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView2): permission_required = 'ipam.add_aggregate' - form = forms.AggregateImportForm + model_form = forms.AggregateCSVForm table = tables.AggregateTable - template_name = 'ipam/aggregate_import.html' default_return_url = 'ipam:aggregate_list' @@ -536,11 +535,10 @@ class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView): default_return_url = 'ipam:prefix_list' -class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView): +class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView2): permission_required = 'ipam.add_prefix' - form = forms.PrefixImportForm + model_form = forms.PrefixCSVForm table = tables.PrefixTable - template_name = 'ipam/prefix_import.html' default_return_url = 'ipam:prefix_list' @@ -638,11 +636,10 @@ class IPAddressBulkAddView(PermissionRequiredMixin, BulkAddView): default_return_url = 'ipam:ipaddress_list' -class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView): +class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView2): permission_required = 'ipam.add_ipaddress' - form = forms.IPAddressImportForm + model_form = forms.IPAddressCSVForm table = tables.IPAddressTable - template_name = 'ipam/ipaddress_import.html' default_return_url = 'ipam:ipaddress_list' def save_obj(self, obj): @@ -746,11 +743,10 @@ class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView): default_return_url = 'ipam:vlan_list' -class VLANBulkImportView(PermissionRequiredMixin, BulkImportView): +class VLANBulkImportView(PermissionRequiredMixin, BulkImportView2): permission_required = 'ipam.add_vlan' - form = forms.VLANImportForm + model_form = forms.VLANCSVForm table = tables.VLANTable - template_name = 'ipam/vlan_import.html' default_return_url = 'ipam:vlan_list' diff --git a/netbox/templates/ipam/aggregate_import.html b/netbox/templates/ipam/aggregate_import.html deleted file mode 100644 index 1a270e450..000000000 --- a/netbox/templates/ipam/aggregate_import.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends 'utilities/obj_import.html' %} - -{% block title %}Aggregate Import{% endblock %} - -{% block instructions %} -

CSV Format

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescriptionExample
PrefixIPv4 or IPv6 network172.16.0.0/12
RIRName of RIRRFC 1918
Date AddedDate in YYYY-MM-DD format (optional)2016-02-23
DescriptionShort description (optional)Private IPv4 space
-

Example

-
172.16.0.0/12,RFC 1918,2016-02-23,Private IPv4 space
-{% endblock %} diff --git a/netbox/templates/ipam/ipaddress_import.html b/netbox/templates/ipam/ipaddress_import.html deleted file mode 100644 index 9193a207d..000000000 --- a/netbox/templates/ipam/ipaddress_import.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends 'utilities/obj_import.html' %} - -{% block title %}IP Address Import{% endblock %} - -{% block instructions %} -

CSV Format

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescriptionExample
AddressIPv4 or IPv6 address192.0.2.42/24
VRFVRF route distinguisher (optional)65000:123
TenantName of tenant (optional)ABC01
StatusCurrent statusActive
DeviceDevice name (optional)switch12
InterfaceInterface name (optional)ge-0/0/31
Is PrimaryIf "true", IP will be primary for device (optional)True
DescriptionShort description (optional)Management IP
-

Example

-
192.0.2.42/24,65000:123,ABC01,Active,switch12,ge-0/0/31,True,Management IP
-{% endblock %} diff --git a/netbox/templates/ipam/prefix_import.html b/netbox/templates/ipam/prefix_import.html deleted file mode 100644 index a135fcb31..000000000 --- a/netbox/templates/ipam/prefix_import.html +++ /dev/null @@ -1,70 +0,0 @@ -{% extends 'utilities/obj_import.html' %} - -{% block title %}Prefix Import{% endblock %} - -{% block instructions %} -

CSV Format

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescriptionExample
PrefixIPv4 or IPv6 network192.168.42.0/24
VRFVRF route distinguisher (optional)65000:123
TenantName of tenant (optional)ABC01
SiteName of assigned site (optional)HQ
VLAN GroupName of group for VLAN selection (optional)Customers
VLAN IDNumeric VLAN ID (optional)801
StatusCurrent statusActive
RoleFunctional role (optional)Customer
Is a poolTrue if all IPs are considered usableFalse
DescriptionShort description (optional)7th floor WiFi
-

Example

-
192.168.42.0/24,65000:123,ABC01,HQ,Customers,801,Active,Customer,False,7th floor WiFi
-{% endblock %} diff --git a/netbox/templates/ipam/vlan_import.html b/netbox/templates/ipam/vlan_import.html deleted file mode 100644 index 7c9081c24..000000000 --- a/netbox/templates/ipam/vlan_import.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends 'utilities/obj_import.html' %} - -{% block title %}VLAN Import{% endblock %} - -{% block instructions %} -

CSV Format

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescriptionExample
SiteName of assigned site (optional)LAS2
GroupName of VLAN group (optional)Backend Network
IDConfigured VLAN ID1400
NameConfigured VLAN nameCameras
TenantName of tenant (optional)Internal
StatusCurrent statusActive
RoleFunctional role (optional)Security
DescriptionShort description (optional)Security team only
-

Example

-
LAS2,Backend Network,1400,Cameras,Internal,Active,Security,Security team only
-{% endblock %} diff --git a/netbox/templates/ipam/vrf_import.html b/netbox/templates/ipam/vrf_import.html deleted file mode 100644 index 5c7821e53..000000000 --- a/netbox/templates/ipam/vrf_import.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'utilities/obj_import.html' %} - -{% block title %}VRF Import{% endblock %} - -{% block instructions %} -

CSV Format

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescriptionExample
NameName of VRFCustomer_ABC
RDRoute distinguisher65000:123456
TenantName of tenant (optional)ABC01
Enforce uniquenessPrevent duplicate prefixes/IP addressesTrue
DescriptionShort description (optional)Native VRF for customer ABC
-

Example

-
Customer_ABC,65000:123456,ABC01,True,Native VRF for customer ABC
-{% endblock %}