mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 18:08:38 -06:00
Work on IP to Prefix ForeignKey relationship
This commit is contained in:
parent
697610db94
commit
747fef0bc2
@ -531,6 +531,16 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
method='search_by_parent',
|
method='search_by_parent',
|
||||||
label=_('Parent prefix'),
|
label=_('Parent prefix'),
|
||||||
)
|
)
|
||||||
|
prefix_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
label=_('Prefix (ID)'),
|
||||||
|
)
|
||||||
|
prefix = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='prefix__prefix',
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
to_field_name='prefix',
|
||||||
|
label=_('Prefix (prefix)'),
|
||||||
|
)
|
||||||
address = MultiValueCharFilter(
|
address = MultiValueCharFilter(
|
||||||
method='filter_address',
|
method='filter_address',
|
||||||
label=_('Address'),
|
label=_('Address'),
|
||||||
|
@ -318,6 +318,11 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
prefix = DynamicModelChoiceField(
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Prefix')
|
||||||
|
)
|
||||||
vrf = DynamicModelChoiceField(
|
vrf = DynamicModelChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -359,10 +364,10 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
model = IPAddress
|
model = IPAddress
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('status', 'role', 'tenant', 'description'),
|
FieldSet('status', 'role', 'tenant', 'description'),
|
||||||
FieldSet('vrf', 'mask_length', 'dns_name', name=_('Addressing')),
|
FieldSet('prefix', 'vrf', 'mask_length', 'dns_name', name=_('Addressing')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
|
'prefix', 'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -274,6 +274,13 @@ class IPRangeImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressImportForm(NetBoxModelImportForm):
|
class IPAddressImportForm(NetBoxModelImportForm):
|
||||||
|
prefix = CSVModelChoiceField(
|
||||||
|
label=_('Prefix'),
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='prefix',
|
||||||
|
help_text=_('Assigned prefix')
|
||||||
|
)
|
||||||
vrf = CSVModelChoiceField(
|
vrf = CSVModelChoiceField(
|
||||||
label=_('VRF'),
|
label=_('VRF'),
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
@ -334,8 +341,8 @@ class IPAddressImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = [
|
fields = [
|
||||||
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
|
'prefix', 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface',
|
||||||
'is_oob', 'dns_name', 'description', 'comments', 'tags',
|
'is_primary', 'is_oob', 'dns_name', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
|
@ -306,14 +306,14 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id', 'tag'),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface', 'dns_name',
|
'prefix', 'parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface', 'dns_name',
|
||||||
name=_('Attributes')
|
name=_('Attributes')
|
||||||
),
|
),
|
||||||
FieldSet('vrf_id', 'present_in_vrf_id', name=_('VRF')),
|
FieldSet('vrf_id', 'present_in_vrf_id', name=_('VRF')),
|
||||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
FieldSet('device_id', 'virtual_machine_id', name=_('Device/VM')),
|
FieldSet('device_id', 'virtual_machine_id', name=_('Device/VM')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'region_id', 'group_id', 'parent', 'status', 'role')
|
selector_fields = ('filter_id', 'q', 'region_id', 'group_id', 'prefix_id', 'parent', 'status', 'role')
|
||||||
parent = forms.CharField(
|
parent = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(
|
||||||
@ -333,6 +333,11 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
choices=IPADDRESS_MASK_LENGTH_CHOICES,
|
choices=IPADDRESS_MASK_LENGTH_CHOICES,
|
||||||
label=_('Mask length')
|
label=_('Mask length')
|
||||||
)
|
)
|
||||||
|
prefix_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Prefix'),
|
||||||
|
)
|
||||||
vrf_id = DynamicModelMultipleChoiceField(
|
vrf_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -272,6 +272,15 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressForm(TenancyForm, NetBoxModelForm):
|
class IPAddressForm(TenancyForm, NetBoxModelForm):
|
||||||
|
prefix = DynamicModelChoiceField(
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
required=False,
|
||||||
|
context={
|
||||||
|
'vrf': 'vrf',
|
||||||
|
},
|
||||||
|
selector=True,
|
||||||
|
label=_('Prefix'),
|
||||||
|
)
|
||||||
interface = DynamicModelChoiceField(
|
interface = DynamicModelChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -318,7 +327,7 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('address', 'status', 'role', 'vrf', 'dns_name', 'description', 'tags', name=_('IP Address')),
|
FieldSet('prefix', 'address', 'status', 'role', 'vrf', 'dns_name', 'description', 'tags', name=_('IP Address')),
|
||||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
TabbedGroups(
|
TabbedGroups(
|
||||||
@ -334,8 +343,8 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = [
|
fields = [
|
||||||
'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'oob_for_parent', 'nat_inside',
|
'prefix', 'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'oob_for_parent',
|
||||||
'tenant_group', 'tenant', 'description', 'comments', 'tags',
|
'nat_inside', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -460,6 +469,15 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
|
class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
|
||||||
|
prefix = DynamicModelChoiceField(
|
||||||
|
queryset=Prefix.objects.all(),
|
||||||
|
required=False,
|
||||||
|
context={
|
||||||
|
'vrf': 'vrf',
|
||||||
|
},
|
||||||
|
selector=True,
|
||||||
|
label=_('Prefix'),
|
||||||
|
)
|
||||||
vrf = DynamicModelChoiceField(
|
vrf = DynamicModelChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -469,7 +487,7 @@ class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = [
|
fields = [
|
||||||
'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'tenant_group', 'tenant', 'tags',
|
'address', 'prefix', 'vrf', 'status', 'role', 'dns_name', 'description', 'tenant_group', 'tenant', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,6 +122,7 @@ class FHRPGroupAssignmentType(BaseObjectType):
|
|||||||
)
|
)
|
||||||
class IPAddressType(NetBoxObjectType, BaseIPAddressFamilyType):
|
class IPAddressType(NetBoxObjectType, BaseIPAddressFamilyType):
|
||||||
address: str
|
address: str
|
||||||
|
prefix: Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')] | None
|
||||||
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
||||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||||
nat_inside: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
nat_inside: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
||||||
|
67
netbox/ipam/migrations/0077_ipaddress_prefix.py
Normal file
67
netbox/ipam/migrations/0077_ipaddress_prefix.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Generated by Django 5.0.9 on 2025-02-20 16:49
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def draw_progress(count, total, length=20):
|
||||||
|
progress = count / total
|
||||||
|
percent = int(progress * 100)
|
||||||
|
bar = int(progress * length)
|
||||||
|
sys.stdout.write('\r')
|
||||||
|
sys.stdout.write(f"[{'=' * bar:{length}s}] {percent}%")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def set_prefix(apps, schema_editor):
|
||||||
|
start = time.time()
|
||||||
|
IPAddress = apps.get_model('ipam', 'IPAddress')
|
||||||
|
Prefix = apps.get_model('ipam', 'Prefix')
|
||||||
|
|
||||||
|
addresses = IPAddress.objects.all()
|
||||||
|
i = 0
|
||||||
|
total = addresses.count()
|
||||||
|
draw_progress(i, total, 50)
|
||||||
|
for ip in addresses:
|
||||||
|
i += 1
|
||||||
|
prefixes = Prefix.objects.filter(
|
||||||
|
vrf=ip.vrf,
|
||||||
|
prefix__net_contains_or_equals=str(ip.address.ip),
|
||||||
|
prefix__net_mask_length__lte=ip.address.prefixlen,
|
||||||
|
)
|
||||||
|
ip.prefix = prefixes.last()
|
||||||
|
ip.save()
|
||||||
|
draw_progress(i, total, 50)
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
print(f"\r\nElapsed Time: {end - start:.2f}s")
|
||||||
|
|
||||||
|
|
||||||
|
def unset_prefix(apps, schema_editor):
|
||||||
|
IPAddress = apps.get_model('ipam', 'IPAddress')
|
||||||
|
IPAddress.objects.update(prefix=None)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ipam', '0076_natural_ordering'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ipaddress',
|
||||||
|
name='prefix',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name='ip_addresses',
|
||||||
|
to='ipam.prefix',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(set_prefix, unset_prefix)
|
||||||
|
]
|
@ -411,7 +411,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
return netaddr.IPSet()
|
return netaddr.IPSet()
|
||||||
|
|
||||||
prefix = netaddr.IPSet(self.prefix)
|
prefix = netaddr.IPSet(self.prefix)
|
||||||
child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()])
|
child_ips = netaddr.IPSet([ip.address.ip for ip in self.ip_addresses.all()])
|
||||||
child_ranges = []
|
child_ranges = []
|
||||||
for iprange in self.get_child_ranges():
|
for iprange in self.get_child_ranges():
|
||||||
child_ranges.append(iprange.range)
|
child_ranges.append(iprange.range)
|
||||||
@ -462,7 +462,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
else:
|
else:
|
||||||
# Compile an IPSet to avoid counting duplicate IPs
|
# Compile an IPSet to avoid counting duplicate IPs
|
||||||
child_ips = netaddr.IPSet(
|
child_ips = netaddr.IPSet(
|
||||||
[_.range for _ in self.get_child_ranges()] + [_.address.ip for _ in self.get_child_ips()]
|
[_.range for _ in self.get_child_ranges()] + [_.address.ip for _ in self.ip_addresses.all()]
|
||||||
)
|
)
|
||||||
|
|
||||||
prefix_size = self.prefix.size
|
prefix_size = self.prefix.size
|
||||||
@ -706,6 +706,14 @@ class IPAddress(ContactsMixin, PrimaryModel):
|
|||||||
for example, when mapping public addresses to private addresses. When an Interface has been assigned an IPAddress
|
for example, when mapping public addresses to private addresses. When an Interface has been assigned an IPAddress
|
||||||
which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
|
which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
|
||||||
"""
|
"""
|
||||||
|
prefix = models.ForeignKey(
|
||||||
|
to='ipam.Prefix',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='ip_addresses',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_('Prefix')
|
||||||
|
)
|
||||||
address = IPAddressField(
|
address = IPAddressField(
|
||||||
verbose_name=_('address'),
|
verbose_name=_('address'),
|
||||||
help_text=_('IPv4 or IPv6 address (with mask)')
|
help_text=_('IPv4 or IPv6 address (with mask)')
|
||||||
@ -835,6 +843,11 @@ class IPAddress(ContactsMixin, PrimaryModel):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
if self.address:
|
if self.address:
|
||||||
|
if self.prefix:
|
||||||
|
if self.address not in self.prefix.prefix:
|
||||||
|
raise ValidationError({
|
||||||
|
'prefix': _("IP address must be part of the selected prefix.")
|
||||||
|
})
|
||||||
|
|
||||||
# /0 masks are not acceptable
|
# /0 masks are not acceptable
|
||||||
if self.address.prefixlen == 0:
|
if self.address.prefixlen == 0:
|
||||||
|
@ -52,11 +52,12 @@ class IPAddressIndex(SearchIndex):
|
|||||||
model = models.IPAddress
|
model = models.IPAddress
|
||||||
fields = (
|
fields = (
|
||||||
('address', 100),
|
('address', 100),
|
||||||
|
('prefix', 200),
|
||||||
('dns_name', 300),
|
('dns_name', 300),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
display_attrs = ('vrf', 'tenant', 'status', 'role', 'description')
|
display_attrs = ('prefix', 'vrf', 'tenant', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from django.db.models import Q
|
||||||
from django.db.models.signals import post_delete, post_save, pre_delete
|
from django.db.models.signals import post_delete, post_save, pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
@ -26,12 +27,51 @@ def update_children_depth(prefix):
|
|||||||
Prefix.objects.bulk_update(children, ['_depth'], batch_size=100)
|
Prefix.objects.bulk_update(children, ['_depth'], batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
|
def update_ipaddress_prefix(prefix, delete=False):
|
||||||
|
if delete:
|
||||||
|
# Get all possible addresses
|
||||||
|
addresses = IPAddress.objects.filter(prefix=prefix)
|
||||||
|
# Find a new containing prefix
|
||||||
|
prefix = Prefix.objects.filter(
|
||||||
|
prefix__net_contains_or_equals=prefix.prefix,
|
||||||
|
vrf=prefix.vrf
|
||||||
|
).exclude(pk=prefix.pk).last()
|
||||||
|
|
||||||
|
for address in addresses:
|
||||||
|
# Set contained addresses to the containing prefix if it exists
|
||||||
|
address.prefix = prefix
|
||||||
|
else:
|
||||||
|
# Get all possible modified addresses
|
||||||
|
addresses = IPAddress.objects.filter(
|
||||||
|
Q(address__net_contained_or_equal=prefix.prefix, vrf=prefix.vrf) |
|
||||||
|
Q(prefix=prefix)
|
||||||
|
)
|
||||||
|
for address in addresses:
|
||||||
|
if not address.prefix or (prefix.prefix in address.prefix.prefix and address.address in prefix.prefix):
|
||||||
|
# Set to new Prefix as the prefix is a child of the old prefix and the address is contained in the
|
||||||
|
# prefix
|
||||||
|
address.prefix = prefix
|
||||||
|
elif address.prefix and address.address not in prefix.prefix:
|
||||||
|
# Find a new prefix as the prefix no longer contains the address
|
||||||
|
address.prefix = Prefix.objects.filter(
|
||||||
|
prefix__net_contains_or_equals=address.address,
|
||||||
|
vrf=prefix.vrf
|
||||||
|
).last()
|
||||||
|
else:
|
||||||
|
# No-OP as the prefix does not require modification
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Update the addresses
|
||||||
|
IPAddress.objects.bulk_update(addresses, ['prefix'], batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Prefix)
|
@receiver(post_save, sender=Prefix)
|
||||||
def handle_prefix_saved(instance, created, **kwargs):
|
def handle_prefix_saved(instance, created, **kwargs):
|
||||||
|
|
||||||
# Prefix has changed (or new instance has been created)
|
# Prefix has changed (or new instance has been created)
|
||||||
if created or instance.vrf_id != instance._vrf_id or instance.prefix != instance._prefix:
|
if created or instance.vrf_id != instance._vrf_id or instance.prefix != instance._prefix:
|
||||||
|
|
||||||
|
update_ipaddress_prefix(instance)
|
||||||
update_parents_children(instance)
|
update_parents_children(instance)
|
||||||
update_children_depth(instance)
|
update_children_depth(instance)
|
||||||
|
|
||||||
@ -42,11 +82,17 @@ def handle_prefix_saved(instance, created, **kwargs):
|
|||||||
update_children_depth(old_prefix)
|
update_children_depth(old_prefix)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_delete, sender=Prefix)
|
||||||
|
def pre_handle_prefix_deleted(instance, **kwargs):
|
||||||
|
update_ipaddress_prefix(instance, True)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Prefix)
|
@receiver(post_delete, sender=Prefix)
|
||||||
def handle_prefix_deleted(instance, **kwargs):
|
def handle_prefix_deleted(instance, **kwargs):
|
||||||
|
|
||||||
update_parents_children(instance)
|
update_parents_children(instance)
|
||||||
update_children_depth(instance)
|
update_children_depth(instance)
|
||||||
|
update_ipaddress_prefix(instance, delete=True)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=IPAddress)
|
@receiver(pre_delete, sender=IPAddress)
|
||||||
|
@ -307,6 +307,10 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
template_code=IPADDRESS_LINK,
|
template_code=IPADDRESS_LINK,
|
||||||
verbose_name=_('IP Address')
|
verbose_name=_('IP Address')
|
||||||
)
|
)
|
||||||
|
prefix = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Prefix')
|
||||||
|
)
|
||||||
vrf = tables.TemplateColumn(
|
vrf = tables.TemplateColumn(
|
||||||
template_code=VRF_LINK,
|
template_code=VRF_LINK,
|
||||||
verbose_name=_('VRF')
|
verbose_name=_('VRF')
|
||||||
|
@ -172,14 +172,14 @@ class TestPrefix(TestCase):
|
|||||||
IPAddress(address=IPNetwork('10.0.2.1/24'), vrf=vrfs[1]),
|
IPAddress(address=IPNetwork('10.0.2.1/24'), vrf=vrfs[1]),
|
||||||
IPAddress(address=IPNetwork('10.0.3.1/24'), vrf=vrfs[2]),
|
IPAddress(address=IPNetwork('10.0.3.1/24'), vrf=vrfs[2]),
|
||||||
))
|
))
|
||||||
child_ip_pks = {p.pk for p in parent_prefix.get_child_ips()}
|
child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
|
||||||
|
|
||||||
# Global container should return all children
|
# Global container should return all children
|
||||||
self.assertSetEqual(child_ip_pks, {ips[0].pk, ips[1].pk, ips[2].pk, ips[3].pk})
|
self.assertSetEqual(child_ip_pks, {ips[0].pk, ips[1].pk, ips[2].pk, ips[3].pk})
|
||||||
|
|
||||||
parent_prefix.vrf = vrfs[0]
|
parent_prefix.vrf = vrfs[0]
|
||||||
parent_prefix.save()
|
parent_prefix.save()
|
||||||
child_ip_pks = {p.pk for p in parent_prefix.get_child_ips()}
|
child_ip_pks = {p.pk for p in parent_prefix.ip_addresses.all()}
|
||||||
|
|
||||||
# VRF container is limited to its own VRF
|
# VRF container is limited to its own VRF
|
||||||
self.assertSetEqual(child_ip_pks, {ips[1].pk})
|
self.assertSetEqual(child_ip_pks, {ips[1].pk})
|
||||||
|
@ -493,7 +493,7 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
IPAddress(address=IPNetwork('192.168.0.3/16')),
|
IPAddress(address=IPNetwork('192.168.0.3/16')),
|
||||||
)
|
)
|
||||||
IPAddress.objects.bulk_create(ip_addresses)
|
IPAddress.objects.bulk_create(ip_addresses)
|
||||||
self.assertEqual(prefix.get_child_ips().count(), 3)
|
self.assertEqual(prefix.ip_addresses.all().count(), 3)
|
||||||
|
|
||||||
url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk})
|
url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk})
|
||||||
self.assertHttpStatus(self.client.get(url), 200)
|
self.assertHttpStatus(self.client.get(url), 200)
|
||||||
|
@ -631,13 +631,13 @@ class PrefixIPAddressesView(generic.ObjectChildrenView):
|
|||||||
template_name = 'ipam/prefix/ip_addresses.html'
|
template_name = 'ipam/prefix/ip_addresses.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('IP Addresses'),
|
label=_('IP Addresses'),
|
||||||
badge=lambda x: x.get_child_ips().count(),
|
badge=lambda x: x.ip_addresses.count(),
|
||||||
permission='ipam.view_ipaddress',
|
permission='ipam.view_ipaddress',
|
||||||
weight=700
|
weight=700
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
|
return parent.ip_addresses.restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
|
||||||
|
|
||||||
def prep_table_data(self, request, queryset, parent):
|
def prep_table_data(self, request, queryset, parent):
|
||||||
if not request.GET.get('q') and not get_table_ordering(request, self.table):
|
if not request.GET.get('q') and not get_table_ordering(request, self.table):
|
||||||
|
@ -14,6 +14,10 @@
|
|||||||
<th scope="row">{% trans "Family" %}</th>
|
<th scope="row">{% trans "Family" %}</th>
|
||||||
<td>IPv{{ object.family }}</td>
|
<td>IPv{{ object.family }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Prefix" %}</th>
|
||||||
|
<td>{{ object.prefix|linkify|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "VRF" %}</th>
|
<th scope="row">{% trans "VRF" %}</th>
|
||||||
<td>
|
<td>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<h2 class="col-9 offset-3">{% trans "IP Addresses" %}</h2>
|
<h2 class="col-9 offset-3">{% trans "IP Addresses" %}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
{% render_field model_form.prefix %}
|
||||||
{% render_field form.pattern %}
|
{% render_field form.pattern %}
|
||||||
{% render_field model_form.status %}
|
{% render_field model_form.status %}
|
||||||
{% render_field model_form.role %}
|
{% render_field model_form.role %}
|
||||||
|
@ -109,7 +109,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% with child_ip_count=object.get_child_ips.count %}
|
{% with child_ip_count=object.ip_addresses.count %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Child IPs" %}</th>
|
<th scope="row">{% trans "Child IPs" %}</th>
|
||||||
<td>
|
<td>
|
||||||
|
Loading…
Reference in New Issue
Block a user