Browse Source

Closes #211: Allow device assignment and removal from IP address view

Jeremy Stretch 8 years ago
parent
commit
e22eafc4a7

+ 2 - 3
netbox/dcim/views.py

@@ -1715,7 +1715,7 @@ class InterfaceConnectionsListView(ObjectListView):
 # IP addresses
 #
 
-@permission_required('ipam.add_ipaddress')
+@permission_required(['dcim.change_device', 'ipam.add_ipaddress'])
 def ipaddress_assign(request, pk):
 
     device = get_object_or_404(Device, pk=pk)
@@ -1727,8 +1727,7 @@ def ipaddress_assign(request, pk):
             ipaddress = form.save(commit=False)
             ipaddress.interface = form.cleaned_data['interface']
             ipaddress.save()
-            messages.success(request, "Added new IP address {0} to interface {1}".format(ipaddress,
-                                                                                         ipaddress.interface))
+            messages.success(request, "Added new IP address {} to interface {}".format(ipaddress, ipaddress.interface))
 
             if form.cleaned_data['set_as_primary']:
                 if ipaddress.family == 4:

+ 24 - 1
netbox/ipam/forms.py

@@ -1,7 +1,7 @@
 from django import forms
 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 tenancy.models import Tenant
 from utilities.forms import (
@@ -336,6 +336,29 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
                 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):
     vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
                                  error_messages={'invalid_choice': 'VRF not found.'})

+ 2 - 0
netbox/ipam/urls.py

@@ -56,6 +56,8 @@ urlpatterns = [
     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+)/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'),
 
     # VLAN groups

+ 72 - 1
netbox/ipam/views.py

@@ -1,11 +1,15 @@
 import netaddr
 from django_tables2 import RequestConfig
 
+from django.contrib.auth.decorators import permission_required
 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.shortcuts import get_object_or_404, render
+from django.shortcuts import get_object_or_404, redirect, render
 
 from dcim.models import Device
+from utilities.forms import ConfirmationForm
 from utilities.paginator import EnhancedPaginator
 from utilities.views import (
     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):
     permission_required = 'ipam.change_ipaddress'
     model = IPAddress

+ 6 - 0
netbox/templates/ipam/ipaddress.html

@@ -97,8 +97,14 @@
                     <td>
                         {% if ipaddress.interface %}
                             <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 %}
                             <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 %}
                     </td>
                 </tr>

+ 56 - 0
netbox/templates/ipam/ipaddress_assign.html

@@ -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 %}

+ 12 - 2
netbox/templates/ipam/ipaddress_edit.html

@@ -17,8 +17,12 @@
                         <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>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 %}
                         </p>
                     </div>
@@ -26,7 +30,13 @@
                 <div class="form-group">
                     <label class="col-md-3 control-label">Interface</label>
                     <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>
             {% endif %}

+ 8 - 0
netbox/templates/ipam/ipaddress_unassign.html

@@ -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 %}