From 5930a6420329f76fb8597a972fb78db0b5652bc9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 18 Aug 2017 16:57:20 -0400 Subject: [PATCH] Converted IPAddress.interface to a GenericForeignKey --- netbox/dcim/migrations/0042_device_cluster.py | 2 +- netbox/dcim/models.py | 5 +++ netbox/ipam/api/views.py | 6 ++- netbox/ipam/fixtures/ipam.json | 36 +++++++-------- .../0019_ipaddress_interface_to_gfk.py | 44 +++++++++++++++++++ netbox/ipam/models.py | 15 +++++-- netbox/ipam/views.py | 18 +++++--- .../virtualization/migrations/0001_initial.py | 7 +-- netbox/virtualization/models.py | 5 +++ 9 files changed, 106 insertions(+), 32 deletions(-) create mode 100644 netbox/ipam/migrations/0019_ipaddress_interface_to_gfk.py diff --git a/netbox/dcim/migrations/0042_device_cluster.py b/netbox/dcim/migrations/0042_device_cluster.py index a3c2b7859..f9a0a8637 100644 --- a/netbox/dcim/migrations/0042_device_cluster.py +++ b/netbox/dcim/migrations/0042_device_cluster.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-08-16 21:06 +# Generated by Django 1.11.4 on 2017-08-18 19:46 from __future__ import unicode_literals from django.db import migrations, models diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index d6e68df9b..5bb2996e0 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -1175,6 +1175,11 @@ class Interface(models.Model): help_text="This interface is used only for out-of-band management" ) description = models.CharField(max_length=100, blank=True) + ip_addresses = GenericRelation( + to='ipam.IPAddress', + content_type_field='interface_type', + object_id_field='interface_id' + ) objects = InterfaceQuerySet.as_manager() diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 9cf93cb4b..50c9c8a95 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -134,7 +134,11 @@ class PrefixViewSet(WritableSerializerMixin, CustomFieldModelViewSet): # class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet): - queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside') + queryset = IPAddress.objects.select_related( + 'vrf__tenant', 'tenant', 'nat_inside' + ).prefetch_related( + 'interface__device' + ) serializer_class = serializers.IPAddressSerializer write_serializer_class = serializers.WritableIPAddressSerializer filter_class = filters.IPAddressFilter diff --git a/netbox/ipam/fixtures/ipam.json b/netbox/ipam/fixtures/ipam.json index 1a981a941..10e22b2d7 100644 --- a/netbox/ipam/fixtures/ipam.json +++ b/netbox/ipam/fixtures/ipam.json @@ -70,7 +70,7 @@ "family": 4, "address": "10.0.255.1/32", "vrf": null, - "interface": 3, + "interface_id": 3, "nat_inside": null, "description": "" } @@ -84,7 +84,7 @@ "family": 4, "address": "169.254.254.1/31", "vrf": null, - "interface": 4, + "interface_id": 4, "nat_inside": null, "description": "" } @@ -98,7 +98,7 @@ "family": 4, "address": "10.0.255.2/32", "vrf": null, - "interface": 185, + "interface_id": 185, "nat_inside": null, "description": "" } @@ -112,7 +112,7 @@ "family": 4, "address": "169.254.1.1/31", "vrf": null, - "interface": 213, + "interface_id": 213, "nat_inside": null, "description": "" } @@ -126,7 +126,7 @@ "family": 4, "address": "10.0.254.1/24", "vrf": null, - "interface": 12, + "interface_id": 12, "nat_inside": null, "description": "" } @@ -140,7 +140,7 @@ "family": 4, "address": "10.15.21.1/31", "vrf": null, - "interface": 218, + "interface_id": 218, "nat_inside": null, "description": "" } @@ -154,7 +154,7 @@ "family": 4, "address": "10.15.21.2/31", "vrf": null, - "interface": 9, + "interface_id": 9, "nat_inside": null, "description": "" } @@ -168,7 +168,7 @@ "family": 4, "address": "10.15.22.1/31", "vrf": null, - "interface": 8, + "interface_id": 8, "nat_inside": null, "description": "" } @@ -182,7 +182,7 @@ "family": 4, "address": "10.15.20.1/31", "vrf": null, - "interface": 7, + "interface_id": 7, "nat_inside": null, "description": "" } @@ -196,7 +196,7 @@ "family": 4, "address": "10.16.20.1/31", "vrf": null, - "interface": 216, + "interface_id": 216, "nat_inside": null, "description": "" } @@ -210,7 +210,7 @@ "family": 4, "address": "10.15.22.2/31", "vrf": null, - "interface": 206, + "interface_id": 206, "nat_inside": null, "description": "" } @@ -224,7 +224,7 @@ "family": 4, "address": "10.16.22.1/31", "vrf": null, - "interface": 217, + "interface_id": 217, "nat_inside": null, "description": "" } @@ -238,7 +238,7 @@ "family": 4, "address": "10.16.22.2/31", "vrf": null, - "interface": 205, + "interface_id": 205, "nat_inside": null, "description": "" } @@ -252,7 +252,7 @@ "family": 4, "address": "10.16.20.2/31", "vrf": null, - "interface": 211, + "interface_id": 211, "nat_inside": null, "description": "" } @@ -266,7 +266,7 @@ "family": 4, "address": "10.15.22.2/31", "vrf": null, - "interface": 212, + "interface_id": 212, "nat_inside": null, "description": "" } @@ -280,7 +280,7 @@ "family": 4, "address": "10.0.254.2/32", "vrf": null, - "interface": 188, + "interface_id": 188, "nat_inside": null, "description": "" } @@ -294,7 +294,7 @@ "family": 4, "address": "169.254.1.1/31", "vrf": null, - "interface": 200, + "interface_id": 200, "nat_inside": null, "description": "" } @@ -308,7 +308,7 @@ "family": 4, "address": "169.254.1.2/31", "vrf": null, - "interface": 194, + "interface_id": 194, "nat_inside": null, "description": "" } diff --git a/netbox/ipam/migrations/0019_ipaddress_interface_to_gfk.py b/netbox/ipam/migrations/0019_ipaddress_interface_to_gfk.py new file mode 100644 index 000000000..ab2f1b7f8 --- /dev/null +++ b/netbox/ipam/migrations/0019_ipaddress_interface_to_gfk.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-08-18 19:31 +from __future__ import unicode_literals + +from django.contrib.contenttypes.models import ContentType +from django.db import migrations, models +import django.db.models.deletion + + +def set_interface_type(apps, schema_editor): + """ + Set the interface_type field to 'Interface' for all IP addresses assigned to an Interface. + """ + Interface = apps.get_model('dcim', 'Interface') + interface_type = ContentType.objects.get_for_model(Interface) + IPAddress = apps.get_model('ipam', 'IPAddress') + IPAddress.objects.filter(interface__isnull=False).update(interface_type=interface_type) + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('ipam', '0018_remove_service_uniqueness_constraint'), + ] + + operations = [ + migrations.AddField( + model_name='ipaddress', + name='interface_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType'), + ), + migrations.RunPython(set_interface_type), + migrations.AlterField( + model_name='IPAddress', + name='interface', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.RenameField( + model_name='IPAddress', + old_name='interface', + new_name='interface_id', + ) + ] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index bbd5e1827..f053c3892 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -2,10 +2,12 @@ from __future__ import unicode_literals import netaddr from django.conf import settings -from django.contrib.contenttypes.fields import GenericRelation +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models +from django.db.models import Q from django.db.models.expressions import RawSQL from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible @@ -407,8 +409,15 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel): role = models.PositiveSmallIntegerField( 'Role', choices=IPADDRESS_ROLE_CHOICES, blank=True, null=True, help_text='The functional role of this IP' ) - interface = models.ForeignKey(Interface, related_name='ip_addresses', on_delete=models.CASCADE, blank=True, - null=True) + interface_type = models.ForeignKey( + to=ContentType, + on_delete=models.PROTECT, + limit_choices_to=Q(app_label='dcim', model='interface') | Q(app_label='virtualization', model='vminterface'), + blank=True, + null=True + ) + interface_id = models.PositiveIntegerField(blank=True, null=True) + interface = GenericForeignKey('interface_type', 'interface_id') nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True, null=True, verbose_name='NAT (Inside)', help_text="The IP for which this address is the \"outside\" IP") diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 05f16aa35..39834e306 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -594,7 +594,11 @@ class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): # class IPAddressListView(ObjectListView): - queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside') + queryset = IPAddress.objects.select_related( + 'vrf__tenant', 'tenant', 'nat_inside' + ).prefetch_related( + 'interface__device' + ) filter = filters.IPAddressFilter filter_form = forms.IPAddressFilterForm table = tables.IPAddressDetailTable @@ -605,7 +609,7 @@ class IPAddressView(View): def get(self, request, pk): - ipaddress = get_object_or_404(IPAddress.objects.select_related('interface__device'), pk=pk) + ipaddress = get_object_or_404(IPAddress.objects.select_related('vrf__tenant', 'tenant'), pk=pk) # Parent prefixes table parent_prefixes = Prefix.objects.filter( @@ -622,12 +626,14 @@ class IPAddressView(View): ).exclude( pk=ipaddress.pk ).select_related( - 'interface__device', 'nat_inside' + 'nat_inside' + ).prefetch_related( + 'interface__device' ) duplicate_ips_table = tables.IPAddressTable(list(duplicate_ips), orderable=False) # Related IP table - related_ips = IPAddress.objects.select_related( + related_ips = IPAddress.objects.prefetch_related( 'interface__device' ).exclude( address=str(ipaddress.address) @@ -681,7 +687,7 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView): class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_ipaddress' cls = IPAddress - queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device') + queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant').prefetch_related('interface__device') filter = filters.IPAddressFilter table = tables.IPAddressTable form = forms.IPAddressBulkEditForm @@ -691,7 +697,7 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView): class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_ipaddress' cls = IPAddress - queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device') + queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant').prefetch_related('interface__device') filter = filters.IPAddressFilter table = tables.IPAddressTable default_return_url = 'ipam:ipaddress_list' diff --git a/netbox/virtualization/migrations/0001_initial.py b/netbox/virtualization/migrations/0001_initial.py index 57b8dc4e0..cc0135b40 100644 --- a/netbox/virtualization/migrations/0001_initial.py +++ b/netbox/virtualization/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2017-08-16 21:06 +# Generated by Django 1.11.4 on 2017-08-18 19:46 from __future__ import unicode_literals import dcim.fields @@ -13,9 +13,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('tenancy', '0003_unicode_literals'), ('dcim', '0041_napalm_integration'), - ('ipam', '0018_remove_service_uniqueness_constraint'), + ('ipam', '0019_ipaddress_interface_to_gfk'), + ('tenancy', '0003_unicode_literals'), ] operations = [ @@ -89,6 +89,7 @@ class Migration(migrations.Migration): ('virtual_machine', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='virtualization.VirtualMachine')), ], options={ + 'verbose_name': 'VM interface', 'ordering': ['virtual_machine', 'name'], }, ), diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index ce5f266d8..d23c6f2df 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -221,6 +221,11 @@ class VMInterface(models.Model): max_length=100, blank=True ) + ip_addresses = GenericRelation( + to='ipam.IPAddress', + content_type_field='interface_type', + object_id_field='interface_id' + ) class Meta: ordering = ['virtual_machine', 'name']