Parcourir la source

Closes #1001: Merged IP interface assignment into ipam.IPAddressForm

Jeremy Stretch il y a 8 ans
Parent
commit
09000ad9b3

+ 0 - 30
netbox/dcim/forms.py

@@ -1692,36 +1692,6 @@ class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
 
 
 #
-# 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.order_naturally(method=device.device_type.interface_ordering)
-        self.fields['interface'].queryset = interfaces
-        self.fields['interface'].required = True
-
-        # If this device has only one interface, select it by default.
-        if 'interface' not in self.initial and 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
-
-
-#
 # Modules
 #
 

+ 0 - 1
netbox/dcim/urls.py

@@ -116,7 +116,6 @@ urlpatterns = [
     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+)/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<device>\d+)/services/assign/$', ServiceEditView.as_view(), name='service_assign'),
 

+ 0 - 41
netbox/dcim/views.py

@@ -1562,47 +1562,6 @@ class InterfaceConnectionsListView(ObjectListView):
 
 
 #
-# 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, initial=request.GET)
-
-    return render(request, 'dcim/ipaddress_assign.html', {
-        'device': device,
-        'form': form,
-        'return_url': reverse('dcim:device', kwargs={'pk': device.pk}),
-    })
-
-
-#
 # Modules
 #
 

+ 66 - 15
netbox/ipam/forms.py

@@ -6,7 +6,7 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi
 from tenancy.models import Tenant
 from utilities.forms import (
     APISelect, BootstrapMixin, BulkImportForm, CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch,
-    SlugField, add_blank_choice,
+    ReturnURLForm, SlugField, add_blank_choice,
 )
 
 from .models import (
@@ -307,21 +307,46 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
 # IP addresses
 #
 
-class IPAddressForm(BootstrapMixin, CustomFieldForm):
-    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 IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
+    interface_site = forms.ModelChoiceField(
+        queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
+            attrs={'filter-for': 'interface_rack'}
+        )
+    )
+    interface_rack = forms.ModelChoiceField(
+        queryset=Rack.objects.all(), required=False, label='Rack', widget=APISelect(
+            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:
         model = IPAddress
-        fields = ['address', 'vrf', 'tenant', 'status', 'nat_inside', 'description']
+        fields = ['address', 'vrf', 'tenant', 'status', 'interface', 'nat_inside', 'description']
         widgets = {
+            'interface': APISelect(api_url='/api/dcim/devices/{{interface_device}}/interfaces/'),
             'nat_inside': APISelect(api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}', display_field='address')
         }
 
@@ -330,8 +355,37 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
 
         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
             # If the IP is assigned to an interface, populate site/device fields accordingly
             if self.instance.nat_inside.interface:
@@ -345,9 +399,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
                 )
             else:
                 self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk)
-
         else:
-
             # Initialize nat_device choices if nat_site is set
             if self.is_bound and self.data.get('nat_site'):
                 self.fields['nat_device'].queryset = Device.objects.filter(site__pk=self.data['nat_site'])
@@ -355,7 +407,6 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
                 self.fields['nat_device'].queryset = Device.objects.filter(site=self.initial['nat_site'])
             else:
                 self.fields['nat_device'].choices = []
-
             # Initialize nat_inside choices if nat_device is set
             if self.is_bound and self.data.get('nat_device'):
                 self.fields['nat_inside'].queryset = IPAddress.objects.filter(

+ 0 - 2
netbox/ipam/urls.py

@@ -57,8 +57,6 @@ 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

+ 0 - 69
netbox/ipam/views.py

@@ -571,75 +571,6 @@ 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):
     permission_required = 'ipam.change_ipaddress'
     model = IPAddress

+ 1 - 1
netbox/templates/dcim/inc/interface.html

@@ -59,7 +59,7 @@
             {% endif %}
         {% endif %}
         {% if perms.ipam.add_ipaddress %}
-            <a href="{% url 'dcim:ipaddress_assign' pk=device.pk %}?interface={{ iface.pk }}" class="btn btn-xs btn-success" title="Assign IP address">
+            <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 %}

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

@@ -98,14 +98,8 @@
                     <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>

+ 11 - 30
netbox/templates/ipam/ipaddress_edit.html

@@ -16,40 +16,21 @@
             {% render_field form.vrf %}
             {% render_field form.tenant %}
             {% 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 %}
         </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-heading"><strong>NAT IP (Inside)</strong></div>
         <div class="panel-body">
             <ul class="nav nav-tabs" role="tablist">

+ 3 - 1
netbox/utilities/views.py

@@ -177,7 +177,9 @@ class ObjectEditView(GetReturnURLMixin, View):
 
         obj = self.get_object(kwargs)
         obj = self.alter_obj(obj, request, args, kwargs)
-        form = self.form_class(instance=obj, initial=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)
 
         return render(request, self.template_name, {
             'obj': obj,