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
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Prefix |
- IPv4 or IPv6 network |
- 172.16.0.0/12 |
-
-
- RIR |
- Name of RIR |
- RFC 1918 |
-
-
- Date Added |
- Date in YYYY-MM-DD format (optional) |
- 2016-02-23 |
-
-
- Description |
- Short 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
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Address |
- IPv4 or IPv6 address |
- 192.0.2.42/24 |
-
-
- VRF |
- VRF route distinguisher (optional) |
- 65000:123 |
-
-
- Tenant |
- Name of tenant (optional) |
- ABC01 |
-
-
- Status |
- Current status |
- Active |
-
-
- Device |
- Device name (optional) |
- switch12 |
-
-
- Interface |
- Interface name (optional) |
- ge-0/0/31 |
-
-
- Is Primary |
- If "true", IP will be primary for device (optional) |
- True |
-
-
- Description |
- Short 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
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Prefix |
- IPv4 or IPv6 network |
- 192.168.42.0/24 |
-
-
- VRF |
- VRF route distinguisher (optional) |
- 65000:123 |
-
-
- Tenant |
- Name of tenant (optional) |
- ABC01 |
-
-
- Site |
- Name of assigned site (optional) |
- HQ |
-
-
- VLAN Group |
- Name of group for VLAN selection (optional) |
- Customers |
-
-
- VLAN ID |
- Numeric VLAN ID (optional) |
- 801 |
-
-
- Status |
- Current status |
- Active |
-
-
- Role |
- Functional role (optional) |
- Customer |
-
-
- Is a pool |
- True if all IPs are considered usable |
- False |
-
-
- Description |
- Short 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
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Site |
- Name of assigned site (optional) |
- LAS2 |
-
-
- Group |
- Name of VLAN group (optional) |
- Backend Network |
-
-
- ID |
- Configured VLAN ID |
- 1400 |
-
-
- Name |
- Configured VLAN name |
- Cameras |
-
-
- Tenant |
- Name of tenant (optional) |
- Internal |
-
-
- Status |
- Current status |
- Active |
-
-
- Role |
- Functional role (optional) |
- Security |
-
-
- Description |
- Short 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
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Name |
- Name of VRF |
- Customer_ABC |
-
-
- RD |
- Route distinguisher |
- 65000:123456 |
-
-
- Tenant |
- Name of tenant (optional) |
- ABC01 |
-
-
- Enforce uniqueness |
- Prevent duplicate prefixes/IP addresses |
- True |
-
-
- Description |
- Short description (optional) |
- Native VRF for customer ABC |
-
-
-
- Example
- Customer_ABC,65000:123456,ABC01,True,Native VRF for customer ABC
-{% endblock %}