mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into v2-develop
Conflicts: netbox/dcim/forms.py netbox/dcim/views.py netbox/ipam/forms.py netbox/templates/_base.html netbox/utilities/views.py
This commit is contained in:
@ -20,7 +20,8 @@ Python 3:
|
|||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
# yum install -y epel-release
|
# yum install -y epel-release
|
||||||
# yum install -y gcc python3 python3-devel python3-pip libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
|
# yum install -y gcc python34 python34-devel python34-setuptools libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
|
||||||
|
# easy_install-3.4 pip
|
||||||
```
|
```
|
||||||
|
|
||||||
Python 2:
|
Python 2:
|
||||||
|
@ -95,7 +95,7 @@ class CircuitTypeEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = CircuitType
|
model = CircuitType
|
||||||
form_class = forms.CircuitTypeForm
|
form_class = forms.CircuitTypeForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('circuits:circuittype_list')
|
return reverse('circuits:circuittype_list')
|
||||||
|
|
||||||
|
|
||||||
@ -142,7 +142,6 @@ class CircuitEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
permission_required = 'circuits.change_circuit'
|
permission_required = 'circuits.change_circuit'
|
||||||
model = Circuit
|
model = Circuit
|
||||||
form_class = forms.CircuitForm
|
form_class = forms.CircuitForm
|
||||||
fields_initial = ['provider']
|
|
||||||
template_name = 'circuits/circuit_edit.html'
|
template_name = 'circuits/circuit_edit.html'
|
||||||
default_return_url = 'circuits:circuit_list'
|
default_return_url = 'circuits:circuit_list'
|
||||||
|
|
||||||
@ -230,7 +229,6 @@ class CircuitTerminationEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
permission_required = 'circuits.change_circuittermination'
|
permission_required = 'circuits.change_circuittermination'
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
form_class = forms.CircuitTerminationForm
|
form_class = forms.CircuitTerminationForm
|
||||||
fields_initial = ['term_side']
|
|
||||||
template_name = 'circuits/circuittermination_edit.html'
|
template_name = 'circuits/circuittermination_edit.html'
|
||||||
|
|
||||||
def alter_obj(self, obj, request, url_args, url_kwargs):
|
def alter_obj(self, obj, request, url_args, url_kwargs):
|
||||||
@ -238,7 +236,7 @@ class CircuitTerminationEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
obj.circuit = get_object_or_404(Circuit, pk=url_kwargs['circuit'])
|
obj.circuit = get_object_or_404(Circuit, pk=url_kwargs['circuit'])
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return obj.circuit.get_absolute_url()
|
return obj.circuit.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1422,9 +1422,16 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
|||||||
super(InterfaceBulkEditForm, self).__init__(*args, **kwargs)
|
super(InterfaceBulkEditForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Limit LAG choices to interfaces which belong to the parent device.
|
# Limit LAG choices to interfaces which belong to the parent device.
|
||||||
|
device = None
|
||||||
if self.initial.get('device'):
|
if self.initial.get('device'):
|
||||||
self.fields['lag'].queryset = Interface.objects.filter(
|
try:
|
||||||
device=self.initial['device'], form_factor=IFACE_FF_LAG
|
device = Device.objects.get(pk=self.initial.get('device'))
|
||||||
|
except Device.DoesNotExist:
|
||||||
|
pass
|
||||||
|
if device is not None:
|
||||||
|
interface_ordering = device.device_type.interface_ordering
|
||||||
|
self.fields['lag'].queryset = Interface.objects.order_naturally(method=interface_ordering).filter(
|
||||||
|
device=device, form_factor=IFACE_FF_LAG
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.fields['lag'].choices = []
|
self.fields['lag'].choices = []
|
||||||
@ -1684,36 +1691,6 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
|
|||||||
device = forms.CharField(required=False, label='Device name')
|
device = forms.CharField(required=False, label='Device name')
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# IP addresses
|
|
||||||
#
|
|
||||||
|
|
||||||
class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
|
||||||
set_as_primary = forms.BooleanField(label='Set as primary IP for device', required=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = IPAddress
|
|
||||||
fields = ['address', 'vrf', 'tenant', 'status', 'interface', 'description']
|
|
||||||
|
|
||||||
def __init__(self, device, *args, **kwargs):
|
|
||||||
|
|
||||||
super(IPAddressForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.fields['vrf'].empty_label = 'Global'
|
|
||||||
|
|
||||||
interfaces = device.interfaces.all()
|
|
||||||
self.fields['interface'].queryset = interfaces
|
|
||||||
self.fields['interface'].required = True
|
|
||||||
|
|
||||||
# If this device has only one interface, select it by default.
|
|
||||||
if len(interfaces) == 1:
|
|
||||||
self.fields['interface'].initial = interfaces[0]
|
|
||||||
|
|
||||||
# If this device does not have any IP addresses assigned, default to setting the first IP as its primary.
|
|
||||||
if not IPAddress.objects.filter(interface__device=device).count():
|
|
||||||
self.fields['set_as_primary'].initial = True
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Inventory items
|
# Inventory items
|
||||||
#
|
#
|
||||||
|
@ -121,7 +121,6 @@ urlpatterns = [
|
|||||||
url(r'^devices/(?P<pk>\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'),
|
url(r'^devices/(?P<pk>\d+)/delete/$', views.DeviceDeleteView.as_view(), name='device_delete'),
|
||||||
url(r'^devices/(?P<pk>\d+)/inventory/$', views.device_inventory, name='device_inventory'),
|
url(r'^devices/(?P<pk>\d+)/inventory/$', views.device_inventory, name='device_inventory'),
|
||||||
url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', views.device_lldp_neighbors, name='device_lldp_neighbors'),
|
url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', views.device_lldp_neighbors, name='device_lldp_neighbors'),
|
||||||
url(r'^devices/(?P<pk>\d+)/ip-addresses/assign/$', views.ipaddress_assign, name='ipaddress_assign'),
|
|
||||||
url(r'^devices/(?P<pk>\d+)/add-secret/$', secret_add, name='device_addsecret'),
|
url(r'^devices/(?P<pk>\d+)/add-secret/$', secret_add, name='device_addsecret'),
|
||||||
url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceEditView.as_view(), name='service_assign'),
|
url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceEditView.as_view(), name='service_assign'),
|
||||||
url(r'^devices/(?P<object_id>\d+)/images/add/$', ImageAttachmentEditView.as_view(), name='device_add_image', kwargs={'model': Device}),
|
url(r'^devices/(?P<object_id>\d+)/images/add/$', ImageAttachmentEditView.as_view(), name='device_add_image', kwargs={'model': Device}),
|
||||||
|
@ -13,7 +13,7 @@ from django.urls import reverse
|
|||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
from ipam.models import Prefix, IPAddress, Service, VLAN
|
from ipam.models import Prefix, Service, VLAN
|
||||||
from circuits.models import Circuit
|
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
|
||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
@ -124,13 +124,13 @@ class ComponentCreateView(View):
|
|||||||
|
|
||||||
class ComponentEditView(ObjectEditView):
|
class ComponentEditView(ObjectEditView):
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return obj.device.get_absolute_url()
|
return obj.device.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
class ComponentDeleteView(ObjectDeleteView):
|
class ComponentDeleteView(ObjectDeleteView):
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return obj.device.get_absolute_url()
|
return obj.device.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ class RegionEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = Region
|
model = Region
|
||||||
form_class = forms.RegionForm
|
form_class = forms.RegionForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('dcim:region_list')
|
return reverse('dcim:region_list')
|
||||||
|
|
||||||
|
|
||||||
@ -242,7 +242,7 @@ class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = RackGroup
|
model = RackGroup
|
||||||
form_class = forms.RackGroupForm
|
form_class = forms.RackGroupForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('dcim:rackgroup_list')
|
return reverse('dcim:rackgroup_list')
|
||||||
|
|
||||||
|
|
||||||
@ -268,7 +268,7 @@ class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = RackRole
|
model = RackRole
|
||||||
form_class = forms.RackRoleForm
|
form_class = forms.RackRoleForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('dcim:rackrole_list')
|
return reverse('dcim:rackrole_list')
|
||||||
|
|
||||||
|
|
||||||
@ -379,7 +379,7 @@ class RackReservationEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
obj.user = request.user
|
obj.user = request.user
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return obj.rack.get_absolute_url()
|
return obj.rack.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
@ -387,7 +387,7 @@ class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
permission_required = 'dcim.delete_rackreservation'
|
permission_required = 'dcim.delete_rackreservation'
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return obj.rack.get_absolute_url()
|
return obj.rack.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
@ -412,7 +412,7 @@ class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
form_class = forms.ManufacturerForm
|
form_class = forms.ManufacturerForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('dcim:manufacturer_list')
|
return reverse('dcim:manufacturer_list')
|
||||||
|
|
||||||
|
|
||||||
@ -632,7 +632,7 @@ class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
form_class = forms.DeviceRoleForm
|
form_class = forms.DeviceRoleForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('dcim:devicerole_list')
|
return reverse('dcim:devicerole_list')
|
||||||
|
|
||||||
|
|
||||||
@ -657,7 +657,7 @@ class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = Platform
|
model = Platform
|
||||||
form_class = forms.PlatformForm
|
form_class = forms.PlatformForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('dcim:platform_list')
|
return reverse('dcim:platform_list')
|
||||||
|
|
||||||
|
|
||||||
@ -700,19 +700,15 @@ def device(request, pk):
|
|||||||
interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\
|
interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\
|
||||||
.filter(device=device, mgmt_only=False)\
|
.filter(device=device, mgmt_only=False)\
|
||||||
.select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
|
.select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
|
||||||
'circuit_termination__circuit')
|
'circuit_termination__circuit').prefetch_related('ip_addresses')
|
||||||
mgmt_interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\
|
mgmt_interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\
|
||||||
.filter(device=device, mgmt_only=True)\
|
.filter(device=device, mgmt_only=True)\
|
||||||
.select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
|
.select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
|
||||||
'circuit_termination__circuit')
|
'circuit_termination__circuit').prefetch_related('ip_addresses')
|
||||||
device_bays = natsorted(
|
device_bays = natsorted(
|
||||||
DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
|
DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
|
||||||
key=attrgetter('name')
|
key=attrgetter('name')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Gather relevant device objects
|
|
||||||
ip_addresses = IPAddress.objects.filter(interface__device=device).select_related('interface', 'vrf')\
|
|
||||||
.order_by('address')
|
|
||||||
services = Service.objects.filter(device=device)
|
services = Service.objects.filter(device=device)
|
||||||
secrets = device.secrets.all()
|
secrets = device.secrets.all()
|
||||||
|
|
||||||
@ -743,7 +739,6 @@ def device(request, pk):
|
|||||||
'interfaces': interfaces,
|
'interfaces': interfaces,
|
||||||
'mgmt_interfaces': mgmt_interfaces,
|
'mgmt_interfaces': mgmt_interfaces,
|
||||||
'device_bays': device_bays,
|
'device_bays': device_bays,
|
||||||
'ip_addresses': ip_addresses,
|
|
||||||
'services': services,
|
'services': services,
|
||||||
'secrets': secrets,
|
'secrets': secrets,
|
||||||
'related_devices': related_devices,
|
'related_devices': related_devices,
|
||||||
@ -755,7 +750,6 @@ class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
permission_required = 'dcim.change_device'
|
permission_required = 'dcim.change_device'
|
||||||
model = Device
|
model = Device
|
||||||
form_class = forms.DeviceForm
|
form_class = forms.DeviceForm
|
||||||
fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
|
|
||||||
template_name = 'dcim/device_edit.html'
|
template_name = 'dcim/device_edit.html'
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -1567,47 +1561,6 @@ class InterfaceConnectionsListView(ObjectListView):
|
|||||||
template_name = 'dcim/interface_connections_list.html'
|
template_name = 'dcim/interface_connections_list.html'
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# IP addresses
|
|
||||||
#
|
|
||||||
|
|
||||||
@permission_required(['dcim.change_device', 'ipam.add_ipaddress'])
|
|
||||||
def ipaddress_assign(request, pk):
|
|
||||||
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = forms.IPAddressForm(device, request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
ipaddress = form.save(commit=False)
|
|
||||||
ipaddress.interface = form.cleaned_data['interface']
|
|
||||||
ipaddress.save()
|
|
||||||
form.save_custom_fields()
|
|
||||||
messages.success(request, u"Added new IP address {} to interface {}.".format(ipaddress, ipaddress.interface))
|
|
||||||
|
|
||||||
if form.cleaned_data['set_as_primary']:
|
|
||||||
if ipaddress.family == 4:
|
|
||||||
device.primary_ip4 = ipaddress
|
|
||||||
elif ipaddress.family == 6:
|
|
||||||
device.primary_ip6 = ipaddress
|
|
||||||
device.save()
|
|
||||||
|
|
||||||
if '_addanother' in request.POST:
|
|
||||||
return redirect('dcim:ipaddress_assign', pk=device.pk)
|
|
||||||
else:
|
|
||||||
return redirect('dcim:device', pk=device.pk)
|
|
||||||
|
|
||||||
else:
|
|
||||||
form = forms.IPAddressForm(device)
|
|
||||||
|
|
||||||
return render(request, 'dcim/ipaddress_assign.html', {
|
|
||||||
'device': device,
|
|
||||||
'form': form,
|
|
||||||
'return_url': reverse('dcim:device', kwargs={'pk': device.pk}),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Inventory items
|
# Inventory items
|
||||||
#
|
#
|
||||||
|
@ -6,7 +6,7 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, BootstrapMixin, BulkImportForm, CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch,
|
APISelect, BootstrapMixin, BulkImportForm, CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch,
|
||||||
SlugField, add_blank_choice,
|
ReturnURLForm, SlugField, add_blank_choice,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -210,28 +210,33 @@ class PrefixFromCSVForm(forms.ModelForm):
|
|||||||
site = self.cleaned_data.get('site')
|
site = self.cleaned_data.get('site')
|
||||||
vlan_group_name = self.cleaned_data.get('vlan_group_name')
|
vlan_group_name = self.cleaned_data.get('vlan_group_name')
|
||||||
vlan_vid = self.cleaned_data.get('vlan_vid')
|
vlan_vid = self.cleaned_data.get('vlan_vid')
|
||||||
|
|
||||||
# Validate VLAN
|
|
||||||
vlan_group = None
|
vlan_group = None
|
||||||
|
vlan = None
|
||||||
|
|
||||||
|
# Validate VLAN group
|
||||||
if vlan_group_name:
|
if vlan_group_name:
|
||||||
try:
|
try:
|
||||||
vlan_group = VLANGroup.objects.get(site=site, name=vlan_group_name)
|
vlan_group = VLANGroup.objects.get(site=site, name=vlan_group_name)
|
||||||
except VLANGroup.DoesNotExist:
|
except VLANGroup.DoesNotExist:
|
||||||
self.add_error('vlan_group_name', "Invalid VLAN group ({} - {}).".format(site, vlan_group_name))
|
if site:
|
||||||
if vlan_vid and vlan_group:
|
self.add_error('vlan_group_name', "Invalid VLAN group ({} - {}).".format(site, vlan_group_name))
|
||||||
|
else:
|
||||||
|
self.add_error('vlan_group_name', "Invalid global VLAN group ({}).".format(vlan_group_name))
|
||||||
|
|
||||||
|
# Validate VLAN
|
||||||
|
if vlan_vid:
|
||||||
try:
|
try:
|
||||||
self.instance.vlan = VLAN.objects.get(group=vlan_group, vid=vlan_vid)
|
self.instance.vlan = VLAN.objects.get(site=site, group=vlan_group, vid=vlan_vid)
|
||||||
except VLAN.DoesNotExist:
|
except VLAN.DoesNotExist:
|
||||||
self.add_error('vlan_vid', "Invalid VLAN ID ({} - {}).".format(vlan_group, vlan_vid))
|
if site:
|
||||||
elif vlan_vid and site:
|
self.add_error('vlan_vid', "Invalid VLAN ID ({}) for site {}.".format(vlan_vid, site))
|
||||||
try:
|
elif vlan_group:
|
||||||
self.instance.vlan = VLAN.objects.get(site=site, vid=vlan_vid)
|
self.add_error('vlan_vid', "Invalid VLAN ID ({}) for group {}.".format(vlan_vid, vlan_group_name))
|
||||||
except VLAN.DoesNotExist:
|
elif not vlan_group_name:
|
||||||
self.add_error('vlan_vid', "Invalid VLAN ID ({}) for site {}.".format(vlan_vid, site))
|
self.add_error('vlan_vid', "Invalid global VLAN ID ({}).".format(vlan_vid))
|
||||||
except VLAN.MultipleObjectsReturned:
|
except VLAN.MultipleObjectsReturned:
|
||||||
self.add_error('vlan_vid', "Multiple VLANs found ({} - VID {})".format(site, vlan_vid))
|
self.add_error('vlan_vid', "Multiple VLANs found ({} - VID {})".format(site, vlan_vid))
|
||||||
elif vlan_vid:
|
self.instance.vlan = vlan
|
||||||
self.add_error('vlan_vid', "Must specify site and/or VLAN group when assigning a VLAN.")
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -302,21 +307,46 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
# IP addresses
|
# IP addresses
|
||||||
#
|
#
|
||||||
|
|
||||||
class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
|
||||||
nat_site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
|
interface_site = forms.ModelChoiceField(
|
||||||
widget=forms.Select(attrs={'filter-for': 'nat_device'}))
|
queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
|
||||||
nat_device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
|
attrs={'filter-for': 'interface_rack'}
|
||||||
widget=APISelect(api_url='/api/dcim/devices/?site_id={{nat_site}}',
|
)
|
||||||
display_field='display_name',
|
)
|
||||||
attrs={'filter-for': 'nat_inside'}))
|
interface_rack = forms.ModelChoiceField(
|
||||||
livesearch = forms.CharField(required=False, label='IP Address', widget=Livesearch(
|
queryset=Rack.objects.all(), required=False, label='Rack', widget=APISelect(
|
||||||
query_key='q', query_url='ipam-api:ipaddress-list', field_to_update='nat_inside', obj_label='address')
|
api_url='/api/dcim/racks/?site_id={{interface_site}}', display_field='display_name',
|
||||||
|
attrs={'filter-for': 'interface_device'}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
interface_device = forms.ModelChoiceField(
|
||||||
|
queryset=Device.objects.all(), required=False, label='Device', widget=APISelect(
|
||||||
|
api_url='/api/dcim/devices/?site_id={{interface_site}}&rack_id={{interface_rack}}',
|
||||||
|
display_field='display_name', attrs={'filter-for': 'interface'}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
nat_site = forms.ModelChoiceField(
|
||||||
|
queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
|
||||||
|
attrs={'filter-for': 'nat_device'}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
nat_device = forms.ModelChoiceField(
|
||||||
|
queryset=Device.objects.all(), required=False, label='Device', widget=APISelect(
|
||||||
|
api_url='/api/dcim/devices/?site_id={{nat_site}}', display_field='display_name',
|
||||||
|
attrs={'filter-for': 'nat_inside'}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
livesearch = forms.CharField(
|
||||||
|
required=False, label='IP Address', widget=Livesearch(
|
||||||
|
query_key='q', query_url='ipam-api:ipaddress_list', field_to_update='nat_inside', obj_label='address'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ['address', 'vrf', 'tenant', 'status', 'nat_inside', 'description']
|
fields = ['address', 'vrf', 'tenant', 'status', 'interface', 'nat_inside', 'description']
|
||||||
widgets = {
|
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')
|
'nat_inside': APISelect(api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}', display_field='address')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,8 +355,37 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
|||||||
|
|
||||||
self.fields['vrf'].empty_label = 'Global'
|
self.fields['vrf'].empty_label = 'Global'
|
||||||
|
|
||||||
if self.instance.nat_inside:
|
# If an interface has been assigned, initialize site, rack, and device
|
||||||
|
if self.instance.interface:
|
||||||
|
self.initial['interface_site'] = self.instance.interface.device.site
|
||||||
|
self.initial['interface_rack'] = self.instance.interface.device.rack
|
||||||
|
self.initial['interface_device'] = self.instance.interface.device
|
||||||
|
|
||||||
|
# Limit rack choices
|
||||||
|
if self.is_bound and self.data.get('interface_site'):
|
||||||
|
self.fields['interface_rack'].queryset = Rack.objects.filter(site__pk=self.data['interface_site'])
|
||||||
|
elif self.initial.get('interface_site'):
|
||||||
|
self.fields['interface_rack'].queryset = Rack.objects.filter(site=self.initial['interface_site'])
|
||||||
|
else:
|
||||||
|
self.fields['interface_rack'].choices = []
|
||||||
|
|
||||||
|
# Limit device choices
|
||||||
|
if self.is_bound and self.data.get('interface_rack'):
|
||||||
|
self.fields['interface_device'].queryset = Device.objects.filter(rack=self.data['interface_rack'])
|
||||||
|
elif self.initial.get('interface_rack'):
|
||||||
|
self.fields['interface_device'].queryset = Device.objects.filter(rack=self.initial['interface_rack'])
|
||||||
|
else:
|
||||||
|
self.fields['interface_device'].choices = []
|
||||||
|
|
||||||
|
# Limit interface choices
|
||||||
|
if self.is_bound and self.data.get('interface_device'):
|
||||||
|
self.fields['interface'].queryset = Interface.objects.filter(device=self.data['interface_device'])
|
||||||
|
elif self.initial.get('interface_device'):
|
||||||
|
self.fields['interface'].queryset = Interface.objects.filter(device=self.initial['interface_device'])
|
||||||
|
else:
|
||||||
|
self.fields['interface'].choices = []
|
||||||
|
|
||||||
|
if self.instance.nat_inside:
|
||||||
nat_inside = self.instance.nat_inside
|
nat_inside = self.instance.nat_inside
|
||||||
# If the IP is assigned to an interface, populate site/device fields accordingly
|
# If the IP is assigned to an interface, populate site/device fields accordingly
|
||||||
if self.instance.nat_inside.interface:
|
if self.instance.nat_inside.interface:
|
||||||
@ -340,9 +399,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk)
|
self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# Initialize nat_device choices if nat_site is set
|
# Initialize nat_device choices if nat_site is set
|
||||||
if self.is_bound and self.data.get('nat_site'):
|
if self.is_bound and self.data.get('nat_site'):
|
||||||
self.fields['nat_device'].queryset = Device.objects.filter(site__pk=self.data['nat_site'])
|
self.fields['nat_device'].queryset = Device.objects.filter(site__pk=self.data['nat_site'])
|
||||||
@ -350,7 +407,6 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
|||||||
self.fields['nat_device'].queryset = Device.objects.filter(site=self.initial['nat_site'])
|
self.fields['nat_device'].queryset = Device.objects.filter(site=self.initial['nat_site'])
|
||||||
else:
|
else:
|
||||||
self.fields['nat_device'].choices = []
|
self.fields['nat_device'].choices = []
|
||||||
|
|
||||||
# Initialize nat_inside choices if nat_device is set
|
# Initialize nat_inside choices if nat_device is set
|
||||||
if self.is_bound and self.data.get('nat_device'):
|
if self.is_bound and self.data.get('nat_device'):
|
||||||
self.fields['nat_inside'].queryset = IPAddress.objects.filter(
|
self.fields['nat_inside'].queryset = IPAddress.objects.filter(
|
||||||
|
@ -58,8 +58,6 @@ urlpatterns = [
|
|||||||
url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'),
|
url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'),
|
||||||
url(r'^ip-addresses/(?P<pk>\d+)/$', views.ipaddress, name='ipaddress'),
|
url(r'^ip-addresses/(?P<pk>\d+)/$', views.ipaddress, name='ipaddress'),
|
||||||
url(r'^ip-addresses/(?P<pk>\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'),
|
url(r'^ip-addresses/(?P<pk>\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'),
|
||||||
url(r'^ip-addresses/(?P<pk>\d+)/assign/$', views.ipaddress_assign, name='ipaddress_assign'),
|
|
||||||
url(r'^ip-addresses/(?P<pk>\d+)/remove/$', views.ipaddress_remove, name='ipaddress_remove'),
|
|
||||||
url(r'^ip-addresses/(?P<pk>\d+)/delete/$', views.IPAddressDeleteView.as_view(), name='ipaddress_delete'),
|
url(r'^ip-addresses/(?P<pk>\d+)/delete/$', views.IPAddressDeleteView.as_view(), name='ipaddress_delete'),
|
||||||
|
|
||||||
# VLAN groups
|
# VLAN groups
|
||||||
|
@ -244,7 +244,7 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = RIR
|
model = RIR
|
||||||
form_class = forms.RIRForm
|
form_class = forms.RIRForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('ipam:rir_list')
|
return reverse('ipam:rir_list')
|
||||||
|
|
||||||
|
|
||||||
@ -370,7 +370,7 @@ class RoleEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = Role
|
model = Role
|
||||||
form_class = forms.RoleForm
|
form_class = forms.RoleForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('ipam:role_list')
|
return reverse('ipam:role_list')
|
||||||
|
|
||||||
|
|
||||||
@ -464,7 +464,6 @@ class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = Prefix
|
model = Prefix
|
||||||
form_class = forms.PrefixForm
|
form_class = forms.PrefixForm
|
||||||
template_name = 'ipam/prefix_edit.html'
|
template_name = 'ipam/prefix_edit.html'
|
||||||
fields_initial = ['vrf', 'tenant', 'site', 'prefix', 'vlan']
|
|
||||||
default_return_url = 'ipam:prefix_list'
|
default_return_url = 'ipam:prefix_list'
|
||||||
|
|
||||||
|
|
||||||
@ -572,80 +571,10 @@ def ipaddress(request, pk):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@permission_required(['dcim.change_device', 'ipam.change_ipaddress'])
|
|
||||||
def ipaddress_assign(request, pk):
|
|
||||||
|
|
||||||
ipaddress = get_object_or_404(IPAddress, pk=pk)
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = forms.IPAddressAssignForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
interface = form.cleaned_data['interface']
|
|
||||||
ipaddress.interface = interface
|
|
||||||
ipaddress.save()
|
|
||||||
messages.success(request, u"Assigned IP address {} to interface {}.".format(ipaddress, ipaddress.interface))
|
|
||||||
|
|
||||||
if form.cleaned_data['set_as_primary']:
|
|
||||||
device = interface.device
|
|
||||||
if ipaddress.family == 4:
|
|
||||||
device.primary_ip4 = ipaddress
|
|
||||||
elif ipaddress.family == 6:
|
|
||||||
device.primary_ip6 = ipaddress
|
|
||||||
device.save()
|
|
||||||
|
|
||||||
return redirect('ipam:ipaddress', pk=ipaddress.pk)
|
|
||||||
else:
|
|
||||||
assert False, form.errors
|
|
||||||
|
|
||||||
else:
|
|
||||||
form = forms.IPAddressAssignForm()
|
|
||||||
|
|
||||||
return render(request, 'ipam/ipaddress_assign.html', {
|
|
||||||
'ipaddress': ipaddress,
|
|
||||||
'form': form,
|
|
||||||
'return_url': reverse('ipam:ipaddress', kwargs={'pk': ipaddress.pk}),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required(['dcim.change_device', 'ipam.change_ipaddress'])
|
|
||||||
def ipaddress_remove(request, pk):
|
|
||||||
|
|
||||||
ipaddress = get_object_or_404(IPAddress, pk=pk)
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = ConfirmationForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
device = ipaddress.interface.device
|
|
||||||
ipaddress.interface = None
|
|
||||||
ipaddress.save()
|
|
||||||
messages.success(request, u"Removed IP address {} from {}.".format(ipaddress, device))
|
|
||||||
|
|
||||||
if device.primary_ip4 == ipaddress.pk:
|
|
||||||
device.primary_ip4 = None
|
|
||||||
device.save()
|
|
||||||
elif device.primary_ip6 == ipaddress.pk:
|
|
||||||
device.primary_ip6 = None
|
|
||||||
device.save()
|
|
||||||
|
|
||||||
return redirect('ipam:ipaddress', pk=ipaddress.pk)
|
|
||||||
|
|
||||||
else:
|
|
||||||
form = ConfirmationForm()
|
|
||||||
|
|
||||||
return render(request, 'ipam/ipaddress_unassign.html', {
|
|
||||||
'ipaddress': ipaddress,
|
|
||||||
'form': form,
|
|
||||||
'return_url': reverse('ipam:ipaddress', kwargs={'pk': ipaddress.pk}),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
|
class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
|
||||||
permission_required = 'ipam.change_ipaddress'
|
permission_required = 'ipam.change_ipaddress'
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
form_class = forms.IPAddressForm
|
form_class = forms.IPAddressForm
|
||||||
fields_initial = ['address', 'vrf']
|
|
||||||
template_name = 'ipam/ipaddress_edit.html'
|
template_name = 'ipam/ipaddress_edit.html'
|
||||||
default_return_url = 'ipam:ipaddress_list'
|
default_return_url = 'ipam:ipaddress_list'
|
||||||
|
|
||||||
@ -718,7 +647,7 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
form_class = forms.VLANGroupForm
|
form_class = forms.VLANGroupForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('ipam:vlangroup_list')
|
return reverse('ipam:vlangroup_list')
|
||||||
|
|
||||||
|
|
||||||
@ -807,7 +736,7 @@ class ServiceEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
|
obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return obj.device.get_absolute_url()
|
return obj.device.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
|
@ -316,6 +316,16 @@ li.occupied + li.available {
|
|||||||
border-top: 1px solid #474747;
|
border-top: 1px solid #474747;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Devices */
|
||||||
|
table.component-list tr.ipaddress td {
|
||||||
|
background-color: #eeffff;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
table.component-list tr.ipaddress:hover td {
|
||||||
|
background-color: #e6f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
.banner-bottom {
|
.banner-bottom {
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
|
@ -42,7 +42,7 @@ class SecretRoleEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = SecretRole
|
model = SecretRole
|
||||||
form_class = forms.SecretRoleForm
|
form_class = forms.SecretRoleForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('secrets:secretrole_list')
|
return reverse('secrets:secretrole_list')
|
||||||
|
|
||||||
|
|
||||||
|
@ -274,10 +274,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container wrapper">
|
<div class="container wrapper">
|
||||||
{% if settings.BANNER_TOP %}
|
{% if settings.BANNER_TOP %}
|
||||||
<div class="alert alert-info text-center" role="alert">
|
<div class="alert alert-info text-center" role="alert">
|
||||||
{{ settings.BANNER_TOP|safe }}
|
{{ settings.BANNER_TOP|safe }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if settings.MAINTENANCE_MODE %}
|
{% if settings.MAINTENANCE_MODE %}
|
||||||
@ -286,24 +286,24 @@
|
|||||||
<p>NetBox is currently in maintenance mode. Functionality may be limited.</p>
|
<p>NetBox is currently in maintenance mode. Functionality may be limited.</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{{ message.tags }} alert-dismissable" role="alert">
|
<div class="alert alert-{{ message.tags }} alert-dismissable" role="alert">
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
{{ message|safe }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
<div class="push"></div>
|
<div class="push"></div>
|
||||||
{% if settings.BANNER_BOTTOM %}
|
{% if settings.BANNER_BOTTOM %}
|
||||||
<div class="alert alert-info text-center banner-bottom" role="alert">
|
<div class="alert alert-info text-center banner-bottom" role="alert">
|
||||||
{{ settings.BANNER_BOTTOM|safe }}
|
{{ settings.BANNER_BOTTOM|safe }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-4">
|
||||||
<p class="text-muted">{{ settings.HOSTNAME }} (v{{ settings.VERSION }})</p>
|
<p class="text-muted">{{ settings.HOSTNAME }} (v{{ settings.VERSION }})</p>
|
||||||
@ -320,8 +320,8 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var netbox_api_path = "/{{ settings.BASE_PATH }}api/";
|
var netbox_api_path = "/{{ settings.BASE_PATH }}api/";
|
||||||
</script>
|
</script>
|
||||||
|
@ -194,35 +194,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<strong>IP Addresses</strong>
|
|
||||||
</div>
|
|
||||||
{% if ip_addresses %}
|
|
||||||
<table class="table table-hover panel-body">
|
|
||||||
{% for ip in ip_addresses %}
|
|
||||||
{% include 'dcim/inc/ipaddress.html' %}
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% elif interfaces or mgmt_interfaces %}
|
|
||||||
<div class="panel-body text-muted">
|
|
||||||
None assigned
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="panel-body">
|
|
||||||
<a href="{% url 'dcim:interface_add' pk=device.pk %}">Create an interface</a> to assign an IP.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.ipam.add_ipaddress %}
|
|
||||||
{% if interfaces or mgmt_interfaces %}
|
|
||||||
<div class="panel-footer text-right">
|
|
||||||
<a href="{% url 'dcim:ipaddress_assign' pk=device.pk %}" class="btn btn-xs btn-primary">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Assign IP address
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Services</strong>
|
<strong>Services</strong>
|
||||||
@ -250,7 +221,7 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Critical Connections</strong>
|
<strong>Critical Connections</strong>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body component-list">
|
||||||
{% for iface in mgmt_interfaces %}
|
{% for iface in mgmt_interfaces %}
|
||||||
{% include 'dcim/inc/interface.html' with icon='wrench' %}
|
{% include 'dcim/inc/interface.html' with icon='wrench' %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
@ -389,7 +360,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body component-list">
|
||||||
{% for devicebay in device_bays %}
|
{% for devicebay in device_bays %}
|
||||||
{% include 'dcim/inc/devicebay.html' with selectable=True %}
|
{% include 'dcim/inc/devicebay.html' with selectable=True %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
@ -430,6 +401,9 @@
|
|||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Interfaces</strong>
|
<strong>Interfaces</strong>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
<button class="btn btn-default btn-xs toggle-ips" selected="selected">
|
||||||
|
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
|
||||||
|
</button>
|
||||||
{% if perms.dcim.change_interface and interfaces|length > 1 %}
|
{% if perms.dcim.change_interface and interfaces|length > 1 %}
|
||||||
<button class="btn btn-default btn-xs toggle">
|
<button class="btn btn-default btn-xs toggle">
|
||||||
<span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all
|
<span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all
|
||||||
@ -442,7 +416,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table id="interfaces_table" class="table table-hover panel-body component-list">
|
||||||
{% for iface in interfaces %}
|
{% for iface in interfaces %}
|
||||||
{% include 'dcim/inc/interface.html' with selectable=True %}
|
{% include 'dcim/inc/interface.html' with selectable=True %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
@ -499,7 +473,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body component-list">
|
||||||
{% for csp in cs_ports %}
|
{% for csp in cs_ports %}
|
||||||
{% include 'dcim/inc/consoleserverport.html' with selectable=True %}
|
{% include 'dcim/inc/consoleserverport.html' with selectable=True %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
@ -551,7 +525,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
<table class="table table-hover panel-body component-list">
|
||||||
{% for po in power_outlets %}
|
{% for po in power_outlets %}
|
||||||
{% include 'dcim/inc/poweroutlet.html' with selectable=True %}
|
{% include 'dcim/inc/poweroutlet.html' with selectable=True %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
@ -642,6 +616,18 @@ $(".powerport-toggle").click(function() {
|
|||||||
$(".interface-toggle").click(function() {
|
$(".interface-toggle").click(function() {
|
||||||
return toggleConnection($(this), "dcim/interface-connections/");
|
return toggleConnection($(this), "dcim/interface-connections/");
|
||||||
});
|
});
|
||||||
|
// Toggle the display of IP addresses under interfaces
|
||||||
|
$('button.toggle-ips').click(function() {
|
||||||
|
var selected = $(this).attr('selected');
|
||||||
|
if (selected) {
|
||||||
|
$('#interfaces_table tr.ipaddress').hide();
|
||||||
|
} else {
|
||||||
|
$('#interfaces_table tr.ipaddress').show();
|
||||||
|
}
|
||||||
|
$(this).attr('selected', !selected);
|
||||||
|
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="{% static 'js/graphs.js' %}?v{{ settings.VERSION }}"></script>
|
<script src="{% static 'js/graphs.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
<script src="{% static 'js/secrets.js' %}?v{{ settings.VERSION }}"></script>
|
<script src="{% static 'js/secrets.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<tr{% if cp.cs_port and not cp.connection_status %} class="info"{% endif %}>
|
<tr class="consoleport{% if cp.cs_port and not cp.connection_status %} info{% endif %}">
|
||||||
{% if selectable and perms.dcim.change_consoleport or perms.dcim.delete_consoleport %}
|
{% if selectable and perms.dcim.change_consoleport or perms.dcim.delete_consoleport %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ cp.pk }}" />
|
<input name="pk" type="checkbox" value="{{ cp.pk }}" />
|
||||||
@ -7,7 +7,6 @@
|
|||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-keyboard-o"></i> {{ cp.name }}
|
<i class="fa fa-fw fa-keyboard-o"></i> {{ cp.name }}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
{% if cp.cs_port %}
|
{% if cp.cs_port %}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device' pk=cp.cs_port.device.pk %}">{{ cp.cs_port.device }}</a>
|
<a href="{% url 'dcim:device' pk=cp.cs_port.device.pk %}">{{ cp.cs_port.device }}</a>
|
||||||
@ -20,7 +19,7 @@
|
|||||||
<span class="text-muted">Not connected</span>
|
<span class="text-muted">Not connected</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="text-right">
|
<td colspan="2" class="text-right">
|
||||||
{% if perms.dcim.change_consoleport %}
|
{% if perms.dcim.change_consoleport %}
|
||||||
{% if cp.cs_port %}
|
{% if cp.cs_port %}
|
||||||
{% if cp.connection_status %}
|
{% if cp.connection_status %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<tr{% if csp.connected_console and not csp.connected_console.connection_status %} class="info"{% endif %}>
|
<tr class="consoleserverport{% if csp.connected_console and not csp.connected_console.connection_status %} info{% endif %}">
|
||||||
{% if selectable and perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
|
{% if selectable and perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ csp.pk }}" />
|
<input name="pk" type="checkbox" value="{{ csp.pk }}" />
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<span class="text-muted">Not connected</span>
|
<span class="text-muted">Not connected</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="text-right">
|
<td colspan="2" class="text-right">
|
||||||
{% if perms.dcim.change_consoleserverport %}
|
{% if perms.dcim.change_consoleserverport %}
|
||||||
{% if csp.connected_console %}
|
{% if csp.connected_console %}
|
||||||
{% if csp.connected_console.connection_status %}
|
{% if csp.connected_console.connection_status %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<tr>
|
<tr class="devicebay">
|
||||||
{% if selectable and perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
|
{% if selectable and perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ devicebay.pk }}" />
|
<input name="pk" type="checkbox" value="{{ devicebay.pk }}" />
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<span class="text-muted">Vacant</span>
|
<span class="text-muted">Vacant</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="text-right">
|
<td colspan="2" class="text-right">
|
||||||
{% if perms.dcim.change_devicebay %}
|
{% if perms.dcim.change_devicebay %}
|
||||||
{% if devicebay.installed_device %}
|
{% if devicebay.installed_device %}
|
||||||
<a href="{% url 'dcim:devicebay_depopulate' pk=devicebay.pk %}" class="btn btn-danger btn-xs">
|
<a href="{% url 'dcim:devicebay_depopulate' pk=devicebay.pk %}" class="btn btn-danger btn-xs">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<tr{% if iface.connection and not iface.connection.connection_status %} class="info"{% endif %}>
|
<tr class="interface{% if iface.connection and not iface.connection.connection_status %} info{% endif %}">
|
||||||
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
||||||
@ -16,10 +16,9 @@
|
|||||||
<br /><small class="text-muted">{{ iface.member_interfaces.all|join:", "|default:"No members" }}</small>
|
<br /><small class="text-muted">{{ iface.member_interfaces.all|join:", "|default:"No members" }}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
{% if iface.is_lag %}
|
||||||
<small>{{ iface.mac_address|default:'' }}</small>
|
<td colspan="2" class="text-muted">LAG interface</td>
|
||||||
</td>
|
{% elif iface.is_virtual %}
|
||||||
{% if iface.is_virtual %}
|
|
||||||
<td colspan="2" class="text-muted">Virtual interface</td>
|
<td colspan="2" class="text-muted">Virtual interface</td>
|
||||||
{% elif iface.connection %}
|
{% elif iface.connection %}
|
||||||
{% with iface.connected_interface as connected_iface %}
|
{% with iface.connected_interface as connected_iface %}
|
||||||
@ -51,7 +50,7 @@
|
|||||||
<span class="text-muted">Not connected</span>
|
<span class="text-muted">Not connected</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="text-right">
|
<td colspan="2" class="text-right">
|
||||||
{% if show_graphs %}
|
{% if show_graphs %}
|
||||||
{% if iface.circuit_termination or iface.connection %}
|
{% if iface.circuit_termination or iface.connection %}
|
||||||
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface-graphs' pk=iface.pk %}" title="Show graphs">
|
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface-graphs' pk=iface.pk %}" title="Show graphs">
|
||||||
@ -59,6 +58,11 @@
|
|||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if perms.ipam.add_ipaddress %}
|
||||||
|
<a href="{% url 'ipam:ipaddress_add' %}?interface_site={{ device.site.pk }}&interface_rack={{ device.rack.pk }}&interface_device={{ device.pk }}&interface={{ iface.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-success" title="Add IP address">
|
||||||
|
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% if perms.dcim.change_interface %}
|
{% if perms.dcim.change_interface %}
|
||||||
{% if not iface.is_virtual %}
|
{% if not iface.is_virtual %}
|
||||||
{% if iface.connection %}
|
{% if iface.connection %}
|
||||||
@ -71,19 +75,19 @@
|
|||||||
<i class="fa fa-plug" aria-hidden="true"></i>
|
<i class="fa fa-plug" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'dcim:interfaceconnection_delete' pk=iface.connection.pk %}?device={{ device.pk }}" class="btn btn-danger btn-xs" title="Delete connection">
|
<a href="{% url 'dcim:interfaceconnection_delete' pk=iface.connection.pk %}?device={{ device.pk }}" class="btn btn-danger btn-xs" title="Disconnect">
|
||||||
<i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% elif iface.circuit_termination and perms.circuits.change_circuittermination %}
|
{% elif iface.circuit_termination and perms.circuits.change_circuittermination %}
|
||||||
<button class="btn btn-warning btn-xs interface-toggle connected" disabled="disabled" title="Circuits cannot be marked as planned or connected">
|
<button class="btn btn-warning btn-xs interface-toggle connected" disabled="disabled" title="Circuits cannot be marked as planned or connected">
|
||||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'circuits:circuittermination_edit' pk=iface.circuit_termination.pk %}" class="btn btn-danger btn-xs" title="Edit circuit termination">
|
<a href="{% url 'circuits:circuittermination_edit' pk=iface.circuit_termination.pk %}" class="btn btn-danger btn-xs" title="Edit circuit termination">
|
||||||
<i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'dcim:interfaceconnection_add' pk=device.pk %}?interface_a={{ iface.pk }}" class="btn btn-success btn-xs" title="Connect">
|
<a href="{% url 'dcim:interfaceconnection_add' pk=device.pk %}?interface_a={{ iface.pk }}" class="btn btn-success btn-xs" title="Connect">
|
||||||
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -104,3 +108,41 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% for ip in iface.ip_addresses.all %}
|
||||||
|
<tr class="ipaddress">
|
||||||
|
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
<td colspan="2">
|
||||||
|
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
|
||||||
|
{% if ip.description %}
|
||||||
|
<i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i>
|
||||||
|
{% endif %}
|
||||||
|
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
|
||||||
|
<span class="label label-success">Primary</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{% if ip.vrf %}
|
||||||
|
<a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}">{{ ip.vrf }}</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">Global</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{% if perms.ipam.edit_ipaddress %}
|
||||||
|
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
|
||||||
|
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.delete_ipaddress %}
|
||||||
|
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
|
||||||
|
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ ip.vrf|default:"Global" }}
|
|
||||||
</td>
|
|
||||||
<td>{{ ip.interface }}</td>
|
|
||||||
<td>
|
|
||||||
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
|
|
||||||
<span class="label label-success">Primary</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if perms.ipam.delete_ipaddress %}
|
|
||||||
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
|
|
||||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
@ -1,4 +1,4 @@
|
|||||||
<tr{% if po.connected_port and not po.connected_port.connection_status %} class="info"{% endif %}>
|
<tr class="poweroutlet{% if po.connected_port and not po.connected_port.connection_status %} info{% endif %}">
|
||||||
{% if selectable and perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
|
{% if selectable and perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ po.pk }}" />
|
<input name="pk" type="checkbox" value="{{ po.pk }}" />
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<span class="text-muted">Not connected</span>
|
<span class="text-muted">Not connected</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="text-right">
|
<td colspan="2" class="text-right">
|
||||||
{% if perms.dcim.change_poweroutlet %}
|
{% if perms.dcim.change_poweroutlet %}
|
||||||
{% if po.connected_port %}
|
{% if po.connected_port %}
|
||||||
{% if po.connected_port.connection_status %}
|
{% if po.connected_port.connection_status %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<tr{% if pp.power_outlet and not pp.connection_status %} class="info"{% endif %}>
|
<tr class="powerport{% if pp.power_outlet and not pp.connection_status %} info{% endif %}">
|
||||||
{% if selectable and perms.dcim.change_powerport or perms.dcim.delete_powerport %}
|
{% if selectable and perms.dcim.change_powerport or perms.dcim.delete_powerport %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ pp.pk }}" />
|
<input name="pk" type="checkbox" value="{{ pp.pk }}" />
|
||||||
@ -7,7 +7,6 @@
|
|||||||
<td>
|
<td>
|
||||||
<i class="fa fa-fw fa-bolt"></i> {{ pp.name }}
|
<i class="fa fa-fw fa-bolt"></i> {{ pp.name }}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
{% if pp.power_outlet %}
|
{% if pp.power_outlet %}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device' pk=pp.power_outlet.device.pk %}">{{ pp.power_outlet.device }}</a>
|
<a href="{% url 'dcim:device' pk=pp.power_outlet.device.pk %}">{{ pp.power_outlet.device }}</a>
|
||||||
@ -20,7 +19,7 @@
|
|||||||
<span class="text-muted">Not connected</span>
|
<span class="text-muted">Not connected</span>
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="text-right">
|
<td colspan="2" class="text-right">
|
||||||
{% if perms.dcim.change_powerport %}
|
{% if perms.dcim.change_powerport %}
|
||||||
{% if pp.power_outlet %}
|
{% if pp.power_outlet %}
|
||||||
{% if pp.connection_status %}
|
{% if pp.connection_status %}
|
||||||
|
@ -98,14 +98,8 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if ipaddress.interface %}
|
{% if ipaddress.interface %}
|
||||||
<span><a href="{% url 'dcim:device' pk=ipaddress.interface.device.pk %}">{{ ipaddress.interface.device }}</a> ({{ ipaddress.interface }})</span>
|
<span><a href="{% url 'dcim:device' pk=ipaddress.interface.device.pk %}">{{ ipaddress.interface.device }}</a> ({{ ipaddress.interface }})</span>
|
||||||
{% if perms.dcim.change_device and perms.ipam.change_ipaddress %}
|
|
||||||
<a href="{% url 'ipam:ipaddress_remove' pk=ipaddress.pk %}" class="btn btn-xs btn-danger"><i class="glyphicon glyphicon-remove"></i> Remove</a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% if perms.dcim.change_device and perms.ipam.change_ipaddress %}
|
|
||||||
<a href="{% url 'ipam:ipaddress_assign' pk=ipaddress.pk %}" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-plus"></i> Assign</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -16,39 +16,20 @@
|
|||||||
{% render_field form.vrf %}
|
{% render_field form.vrf %}
|
||||||
{% render_field form.tenant %}
|
{% render_field form.tenant %}
|
||||||
{% render_field form.status %}
|
{% render_field form.status %}
|
||||||
{% if obj.pk %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-md-3 control-label">Device</label>
|
|
||||||
<div class="col-md-9">
|
|
||||||
<p class="form-control-static">
|
|
||||||
{% if obj.interface %}
|
|
||||||
<a href="{% url 'dcim:device' pk=obj.interface.device.pk %}">{{ obj.interface.device }}</a>
|
|
||||||
<a href="{% url 'ipam:ipaddress_remove' pk=obj.pk %}" class="btn btn-xs btn-danger"><i class="glyphicon glyphicon-remove"></i> Remove</a>
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">None</span>
|
|
||||||
{% if obj.pk %}
|
|
||||||
<a href="{% url 'ipam:ipaddress_assign' pk=obj.pk %}" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-plus"></i> Assign</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-md-3 control-label">Interface</label>
|
|
||||||
<div class="col-md-9">
|
|
||||||
<p class="form-control-static">
|
|
||||||
{% if obj.interface %}
|
|
||||||
{{ obj.interface }}
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">None</span>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% render_field form.description %}
|
{% render_field form.description %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Interface Assignment</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% render_field form.interface_site %}
|
||||||
|
{% render_field form.interface_rack %}
|
||||||
|
{% render_field form.interface_device %}
|
||||||
|
{% render_field form.interface %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>NAT IP (Inside)</strong></div>
|
<div class="panel-heading"><strong>NAT IP (Inside)</strong></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
@ -6,13 +6,13 @@
|
|||||||
<div class="col-md-6 col-md-offset-3">
|
<div class="col-md-6 col-md-offset-3">
|
||||||
<form action="." method="post" class="form">
|
<form action="." method="post" class="form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{% for field in form.hidden_fields %}
|
||||||
|
{{ field }}
|
||||||
|
{% endfor %}
|
||||||
<div class="panel panel-{{ panel_class|default:"danger" }}">
|
<div class="panel panel-{{ panel_class|default:"danger" }}">
|
||||||
<div class="panel-heading">{% block title %}{% endblock %}</div>
|
<div class="panel-heading">{% block title %}{% endblock %}</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% block message %}<p>Are you sure?</p>{% endblock %}
|
{% block message %}<p>Are you sure?</p>{% endblock %}
|
||||||
{% for field in form.hidden_fields %}
|
|
||||||
{{ field }}
|
|
||||||
{% endfor %}
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox{% if form.confirm.errors %} has-error{% endif %}">
|
<div class="checkbox{% if form.confirm.errors %} has-error{% endif %}">
|
||||||
<label for="{{ form.confirm.id_for_label }}">
|
<label for="{{ form.confirm.id_for_label }}">
|
||||||
|
@ -29,7 +29,7 @@ class TenantGroupEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
form_class = forms.TenantGroupForm
|
form_class = forms.TenantGroupForm
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
def get_return_url(self, request, obj):
|
||||||
return reverse('tenancy:tenantgroup_list')
|
return reverse('tenancy:tenantgroup_list')
|
||||||
|
|
||||||
|
|
||||||
@ -81,7 +81,6 @@ class TenantEditView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
permission_required = 'tenancy.change_tenant'
|
permission_required = 'tenancy.change_tenant'
|
||||||
model = Tenant
|
model = Tenant
|
||||||
form_class = forms.TenantForm
|
form_class = forms.TenantForm
|
||||||
fields_initial = ['group']
|
|
||||||
template_name = 'tenancy/tenant_edit.html'
|
template_name = 'tenancy/tenant_edit.html'
|
||||||
default_return_url = 'tenancy:tenant_list'
|
default_return_url = 'tenancy:tenant_list'
|
||||||
|
|
||||||
|
@ -398,13 +398,18 @@ class BootstrapMixin(forms.BaseForm):
|
|||||||
field.widget.attrs['placeholder'] = field.label
|
field.widget.attrs['placeholder'] = field.label
|
||||||
|
|
||||||
|
|
||||||
class ConfirmationForm(BootstrapMixin, forms.Form):
|
class ReturnURLForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
A generic confirmation form. The form is not valid unless the confirm field is checked. An optional return_url can
|
Provides a hidden return URL field to control where the user is directed after the form is submitted.
|
||||||
be specified to direct the user to a specific URL after the action has been taken.
|
"""
|
||||||
|
return_url = forms.CharField(required=False, widget=forms.HiddenInput())
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmationForm(BootstrapMixin, ReturnURLForm):
|
||||||
|
"""
|
||||||
|
A generic confirmation form. The form is not valid unless the confirm field is checked.
|
||||||
"""
|
"""
|
||||||
confirm = forms.BooleanField(required=True)
|
confirm = forms.BooleanField(required=True)
|
||||||
return_url = forms.CharField(required=False, widget=forms.HiddenInput())
|
|
||||||
|
|
||||||
|
|
||||||
class BulkEditForm(forms.Form):
|
class BulkEditForm(forms.Form):
|
||||||
|
@ -12,7 +12,9 @@ from django.http import HttpResponse
|
|||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.template import TemplateSyntaxError
|
from django.template import TemplateSyntaxError
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.html import escape
|
||||||
from django.utils.http import is_safe_url
|
from django.utils.http import is_safe_url
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
from extras.forms import CustomFieldForm
|
from extras.forms import CustomFieldForm
|
||||||
@ -39,6 +41,23 @@ class CustomFieldQueryset:
|
|||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
|
class GetReturnURLMixin(object):
|
||||||
|
"""
|
||||||
|
Provides logic for determining where a user should be redirected after processing a form.
|
||||||
|
"""
|
||||||
|
default_return_url = None
|
||||||
|
|
||||||
|
def get_return_url(self, request, obj):
|
||||||
|
query_param = request.GET.get('return_url')
|
||||||
|
if query_param and is_safe_url(url=query_param, host=request.get_host()):
|
||||||
|
return query_param
|
||||||
|
elif obj.pk and hasattr(obj, 'get_absolute_url'):
|
||||||
|
return obj.get_absolute_url()
|
||||||
|
elif self.default_return_url is not None:
|
||||||
|
return reverse(self.default_return_url)
|
||||||
|
return reverse('home')
|
||||||
|
|
||||||
|
|
||||||
class ObjectListView(View):
|
class ObjectListView(View):
|
||||||
"""
|
"""
|
||||||
List a series of objects.
|
List a series of objects.
|
||||||
@ -128,21 +147,18 @@ class ObjectListView(View):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
class ObjectEditView(View):
|
class ObjectEditView(GetReturnURLMixin, View):
|
||||||
"""
|
"""
|
||||||
Create or edit a single object.
|
Create or edit a single object.
|
||||||
|
|
||||||
model: The model of the object being edited
|
model: The model of the object being edited
|
||||||
form_class: The form used to create or edit the object
|
form_class: The form used to create or edit the object
|
||||||
fields_initial: A set of fields that will be prepopulated in the form from the request parameters
|
|
||||||
template_name: The name of the template
|
template_name: The name of the template
|
||||||
default_return_url: The name of the URL used to display a list of this object type
|
default_return_url: The name of the URL used to display a list of this object type
|
||||||
"""
|
"""
|
||||||
model = None
|
model = None
|
||||||
form_class = None
|
form_class = None
|
||||||
fields_initial = []
|
|
||||||
template_name = 'utilities/obj_edit.html'
|
template_name = 'utilities/obj_edit.html'
|
||||||
default_return_url = 'home'
|
|
||||||
|
|
||||||
def get_object(self, kwargs):
|
def get_object(self, kwargs):
|
||||||
# Look up object by slug or PK. Return None if neither was provided.
|
# Look up object by slug or PK. Return None if neither was provided.
|
||||||
@ -157,24 +173,19 @@ class ObjectEditView(View):
|
|||||||
# given some parameter from the request URL.
|
# given some parameter from the request URL.
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
|
||||||
# Determine where to redirect the user after updating an object (or aborting an update).
|
|
||||||
if obj.pk and hasattr(obj, 'get_absolute_url'):
|
|
||||||
return obj.get_absolute_url()
|
|
||||||
return reverse(self.default_return_url)
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
obj = self.get_object(kwargs)
|
obj = self.get_object(kwargs)
|
||||||
obj = self.alter_obj(obj, request, args, kwargs)
|
obj = self.alter_obj(obj, request, args, kwargs)
|
||||||
initial_data = {k: request.GET[k] for k in self.fields_initial if k in request.GET}
|
# Parse initial data manually to avoid setting field values as lists
|
||||||
|
initial_data = {k: request.GET[k] for k in request.GET}
|
||||||
form = self.form_class(instance=obj, initial=initial_data)
|
form = self.form_class(instance=obj, initial=initial_data)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'obj': obj,
|
||||||
'obj_type': self.model._meta.verbose_name,
|
'obj_type': self.model._meta.verbose_name,
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': self.get_return_url(obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
@ -194,10 +205,10 @@ class ObjectEditView(View):
|
|||||||
msg = u'Created ' if obj_created else u'Modified '
|
msg = u'Created ' if obj_created else u'Modified '
|
||||||
msg += self.model._meta.verbose_name
|
msg += self.model._meta.verbose_name
|
||||||
if hasattr(obj, 'get_absolute_url'):
|
if hasattr(obj, 'get_absolute_url'):
|
||||||
msg = u'{} <a href="{}">{}</a>'.format(msg, obj.get_absolute_url(), obj)
|
msg = u'{} <a href="{}">{}</a>'.format(msg, obj.get_absolute_url(), escape(obj))
|
||||||
else:
|
else:
|
||||||
msg = u'{} {}'.format(msg, obj)
|
msg = u'{} {}'.format(msg, escape(obj))
|
||||||
messages.success(request, msg)
|
messages.success(request, mark_safe(msg))
|
||||||
if obj_created:
|
if obj_created:
|
||||||
UserAction.objects.log_create(request.user, obj, msg)
|
UserAction.objects.log_create(request.user, obj, msg)
|
||||||
else:
|
else:
|
||||||
@ -205,17 +216,22 @@ class ObjectEditView(View):
|
|||||||
|
|
||||||
if '_addanother' in request.POST:
|
if '_addanother' in request.POST:
|
||||||
return redirect(request.path)
|
return redirect(request.path)
|
||||||
return redirect(self.get_return_url(obj))
|
|
||||||
|
return_url = form.cleaned_data.get('return_url')
|
||||||
|
if return_url is not None and is_safe_url(url=return_url, host=request.get_host()):
|
||||||
|
return redirect(return_url)
|
||||||
|
else:
|
||||||
|
return redirect(self.get_return_url(request, obj))
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'obj': obj,
|
||||||
'obj_type': self.model._meta.verbose_name,
|
'obj_type': self.model._meta.verbose_name,
|
||||||
'form': form,
|
'form': form,
|
||||||
'return_url': self.get_return_url(obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class ObjectDeleteView(View):
|
class ObjectDeleteView(GetReturnURLMixin, View):
|
||||||
"""
|
"""
|
||||||
Delete a single object.
|
Delete a single object.
|
||||||
|
|
||||||
@ -225,7 +241,6 @@ class ObjectDeleteView(View):
|
|||||||
"""
|
"""
|
||||||
model = None
|
model = None
|
||||||
template_name = 'utilities/obj_delete.html'
|
template_name = 'utilities/obj_delete.html'
|
||||||
default_return_url = 'home'
|
|
||||||
|
|
||||||
def get_object(self, kwargs):
|
def get_object(self, kwargs):
|
||||||
# Look up object by slug if one has been provided. Otherwise, use PK.
|
# Look up object by slug if one has been provided. Otherwise, use PK.
|
||||||
@ -234,24 +249,16 @@ class ObjectDeleteView(View):
|
|||||||
else:
|
else:
|
||||||
return get_object_or_404(self.model, pk=kwargs['pk'])
|
return get_object_or_404(self.model, pk=kwargs['pk'])
|
||||||
|
|
||||||
def get_return_url(self, obj):
|
|
||||||
if obj.pk and hasattr(obj, 'get_absolute_url'):
|
|
||||||
return obj.get_absolute_url()
|
|
||||||
return reverse(self.default_return_url)
|
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
|
|
||||||
obj = self.get_object(kwargs)
|
obj = self.get_object(kwargs)
|
||||||
initial_data = {
|
form = ConfirmationForm(initial=request.GET)
|
||||||
'return_url': request.GET.get('return_url'),
|
|
||||||
}
|
|
||||||
form = ConfirmationForm(initial=initial_data)
|
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'obj': obj,
|
||||||
'form': form,
|
'form': form,
|
||||||
'obj_type': self.model._meta.verbose_name,
|
'obj_type': self.model._meta.verbose_name,
|
||||||
'return_url': request.GET.get('return_url') or self.get_return_url(obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request, **kwargs):
|
def post(self, request, **kwargs):
|
||||||
@ -270,17 +277,17 @@ class ObjectDeleteView(View):
|
|||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
UserAction.objects.log_delete(request.user, obj, msg)
|
UserAction.objects.log_delete(request.user, obj, msg)
|
||||||
|
|
||||||
return_url = form.cleaned_data['return_url']
|
return_url = form.cleaned_data.get('return_url')
|
||||||
if return_url and is_safe_url(url=return_url, host=request.get_host()):
|
if return_url is not None and is_safe_url(url=return_url, host=request.get_host()):
|
||||||
return redirect(return_url)
|
return redirect(return_url)
|
||||||
else:
|
else:
|
||||||
return redirect(self.get_return_url(obj))
|
return redirect(self.get_return_url(request, obj))
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'obj': obj,
|
'obj': obj,
|
||||||
'form': form,
|
'form': form,
|
||||||
'obj_type': self.model._meta.verbose_name,
|
'obj_type': self.model._meta.verbose_name,
|
||||||
'return_url': request.GET.get('return_url') or self.get_return_url(obj),
|
'return_url': self.get_return_url(request, obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ djangorestframework>=3.6.2
|
|||||||
graphviz>=0.6
|
graphviz>=0.6
|
||||||
Markdown>=2.6.7
|
Markdown>=2.6.7
|
||||||
natsort>=5.0.0
|
natsort>=5.0.0
|
||||||
ncclient==0.5.2
|
ncclient==0.5.3
|
||||||
netaddr==0.7.18
|
netaddr==0.7.18
|
||||||
paramiko>=2.0.0
|
paramiko>=2.0.0
|
||||||
Pillow>=4.0.0
|
Pillow>=4.0.0
|
||||||
|
Reference in New Issue
Block a user