mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #211: Allow device assignment and removal from IP address view
This commit is contained in:
@ -1715,7 +1715,7 @@ class InterfaceConnectionsListView(ObjectListView):
|
|||||||
# IP addresses
|
# IP addresses
|
||||||
#
|
#
|
||||||
|
|
||||||
@permission_required('ipam.add_ipaddress')
|
@permission_required(['dcim.change_device', 'ipam.add_ipaddress'])
|
||||||
def ipaddress_assign(request, pk):
|
def ipaddress_assign(request, pk):
|
||||||
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
device = get_object_or_404(Device, pk=pk)
|
||||||
@ -1727,8 +1727,7 @@ def ipaddress_assign(request, pk):
|
|||||||
ipaddress = form.save(commit=False)
|
ipaddress = form.save(commit=False)
|
||||||
ipaddress.interface = form.cleaned_data['interface']
|
ipaddress.interface = form.cleaned_data['interface']
|
||||||
ipaddress.save()
|
ipaddress.save()
|
||||||
messages.success(request, "Added new IP address {0} to interface {1}".format(ipaddress,
|
messages.success(request, "Added new IP address {} to interface {}".format(ipaddress, ipaddress.interface))
|
||||||
ipaddress.interface))
|
|
||||||
|
|
||||||
if form.cleaned_data['set_as_primary']:
|
if form.cleaned_data['set_as_primary']:
|
||||||
if ipaddress.family == 4:
|
if ipaddress.family == 4:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from dcim.models import Site, Device, Interface
|
from dcim.models import Site, Rack, Device, Interface
|
||||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
@ -336,6 +336,29 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
|
|||||||
self.fields['nat_inside'].choices = []
|
self.fields['nat_inside'].choices = []
|
||||||
|
|
||||||
|
|
||||||
|
class IPAddressAssignForm(BootstrapMixin, forms.Form):
|
||||||
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), label='Site', required=False,
|
||||||
|
widget=forms.Select(attrs={'filter-for': 'rack'}))
|
||||||
|
rack = forms.ModelChoiceField(queryset=Rack.objects.all(), label='Rack', required=False,
|
||||||
|
widget=APISelect(api_url='/api/dcim/racks/?site_id={{site}}', display_field='display_name', attrs={'filter-for': 'device'}))
|
||||||
|
device = forms.ModelChoiceField(queryset=Device.objects.all(), label='Device', required=False,
|
||||||
|
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}', display_field='display_name', attrs={'filter-for': 'interface'}))
|
||||||
|
livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
|
||||||
|
query_key='q', query_url='dcim-api:device_list', field_to_update='device')
|
||||||
|
)
|
||||||
|
interface = forms.ModelChoiceField(queryset=Interface.objects.all(), label='Interface',
|
||||||
|
widget=APISelect(api_url='/api/dcim/devices/{{device}}/interfaces/'))
|
||||||
|
set_as_primary = forms.BooleanField(label='Set as primary IP for device', required=False)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
super(IPAddressAssignForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.fields['rack'].choices = []
|
||||||
|
self.fields['device'].choices = []
|
||||||
|
self.fields['interface'].choices = []
|
||||||
|
|
||||||
|
|
||||||
class IPAddressFromCSVForm(forms.ModelForm):
|
class IPAddressFromCSVForm(forms.ModelForm):
|
||||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
||||||
error_messages={'invalid_choice': 'VRF not found.'})
|
error_messages={'invalid_choice': 'VRF not found.'})
|
||||||
|
@ -56,6 +56,8 @@ 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
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import netaddr
|
import netaddr
|
||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
|
|
||||||
|
from django.contrib.auth.decorators import permission_required
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.paginator import EnhancedPaginator
|
from utilities.paginator import EnhancedPaginator
|
||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
@ -446,6 +450,73 @@ 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, "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:
|
||||||
|
form = forms.IPAddressAssignForm()
|
||||||
|
|
||||||
|
return render(request, 'ipam/ipaddress_assign.html', {
|
||||||
|
'ipaddress': ipaddress,
|
||||||
|
'form': form,
|
||||||
|
'cancel_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, "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,
|
||||||
|
'cancel_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
|
||||||
|
@ -97,8 +97,14 @@
|
|||||||
<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>
|
||||||
|
56
netbox/templates/ipam/ipaddress_assign.html
Normal file
56
netbox/templates/ipam/ipaddress_assign.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{% extends '_base.html' %}
|
||||||
|
{% load static from staticfiles %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block title %}Assign IP Address{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="." method="post" class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="panel panel-danger">
|
||||||
|
<div class="panel-heading"><strong>Errors</strong></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<strong>Assign IP Address {{ ipaddress }} ({% if ipaddress.vrf %}VRF {{ ipaddress.vrf }}{% else %}Global Table{% endif %})</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
|
<li role="presentation" class="active"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
|
||||||
|
<li role="presentation"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active" id="search">
|
||||||
|
{% render_field form.livesearch %}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="select">
|
||||||
|
{% render_field form.site %}
|
||||||
|
{% render_field form.rack %}
|
||||||
|
{% render_field form.device %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% render_field form.interface %}
|
||||||
|
{% render_field form.set_as_primary %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-md-9 col-md-offset-3">
|
||||||
|
<button type="submit" name="_assign" class="btn btn-primary">Assign</button>
|
||||||
|
<a href="{{ cancel_url }}" class="btn btn-default">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script src="{% static 'js/livesearch.js' %}"></script>
|
||||||
|
{% endblock %}
|
@ -17,8 +17,12 @@
|
|||||||
<p class="form-control-static">
|
<p class="form-control-static">
|
||||||
{% if obj.interface %}
|
{% if obj.interface %}
|
||||||
<a href="{% url 'dcim:device' pk=obj.interface.device.pk %}">{{ obj.interface.device }}</a>
|
<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 %}
|
{% else %}
|
||||||
<span>None</span>
|
<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 %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -26,7 +30,13 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-3 control-label">Interface</label>
|
<label class="col-md-3 control-label">Interface</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<p class="form-control-static">{{ obj.interface }}</p>
|
<p class="form-control-static">
|
||||||
|
{% if obj.interface %}
|
||||||
|
{{ obj.interface }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">None</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
8
netbox/templates/ipam/ipaddress_unassign.html
Normal file
8
netbox/templates/ipam/ipaddress_unassign.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% extends 'utilities/confirmation_form.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
|
|
||||||
|
{% block title %}Remove {{ ipaddress }} from {{ ipaddress.interface }}?{% endblock %}
|
||||||
|
|
||||||
|
{% block message %}
|
||||||
|
<p>Are you sure you want to remove this IP address from <strong>{{ ipaddress.interface.device }} {{ ipaddress.interface }}</strong>?</p>
|
||||||
|
{% endblock %}
|
Reference in New Issue
Block a user