diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py
index 9e1cc657d..f1737b366 100644
--- a/netbox/dcim/forms.py
+++ b/netbox/dcim/forms.py
@@ -107,29 +107,34 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
}
-class SiteFromCSVForm(forms.ModelForm):
+class SiteCSVForm(forms.ModelForm):
region = forms.ModelChoiceField(
- Region.objects.all(), to_field_name='name', required=False, error_messages={
- 'invalid_choice': 'Tenant not found.'
+ queryset=Region.objects.all(),
+ required=False,
+ to_field_name='name',
+ help_text='Name of assigned region',
+ error_messages={
+ 'invalid_choice': 'Region not found.',
}
)
tenant = forms.ModelChoiceField(
- Tenant.objects.all(), to_field_name='name', required=False, error_messages={
- 'invalid_choice': 'Tenant not found.'
+ 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 = Site
fields = [
- 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'contact_name', 'contact_phone', 'contact_email',
+ 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address',
+ 'contact_name', 'contact_phone', 'contact_email', 'comments',
]
-class SiteImportForm(BootstrapMixin, BulkImportForm):
- csv = CSVDataField(csv_form=SiteFromCSVForm)
-
-
class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=forms.MultipleHiddenInput)
region = TreeNodeChoiceField(queryset=Region.objects.all(), required=False)
@@ -217,35 +222,62 @@ class RackForm(BootstrapMixin, TenancyForm, CustomFieldForm):
}
-class RackFromCSVForm(forms.ModelForm):
- site = forms.ModelChoiceField(queryset=Site.objects.all(), to_field_name='name',
- error_messages={'invalid_choice': 'Site not found.'})
- 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.'})
- role = forms.ModelChoiceField(RackRole.objects.all(), to_field_name='name', required=False,
- error_messages={'invalid_choice': 'Role not found.'})
+class RackCSVForm(forms.ModelForm):
+ site = forms.ModelChoiceField(
+ queryset=Site.objects.all(),
+ to_field_name='name',
+ help_text='Name of parent site',
+ error_messages={
+ 'invalid_choice': 'Site not found.',
+ }
+ )
+ group = forms.ModelChoiceField(
+ queryset=RackGroup.objects.all(),
+ to_field_name='name',
+ required=False,
+ help_text='Name of parent group',
+ error_messages={
+ 'invalid_choice': 'Rack group 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.',
+ }
+ )
+ role = forms.ModelChoiceField(
+ queryset=RackRole.objects.all(),
+ required=False,
+ to_field_name='name',
+ help_text='Name of assigned role',
+ error_messages={
+ 'invalid_choice': 'Role not found.',
+ }
+ )
type = forms.CharField(required=False)
class Meta:
model = Rack
- fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height',
- 'desc_units']
+ fields = [
+ 'site', 'group', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units',
+ ]
- def clean(self):
+ def clean_group(self):
site = self.cleaned_data.get('site')
- group = self.cleaned_data.get('group_name')
+ group = self.cleaned_data.get('group')
- # Validate rack group
- if site and group:
- try:
- self.instance.group = RackGroup.objects.get(site=site, name=group)
- except RackGroup.DoesNotExist:
- self.add_error('group_name', "Invalid rack group ({})".format(group))
+ if group and group.site != site:
+ raise ValidationError("Invalid group for site {}: {}".format(site, group))
def clean_type(self):
+
rack_type = self.cleaned_data['type']
+
if not rack_type:
return None
try:
@@ -258,10 +290,6 @@ class RackFromCSVForm(forms.ModelForm):
))
-class RackImportForm(BootstrapMixin, BulkImportForm):
- csv = CSVDataField(csv_form=RackFromCSVForm)
-
-
class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site')
@@ -663,25 +691,47 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
self.initial['rack'] = self.instance.parent_bay.device.rack_id
-class BaseDeviceFromCSVForm(forms.ModelForm):
+class BaseDeviceCSVForm(forms.ModelForm):
device_role = forms.ModelChoiceField(
- queryset=DeviceRole.objects.all(), to_field_name='name',
- error_messages={'invalid_choice': 'Invalid device role.'}
+ queryset=DeviceRole.objects.all(),
+ to_field_name='name',
+ help_text='Name of assigned role',
+ error_messages={
+ 'invalid_choice': 'Invalid device role.',
+ }
)
tenant = forms.ModelChoiceField(
- Tenant.objects.all(), to_field_name='name', required=False,
- error_messages={'invalid_choice': 'Tenant not found.'}
+ queryset=Tenant.objects.all(),
+ required=False,
+ to_field_name='name',
+ help_text='Name of assigned tenant',
+ error_messages={
+ 'invalid_choice': 'Tenant not found.',
+ }
)
manufacturer = forms.ModelChoiceField(
- queryset=Manufacturer.objects.all(), to_field_name='name',
- error_messages={'invalid_choice': 'Invalid manufacturer.'}
+ queryset=Manufacturer.objects.all(),
+ to_field_name='name',
+ help_text='Manufacturer name',
+ error_messages={
+ 'invalid_choice': 'Invalid manufacturer.',
+ }
+ )
+ model_name = forms.CharField(
+ help_text='Model name'
)
- model_name = forms.CharField()
platform = forms.ModelChoiceField(
- queryset=Platform.objects.all(), required=False, to_field_name='name',
- error_messages={'invalid_choice': 'Invalid platform.'}
+ queryset=Platform.objects.all(),
+ required=False,
+ to_field_name='name',
+ help_text='Name of assigned platform',
+ error_messages={
+ 'invalid_choice': 'Invalid platform.',
+ }
+ )
+ status = forms.CharField(
+ help_text='Status name'
)
- status = forms.CharField()
class Meta:
fields = []
@@ -707,16 +757,25 @@ class BaseDeviceFromCSVForm(forms.ModelForm):
raise ValidationError("Invalid status: {}".format(self.cleaned_data['status']))
-class DeviceFromCSVForm(BaseDeviceFromCSVForm):
+class DeviceCSVForm(BaseDeviceCSVForm):
site = forms.ModelChoiceField(
- queryset=Site.objects.all(), to_field_name='name', error_messages={
+ queryset=Site.objects.all(),
+ to_field_name='name',
+ help_text='Name of parent site',
+ error_messages={
'invalid_choice': 'Invalid site name.',
}
)
- rack_name = forms.CharField(required=False)
- face = forms.CharField(required=False)
+ rack_name = forms.CharField(
+ required=False,
+ help_text='Name of parent rack'
+ )
+ face = forms.CharField(
+ required=False,
+ help_text='Mounted rack face (front or rear)'
+ )
- class Meta(BaseDeviceFromCSVForm.Meta):
+ class Meta(BaseDeviceCSVForm.Meta):
fields = [
'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status',
'site', 'rack_name', 'position', 'face',
@@ -724,7 +783,7 @@ class DeviceFromCSVForm(BaseDeviceFromCSVForm):
def clean(self):
- super(DeviceFromCSVForm, self).clean()
+ super(DeviceCSVForm, self).clean()
site = self.cleaned_data.get('site')
rack_name = self.cleaned_data.get('rack_name')
@@ -749,18 +808,20 @@ class DeviceFromCSVForm(BaseDeviceFromCSVForm):
raise forms.ValidationError('Invalid rack face ({}); must be "front" or "rear".'.format(face))
-class ChildDeviceFromCSVForm(BaseDeviceFromCSVForm):
+class ChildDeviceCSVForm(BaseDeviceCSVForm):
parent = FlexibleModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name',
- required=False,
+ help_text='Name of parent device',
error_messages={
- 'invalid_choice': 'Parent device not found.'
+ 'invalid_choice': 'Parent device not found.',
}
)
- device_bay_name = forms.CharField(required=False)
+ device_bay_name = forms.CharField(
+ help_text='Name of device bay',
+ )
- class Meta(BaseDeviceFromCSVForm.Meta):
+ class Meta(BaseDeviceCSVForm.Meta):
fields = [
'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status',
'parent', 'device_bay_name',
@@ -768,7 +829,7 @@ class ChildDeviceFromCSVForm(BaseDeviceFromCSVForm):
def clean(self):
- super(ChildDeviceFromCSVForm, self).clean()
+ super(ChildDeviceCSVForm, self).clean()
parent = self.cleaned_data.get('parent')
device_bay_name = self.cleaned_data.get('device_bay_name')
@@ -778,20 +839,15 @@ class ChildDeviceFromCSVForm(BaseDeviceFromCSVForm):
try:
device_bay = DeviceBay.objects.get(device=parent, name=device_bay_name)
if device_bay.installed_device:
- self.add_error('device_bay_name',
- "Device bay ({} {}) is already occupied".format(parent, device_bay_name))
+ self.add_error(
+ 'device_bay_name', "Device bay ({} {}) is already occupied".format(parent, device_bay_name)
+ )
else:
self.instance.parent_bay = device_bay
except DeviceBay.DoesNotExist:
- self.add_error('device_bay_name', "Parent device/bay ({} {}) not found".format(parent, device_bay_name))
-
-
-class DeviceImportForm(BootstrapMixin, BulkImportForm):
- csv = CSVDataField(csv_form=DeviceFromCSVForm)
-
-
-class ChildDeviceImportForm(BootstrapMixin, BulkImportForm):
- csv = CSVDataField(csv_form=ChildDeviceFromCSVForm)
+ self.add_error(
+ 'device_bay_name', "Parent device/bay ({} {}) not found".format(parent, device_bay_name)
+ )
class DeviceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
index f6e00be04..888a54aaf 100644
--- a/netbox/dcim/views.py
+++ b/netbox/dcim/views.py
@@ -23,14 +23,14 @@ from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_S
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator
from utilities.views import (
- BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
+ BulkDeleteView, BulkEditView, BulkImportView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView,
)
from . import filters, forms, tables
from .models import (
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
- Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
- RackReservation, RackRole, Region, Site,
+ Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
+ RackGroup, RackReservation, RackRole, Region, Site,
)
@@ -217,11 +217,10 @@ class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
default_return_url = 'dcim:site_list'
-class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
+class SiteBulkImportView(PermissionRequiredMixin, BulkImportView2):
permission_required = 'dcim.add_site'
- form = forms.SiteImportForm
+ model_form = forms.SiteCSVForm
table = tables.SiteTable
- template_name = 'dcim/site_import.html'
default_return_url = 'dcim:site_list'
@@ -388,11 +387,10 @@ class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
default_return_url = 'dcim:rack_list'
-class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
+class RackBulkImportView(PermissionRequiredMixin, BulkImportView2):
permission_required = 'dcim.add_rack'
- form = forms.RackImportForm
+ model_form = forms.RackCSVForm
table = tables.RackImportTable
- template_name = 'dcim/rack_import.html'
default_return_url = 'dcim:rack_list'
@@ -864,17 +862,17 @@ class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
default_return_url = 'dcim:device_list'
-class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
+class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView2):
permission_required = 'dcim.add_device'
- form = forms.DeviceImportForm
+ model_form = forms.DeviceCSVForm
table = tables.DeviceImportTable
template_name = 'dcim/device_import.html'
default_return_url = 'dcim:device_list'
-class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
+class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView2):
permission_required = 'dcim.add_device'
- form = forms.ChildDeviceImportForm
+ model_form = forms.ChildDeviceCSVForm
table = tables.DeviceImportTable
template_name = 'dcim/device_import_child.html'
default_return_url = 'dcim:device_list'
diff --git a/netbox/templates/dcim/device_import.html b/netbox/templates/dcim/device_import.html
index 8a1cfa1a5..85ebfbbc6 100644
--- a/netbox/templates/dcim/device_import.html
+++ b/netbox/templates/dcim/device_import.html
@@ -1,103 +1,5 @@
-{% extends '_base.html' %}
-{% load form_helpers %}
+{% extends 'utilities/obj_import.html' %}
-{% block title %}Device Import{% endblock %}
-
-{% block content %}
-{% include 'dcim/inc/device_import_header.html' %}
-
-
-
-
CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Name |
- Device name (optional) |
- rack101_sw1 |
-
-
- Device role |
- Functional role of device |
- ToR Switch |
-
-
- Tenant |
- Name of tenant (optional) |
- Pied Piper |
-
-
- Device manufacturer |
- Hardware manufacturer |
- Juniper |
-
-
- Device model |
- Hardware model |
- EX4300-48T |
-
-
- Platform |
- Software running on device (optional) |
- Juniper Junos |
-
-
- Serial number |
- Physical serial number (optional) |
- CAB00577291 |
-
-
- Asset tag |
- Unique alphanumeric tag (optional) |
- ABC123456 |
-
-
- Status |
- Current status |
- Active |
-
-
- Site |
- Site name |
- Ashburn-VA |
-
-
- Rack |
- Rack name (optional) |
- R101 |
-
-
- Position (U) |
- Lowest-numbered rack unit occupied by the device (optional) |
- 21 |
-
-
- Face |
- Rack face; front or rear (required if position is set) |
- Rear |
-
-
-
-
Example
-
rack101_sw1,ToR Switch,Pied Piper,Juniper,EX4300-48T,Juniper Junos,CAB00577291,ABC123456,Active,Ashburn-VA,R101,21,Rear
-
-
+{% block tabs %}
+ {% include 'dcim/inc/device_import_header.html' %}
{% endblock %}
diff --git a/netbox/templates/dcim/device_import_child.html b/netbox/templates/dcim/device_import_child.html
index 668a9c810..406d239d7 100644
--- a/netbox/templates/dcim/device_import_child.html
+++ b/netbox/templates/dcim/device_import_child.html
@@ -1,93 +1,5 @@
-{% extends '_base.html' %}
-{% load form_helpers %}
+{% extends 'utilities/obj_import.html' %}
-{% block title %}Device Import{% endblock %}
-
-{% block content %}
-{% include 'dcim/inc/device_import_header.html' with active_tab='child_import' %}
-
-
-
-
CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Name |
- Device name (optional) |
- Blade12 |
-
-
- Device role |
- Functional role of device |
- Blade Server |
-
-
- Tenant |
- Name of tenant (optional) |
- Pied Piper |
-
-
- Device manufacturer |
- Hardware manufacturer |
- Dell |
-
-
- Device model |
- Hardware model |
- BS2000T |
-
-
- Platform |
- Software running on device (optional) |
- Linux |
-
-
- Serial number |
- Physical serial number (optional) |
- CAB00577291 |
-
-
- Asset tag |
- Unique alphanumeric tag (optional) |
- ABC123456 |
-
-
- Status |
- Current status |
- Active |
-
-
- Parent device |
- Parent device |
- Server101 |
-
-
- Device bay |
- Device bay name |
- Slot 4 |
-
-
-
-
Example
-
Blade12,Blade Server,Pied Piper,Dell,BS2000T,Linux,CAB00577291,ABC123456,Active,Server101,Slot4
-
-
+{% block tabs %}
+ {% include 'dcim/inc/device_import_header.html' with active_tab='child_import' %}
{% endblock %}
diff --git a/netbox/templates/dcim/inc/device_import_header.html b/netbox/templates/dcim/inc/device_import_header.html
index 57dd1b46e..2adc867b1 100644
--- a/netbox/templates/dcim/inc/device_import_header.html
+++ b/netbox/templates/dcim/inc/device_import_header.html
@@ -1,4 +1,3 @@
-Device Import
- Racked Devices
- Child Devices
diff --git a/netbox/templates/dcim/rack_import.html b/netbox/templates/dcim/rack_import.html
deleted file mode 100644
index e3e40c5cd..000000000
--- a/netbox/templates/dcim/rack_import.html
+++ /dev/null
@@ -1,70 +0,0 @@
-{% extends 'utilities/obj_import.html' %}
-
-{% block title %}Rack Import{% endblock %}
-
-{% block instructions %}
- CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Site |
- Name of the assigned site |
- DC-4 |
-
-
- Group |
- Rack group name (optional) |
- Cage 1400 |
-
-
- Name |
- Internal rack name |
- R101 |
-
-
- Facility ID |
- Rack ID assigned by the facility (optional) |
- J12.100 |
-
-
- Tenant |
- Name of tenant (optional) |
- Pied Piper |
-
-
- Role |
- Functional role (optional) |
- Compute |
-
-
- Type |
- Rack type (optional) |
- 4-post cabinet |
-
-
- Width |
- Rail-to-rail width (19 or 23 inches) |
- 19 |
-
-
- Height |
- Height in rack units |
- 42 |
-
-
- Descending units |
- Units are numbered top-to-bottom |
- False |
-
-
-
- Example
-
DC-4,Cage 1400,R101,J12.100,Pied Piper,Compute,4-post cabinet,19,42,False
-{% endblock %}
diff --git a/netbox/templates/dcim/site_import.html b/netbox/templates/dcim/site_import.html
deleted file mode 100644
index 3a4e6277a..000000000
--- a/netbox/templates/dcim/site_import.html
+++ /dev/null
@@ -1,81 +0,0 @@
-{% extends '_base.html' %}
-{% load form_helpers %}
-
-{% block title %}Site Import{% endblock %}
-
-{% block content %}
-Site Import
-
-
-
-
CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Name |
- Site's proper name |
- ASH-4 South |
-
-
- Slug |
- URL-friendly name |
- ash4-south |
-
-
- Region |
- Name of region (optional) |
- North America |
-
-
- Tenant |
- Name of tenant (optional) |
- Pied Piper |
-
-
- Facility |
- Name of the hosting facility (optional) |
- Equinix DC6 |
-
-
- ASN |
- Autonomous system number (optional) |
- 65000 |
-
-
- Contact Name |
- Name of administrative contact (optional) |
- Hank Hill |
-
-
- Contact Phone |
- Phone number (optional) |
- +1-214-555-1234 |
-
-
- Contact E-mail |
- E-mail address (optional) |
- hhill@example.com |
-
-
-
-
Example
-
ASH-4 South,ash4-south,North America,Pied Piper,Equinix DC6,65000,Hank Hill,+1-214-555-1234,hhill@example.com
-
-
-{% endblock %}
diff --git a/netbox/templates/utilities/obj_import.html b/netbox/templates/utilities/obj_import.html
index 25b737175..7aa7d3b0f 100644
--- a/netbox/templates/utilities/obj_import.html
+++ b/netbox/templates/utilities/obj_import.html
@@ -4,6 +4,7 @@
{% block content %}
{% block title %}{{ obj_type|bettertitle }} Import{% endblock %}
+{% block tabs %}{% endblock %}
{% if form.non_field_errors %}