Browse Source

Allowed assigning an IP address to either a device or a VM

Jeremy Stretch 7 years ago
parent
commit
fa95191792

+ 4 - 0
netbox/dcim/models.py

@@ -1242,6 +1242,10 @@ class Interface(models.Model):
             })
 
     @property
+    def parent(self):
+        return self.device or self.virtual_machine
+
+    @property
     def is_virtual(self):
         return self.form_factor in VIRTUAL_IFACE_TYPES
 

+ 26 - 63
netbox/ipam/forms.py

@@ -377,50 +377,9 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
 #
 
 class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm):
-    interface_site = forms.ModelChoiceField(
-        queryset=Site.objects.all(),
-        required=False,
-        label='Site',
-        widget=forms.Select(
-            attrs={'filter-for': 'interface_rack'}
-        )
-    )
-    interface_rack = ChainedModelChoiceField(
-        queryset=Rack.objects.all(),
-        chains=(
-            ('site', 'interface_site'),
-        ),
-        required=False,
-        label='Rack',
-        widget=APISelect(
-            api_url='/api/dcim/racks/?site_id={{interface_site}}',
-            display_field='display_name',
-            attrs={'filter-for': 'interface_device', 'nullable': 'true'}
-        )
-    )
-    interface_device = ChainedModelChoiceField(
-        queryset=Device.objects.all(),
-        chains=(
-            ('site', 'interface_site'),
-            ('rack', 'interface_rack'),
-        ),
-        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'}
-        )
-    )
-    interface = ChainedModelChoiceField(
+    interface = forms.ModelChoiceField(
         queryset=Interface.objects.all(),
-        chains=(
-            ('device', 'interface_device'),
-        ),
-        required=False,
-        widget=APISelect(
-            api_url='/api/dcim/interfaces/?device_id={{interface_device}}'
-        )
+        required=False
     )
     nat_site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
@@ -479,13 +438,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
             obj_label='address'
         )
     )
-    primary_for_device = forms.BooleanField(required=False, label='Make this the primary IP for the device')
+    primary_for_parent = forms.BooleanField(required=False, label='Make this the primary IP for the device/VM')
 
     class Meta:
         model = IPAddress
         fields = [
-            'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_device', 'nat_site', 'nat_rack',
-            'nat_inside', 'tenant_group', 'tenant',
+            'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site',
+            'nat_rack', 'nat_inside', 'tenant_group', 'tenant',
         ]
 
     def __init__(self, *args, **kwargs):
@@ -493,10 +452,6 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         # Initialize helper selectors
         instance = kwargs.get('instance')
         initial = kwargs.get('initial', {}).copy()
-        if instance and instance.interface is not None:
-            initial['interface_site'] = instance.interface.device.site
-            initial['interface_rack'] = instance.interface.device.rack
-            initial['interface_device'] = instance.interface.device
         if instance and instance.nat_inside and instance.nat_inside.device is not None:
             initial['nat_site'] = instance.nat_inside.device.site
             initial['nat_rack'] = instance.nat_inside.device.rack
@@ -507,22 +462,30 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
 
         self.fields['vrf'].empty_label = 'Global'
 
-        # Initialize primary_for_device if IP address is already assigned
-        if self.instance.interface is not None:
-            device = self.instance.interface.device
+        # Limit interface selections to those belonging to the parent device/VM
+        if self.instance and self.instance.interface:
+            self.fields['interface'].queryset = Interface.objects.filter(
+                device=self.instance.interface.device, virtual_machine=self.instance.interface.virtual_machine
+            )
+        else:
+            self.fields['interface'].choices = []
+
+        # Initialize primary_for_parent if IP address is already assigned
+        if self.instance.pk and self.instance.interface is not None:
+            parent = self.instance.interface.parent
             if (
-                self.instance.address.version == 4 and device.primary_ip4 == self.instance or
-                self.instance.address.version == 6 and device.primary_ip6 == self.instance
+                self.instance.address.version == 4 and parent.primary_ip4_id == self.instance.pk or
+                self.instance.address.version == 6 and parent.primary_ip6_id == self.instance.pk
             ):
-                self.initial['primary_for_device'] = True
+                self.initial['primary_for_parent'] = True
 
     def clean(self):
         super(IPAddressForm, self).clean()
 
         # Primary IP assignment is only available if an interface has been assigned.
-        if self.cleaned_data.get('primary_for_device') and not self.cleaned_data.get('interface'):
+        if self.cleaned_data.get('primary_for_parent') and not self.cleaned_data.get('interface'):
             self.add_error(
-                'primary_for_device', "Only IP addresses assigned to an interface can be designated as primary IPs."
+                'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs."
             )
 
     def save(self, *args, **kwargs):
@@ -530,13 +493,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
         ipaddress = super(IPAddressForm, self).save(*args, **kwargs)
 
         # Assign this IPAddress as the primary for the associated Device.
-        if self.cleaned_data['primary_for_device']:
-            device = self.cleaned_data['interface'].device
+        if self.cleaned_data['primary_for_parent']:
+            parent = self.cleaned_data['interface'].parent
             if ipaddress.address.version == 4:
-                device.primary_ip4 = ipaddress
+                parent.primary_ip4 = ipaddress
             else:
-                device.primary_ip6 = ipaddress
-            device.save()
+                parent.primary_ip6 = ipaddress
+            parent.save()
 
         # Clear assignment as primary for device if set.
         else:

+ 5 - 5
netbox/ipam/tables.py

@@ -77,9 +77,9 @@ IPADDRESS_LINK = """
 {% endif %}
 """
 
-IPADDRESS_DEVICE = """
+IPADDRESS_PARENT = """
 {% if record.interface %}
-    <a href="{{ record.interface.device.get_absolute_url }}">{{ record.interface.device }}</a>
+    <a href="{{ record.interface.parent.get_absolute_url }}">{{ record.interface.parent }}</a>
 {% else %}
     &mdash;
 {% endif %}
@@ -265,12 +265,12 @@ class IPAddressTable(BaseTable):
     status = tables.TemplateColumn(STATUS_LABEL)
     vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
     tenant = tables.TemplateColumn(TENANT_LINK)
-    device = tables.TemplateColumn(IPADDRESS_DEVICE, orderable=False)
+    parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
     interface = tables.Column(orderable=False)
 
     class Meta(BaseTable.Meta):
         model = IPAddress
-        fields = ('pk', 'address', 'vrf', 'status', 'role', 'tenant', 'device', 'interface', 'description')
+        fields = ('pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'description')
         row_attrs = {
             'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
         }
@@ -283,7 +283,7 @@ class IPAddressDetailTable(IPAddressTable):
 
     class Meta(IPAddressTable.Meta):
         fields = (
-            'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'device', 'interface', 'description',
+            'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'parent', 'interface', 'description',
         )
 
 

+ 13 - 2
netbox/ipam/views.py

@@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404, render
 from django.urls import reverse
 from django.views.generic import View
 
-from dcim.models import Device
+from dcim.models import Device, Interface
 from utilities.paginator import EnhancedPaginator
 from utilities.views import (
     BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
@@ -597,7 +597,7 @@ class IPAddressListView(ObjectListView):
     queryset = IPAddress.objects.select_related(
         'vrf__tenant', 'tenant', 'nat_inside'
     ).prefetch_related(
-        'interface__device'
+        'interface__device', 'interface__virtual_machine'
     )
     filter = filters.IPAddressFilter
     filter_form = forms.IPAddressFilterForm
@@ -657,6 +657,17 @@ class IPAddressCreateView(PermissionRequiredMixin, ObjectEditView):
     template_name = 'ipam/ipaddress_edit.html'
     default_return_url = 'ipam:ipaddress_list'
 
+    def alter_obj(self, obj, request, url_args, url_kwargs):
+
+        interface_id = request.GET.get('interface')
+        if interface_id:
+            try:
+                obj.interface = Interface.objects.get(pk=interface_id)
+            except (ValueError, Interface.DoesNotExist):
+                pass
+
+        return obj
+
 
 class IPAddressEditView(IPAddressCreateView):
     permission_required = 'ipam.change_ipaddress'

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

@@ -64,7 +64,7 @@
             {% 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">
+            <a href="{% url 'ipam:ipaddress_add' %}?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 %}

+ 2 - 2
netbox/templates/ipam/ipaddress.html

@@ -100,7 +100,7 @@
                     <td>Assignment</td>
                     <td>
                         {% if ipaddress.interface %}
-                            <span><a href="{% url 'dcim:device' pk=ipaddress.interface.device.pk %}">{{ ipaddress.interface.device }}</a> ({{ ipaddress.interface }})</span>
+                            <span><a href="{{ ipaddress.interface.parent.get_absolute_url }}">{{ ipaddress.interface.parent }}</a> ({{ ipaddress.interface }})</span>
                         {% else %}
                             <span class="text-muted">None</span>
                         {% endif %}
@@ -112,7 +112,7 @@
                         {% if ipaddress.nat_inside %}
                             <a href="{% url 'ipam:ipaddress' pk=ipaddress.nat_inside.pk %}">{{ ipaddress.nat_inside }}</a>
                             {% if ipaddress.nat_inside.interface %}
-                                (<a href="{% url 'dcim:device' pk=ipaddress.nat_inside.interface.device.pk %}">{{ ipaddress.nat_inside.interface.device }}</a>)
+                                (<a href="{{ ipaddress.nat_inside.interface.parent.get_absolute_url }}">{{ ipaddress.nat_inside.interface.parent }}</a>)
                             {% endif %}
                         {% else %}
                             <span class="text-muted">None</span>

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

@@ -1,6 +1,7 @@
 {% extends 'utilities/obj_edit.html' %}
 {% load static from staticfiles %}
 {% load form_helpers %}
+{% load helpers %}
 
 {% block tabs %}
     {% if not obj.pk %}
@@ -26,18 +27,25 @@
             {% render_field form.tenant %}
         </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 %}
-            {% render_field form.primary_for_device %}
+    {% if obj.interface %}
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                <strong>Interface Assignment</strong>
+            </div>
+            <div class="panel-body">
+                <div class="form-group">
+                    <label class="col-md-3 control-label">{{ obj.interface.parent|model_name|bettertitle }}</label>
+                    <div class="col-md-9">
+                        <p class="form-control-static">
+                            <a href="{{ obj.interface.parent.get_absolute_url }}">{{ obj.interface.parent }}</a>
+                        </p>
+                    </div>
+                </div>
+                {% render_field form.interface %}
+                {% render_field form.primary_for_parent %}
+            </div>
         </div>
-    </div>
+    {% endif %}
     <div class="panel panel-default">
         <div class="panel-heading"><strong>NAT IP (Inside)</strong></div>
         <div class="panel-body">

+ 6 - 1
netbox/templates/virtualization/inc/interface.html

@@ -13,6 +13,11 @@
     <td>{{ iface.mtu|default:"" }}</td>
     <td>{{ iface.mac_address|default:"" }}</td>
     <td class="text-right">
+        {% if perms.ipam.add_ipaddress %}
+            <a href="{% url 'ipam:ipaddress_add' %}?interface={{ iface.pk }}&return_url={{ vm.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 %}
             <a href="{% url 'virtualization:interface_edit' pk=iface.pk %}" class="btn btn-info btn-xs" title="Edit interface">
                 <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
@@ -30,7 +35,7 @@
         {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
             <td></td>
         {% endif %}
-        <td colspan="3">
+        <td>
             <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>

+ 30 - 0
netbox/templates/virtualization/virtualmachine.html

@@ -79,6 +79,36 @@
                         {% endif %}
                     </td>
                 </tr>
+                <tr>
+                    <td>Primary IPv4</td>
+                    <td>
+                        {% if vm.primary_ip4 %}
+                            <a href="{% url 'ipam:ipaddress' pk=vm.primary_ip4.pk %}">{{ vm.primary_ip4.address.ip }}</a>
+                            {% if vm.primary_ip4.nat_inside %}
+                                <span>(NAT for {{ vm.primary_ip4.nat_inside.address.ip }})</span>
+                            {% elif vm.primary_ip4.nat_outside %}
+                                <span>(NAT: {{ vm.primary_ip4.nat_outside.address.ip }})</span>
+                            {% endif %}
+                        {% else %}
+                            <span class="text-muted">N/A</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
+                    <td>Primary IPv6</td>
+                    <td>
+                        {% if vm.primary_ip6 %}
+                            <a href="{% url 'ipam:ipaddress' pk=vm.primary_ip6.pk %}">{{ vm.primary_ip6.address.ip }}</a>
+                            {% if vm.primary_ip6.nat_inside %}
+                                <span>(NAT for {{ vm.primary_ip6.nat_inside.address.ip }})</span>
+                            {% elif vm.primary_ip6.nat_outside %}
+                                <span>(NAT: {{ vm.primary_ip6.nat_outside.address.ip }})</span>
+                            {% endif %}
+                        {% else %}
+                            <span class="text-muted">N/A</span>
+                        {% endif %}
+                    </td>
+                </tr>
             </table>
         </div>
         <div class="panel panel-default">