Explorar o código

Fixed IPAM tests

Jeremy Stretch %!s(int64=5) %!d(string=hai) anos
pai
achega
31bb70d9a2

+ 1 - 2
netbox/ipam/api/views.py

@@ -233,8 +233,7 @@ class PrefixViewSet(CustomFieldModelViewSet):
 
 
 class IPAddressViewSet(CustomFieldModelViewSet):
 class IPAddressViewSet(CustomFieldModelViewSet):
     queryset = IPAddress.objects.prefetch_related(
     queryset = IPAddress.objects.prefetch_related(
-        'vrf__tenant', 'tenant', 'nat_inside', 'interface__device__device_type', 'interface__virtual_machine',
-        'nat_outside', 'tags',
+        'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags',
     )
     )
     serializer_class = serializers.IPAddressSerializer
     serializer_class = serializers.IPAddressSerializer
     filterset_class = filters.IPAddressFilterSet
     filterset_class = filters.IPAddressFilterSet

+ 44 - 30
netbox/ipam/filters.py

@@ -1,5 +1,6 @@
 import django_filters
 import django_filters
 import netaddr
 import netaddr
+from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.db.models import Q
 from django.db.models import Q
 from netaddr.core import AddrFormatError
 from netaddr.core import AddrFormatError
@@ -11,7 +12,7 @@ from utilities.filters import (
     BaseFilterSet, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, TagFilter,
     BaseFilterSet, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, TagFilter,
     TreeNodeMultipleChoiceFilter,
     TreeNodeMultipleChoiceFilter,
 )
 )
-from virtualization.models import VirtualMachine
+from virtualization.models import Interface as VMInterface, VirtualMachine
 from .choices import *
 from .choices import *
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 
 
@@ -299,27 +300,26 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet,
         to_field_name='rd',
         to_field_name='rd',
         label='VRF (RD)',
         label='VRF (RD)',
     )
     )
-    # device = MultiValueCharFilter(
-    #     method='filter_device',
-    #     field_name='name',
-    #     label='Device (name)',
-    # )
-    # device_id = MultiValueNumberFilter(
-    #     method='filter_device',
-    #     field_name='pk',
-    #     label='Device (ID)',
-    # )
-    # virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
-    #     field_name='interface__virtual_machine',
-    #     queryset=VirtualMachine.objects.unrestricted(),
-    #     label='Virtual machine (ID)',
-    # )
-    # virtual_machine = django_filters.ModelMultipleChoiceFilter(
-    #     field_name='interface__virtual_machine__name',
-    #     queryset=VirtualMachine.objects.unrestricted(),
-    #     to_field_name='name',
-    #     label='Virtual machine (name)',
-    # )
+    device = MultiValueCharFilter(
+        method='filter_device',
+        field_name='name',
+        label='Device (name)',
+    )
+    device_id = MultiValueNumberFilter(
+        method='filter_device',
+        field_name='pk',
+        label='Device (ID)',
+    )
+    virtual_machine = MultiValueCharFilter(
+        method='filter_virtual_machine',
+        field_name='name',
+        label='Virtual machine (name)',
+    )
+    virtual_machine_id = MultiValueNumberFilter(
+        method='filter_virtual_machine',
+        field_name='pk',
+        label='Virtual machine (ID)',
+    )
     # interface = django_filters.ModelMultipleChoiceFilter(
     # interface = django_filters.ModelMultipleChoiceFilter(
     #     field_name='interface__name',
     #     field_name='interface__name',
     #     queryset=Interface.objects.unrestricted(),
     #     queryset=Interface.objects.unrestricted(),
@@ -379,17 +379,31 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet,
         return queryset.filter(address__net_mask_length=value)
         return queryset.filter(address__net_mask_length=value)
 
 
     def filter_device(self, queryset, name, value):
     def filter_device(self, queryset, name, value):
-        try:
-            devices = Device.objects.prefetch_related('device_type').filter(**{'{}__in'.format(name): value})
-            vc_interface_ids = []
-            for device in devices:
-                vc_interface_ids.extend([i['id'] for i in device.vc_interfaces.values('id')])
-            return queryset.filter(interface_id__in=vc_interface_ids)
-        except Device.DoesNotExist:
+        devices = Device.objects.filter(**{'{}__in'.format(name): value})
+        if not devices.exists():
+            return queryset.none()
+        interface_ids = []
+        for device in devices:
+            interface_ids.extend(device.vc_interfaces.values_list('id', flat=True))
+        return queryset.filter(
+            assigned_object_type=ContentType.objects.get_for_model(Interface),
+            assigned_object_id__in=interface_ids
+        )
+
+    def filter_virtual_machine(self, queryset, name, value):
+        virtual_machines = VirtualMachine.objects.filter(**{'{}__in'.format(name): value})
+        if not virtual_machines.exists():
             return queryset.none()
             return queryset.none()
+        interface_ids = []
+        for vm in virtual_machines:
+            interface_ids.extend(vm.interfaces.values_list('id', flat=True))
+        return queryset.filter(
+            assigned_object_type=ContentType.objects.get_for_model(VMInterface),
+            assigned_object_id__in=interface_ids
+        )
 
 
     def _assigned_to_interface(self, queryset, name, value):
     def _assigned_to_interface(self, queryset, name, value):
-        return queryset.exclude(interface__isnull=value)
+        return queryset.exclude(assigned_object_id__isnull=value)
 
 
 
 
 class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
 class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):

+ 71 - 71
netbox/ipam/forms.py

@@ -523,10 +523,10 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
 #
 #
 
 
 class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModelForm):
 class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModelForm):
-    interface = forms.ModelChoiceField(
-        queryset=Interface.objects.all(),
-        required=False
-    )
+    # interface = forms.ModelChoiceField(
+    #     queryset=Interface.objects.all(),
+    #     required=False
+    # )
     vrf = DynamicModelChoiceField(
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
@@ -598,8 +598,8 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
     class Meta:
     class Meta:
         model = IPAddress
         model = IPAddress
         fields = [
         fields = [
-            'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'interface', 'primary_for_parent',
-            'nat_site', 'nat_rack', 'nat_inside', 'tenant_group', 'tenant', 'tags',
+            'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'primary_for_parent', 'nat_site', 'nat_rack',
+            'nat_inside', 'tenant_group', 'tenant', 'tags',
         ]
         ]
         widgets = {
         widgets = {
             'status': StaticSelect2(),
             'status': StaticSelect2(),
@@ -621,27 +621,27 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
 
 
         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
+        # # 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
 
 
     def clean(self):
     def clean(self):
         super().clean()
         super().clean()
@@ -664,14 +664,14 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel
             else:
             else:
                 parent.primary_ip6 = ipaddress
                 parent.primary_ip6 = ipaddress
             parent.save()
             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()
+        # 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()
 
 
         return ipaddress
         return ipaddress
 
 
@@ -730,24 +730,24 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
         required=False,
         required=False,
         help_text='Functional role'
         help_text='Functional role'
     )
     )
-    device = CSVModelChoiceField(
-        queryset=Device.objects.all(),
-        required=False,
-        to_field_name='name',
-        help_text='Parent device of assigned interface (if any)'
-    )
-    virtual_machine = CSVModelChoiceField(
-        queryset=VirtualMachine.objects.all(),
-        required=False,
-        to_field_name='name',
-        help_text='Parent VM of assigned interface (if any)'
-    )
-    interface = CSVModelChoiceField(
-        queryset=Interface.objects.all(),
-        required=False,
-        to_field_name='name',
-        help_text='Assigned interface'
-    )
+    # device = CSVModelChoiceField(
+    #     queryset=Device.objects.all(),
+    #     required=False,
+    #     to_field_name='name',
+    #     help_text='Parent device of assigned interface (if any)'
+    # )
+    # virtual_machine = CSVModelChoiceField(
+    #     queryset=VirtualMachine.objects.all(),
+    #     required=False,
+    #     to_field_name='name',
+    #     help_text='Parent VM of assigned interface (if any)'
+    # )
+    # interface = CSVModelChoiceField(
+    #     queryset=Interface.objects.all(),
+    #     required=False,
+    #     to_field_name='name',
+    #     help_text='Assigned interface'
+    # )
     is_primary = forms.BooleanField(
     is_primary = forms.BooleanField(
         help_text='Make this the primary IP for the assigned device',
         help_text='Make this the primary IP for the assigned device',
         required=False
         required=False
@@ -760,23 +760,23 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
     def __init__(self, data=None, *args, **kwargs):
     def __init__(self, data=None, *args, **kwargs):
         super().__init__(data, *args, **kwargs)
         super().__init__(data, *args, **kwargs)
 
 
-        if data:
-
-            # Limit interface queryset by assigned device or virtual machine
-            if data.get('device'):
-                params = {
-                    f"device__{self.fields['device'].to_field_name}": data.get('device')
-                }
-            elif data.get('virtual_machine'):
-                params = {
-                    f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data.get('virtual_machine')
-                }
-            else:
-                params = {
-                    'device': None,
-                    'virtual_machine': None,
-                }
-            self.fields['interface'].queryset = self.fields['interface'].queryset.filter(**params)
+        # if data:
+        #
+        #     # Limit interface queryset by assigned device or virtual machine
+        #     if data.get('device'):
+        #         params = {
+        #             f"device__{self.fields['device'].to_field_name}": data.get('device')
+        #         }
+        #     elif data.get('virtual_machine'):
+        #         params = {
+        #             f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data.get('virtual_machine')
+        #         }
+        #     else:
+        #         params = {
+        #             'device': None,
+        #             'virtual_machine': None,
+        #         }
+        #     self.fields['interface'].queryset = self.fields['interface'].queryset.filter(**params)
 
 
     def clean(self):
     def clean(self):
         super().clean()
         super().clean()
@@ -1197,7 +1197,7 @@ class ServiceForm(BootstrapMixin, CustomFieldModelForm):
         if self.instance.device:
         if self.instance.device:
             self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
             self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
                 assigned_object_type=ContentType.objects.get_for_model(Interface),
                 assigned_object_type=ContentType.objects.get_for_model(Interface),
-                assigned_object_id__in=self.instance.device.vc_interfaces.values('id', flat=True)
+                assigned_object_id__in=self.instance.device.vc_interfaces.values_list('id', flat=True)
             )
             )
         elif self.instance.virtual_machine:
         elif self.instance.virtual_machine:
             self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
             self.fields['ipaddresses'].queryset = IPAddress.objects.filter(

+ 5 - 24
netbox/ipam/models.py

@@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError, ObjectDoesNotExist
 from django.core.exceptions import ValidationError, ObjectDoesNotExist
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
-from django.db.models import F, Q
+from django.db.models import F
 from django.urls import reverse
 from django.urls import reverse
 from taggit.managers import TaggableManager
 from taggit.managers import TaggableManager
 
 
@@ -653,7 +653,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
     objects = IPAddressManager()
     objects = IPAddressManager()
 
 
     csv_headers = [
     csv_headers = [
-        'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
+        'address', 'vrf', 'tenant', 'status', 'role', 'assigned_object_type', 'assigned_object_id', 'is_primary',
         'dns_name', 'description',
         'dns_name', 'description',
     ]
     ]
     clone_fields = [
     clone_fields = [
@@ -753,17 +753,11 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
         super().save(*args, **kwargs)
         super().save(*args, **kwargs)
 
 
     def to_objectchange(self, action):
     def to_objectchange(self, action):
-        # Annotate the assigned Interface (if any)
-        try:
-            parent_obj = self.interface
-        except ObjectDoesNotExist:
-            parent_obj = None
-
         return ObjectChange(
         return ObjectChange(
             changed_object=self,
             changed_object=self,
             object_repr=str(self),
             object_repr=str(self),
             action=action,
             action=action,
-            related_object=parent_obj,
+            related_object=self.assigned_object,
             object_data=serialize_object(self)
             object_data=serialize_object(self)
         )
         )
 
 
@@ -783,9 +777,8 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
             self.tenant.name if self.tenant else None,
             self.tenant.name if self.tenant else None,
             self.get_status_display(),
             self.get_status_display(),
             self.get_role_display(),
             self.get_role_display(),
-            self.device.identifier if self.device else None,
-            self.virtual_machine.name if self.virtual_machine else None,
-            self.interface.name if self.interface else None,
+            '{}.{}'.format(self.assigned_object_type.app_label, self.assigned_object_type.model) if self.assigned_object_type else None,
+            self.assigned_object_id,
             is_primary,
             is_primary,
             self.dns_name,
             self.dns_name,
             self.description,
             self.description,
@@ -806,18 +799,6 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
             self.address.prefixlen = value
             self.address.prefixlen = value
     mask_length = property(fset=_set_mask_length)
     mask_length = property(fset=_set_mask_length)
 
 
-    @property
-    def device(self):
-        if self.interface:
-            return self.interface.device
-        return None
-
-    @property
-    def virtual_machine(self):
-        if self.interface:
-            return self.interface.virtual_machine
-        return None
-
     def get_status_class(self):
     def get_status_class(self):
         return self.STATUS_CLASS_MAP.get(self.status)
         return self.STATUS_CLASS_MAP.get(self.status)
 
 

+ 2 - 2
netbox/ipam/tables.py

@@ -481,13 +481,13 @@ class IPAddressAssignTable(BaseTable):
         template_code=IPADDRESS_PARENT,
         template_code=IPADDRESS_PARENT,
         orderable=False
         orderable=False
     )
     )
-    interface = tables.Column(
+    assigned_object = tables.Column(
         orderable=False
         orderable=False
     )
     )
 
 
     class Meta(BaseTable.Meta):
     class Meta(BaseTable.Meta):
         model = IPAddress
         model = IPAddress
-        fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'description')
+        fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'parent', 'assigned_object', 'description')
         orderable = False
         orderable = False
 
 
 
 

+ 18 - 17
netbox/ipam/tests/test_filters.py

@@ -4,7 +4,7 @@ from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer,
 from ipam.choices import *
 from ipam.choices import *
 from ipam.filters import *
 from ipam.filters import *
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
-from virtualization.models import Cluster, ClusterType, Interfaces as VMInterface, VirtualMachine
+from virtualization.models import Cluster, ClusterType, Interface as VMInterface, VirtualMachine
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 
 
 
 
@@ -415,16 +415,16 @@ class IPAddressTestCase(TestCase):
         Tenant.objects.bulk_create(tenants)
         Tenant.objects.bulk_create(tenants)
 
 
         ipaddresses = (
         ipaddresses = (
-            IPAddress(address='10.0.0.1/24', tenant=None, vrf=None, interface=None, status=IPAddressStatusChoices.STATUS_ACTIVE, dns_name='ipaddress-a'),
-            IPAddress(address='10.0.0.2/24', tenant=tenants[0], vrf=vrfs[0], interface=interfaces[0], status=IPAddressStatusChoices.STATUS_ACTIVE, dns_name='ipaddress-b'),
-            IPAddress(address='10.0.0.3/24', tenant=tenants[1], vrf=vrfs[1], interface=interfaces[1], status=IPAddressStatusChoices.STATUS_RESERVED, role=IPAddressRoleChoices.ROLE_VIP, dns_name='ipaddress-c'),
-            IPAddress(address='10.0.0.4/24', tenant=tenants[2], vrf=vrfs[2], interface=interfaces[2], status=IPAddressStatusChoices.STATUS_DEPRECATED, role=IPAddressRoleChoices.ROLE_SECONDARY, dns_name='ipaddress-d'),
-            IPAddress(address='10.0.0.1/25', tenant=None, vrf=None, interface=None, status=IPAddressStatusChoices.STATUS_ACTIVE),
-            IPAddress(address='2001:db8::1/64', tenant=None, vrf=None, interface=None, status=IPAddressStatusChoices.STATUS_ACTIVE, dns_name='ipaddress-a'),
-            IPAddress(address='2001:db8::2/64', tenant=tenants[0], vrf=vrfs[0], interface=interfaces[3], status=IPAddressStatusChoices.STATUS_ACTIVE, dns_name='ipaddress-b'),
-            IPAddress(address='2001:db8::3/64', tenant=tenants[1], vrf=vrfs[1], interface=interfaces[4], status=IPAddressStatusChoices.STATUS_RESERVED, role=IPAddressRoleChoices.ROLE_VIP, dns_name='ipaddress-c'),
-            IPAddress(address='2001:db8::4/64', tenant=tenants[2], vrf=vrfs[2], interface=interfaces[5], status=IPAddressStatusChoices.STATUS_DEPRECATED, role=IPAddressRoleChoices.ROLE_SECONDARY, dns_name='ipaddress-d'),
-            IPAddress(address='2001:db8::1/65', tenant=None, vrf=None, interface=None, status=IPAddressStatusChoices.STATUS_ACTIVE),
+            IPAddress(address='10.0.0.1/24', tenant=None, vrf=None, assigned_object=None, status=IPAddressStatusChoices.STATUS_ACTIVE, dns_name='ipaddress-a'),
+            IPAddress(address='10.0.0.2/24', tenant=tenants[0], vrf=vrfs[0], assigned_object=interfaces[0], status=IPAddressStatusChoices.STATUS_ACTIVE, dns_name='ipaddress-b'),
+            IPAddress(address='10.0.0.3/24', tenant=tenants[1], vrf=vrfs[1], assigned_object=interfaces[1], status=IPAddressStatusChoices.STATUS_RESERVED, role=IPAddressRoleChoices.ROLE_VIP, dns_name='ipaddress-c'),
+            IPAddress(address='10.0.0.4/24', tenant=tenants[2], vrf=vrfs[2], assigned_object=interfaces[2], status=IPAddressStatusChoices.STATUS_DEPRECATED, role=IPAddressRoleChoices.ROLE_SECONDARY, dns_name='ipaddress-d'),
+            IPAddress(address='10.0.0.1/25', tenant=None, vrf=None, assigned_object=None, status=IPAddressStatusChoices.STATUS_ACTIVE),
+            IPAddress(address='2001:db8::1/64', tenant=None, vrf=None, assigned_object=None, status=IPAddressStatusChoices.STATUS_ACTIVE, dns_name='ipaddress-a'),
+            IPAddress(address='2001:db8::2/64', tenant=tenants[0], vrf=vrfs[0], assigned_object=vm_interfaces[0], status=IPAddressStatusChoices.STATUS_ACTIVE, dns_name='ipaddress-b'),
+            IPAddress(address='2001:db8::3/64', tenant=tenants[1], vrf=vrfs[1], assigned_object=vm_interfaces[1], status=IPAddressStatusChoices.STATUS_RESERVED, role=IPAddressRoleChoices.ROLE_VIP, dns_name='ipaddress-c'),
+            IPAddress(address='2001:db8::4/64', tenant=tenants[2], vrf=vrfs[2], assigned_object=vm_interfaces[2], status=IPAddressStatusChoices.STATUS_DEPRECATED, role=IPAddressRoleChoices.ROLE_SECONDARY, dns_name='ipaddress-d'),
+            IPAddress(address='2001:db8::1/65', tenant=None, vrf=None, assigned_object=None, status=IPAddressStatusChoices.STATUS_ACTIVE),
         )
         )
         IPAddress.objects.bulk_create(ipaddresses)
         IPAddress.objects.bulk_create(ipaddresses)
 
 
@@ -486,12 +486,13 @@ class IPAddressTestCase(TestCase):
         params = {'virtual_machine': [vms[0].name, vms[1].name]}
         params = {'virtual_machine': [vms[0].name, vms[1].name]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
-    def test_interface(self):
-        interfaces = Interface.objects.all()[:2]
-        params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-        params = {'interface': ['Interface 1', 'Interface 2']}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+    # TODO: Restore filtering by interface
+    # def test_interface(self):
+    #     interfaces = Interface.objects.all()[:2]
+    #     params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
+    #     self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+    #     params = {'interface': ['Interface 1', 'Interface 2']}
+    #     self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_assigned_to_interface(self):
     def test_assigned_to_interface(self):
         params = {'assigned_to_interface': 'true'}
         params = {'assigned_to_interface': 'true'}

+ 0 - 1
netbox/ipam/tests/test_views.py

@@ -236,7 +236,6 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             'tenant': None,
             'tenant': None,
             'status': IPAddressStatusChoices.STATUS_RESERVED,
             'status': IPAddressStatusChoices.STATUS_RESERVED,
             'role': IPAddressRoleChoices.ROLE_ANYCAST,
             'role': IPAddressRoleChoices.ROLE_ANYCAST,
-            'interface': None,
             'nat_inside': None,
             'nat_inside': None,
             'dns_name': 'example',
             'dns_name': 'example',
             'description': 'A new IP address',
             'description': 'A new IP address',

+ 43 - 47
netbox/ipam/views.py

@@ -517,7 +517,7 @@ class PrefixIPAddressesView(ObjectView):
 
 
         # Find all IPAddresses belonging to this Prefix
         # Find all IPAddresses belonging to this Prefix
         ipaddresses = prefix.get_child_ips().restrict(request.user, 'view').prefetch_related(
         ipaddresses = prefix.get_child_ips().restrict(request.user, 'view').prefetch_related(
-            'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
+            'vrf', 'primary_ip4_for', 'primary_ip6_for'
         )
         )
 
 
         # Add available IP addresses to the table if requested
         # Add available IP addresses to the table if requested
@@ -593,7 +593,7 @@ class PrefixBulkDeleteView(BulkDeleteView):
 
 
 class IPAddressListView(ObjectListView):
 class IPAddressListView(ObjectListView):
     queryset = IPAddress.objects.prefetch_related(
     queryset = IPAddress.objects.prefetch_related(
-        'vrf__tenant', 'tenant', 'nat_inside', 'interface__device', 'interface__virtual_machine'
+        'vrf__tenant', 'tenant', 'nat_inside'
     )
     )
     filterset = filters.IPAddressFilterSet
     filterset = filters.IPAddressFilterSet
     filterset_form = forms.IPAddressFilterForm
     filterset_form = forms.IPAddressFilterForm
@@ -607,49 +607,47 @@ class IPAddressView(ObjectView):
 
 
         ipaddress = get_object_or_404(self.queryset, pk=pk)
         ipaddress = get_object_or_404(self.queryset, pk=pk)
 
 
-        # Parent prefixes table
-        parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
-            vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip)
-        ).prefetch_related(
-            'site', 'role'
-        )
-        parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
-        parent_prefixes_table.exclude = ('vrf',)
-
-        # Duplicate IPs table
-        duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
-            vrf=ipaddress.vrf, address=str(ipaddress.address)
-        ).exclude(
-            pk=ipaddress.pk
-        ).prefetch_related(
-            'nat_inside', 'interface__device'
-        )
-        # Exclude anycast IPs if this IP is anycast
-        if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST:
-            duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
-        duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
-
-        # Related IP table
-        related_ips = IPAddress.objects.restrict(request.user, 'view').prefetch_related(
-            'interface__device'
-        ).exclude(
-            address=str(ipaddress.address)
-        ).filter(
-            vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address)
-        )
-        related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
-
-        paginate = {
-            'paginator_class': EnhancedPaginator,
-            'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
-        }
-        RequestConfig(request, paginate).configure(related_ips_table)
+        # # Parent prefixes table
+        # parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
+        #     vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip)
+        # ).prefetch_related(
+        #     'site', 'role'
+        # )
+        # parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
+        # parent_prefixes_table.exclude = ('vrf',)
+        #
+        # # Duplicate IPs table
+        # duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
+        #     vrf=ipaddress.vrf, address=str(ipaddress.address)
+        # ).exclude(
+        #     pk=ipaddress.pk
+        # ).prefetch_related(
+        #     'nat_inside'
+        # )
+        # # Exclude anycast IPs if this IP is anycast
+        # if ipaddress.role == IPAddressRoleChoices.ROLE_ANYCAST:
+        #     duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
+        # duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False)
+        #
+        # # Related IP table
+        # related_ips = IPAddress.objects.restrict(request.user, 'view').exclude(
+        #     address=str(ipaddress.address)
+        # ).filter(
+        #     vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address)
+        # )
+        # related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
+        #
+        # paginate = {
+        #     'paginator_class': EnhancedPaginator,
+        #     'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
+        # }
+        # RequestConfig(request, paginate).configure(related_ips_table)
 
 
         return render(request, 'ipam/ipaddress.html', {
         return render(request, 'ipam/ipaddress.html', {
             'ipaddress': ipaddress,
             'ipaddress': ipaddress,
-            'parent_prefixes_table': parent_prefixes_table,
-            'duplicate_ips_table': duplicate_ips_table,
-            'related_ips_table': related_ips_table,
+            # 'parent_prefixes_table': parent_prefixes_table,
+            # 'duplicate_ips_table': duplicate_ips_table,
+            # 'related_ips_table': related_ips_table,
         })
         })
 
 
 
 
@@ -699,9 +697,7 @@ class IPAddressAssignView(ObjectView):
 
 
         if form.is_valid():
         if form.is_valid():
 
 
-            addresses = self.queryset.prefetch_related(
-                'vrf', 'tenant', 'interface__device', 'interface__virtual_machine'
-            )
+            addresses = self.queryset.prefetch_related('vrf', 'tenant')
             # Limit to 100 results
             # Limit to 100 results
             addresses = filters.IPAddressFilterSet(request.POST, addresses).qs[:100]
             addresses = filters.IPAddressFilterSet(request.POST, addresses).qs[:100]
             table = tables.IPAddressAssignTable(addresses)
             table = tables.IPAddressAssignTable(addresses)
@@ -734,7 +730,7 @@ class IPAddressBulkImportView(BulkImportView):
 
 
 
 
 class IPAddressBulkEditView(BulkEditView):
 class IPAddressBulkEditView(BulkEditView):
-    queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant').prefetch_related('interface__device')
+    queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
     filterset = filters.IPAddressFilterSet
     filterset = filters.IPAddressFilterSet
     table = tables.IPAddressTable
     table = tables.IPAddressTable
     form = forms.IPAddressBulkEditForm
     form = forms.IPAddressBulkEditForm
@@ -742,7 +738,7 @@ class IPAddressBulkEditView(BulkEditView):
 
 
 
 
 class IPAddressBulkDeleteView(BulkDeleteView):
 class IPAddressBulkDeleteView(BulkDeleteView):
-    queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant').prefetch_related('interface__device')
+    queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
     filterset = filters.IPAddressFilterSet
     filterset = filters.IPAddressFilterSet
     table = tables.IPAddressTable
     table = tables.IPAddressTable
     default_return_url = 'ipam:ipaddress_list'
     default_return_url = 'ipam:ipaddress_list'

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

@@ -120,8 +120,8 @@
                 <tr>
                 <tr>
                     <td>Assignment</td>
                     <td>Assignment</td>
                     <td>
                     <td>
-                        {% if ipaddress.interface %}
-                            <span><a href="{{ ipaddress.interface.parent.get_absolute_url }}">{{ ipaddress.interface.parent }}</a> ({{ ipaddress.interface }})</span>
+                        {% if ipaddress.assigned_object %}
+                            <span><a href="{{ ipaddress.assigned_object.parent.get_absolute_url }}">{{ ipaddress.assigned_object.parent }}</a> ({{ ipaddress.assigned_object }})</span>
                         {% else %}
                         {% else %}
                             <span class="text-muted">&mdash;</span>
                             <span class="text-muted">&mdash;</span>
                         {% endif %}
                         {% endif %}
@@ -132,8 +132,8 @@
                     <td>
                     <td>
                         {% if ipaddress.nat_inside %}
                         {% if ipaddress.nat_inside %}
                             <a href="{% url 'ipam:ipaddress' pk=ipaddress.nat_inside.pk %}">{{ ipaddress.nat_inside }}</a>
                             <a href="{% url 'ipam:ipaddress' pk=ipaddress.nat_inside.pk %}">{{ ipaddress.nat_inside }}</a>
-                            {% if ipaddress.nat_inside.interface %}
-                                (<a href="{{ ipaddress.nat_inside.interface.parent.get_absolute_url }}">{{ ipaddress.nat_inside.interface.parent }}</a>)
+                            {% if ipaddress.nat_inside.assigned_object %}
+                                (<a href="{{ ipaddress.nat_inside.assigned_object.parent.get_absolute_url }}">{{ ipaddress.nat_inside.assigned_object.parent }}</a>)
                             {% endif %}
                             {% endif %}
                         {% else %}
                         {% else %}
                             <span class="text-muted">None</span>
                             <span class="text-muted">None</span>

+ 0 - 7
netbox/virtualization/tests/test_views.py

@@ -267,10 +267,3 @@ class InterfaceTestCase(
             # 'untagged_vlan': vlans[0].pk,
             # 'untagged_vlan': vlans[0].pk,
             # 'tagged_vlans': [v.pk for v in vlans[1:4]],
             # 'tagged_vlans': [v.pk for v in vlans[1:4]],
         }
         }
-
-        cls.csv_data = (
-            "device,name,type",
-            "Device 1,Interface 4,1000BASE-T (1GE)",
-            "Device 1,Interface 5,1000BASE-T (1GE)",
-            "Device 1,Interface 6,1000BASE-T (1GE)",
-        )