diff --git a/README.md b/README.md
index e2a8e708c..c21b140cf 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,9 @@ Questions? Comments? Please subscribe to [the netbox-discuss mailing list](https
### Build Status
-| | python 2.7 |
+NetBox is built against both Python 2.7 and 3.5. Python 3.5 is recommended.
+
+| | status |
|-------------|------------|
| **master** | [](https://travis-ci.org/digitalocean/netbox) |
| **develop** | [](https://travis-ci.org/digitalocean/netbox) |
diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py
index 074f0dfa4..4fea49ca7 100644
--- a/netbox/dcim/forms.py
+++ b/netbox/dcim/forms.py
@@ -10,9 +10,9 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi
from ipam.models import IPAddress
from tenancy.models import Tenant
from utilities.forms import (
- APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkImportForm, CommentField,
- CSVDataField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled,
- SmallTextarea, SlugField, FilterTreeNodeMultipleChoiceField,
+ APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
+ BulkImportForm, CommentField, CSVDataField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField,
+ Livesearch, SelectWithDisabled, SmallTextarea, SlugField, FilterTreeNodeMultipleChoiceField,
)
from .formfields import MACAddressFormField
@@ -271,6 +271,7 @@ class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
type = forms.ChoiceField(choices=add_blank_choice(RACK_TYPE_CHOICES), required=False, label='Type')
width = forms.ChoiceField(choices=add_blank_choice(RACK_WIDTH_CHOICES), required=False, label='Width')
u_height = forms.IntegerField(required=False, label='Height (U)')
+ desc_units = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Descending units')
comments = CommentField(widget=SmallTextarea)
class Meta:
@@ -374,7 +375,13 @@ class DeviceTypeBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput)
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
u_height = forms.IntegerField(min_value=1, required=False)
+ is_full_depth = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is full depth')
interface_ordering = forms.ChoiceField(choices=add_blank_choice(IFACE_ORDERING_CHOICES), required=False)
+ is_console_server = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is full depth')
+ is_pdu = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is a PDU')
+ is_network_device = forms.NullBooleanField(
+ required=False, widget=BulkEditNullBooleanSelect, label='Is a network device'
+ )
class Meta:
nullable_fields = []
@@ -483,6 +490,7 @@ class InterfaceTemplateCreateForm(DeviceComponentForm):
class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=InterfaceTemplate.objects.all(), widget=forms.MultipleHiddenInput)
form_factor = forms.ChoiceField(choices=add_blank_choice(IFACE_FF_CHOICES), required=False)
+ mgmt_only = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Management only')
class Meta:
nullable_fields = []
@@ -1415,6 +1423,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
device = forms.ModelChoiceField(queryset=Device.objects.all(), widget=forms.HiddenInput)
lag = forms.ModelChoiceField(queryset=Interface.objects.all(), required=False, label='Parent LAG')
form_factor = forms.ChoiceField(choices=add_blank_choice(IFACE_FF_CHOICES), required=False)
+ mgmt_only = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Management only')
description = forms.CharField(max_length=100, required=False)
class Meta:
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
index 5eb80005d..9b0d41065 100644
--- a/netbox/dcim/views.py
+++ b/netbox/dcim/views.py
@@ -12,11 +12,12 @@ from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.http import urlencode
+from django.utils.safestring import mark_safe
from django.views.generic import View
from ipam.models import Prefix, Service, VLAN
from circuits.models import Circuit
-from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
+from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE, UserAction
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator
from utilities.views import (
@@ -892,12 +893,16 @@ def consoleport_connect(request, pk):
form = forms.ConsolePortConnectionForm(request.POST, instance=consoleport)
if form.is_valid():
consoleport = form.save()
- messages.success(request, u"Connected {} {} to {} {}.".format(
- consoleport.device,
- consoleport.name,
- consoleport.cs_port.device,
- consoleport.cs_port.name,
- ))
+ msg = u'Connected {} {} to {} {}'.format(
+ consoleport.device.get_absolute_url(),
+ escape(consoleport.device),
+ escape(consoleport.name),
+ consoleport.cs_port.device.get_absolute_url(),
+ escape(consoleport.cs_port.device),
+ escape(consoleport.cs_port.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, consoleport, msg)
return redirect('dcim:device', pk=consoleport.device.pk)
else:
@@ -921,17 +926,28 @@ def consoleport_disconnect(request, pk):
consoleport = get_object_or_404(ConsolePort, pk=pk)
if not consoleport.cs_port:
- messages.warning(request, u"Cannot disconnect console port {}: It is not connected to anything."
- .format(consoleport))
+ messages.warning(
+ request, u"Cannot disconnect console port {}: It is not connected to anything.".format(consoleport)
+ )
return redirect('dcim:device', pk=consoleport.device.pk)
if request.method == 'POST':
form = ConfirmationForm(request.POST)
if form.is_valid():
+ cs_port = consoleport.cs_port
consoleport.cs_port = None
consoleport.connection_status = None
consoleport.save()
- messages.success(request, u"Console port {} has been disconnected.".format(consoleport))
+ msg = u'Disconnected {} {} from {} {}'.format(
+ consoleport.device.get_absolute_url(),
+ escape(consoleport.device),
+ escape(consoleport.name),
+ cs_port.device.get_absolute_url(),
+ escape(cs_port.device),
+ escape(cs_port.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, consoleport, msg)
return redirect('dcim:device', pk=consoleport.device.pk)
else:
@@ -966,6 +982,7 @@ class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
form = forms.ConsoleConnectionImportForm
table = tables.ConsoleConnectionTable
template_name = 'dcim/console_connections_import.html'
+ default_return_url = 'dcim:console_connections_list'
#
@@ -993,12 +1010,16 @@ def consoleserverport_connect(request, pk):
consoleport.cs_port = consoleserverport
consoleport.connection_status = form.cleaned_data['connection_status']
consoleport.save()
- messages.success(request, u"Connected {} {} to {} {}.".format(
- consoleport.device,
- consoleport.name,
- consoleserverport.device,
- consoleserverport.name,
- ))
+ msg = u'Connected {} {} to {} {}'.format(
+ consoleport.device.get_absolute_url(),
+ escape(consoleport.device),
+ escape(consoleport.name),
+ consoleserverport.device.get_absolute_url(),
+ escape(consoleserverport.device),
+ escape(consoleserverport.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, consoleport, msg)
return redirect('dcim:device', pk=consoleserverport.device.pk)
else:
@@ -1022,8 +1043,9 @@ def consoleserverport_disconnect(request, pk):
consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
if not hasattr(consoleserverport, 'connected_console'):
- messages.warning(request, u"Cannot disconnect console server port {}: Nothing is connected to it."
- .format(consoleserverport))
+ messages.warning(
+ request, u"Cannot disconnect console server port {}: Nothing is connected to it.".format(consoleserverport)
+ )
return redirect('dcim:device', pk=consoleserverport.device.pk)
if request.method == 'POST':
@@ -1033,7 +1055,16 @@ def consoleserverport_disconnect(request, pk):
consoleport.cs_port = None
consoleport.connection_status = None
consoleport.save()
- messages.success(request, u"Console server port {} has been disconnected.".format(consoleserverport))
+ msg = u'Disconnected {} {} from {} {}'.format(
+ consoleport.device.get_absolute_url(),
+ escape(consoleport.device),
+ escape(consoleport.name),
+ consoleserverport.device.get_absolute_url(),
+ escape(consoleserverport.device),
+ escape(consoleserverport.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, consoleport, msg)
return redirect('dcim:device', pk=consoleserverport.device.pk)
else:
@@ -1085,12 +1116,16 @@ def powerport_connect(request, pk):
form = forms.PowerPortConnectionForm(request.POST, instance=powerport)
if form.is_valid():
powerport = form.save()
- messages.success(request, u"Connected {} {} to {} {}.".format(
- powerport.device,
- powerport.name,
- powerport.power_outlet.device,
- powerport.power_outlet.name,
- ))
+ msg = u'Connected {} {} to {} {}'.format(
+ powerport.device.get_absolute_url(),
+ escape(powerport.device),
+ escape(powerport.name),
+ powerport.power_outlet.device.get_absolute_url(),
+ escape(powerport.power_outlet.device),
+ escape(powerport.power_outlet.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, powerport, msg)
return redirect('dcim:device', pk=powerport.device.pk)
else:
@@ -1114,17 +1149,28 @@ def powerport_disconnect(request, pk):
powerport = get_object_or_404(PowerPort, pk=pk)
if not powerport.power_outlet:
- messages.warning(request, u"Cannot disconnect power port {}: It is not connected to an outlet."
- .format(powerport))
+ messages.warning(
+ request, u"Cannot disconnect power port {}: It is not connected to an outlet.".format(powerport)
+ )
return redirect('dcim:device', pk=powerport.device.pk)
if request.method == 'POST':
form = ConfirmationForm(request.POST)
if form.is_valid():
+ power_outlet = powerport.power_outlet
powerport.power_outlet = None
powerport.connection_status = None
powerport.save()
- messages.success(request, u"Power port {} has been disconnected.".format(powerport))
+ msg = u'Disconnected {} {} from {} {}'.format(
+ powerport.device.get_absolute_url(),
+ escape(powerport.device),
+ escape(powerport.name),
+ power_outlet.device.get_absolute_url(),
+ escape(power_outlet.device),
+ escape(power_outlet.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, powerport, msg)
return redirect('dcim:device', pk=powerport.device.pk)
else:
@@ -1159,6 +1205,7 @@ class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
form = forms.PowerConnectionImportForm
table = tables.PowerConnectionTable
template_name = 'dcim/power_connections_import.html'
+ default_return_url = 'dcim:power_connections_list'
#
@@ -1186,12 +1233,16 @@ def poweroutlet_connect(request, pk):
powerport.power_outlet = poweroutlet
powerport.connection_status = form.cleaned_data['connection_status']
powerport.save()
- messages.success(request, u"Connected {} {} to {} {}.".format(
- powerport.device,
- powerport.name,
- poweroutlet.device,
- poweroutlet.name,
- ))
+ msg = u'Connected {} {} to {} {}'.format(
+ powerport.device.get_absolute_url(),
+ escape(powerport.device),
+ escape(powerport.name),
+ poweroutlet.device.get_absolute_url(),
+ escape(poweroutlet.device),
+ escape(poweroutlet.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, powerport, msg)
return redirect('dcim:device', pk=poweroutlet.device.pk)
else:
@@ -1215,7 +1266,9 @@ def poweroutlet_disconnect(request, pk):
poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
if not hasattr(poweroutlet, 'connected_port'):
- messages.warning(request, u"Cannot disconnect power outlet {}: Nothing is connected to it.".format(poweroutlet))
+ messages.warning(
+ request, u"Cannot disconnect power outlet {}: Nothing is connected to it.".format(poweroutlet)
+ )
return redirect('dcim:device', pk=poweroutlet.device.pk)
if request.method == 'POST':
@@ -1225,7 +1278,16 @@ def poweroutlet_disconnect(request, pk):
powerport.power_outlet = None
powerport.connection_status = None
powerport.save()
- messages.success(request, u"Power outlet {} has been disconnected.".format(poweroutlet))
+ msg = u'Disconnected {} {} from {} {}'.format(
+ powerport.device.get_absolute_url(),
+ escape(powerport.device),
+ escape(powerport.name),
+ poweroutlet.device.get_absolute_url(),
+ escape(poweroutlet.device),
+ escape(poweroutlet.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, powerport, msg)
return redirect('dcim:device', pk=poweroutlet.device.pk)
else:
@@ -1491,13 +1553,19 @@ def interfaceconnection_add(request, pk):
if request.method == 'POST':
form = forms.InterfaceConnectionForm(device, request.POST)
if form.is_valid():
+
interfaceconnection = form.save()
- messages.success(request, u"Connected {} {} to {} {}.".format(
- interfaceconnection.interface_a.device,
- interfaceconnection.interface_a,
- interfaceconnection.interface_b.device,
- interfaceconnection.interface_b,
- ))
+ msg = u'Connected {} {} to {} {}'.format(
+ interfaceconnection.interface_a.device.get_absolute_url(),
+ escape(interfaceconnection.interface_a.device),
+ escape(interfaceconnection.interface_a.name),
+ interfaceconnection.interface_b.device.get_absolute_url(),
+ escape(interfaceconnection.interface_b.device),
+ escape(interfaceconnection.interface_b.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, interfaceconnection, msg)
+
if '_addanother' in request.POST:
base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk})
device_b = interfaceconnection.interface_b.device
@@ -1535,12 +1603,16 @@ def interfaceconnection_delete(request, pk):
form = forms.InterfaceConnectionDeletionForm(request.POST)
if form.is_valid():
interfaceconnection.delete()
- messages.success(request, u"Deleted the connection between {} {} and {} {}.".format(
- interfaceconnection.interface_a.device,
- interfaceconnection.interface_a,
- interfaceconnection.interface_b.device,
- interfaceconnection.interface_b,
- ))
+ msg = u'Disconnected {} {} from {} {}'.format(
+ interfaceconnection.interface_a.device.get_absolute_url(),
+ escape(interfaceconnection.interface_a.device),
+ escape(interfaceconnection.interface_a.name),
+ interfaceconnection.interface_b.device.get_absolute_url(),
+ escape(interfaceconnection.interface_b.device),
+ escape(interfaceconnection.interface_b.name),
+ )
+ messages.success(request, mark_safe(msg))
+ UserAction.objects.log_edit(request.user, interfaceconnection, msg)
if form.cleaned_data['device']:
return redirect('dcim:device', pk=form.cleaned_data['device'].pk)
else:
@@ -1570,6 +1642,7 @@ class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView
form = forms.InterfaceConnectionImportForm
table = tables.InterfaceConnectionTable
template_name = 'dcim/interface_connections_import.html'
+ default_return_url = 'dcim:interface_connections_list'
#
diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py
index 3c39a4308..3229ad2b8 100644
--- a/netbox/ipam/filters.py
+++ b/netbox/ipam/filters.py
@@ -9,7 +9,10 @@ from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
-from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
+from .models import (
+ Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN,
+ VLAN_STATUS_CHOICES, VLANGroup, VRF,
+)
class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
@@ -153,10 +156,13 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Role (slug)',
)
+ status = django_filters.MultipleChoiceFilter(
+ choices=PREFIX_STATUS_CHOICES
+ )
class Meta:
model = Prefix
- fields = ['family', 'status']
+ fields = ['family']
def search(self, queryset, name, value):
if not value.strip():
@@ -237,10 +243,13 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
queryset=Interface.objects.all(),
label='Interface (ID)',
)
+ status = django_filters.MultipleChoiceFilter(
+ choices=IPADDRESS_STATUS_CHOICES
+ )
class Meta:
model = IPAddress
- fields = ['family', 'status']
+ fields = ['family']
def search(self, queryset, name, value):
if not value.strip():
@@ -337,10 +346,13 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Role (slug)',
)
+ status = django_filters.MultipleChoiceFilter(
+ choices=VLAN_STATUS_CHOICES
+ )
class Meta:
model = VLAN
- fields = ['name', 'vid', 'status']
+ fields = ['name', 'vid']
def search(self, queryset, name, value):
if not value.strip():
diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py
index 8734107d8..25685c171 100644
--- a/netbox/ipam/forms.py
+++ b/netbox/ipam/forms.py
@@ -5,8 +5,8 @@ from dcim.models import Site, Rack, Device, Interface
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
from tenancy.models import Tenant
from utilities.forms import (
- APISelect, BootstrapMixin, BulkImportForm, CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch,
- ReturnURLForm, SlugField, add_blank_choice,
+ APISelect, BootstrapMixin, BulkEditNullBooleanSelect, BulkImportForm, CSVDataField, ExpandableIPAddressField,
+ FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice,
)
from .models import (
@@ -61,6 +61,9 @@ class VRFImportForm(BootstrapMixin, BulkImportForm):
class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
+ enforce_unique = forms.NullBooleanField(
+ required=False, widget=BulkEditNullBooleanSelect, label='Enforce unique space'
+ )
description = forms.CharField(max_length=100, required=False)
class Meta:
@@ -256,6 +259,7 @@ class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
status = forms.ChoiceField(choices=add_blank_choice(PREFIX_STATUS_CHOICES), required=False)
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
+ is_pool = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is a pool')
description = forms.CharField(max_length=100, required=False)
class Meta:
@@ -340,10 +344,11 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
query_key='q', query_url='ipam-api:ipaddress-list', field_to_update='nat_inside', obj_label='address'
)
)
+ primary_for_device = forms.BooleanField(required=False, label='Make this the primary IP for the device')
class Meta:
model = IPAddress
- fields = ['address', 'vrf', 'tenant', 'status', 'interface', 'nat_inside', 'description']
+ fields = ['address', 'vrf', 'tenant', 'status', 'description', 'interface', 'primary_for_device', 'nat_inside']
widgets = {
'interface': APISelect(api_url='/api/dcim/devices/interfaces/?device_id={{interface_device}}'),
'nat_inside': APISelect(api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}', display_field='address')
@@ -384,6 +389,15 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
else:
self.fields['interface'].choices = []
+ # Initialize primary_for_device if IP address is already assigned
+ if self.instance.interface is not None:
+ device = self.instance.interface.device
+ if (
+ self.instance.address.version == 4 and device.primary_ip4 == self.instance or
+ self.instance.address.version == 6 and device.primary_ip6 == self.instance
+ ):
+ self.initial['primary_for_device'] = True
+
if self.instance.nat_inside:
nat_inside = self.instance.nat_inside
# If the IP is assigned to an interface, populate site/device fields accordingly
@@ -416,6 +430,43 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
else:
self.fields['nat_inside'].choices = []
+ def clean(self):
+ super(IPAddressForm, self).clean()
+
+ # Primary IP assignment is only available if an interface has been assigned.
+ if self.cleaned_data.get('primary_for_device') and not self.cleaned_data.get('interface'):
+ self.add_error(
+ 'primary_for_device', "Only IP addresses assigned to an interface can be designated as primary IPs."
+ )
+
+ def save(self, *args, **kwargs):
+
+ ipaddress = super(IPAddressForm, self).save(*args, **kwargs)
+
+ # Assign this IPAddress as the primary for the associated Device.
+ if self.cleaned_data['primary_for_device']:
+ device = self.cleaned_data['interface'].device
+ if ipaddress.address.version == 4:
+ device.primary_ip4 = ipaddress
+ else:
+ device.primary_ip6 = ipaddress
+ device.save()
+
+ # Clear assignment as primary for device if set.
+ else:
+ try:
+ if ipaddress.address.version == 4:
+ device = ipaddress.primary_ip4_for
+ device.primary_ip4 = None
+ else:
+ device = ipaddress.primary_ip6_for
+ device.primary_ip6 = None
+ device.save()
+ except Device.DoesNotExist:
+ pass
+
+ return ipaddress
+
class IPAddressBulkAddForm(BootstrapMixin, CustomFieldForm):
address_pattern = ExpandableIPAddressField(label='Address Pattern')
diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py
index 32f1ca1cf..0a132dca3 100644
--- a/netbox/secrets/views.py
+++ b/netbox/secrets/views.py
@@ -221,6 +221,7 @@ def secret_import(request):
return render(request, 'import_success.html', {
'table': table,
+ 'return_url': 'secrets:secret_list',
})
except IntegrityError as e:
@@ -231,7 +232,7 @@ def secret_import(request):
return render(request, 'secrets/secret_import.html', {
'form': form,
- 'return_url': reverse('secrets:secret_list'),
+ 'return_url': 'secrets:secret_list',
})
diff --git a/netbox/templates/circuits/circuit_import.html b/netbox/templates/circuits/circuit_import.html
index e2fc9fa36..991a99c9b 100644
--- a/netbox/templates/circuits/circuit_import.html
+++ b/netbox/templates/circuits/circuit_import.html
@@ -1,72 +1,57 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Circuit Import{% endblock %}
-{% block content %}
-
Circuit Import
-
-
-
-
CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Circuit ID |
- Alphanumeric circuit identifier |
- IC-603122 |
-
-
- Provider |
- Name of circuit provider |
- TeliaSonera |
-
-
- Type |
- Circuit type |
- Transit |
-
-
- Tenant |
- Name of tenant (optional) |
- Strickland Propane |
-
-
- Install Date |
- Date in YYYY-MM-DD format (optional) |
- 2016-02-23 |
-
-
- Commit rate |
- Commited rate in Kbps (optional) |
- 2000 |
-
-
- Description |
- Short description (optional) |
- Primary for voice |
-
-
-
-
Example
-
IC-603122,TeliaSonera,Transit,Strickland Propane,2016-02-23,2000,Primary for voice
-
-
+{% block instructions %}
+ CSV Format
+
+
+
+ Field |
+ Description |
+ Example |
+
+
+
+
+ Circuit ID |
+ Alphanumeric circuit identifier |
+ IC-603122 |
+
+
+ Provider |
+ Name of circuit provider |
+ TeliaSonera |
+
+
+ Type |
+ Circuit type |
+ Transit |
+
+
+ Tenant |
+ Name of tenant (optional) |
+ Strickland Propane |
+
+
+ Install Date |
+ Date in YYYY-MM-DD format (optional) |
+ 2016-02-23 |
+
+
+ Commit rate |
+ Commited rate in Kbps (optional) |
+ 2000 |
+
+
+ Description |
+ Short description (optional) |
+ Primary for voice |
+
+
+
+ Example
+ IC-603122,TeliaSonera,Transit,Strickland Propane,2016-02-23,2000,Primary for voice
{% endblock %}
diff --git a/netbox/templates/circuits/provider_import.html b/netbox/templates/circuits/provider_import.html
index a605164df..e60ee3e76 100644
--- a/netbox/templates/circuits/provider_import.html
+++ b/netbox/templates/circuits/provider_import.html
@@ -1,62 +1,47 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Provider Import{% endblock %}
-{% block content %}
-Provider Import
-
-
-
-
CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Name |
- Provider's proper name |
- Level 3 |
-
-
- Slug |
- URL-friendly name |
- level3 |
-
-
- ASN |
- Autonomous system number (optional) |
- 3356 |
-
-
- Account |
- Account number (optional) |
- 08931544 |
-
-
- Portal URL |
- Customer service portal URL (optional) |
- https://mylevel3.net |
-
-
-
-
Example
-
Level 3,level3,3356,08931544,https://mylevel3.net
-
-
+{% block instructions %}
+ CSV Format
+
+
+
+ Field |
+ Description |
+ Example |
+
+
+
+
+ Name |
+ Provider's proper name |
+ Level 3 |
+
+
+ Slug |
+ URL-friendly name |
+ level3 |
+
+
+ ASN |
+ Autonomous system number (optional) |
+ 3356 |
+
+
+ Account |
+ Account number (optional) |
+ 08931544 |
+
+
+ Portal URL |
+ Customer service portal URL (optional) |
+ https://mylevel3.net |
+
+
+
+ Example
+ Level 3,level3,3356,08931544,https://mylevel3.net
{% endblock %}
diff --git a/netbox/templates/dcim/console_connections_import.html b/netbox/templates/dcim/console_connections_import.html
index 6b47ba3bb..c7308168b 100644
--- a/netbox/templates/dcim/console_connections_import.html
+++ b/netbox/templates/dcim/console_connections_import.html
@@ -1,61 +1,47 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Console Connections Import{% endblock %}
-{% block content %}
-Console Connections Import
-
-
-
-
CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Console server |
- Device name or {ID} |
- abc1-cs3 |
-
-
- Console server port |
- Full CS port name |
- Port 35 |
-
-
- Device |
- Device name or {ID} |
- abc1-switch7 |
-
-
- Console Port |
- Console port name |
- Console |
-
-
- Connection Status |
- "planned" or "connected" |
- planned |
-
-
-
-
Example
-
abc1-cs3,Port 35,abc1-switch7,Console,planned
-
-
+{% block instructions %}
+ CSV Format
+
+
+
+ Field |
+ Description |
+ Example |
+
+
+
+
+ Console server |
+ Device name or {ID} |
+ abc1-cs3 |
+
+
+ Console server port |
+ Full CS port name |
+ Port 35 |
+
+
+ Device |
+ Device name or {ID} |
+ abc1-switch7 |
+
+
+ Console Port |
+ Console port name |
+ Console |
+
+
+ Connection Status |
+ "planned" or "connected" |
+ planned |
+
+
+
+ Example
+ abc1-cs3,Port 35,abc1-switch7,Console,planned
{% endblock %}
diff --git a/netbox/templates/dcim/device_import.html b/netbox/templates/dcim/device_import.html
index 50d2f81db..83d0d2195 100644
--- a/netbox/templates/dcim/device_import.html
+++ b/netbox/templates/dcim/device_import.html
@@ -12,8 +12,12 @@
{% csrf_token %}
{% render_form form %}
CSV Format
diff --git a/netbox/templates/dcim/device_import_child.html b/netbox/templates/dcim/device_import_child.html
index ca69d7aa5..49433686f 100644
--- a/netbox/templates/dcim/device_import_child.html
+++ b/netbox/templates/dcim/device_import_child.html
@@ -12,8 +12,12 @@
{% csrf_token %}
{% render_form form %}
CSV Format
diff --git a/netbox/templates/dcim/inc/consoleport.html b/netbox/templates/dcim/inc/consoleport.html
index 5352c949c..58f5fa7de 100644
--- a/netbox/templates/dcim/inc/consoleport.html
+++ b/netbox/templates/dcim/inc/consoleport.html
@@ -7,6 +7,7 @@
{{ cp.name }}
|
+ |
{% if cp.cs_port %}
{{ cp.cs_port.device }}
@@ -32,11 +33,11 @@
{% endif %}
-
+
{% else %}
-
+
{% endif %}
diff --git a/netbox/templates/dcim/inc/consoleserverport.html b/netbox/templates/dcim/inc/consoleserverport.html
index d317cf5a4..cfeab9212 100644
--- a/netbox/templates/dcim/inc/consoleserverport.html
+++ b/netbox/templates/dcim/inc/consoleserverport.html
@@ -7,6 +7,7 @@
|
{{ csp.name }}
|
+ |
{% if csp.connected_console %}
{{ csp.connected_console.device }}
@@ -32,11 +33,11 @@
{% endif %}
-
+
{% else %}
-
+
{% endif %}
diff --git a/netbox/templates/dcim/inc/interface.html b/netbox/templates/dcim/inc/interface.html
index 8d8457d71..9e2f9f8a5 100644
--- a/netbox/templates/dcim/inc/interface.html
+++ b/netbox/templates/dcim/inc/interface.html
@@ -12,12 +12,13 @@
{% if iface.description %}
{% endif %}
- {% if iface.is_lag %}
- {{ iface.member_interfaces.all|join:", "|default:"No members" }}
- {% endif %}
|
+ {{ iface.mac_address|default:"" }} |
{% if iface.is_lag %}
- LAG interface |
+
+ LAG interface
+ {{ iface.member_interfaces.all|join:", "|default:"No members" }}
+ |
{% elif iface.is_virtual %}
Virtual interface |
{% elif iface.connection %}
diff --git a/netbox/templates/dcim/inc/poweroutlet.html b/netbox/templates/dcim/inc/poweroutlet.html
index 652ac8e47..eef4874d6 100644
--- a/netbox/templates/dcim/inc/poweroutlet.html
+++ b/netbox/templates/dcim/inc/poweroutlet.html
@@ -7,6 +7,7 @@
{{ po.name }}
|
+ |
{% if po.connected_port %}
{{ po.connected_port.device }}
@@ -32,11 +33,11 @@
{% endif %}
-
+
{% else %}
-
+
{% endif %}
diff --git a/netbox/templates/dcim/inc/powerport.html b/netbox/templates/dcim/inc/powerport.html
index 785186670..ce4ac6967 100644
--- a/netbox/templates/dcim/inc/powerport.html
+++ b/netbox/templates/dcim/inc/powerport.html
@@ -7,6 +7,7 @@
|
{{ pp.name }}
|
+ |
{% if pp.power_outlet %}
{{ pp.power_outlet.device }}
@@ -32,11 +33,11 @@
{% endif %}
-
+
{% else %}
-
+
{% endif %}
diff --git a/netbox/templates/dcim/interface_connections_import.html b/netbox/templates/dcim/interface_connections_import.html
index 6329e0680..eab0acdba 100644
--- a/netbox/templates/dcim/interface_connections_import.html
+++ b/netbox/templates/dcim/interface_connections_import.html
@@ -1,69 +1,47 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Interface Connections Import{% endblock %}
-{% block content %}
-Interface Connections Import
-
-
- {% if form.non_field_errors %}
-
- Errors
-
- {{ form.non_field_errors }}
-
-
- {% endif %}
-
-
-
- CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Device A |
- Device name or {ID} |
- abc1-core1 |
-
-
- Interface A |
- Interface name |
- xe-0/0/6 |
-
-
- Device B |
- Device name or {ID} |
- abc1-switch7 |
-
-
- Interface B |
- Interface name |
- xe-0/0/0 |
-
-
- Connection Status |
- "planned" or "connected" |
- planned |
-
-
-
- Example
- abc1-core1,xe-0/0/6,abc1-switch7,xe-0/0/0,planned
-
-
+{% block instructions %}
+ CSV Format
+
+
+
+ Field |
+ Description |
+ Example |
+
+
+
+
+ Device A |
+ Device name or {ID} |
+ abc1-core1 |
+
+
+ Interface A |
+ Interface name |
+ xe-0/0/6 |
+
+
+ Device B |
+ Device name or {ID} |
+ abc1-switch7 |
+
+
+ Interface B |
+ Interface name |
+ xe-0/0/0 |
+
+
+ Connection Status |
+ "planned" or "connected" |
+ planned |
+
+
+
+ Example
+ abc1-core1,xe-0/0/6,abc1-switch7,xe-0/0/0,planned
{% endblock %}
diff --git a/netbox/templates/dcim/power_connections_import.html b/netbox/templates/dcim/power_connections_import.html
index 7c436508a..56f34c456 100644
--- a/netbox/templates/dcim/power_connections_import.html
+++ b/netbox/templates/dcim/power_connections_import.html
@@ -1,61 +1,47 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Power Connections Import{% endblock %}
-{% block content %}
-Power Connections Import
-
-
-
- CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- PDU |
- Device name or {ID} |
- abc1-pdu1 |
-
-
- Power Outlet |
- Power outlet name |
- AC4 |
-
-
- Device |
- Device name or {ID} |
- abc1-switch7 |
-
-
- Power Port |
- Power port name |
- PSU0 |
-
-
- Connection Status |
- "planned" or "connected" |
- connected |
-
-
-
- Example
- abc1-pdu1,AC4,abc1-switch7,PSU0,connected
-
-
+{% block instructions %}
+ CSV Format
+
+
+
+ Field |
+ Description |
+ Example |
+
+
+
+
+ PDU |
+ Device name or {ID} |
+ abc1-pdu1 |
+
+
+ Power Outlet |
+ Power outlet name |
+ AC4 |
+
+
+ Device |
+ Device name or {ID} |
+ abc1-switch7 |
+
+
+ Power Port |
+ Power port name |
+ PSU0 |
+
+
+ Connection Status |
+ "planned" or "connected" |
+ connected |
+
+
+
+ Example
+ abc1-pdu1,AC4,abc1-switch7,PSU0,connected
{% endblock %}
diff --git a/netbox/templates/dcim/rack_import.html b/netbox/templates/dcim/rack_import.html
index c462a0be9..207fcfcab 100644
--- a/netbox/templates/dcim/rack_import.html
+++ b/netbox/templates/dcim/rack_import.html
@@ -1,87 +1,72 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Rack Import{% endblock %}
-{% block content %}
-Rack Import
-
-
-
- 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
-
-
+{% 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/import_success.html b/netbox/templates/import_success.html
index 3056e39df..04c454e1d 100644
--- a/netbox/templates/import_success.html
+++ b/netbox/templates/import_success.html
@@ -1,13 +1,14 @@
{% extends '_base.html' %}
{% load render_table from django_tables2 %}
-{% block title %}Import Completed{% endblock %}
-
{% block content %}
-Import Completed
-{% render_table table %}
-
-
- Import more
-
+ {% block title %}Import Completed{% endblock %}
+ {% render_table table %}
+
+
+ Import more
+
+ {% if return_url %}
+ View All
+ {% endif %}
{% endblock %}
diff --git a/netbox/templates/ipam/aggregate_import.html b/netbox/templates/ipam/aggregate_import.html
index 8075b4874..1f0a50feb 100644
--- a/netbox/templates/ipam/aggregate_import.html
+++ b/netbox/templates/ipam/aggregate_import.html
@@ -1,57 +1,42 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Aggregate Import{% endblock %}
-{% block content %}
-Aggregate Import
-
-
-
- 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
-
-
+{% 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_edit.html b/netbox/templates/ipam/ipaddress_edit.html
index 67fb64eea..d5c5ad1af 100644
--- a/netbox/templates/ipam/ipaddress_edit.html
+++ b/netbox/templates/ipam/ipaddress_edit.html
@@ -28,6 +28,7 @@
{% render_field form.interface_rack %}
{% render_field form.interface_device %}
{% render_field form.interface %}
+ {% render_field form.primary_for_device %}
diff --git a/netbox/templates/ipam/ipaddress_import.html b/netbox/templates/ipam/ipaddress_import.html
index 3c01b4af0..362f64829 100644
--- a/netbox/templates/ipam/ipaddress_import.html
+++ b/netbox/templates/ipam/ipaddress_import.html
@@ -1,77 +1,62 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}IP Address Import{% endblock %}
-{% block content %}
- IP Address Import
-
-
-
- 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
-
-
+{% 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
index 0a9cc8694..b9aa7ff47 100644
--- a/netbox/templates/ipam/prefix_import.html
+++ b/netbox/templates/ipam/prefix_import.html
@@ -1,87 +1,72 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Prefix Import{% endblock %}
-{% block content %}
- Prefix Import
-
-
-
- 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
-
-
+{% 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
index 16456ba01..8d1741fd4 100644
--- a/netbox/templates/ipam/vlan_import.html
+++ b/netbox/templates/ipam/vlan_import.html
@@ -1,77 +1,62 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}VLAN Import{% endblock %}
-{% block content %}
- VLAN Import
-
-
-
- CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Site |
- Name of assigned site |
- 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
-
-
+{% block instructions %}
+ CSV Format
+
+
+
+ Field |
+ Description |
+ Example |
+
+
+
+
+ Site |
+ Name of assigned site |
+ 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
index 9953542d2..0a1a31205 100644
--- a/netbox/templates/ipam/vrf_import.html
+++ b/netbox/templates/ipam/vrf_import.html
@@ -1,62 +1,47 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}VRF Import{% endblock %}
-{% block content %}
- VRF Import
-
-
-
- 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
-
-
+{% 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 %}
diff --git a/netbox/templates/secrets/secret_import.html b/netbox/templates/secrets/secret_import.html
index 76022fac1..73daf72d9 100644
--- a/netbox/templates/secrets/secret_import.html
+++ b/netbox/templates/secrets/secret_import.html
@@ -20,10 +20,14 @@
diff --git a/netbox/templates/table.html b/netbox/templates/table.html
index 31f89ee84..6da4a91e4 100644
--- a/netbox/templates/table.html
+++ b/netbox/templates/table.html
@@ -1,4 +1,4 @@
-{% extends 'django_tables2/table.html' %}
+{% extends 'django_tables2/bootstrap-responsive.html' %}
{% load django_tables2 %}
{# Extends the stock django_tables2 template to provide custom formatting of the pagination controls #}
diff --git a/netbox/templates/tenancy/tenant_import.html b/netbox/templates/tenancy/tenant_import.html
index 81f82989f..c0e94269a 100644
--- a/netbox/templates/tenancy/tenant_import.html
+++ b/netbox/templates/tenancy/tenant_import.html
@@ -1,57 +1,42 @@
-{% extends '_base.html' %}
+{% extends 'utilities/obj_import.html' %}
{% load render_table from django_tables2 %}
{% load form_helpers %}
{% block title %}Tenant Import{% endblock %}
-{% block content %}
- Tenant Import
-
-
-
- CSV Format
-
-
-
- Field |
- Description |
- Example |
-
-
-
-
- Name |
- Tenant name |
- WIDG01 |
-
-
- Slug |
- URL-friendly name |
- widg01 |
-
-
- Group |
- Tenant group (optional) |
- Customers |
-
-
- Description |
- Long-form name or other text (optional) |
- Widgets Inc. |
-
-
-
- Example
- WIDG01,widg01,Customers,Widgets Inc.
-
-
+{% block instructions %}
+ CSV Format
+
+
+
+ Field |
+ Description |
+ Example |
+
+
+
+
+ Name |
+ Tenant name |
+ WIDG01 |
+
+
+ Slug |
+ URL-friendly name |
+ widg01 |
+
+
+ Group |
+ Tenant group (optional) |
+ Customers |
+
+
+ Description |
+ Long-form name or other text (optional) |
+ Widgets Inc. |
+
+
+
+ Example
+ WIDG01,widg01,Customers,Widgets Inc.
{% endblock %}
diff --git a/netbox/templates/utilities/obj_import.html b/netbox/templates/utilities/obj_import.html
new file mode 100644
index 000000000..bea9a2319
--- /dev/null
+++ b/netbox/templates/utilities/obj_import.html
@@ -0,0 +1,34 @@
+{% extends '_base.html' %}
+{% load render_table from django_tables2 %}
+{% load form_helpers %}
+
+{% block content %}
+ {% block title %}{% endblock %}
+
+
+ {% if form.non_field_errors %}
+
+ Errors
+
+ {{ form.non_field_errors }}
+
+
+ {% endif %}
+
+
+
+ {% block instructions %}{% endblock %}
+
+
+{% endblock %}
diff --git a/netbox/utilities/error_handlers.py b/netbox/utilities/error_handlers.py
index 1691b41cd..e87b6f0e1 100644
--- a/netbox/utilities/error_handlers.py
+++ b/netbox/utilities/error_handlers.py
@@ -1,4 +1,6 @@
from django.contrib import messages
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
def handle_protectederror(obj, request, e):
@@ -25,11 +27,11 @@ def handle_protectederror(obj, request, e):
# Append dependent objects to error message
dependent_objects = []
- for o in e.protected_objects:
- if hasattr(o, 'get_absolute_url'):
- dependent_objects.append(u' {}'.format(o.get_absolute_url(), o))
+ for obj in e.protected_objects:
+ if hasattr(obj, 'get_absolute_url'):
+ dependent_objects.append(u' {}'.format(obj.get_absolute_url(), escape(obj)))
else:
- dependent_objects.append(str(o))
+ dependent_objects.append(str(obj))
err_message += u', '.join(dependent_objects)
- messages.error(request, err_message)
+ messages.error(request, mark_safe(err_message))
diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py
index aa3d0634f..d14c56e7b 100644
--- a/netbox/utilities/forms.py
+++ b/netbox/utilities/forms.py
@@ -125,6 +125,19 @@ class ColorSelect(forms.Select):
super(ColorSelect, self).__init__(*args, **kwargs)
+class BulkEditNullBooleanSelect(forms.NullBooleanSelect):
+
+ def __init__(self, *args, **kwargs):
+ super(BulkEditNullBooleanSelect, self).__init__(*args, **kwargs)
+
+ # Override the built-in choice labels
+ self.choices = (
+ ('1', '---------'),
+ ('2', 'Yes'),
+ ('3', 'No'),
+ )
+
+
class SelectWithDisabled(forms.Select):
"""
Modified the stock Select widget to accept choices using a dict() for a label. The dict for each option must include
diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py
index ef36680eb..a91055964 100644
--- a/netbox/utilities/views.py
+++ b/netbox/utilities/views.py
@@ -17,7 +17,6 @@ from django.utils.http import is_safe_url
from django.utils.safestring import mark_safe
from django.views.generic import View
-from extras.forms import CustomFieldForm
from extras.models import CustomField, CustomFieldValue, ExportTemplate, UserAction
from .error_handlers import handle_protectederror
@@ -195,12 +194,8 @@ class ObjectEditView(GetReturnURLMixin, View):
form = self.form_class(request.POST, request.FILES, instance=obj)
if form.is_valid():
- obj = form.save(commit=False)
- obj_created = not obj.pk
- obj.save()
- form.save_m2m()
- if isinstance(form, CustomFieldForm):
- form.save_custom_fields()
+ obj_created = not form.instance.pk
+ obj = form.save()
msg = u'Created ' if obj_created else u'Modified '
msg += self.model._meta.verbose_name
@@ -400,6 +395,7 @@ class BulkImportView(View):
return render(request, "import_success.html", {
'table': obj_table,
+ 'return_url': self.default_return_url,
})
except IntegrityError as e:
@@ -423,7 +419,7 @@ class BulkEditView(View):
filter: FilterSet to apply when deleting by QuerySet
form: The form class used to edit objects in bulk
template_name: The name of the template
- default_return_url: Name of the URL to which the user is redirected after editing the objects (can be overriden by
+ default_return_url: Name of the URL to which the user is redirected after editing the objects (can be overridden by
POSTing return_url)
"""
cls = None
@@ -475,7 +471,7 @@ class BulkEditView(View):
fields_to_update[field] = ''
else:
fields_to_update[field] = None
- elif form.cleaned_data[field]:
+ elif form.cleaned_data[field] not in (None, ''):
fields_to_update[field] = form.cleaned_data[field]
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
diff --git a/requirements.txt b/requirements.txt
index 48e58b12e..21382bb6c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ django-debug-toolbar>=1.7
django-filter>=1.0.2
django-mptt==0.8.7
django-rest-swagger>=2.1.0
-django-tables2>=1.4.0
+django-tables2>=1.6.0
djangorestframework>=3.6.2
graphviz>=0.6
Markdown>=2.6.7
|