mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-26 23:27:46 -06:00
Initial work on #4721 (WIP)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from .choices import IPAddressRoleChoices
|
||||
|
||||
# BGP ASN bounds
|
||||
@@ -29,6 +31,11 @@ PREFIX_LENGTH_MAX = 127 # IPv6
|
||||
# IPAddresses
|
||||
#
|
||||
|
||||
IPADDRESS_ASSIGNMENT_MODELS = Q(
|
||||
Q(app_label='dcim', model='interface') |
|
||||
Q(app_label='virtualization', model='interface')
|
||||
)
|
||||
|
||||
IPADDRESS_MASK_LENGTH_MIN = 1
|
||||
IPADDRESS_MASK_LENGTH_MAX = 128 # IPv6
|
||||
|
||||
|
||||
@@ -299,37 +299,37 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet,
|
||||
to_field_name='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)',
|
||||
)
|
||||
interface = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='interface__name',
|
||||
queryset=Interface.objects.unrestricted(),
|
||||
to_field_name='name',
|
||||
label='Interface (ID)',
|
||||
)
|
||||
interface_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Interface.objects.unrestricted(),
|
||||
label='Interface (ID)',
|
||||
)
|
||||
# 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)',
|
||||
# )
|
||||
# interface = django_filters.ModelMultipleChoiceFilter(
|
||||
# field_name='interface__name',
|
||||
# queryset=Interface.objects.unrestricted(),
|
||||
# to_field_name='name',
|
||||
# label='Interface (ID)',
|
||||
# )
|
||||
# interface_id = django_filters.ModelMultipleChoiceFilter(
|
||||
# queryset=Interface.objects.unrestricted(),
|
||||
# label='Interface (ID)',
|
||||
# )
|
||||
assigned_to_interface = django_filters.BooleanFilter(
|
||||
method='_assigned_to_interface',
|
||||
label='Is assigned to an interface',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
|
||||
from dcim.models import Device, Interface, Rack, Region, Site
|
||||
@@ -14,7 +15,7 @@ from utilities.forms import (
|
||||
ExpandableIPAddressField, ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
|
||||
BOOLEAN_WITH_BLANK_CHOICES,
|
||||
)
|
||||
from virtualization.models import VirtualMachine
|
||||
from virtualization.models import Interface as VMInterface, VirtualMachine
|
||||
from .choices import *
|
||||
from .constants import *
|
||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||
@@ -1194,13 +1195,14 @@ class ServiceForm(BootstrapMixin, CustomFieldModelForm):
|
||||
|
||||
# Limit IP address choices to those assigned to interfaces of the parent device/VM
|
||||
if self.instance.device:
|
||||
vc_interface_ids = [i['id'] for i in self.instance.device.vc_interfaces.values('id')]
|
||||
self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
|
||||
interface_id__in=vc_interface_ids
|
||||
assigned_object_type=ContentType.objects.get_for_model(Interface),
|
||||
assigned_object_id__in=self.instance.device.vc_interfaces.values('id', flat=True)
|
||||
)
|
||||
elif self.instance.virtual_machine:
|
||||
self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
|
||||
interface__virtual_machine=self.instance.virtual_machine
|
||||
assigned_object_type=ContentType.objects.get_for_model(VMInterface),
|
||||
assigned_object_id__in=self.instance.virtual_machine.interfaces.values_list('id', flat=True)
|
||||
)
|
||||
else:
|
||||
self.fields['ipaddresses'].choices = []
|
||||
|
||||
35
netbox/ipam/migrations/0037_ipaddress_assignment.py
Normal file
35
netbox/ipam/migrations/0037_ipaddress_assignment.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def set_assigned_object_type(apps, schema_editor):
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
IPAddress = apps.get_model('ipam', 'IPAddress')
|
||||
|
||||
device_ct = ContentType.objects.get(app_label='dcim', model='interface').pk
|
||||
IPAddress.objects.update(assigned_object_type=device_ct)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('ipam', '0036_standardize_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='ipaddress',
|
||||
old_name='interface',
|
||||
new_name='assigned_object_id',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='assigned_object_type',
|
||||
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'dcim'), ('model', 'interface')), models.Q(('app_label', 'virtualization'), ('model', 'interface')), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType', blank=True, null=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=set_assigned_object_type
|
||||
),
|
||||
]
|
||||
@@ -1,6 +1,7 @@
|
||||
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, ObjectDoesNotExist
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
@@ -606,13 +607,25 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
||||
blank=True,
|
||||
help_text='The functional role of this IP'
|
||||
)
|
||||
interface = models.ForeignKey(
|
||||
assigned_object_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
limit_choices_to=IPADDRESS_ASSIGNMENT_MODELS,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
assigned_object_id = models.ForeignKey(
|
||||
to='dcim.Interface',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='ip_addresses',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
assigned_object = GenericForeignKey(
|
||||
ct_field='assigned_object_type',
|
||||
fk_field='assigned_object_id'
|
||||
)
|
||||
nat_inside = models.OneToOneField(
|
||||
to='self',
|
||||
on_delete=models.SET_NULL,
|
||||
|
||||
@@ -431,18 +431,14 @@ class IPAddressTable(BaseTable):
|
||||
tenant = tables.TemplateColumn(
|
||||
template_code=TENANT_LINK
|
||||
)
|
||||
parent = tables.TemplateColumn(
|
||||
template_code=IPADDRESS_PARENT,
|
||||
orderable=False
|
||||
)
|
||||
interface = tables.Column(
|
||||
orderable=False
|
||||
assigned = tables.BooleanColumn(
|
||||
accessor='assigned_object_id'
|
||||
)
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = IPAddress
|
||||
fields = (
|
||||
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'dns_name', 'description',
|
||||
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
|
||||
)
|
||||
row_attrs = {
|
||||
'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
|
||||
@@ -465,11 +461,11 @@ class IPAddressDetailTable(IPAddressTable):
|
||||
|
||||
class Meta(IPAddressTable.Meta):
|
||||
fields = (
|
||||
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'parent', 'interface', 'dns_name',
|
||||
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name',
|
||||
'description', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'dns_name', 'description',
|
||||
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer,
|
||||
from ipam.choices import *
|
||||
from ipam.filters import *
|
||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||
from virtualization.models import Cluster, ClusterType, VirtualMachine
|
||||
from virtualization.models import Cluster, ClusterType, Interfaces as VMInterface, VirtualMachine
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
|
||||
|
||||
@@ -375,6 +375,13 @@ class IPAddressTestCase(TestCase):
|
||||
)
|
||||
Device.objects.bulk_create(devices)
|
||||
|
||||
interfaces = (
|
||||
Interface(device=devices[0], name='Interface 1'),
|
||||
Interface(device=devices[1], name='Interface 2'),
|
||||
Interface(device=devices[2], name='Interface 3'),
|
||||
)
|
||||
Interface.objects.bulk_create(interfaces)
|
||||
|
||||
clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||
cluster = Cluster.objects.create(type=clustertype, name='Cluster 1')
|
||||
|
||||
@@ -385,15 +392,12 @@ class IPAddressTestCase(TestCase):
|
||||
)
|
||||
VirtualMachine.objects.bulk_create(virtual_machines)
|
||||
|
||||
interfaces = (
|
||||
Interface(device=devices[0], name='Interface 1'),
|
||||
Interface(device=devices[1], name='Interface 2'),
|
||||
Interface(device=devices[2], name='Interface 3'),
|
||||
Interface(virtual_machine=virtual_machines[0], name='Interface 1'),
|
||||
Interface(virtual_machine=virtual_machines[1], name='Interface 2'),
|
||||
Interface(virtual_machine=virtual_machines[2], name='Interface 3'),
|
||||
vm_interfaces = (
|
||||
VMInterface(virtual_machine=virtual_machines[0], name='Interface 1'),
|
||||
VMInterface(virtual_machine=virtual_machines[1], name='Interface 2'),
|
||||
VMInterface(virtual_machine=virtual_machines[2], name='Interface 3'),
|
||||
)
|
||||
Interface.objects.bulk_create(interfaces)
|
||||
VMInterface.objects.bulk_create(vm_interfaces)
|
||||
|
||||
tenant_groups = (
|
||||
TenantGroup(name='Tenant group 1', slug='tenant-group-1'),
|
||||
|
||||
Reference in New Issue
Block a user