Просмотр исходного кода

Restore ability to assign interface when editing an IPAddress

Jeremy Stretch 5 лет назад
Родитель
Сommit
4d2c75a824
3 измененных файлов с 102 добавлено и 82 удалено
  1. 67 44
      netbox/ipam/forms.py
  2. 15 16
      netbox/ipam/models.py
  3. 20 22
      netbox/templates/ipam/ipaddress_edit.html

+ 67 - 44
netbox/ipam/forms.py

@@ -522,11 +522,33 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
 #
 #
 
 
 class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModelForm):
 class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModelForm):
-    # TODO: Restore ability to select assigned object when editing IPAddress
-    # interface = forms.ModelChoiceField(
-    #     queryset=Interface.objects.all(),
-    #     required=False
-    # )
+    device = DynamicModelChoiceField(
+        queryset=Device.objects.all(),
+        required=False,
+        widget=APISelect(
+            filter_for={
+                'interface': 'device_id'
+            }
+        )
+    )
+    interface = DynamicModelChoiceField(
+        queryset=Interface.objects.all(),
+        required=False
+    )
+    virtual_machine = DynamicModelChoiceField(
+        queryset=VirtualMachine.objects.all(),
+        required=False,
+        widget=APISelect(
+            filter_for={
+                'vminterface': 'virtual_machine_id'
+            }
+        )
+    )
+    vminterface = DynamicModelChoiceField(
+        queryset=VMInterface.objects.all(),
+        required=False,
+        label='Interface'
+    )
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
@@ -611,67 +633,68 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
         # Initialize helper selectors
         # Initialize helper selectors
         instance = kwargs.get('instance')
         instance = kwargs.get('instance')
         initial = kwargs.get('initial', {}).copy()
         initial = kwargs.get('initial', {}).copy()
-        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
-            initial['nat_device'] = instance.nat_inside.device
+        if instance:
+            if type(instance.assigned_object) is Interface:
+                initial['device'] = instance.assigned_object.device
+                initial['interface'] = instance.assigned_object
+            elif type(instance.assigned_object) is VMInterface:
+                initial['virtual_machine'] = instance.assigned_object.virtual_machine
+                initial['vminterface'] = instance.assigned_object
+            if 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
+                initial['nat_device'] = instance.nat_inside.device
         kwargs['initial'] = initial
         kwargs['initial'] = initial
 
 
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
 
 
         self.fields['vrf'].empty_label = 'Global'
         self.fields['vrf'].empty_label = 'Global'
 
 
-        # # 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
-        #     ).prefetch_related(
-        #         'device__primary_ip4',
-        #         'device__primary_ip6',
-        #         'virtual_machine__primary_ip4',
-        #         'virtual_machine__primary_ip6',
-        #     )  # We prefetch the primary address fields to ensure cache invalidation does not balk on the save()
-        # 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 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_parent'] = True
+        # Initialize primary_for_parent if IP address is already assigned
+        if self.instance.pk and self.instance.assigned_object:
+            parent = self.instance.assigned_object.parent
+            if (
+                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_parent'] = True
 
 
     def clean(self):
     def clean(self):
         super().clean()
         super().clean()
 
 
+        # Cannot select both a device interface and a VM interface
+        if self.cleaned_data.get('interface') and self.cleaned_data.get('vminterface'):
+            raise forms.ValidationError("Cannot select both a device interface and a virtual machine interface")
+
         # Primary IP assignment is only available if an interface has been assigned.
         # Primary IP assignment is only available if an interface has been assigned.
-        if self.cleaned_data.get('primary_for_parent') and not self.cleaned_data.get('interface'):
+        interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
+        if self.cleaned_data.get('primary_for_parent') and not interface:
             self.add_error(
             self.add_error(
                 'primary_for_parent', "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):
     def save(self, *args, **kwargs):
 
 
+        # Set assigned object
+        interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface')
+        if interface:
+            self.instance.assigned_object = interface
+
         ipaddress = super().save(*args, **kwargs)
         ipaddress = super().save(*args, **kwargs)
 
 
         # Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
         # Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
-        if self.cleaned_data['primary_for_parent']:
-            parent = self.cleaned_data['interface'].parent
+        if interface and self.cleaned_data['primary_for_parent']:
             if ipaddress.address.version == 4:
             if ipaddress.address.version == 4:
-                parent.primary_ip4 = ipaddress
+                interface.parent.primary_ip4 = ipaddress
             else:
             else:
-                parent.primary_ip6 = ipaddress
-            parent.save()
-        # elif self.cleaned_data['interface']:
-        #     parent = self.cleaned_data['interface'].parent
-        #     if ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress:
-        #         parent.primary_ip4 = None
-        #         parent.save()
-        #     elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress:
-        #         parent.primary_ip6 = None
-        #         parent.save()
+                interface.primary_ip6 = ipaddress
+            interface.parent.save()
+        elif interface and ipaddress.address.version == 4 and interface.parent.primary_ip4 == ipaddress:
+            interface.parent.primary_ip4 = None
+            interface.parent.save()
+        elif interface and ipaddress.address.version == 6 and interface.parent.primary_ip6 == ipaddress:
+            interface.parent.primary_ip4 = None
+            interface.parent.save()
 
 
         return ipaddress
         return ipaddress
 
 

+ 15 - 16
netbox/ipam/models.py

@@ -15,7 +15,7 @@ from extras.utils import extras_features
 from utilities.models import ChangeLoggedModel
 from utilities.models import ChangeLoggedModel
 from utilities.querysets import RestrictedQuerySet
 from utilities.querysets import RestrictedQuerySet
 from utilities.utils import serialize_object
 from utilities.utils import serialize_object
-from virtualization.models import VirtualMachine
+from virtualization.models import VirtualMachine, VMInterface
 from .choices import *
 from .choices import *
 from .constants import *
 from .constants import *
 from .fields import IPNetworkField, IPAddressField
 from .fields import IPNetworkField, IPAddressField
@@ -717,32 +717,31 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
                         )
                         )
                     })
                     })
 
 
-        if self.pk:
-
-            # Check for primary IP assignment that doesn't match the assigned device/VM
+        # Check for primary IP assignment that doesn't match the assigned device/VM
+        if self.pk and type(self.assigned_object) is Interface:
             device = Device.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
             device = Device.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
             if device:
             if device:
-                if self.interface is None:
+                if self.assigned_object is None:
                     raise ValidationError({
                     raise ValidationError({
-                        'interface': "IP address is primary for device {} but not assigned".format(device)
+                        'interface': f"IP address is primary for device {device} but not assigned to an interface"
                     })
                     })
-                elif (device.primary_ip4 == self or device.primary_ip6 == self) and self.interface.device != device:
+                elif self.assigned_object.device != device:
                     raise ValidationError({
                     raise ValidationError({
-                        'interface': "IP address is primary for device {} but assigned to {} ({})".format(
-                            device, self.interface.device, self.interface
-                        )
+                        'interface': f"IP address is primary for device {device} but assigned to "
+                                     f"{self.assigned_object.device} ({self.assigned_object})"
                     })
                     })
+        elif self.pk and type(self.assigned_object) is VMInterface:
             vm = VirtualMachine.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
             vm = VirtualMachine.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
             if vm:
             if vm:
-                if self.interface is None:
+                if self.assigned_object is None:
                     raise ValidationError({
                     raise ValidationError({
-                        'interface': "IP address is primary for virtual machine {} but not assigned".format(vm)
+                        'vminterface': f"IP address is primary for virtual machine {vm} but not assigned to an "
+                                       f"interface"
                     })
                     })
-                elif (vm.primary_ip4 == self or vm.primary_ip6 == self) and self.interface.virtual_machine != vm:
+                elif self.interface.virtual_machine != vm:
                     raise ValidationError({
                     raise ValidationError({
-                        'interface': "IP address is primary for virtual machine {} but assigned to {} ({})".format(
-                            vm, self.interface.virtual_machine, self.interface
-                        )
+                        'vminterface': f"IP address is primary for virtual machine {vm} but assigned to "
+                                       f"{self.assigned_object.virtual_machine} ({self.assigned_object})"
                     })
                     })
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):

+ 20 - 22
netbox/templates/ipam/ipaddress_edit.html

@@ -28,32 +28,30 @@
             {% render_field form.tenant %}
             {% render_field form.tenant %}
         </div>
         </div>
     </div>
     </div>
-    {% if obj.assigned_object %}
-        <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.assigned_object.parent|meta:"verbose_name"|bettertitle }}</label>
-                    <div class="col-md-9">
-                        <p class="form-control-static">
-                            <a href="{{ obj.assigned_object.parent.get_absolute_url }}">{{ obj.assigned_object.parent }}</a>
-                        </p>
+    <div class="panel panel-default">
+        <div class="panel-heading">
+            <strong>Interface Assignment</strong>
+        </div>
+        <div class="panel-body">
+            {% with vm_tab_active=obj.vminterface.exists %}
+                <ul class="nav nav-tabs" role="tablist">
+                    <li role="presentation"{% if not vm_tab_active %} class="active"{% endif %}><a href="#device" role="tab" data-toggle="tab">Device</a></li>
+                    <li role="presentation"{% if vm_tab_active %} class="active"{% endif %}><a href="#virtualmachine" role="tab" data-toggle="tab">Virtual Machine</a></li>
+                </ul>
+                <div class="tab-content">
+                    <div class="tab-pane{% if not vm_tab_active %} active{% endif %}" id="device">
+                        {% render_field form.device %}
+                        {% render_field form.interface %}
                     </div>
                     </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">
-                            <a href="{{ obj.assigned_object.get_absolute_url }}">{{ obj.assigned_object }}</a>
-                        </p>
+                    <div class="tab-pane{% if vm_tab_active %} active{% endif %}" id="virtualmachine">
+                        {% render_field form.virtual_machine %}
+                        {% render_field form.vminterface %}
                     </div>
                     </div>
                 </div>
                 </div>
-                {% render_field form.primary_for_parent %}
-            </div>
+            {% endwith %}
+            {% render_field form.primary_for_parent %}
         </div>
         </div>
-    {% endif %}
+    </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">