From 67615368b2405f976fb9335928464b0ed8fe6813 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 1 Apr 2025 15:21:19 -0400 Subject: [PATCH] Add mark_reserved boolean field to IPRange --- docs/models/ipam/iprange.md | 4 ++++ netbox/ipam/api/serializers_/ip.py | 3 ++- netbox/ipam/filtersets.py | 2 +- netbox/ipam/forms/bulk_edit.py | 5 +++++ netbox/ipam/forms/bulk_import.py | 4 ++-- netbox/ipam/forms/filtersets.py | 9 ++++++++- netbox/ipam/forms/model_forms.py | 8 ++++---- .../migrations/0078_iprange_mark_utilized.py | 16 ++++++++++++++++ netbox/ipam/models/ip.py | 7 ++++++- netbox/ipam/tables/ip.py | 7 ++++++- netbox/templates/ipam/iprange.html | 4 ++++ 11 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 netbox/ipam/migrations/0078_iprange_mark_utilized.py diff --git a/docs/models/ipam/iprange.md b/docs/models/ipam/iprange.md index 71f0884d9..b748ed1d2 100644 --- a/docs/models/ipam/iprange.md +++ b/docs/models/ipam/iprange.md @@ -29,6 +29,10 @@ The IP range's operational status. Note that the status of a range does _not_ ha !!! tip Additional statuses may be defined by setting `IPRange.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter. +### Mark Reserved + +If enabled, NetBox will prevent the creation of IP addresses which fall within the declared range (and assigned VRF, if any). + ### Mark Utilized If enabled, the IP range will be considered 100% utilized regardless of how many IP addresses are defined within it. This is useful for documenting DHCP ranges, for example. diff --git a/netbox/ipam/api/serializers_/ip.py b/netbox/ipam/api/serializers_/ip.py index bfc7ac546..ed33cd5d1 100644 --- a/netbox/ipam/api/serializers_/ip.py +++ b/netbox/ipam/api/serializers_/ip.py @@ -147,7 +147,8 @@ class IPRangeSerializer(NetBoxModelSerializer): fields = [ 'id', 'url', 'display_url', 'display', 'family', 'start_address', 'end_address', 'size', 'vrf', 'tenant', 'status', 'role', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', - 'mark_utilized', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'mark_reserved', 'mark_utilized', 'description', 'comments', 'tags', 'custom_fields', 'created', + 'last_updated', ] brief_fields = ('id', 'url', 'display', 'family', 'start_address', 'end_address', 'description') diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index d9507ec2e..e0ef03b18 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -478,7 +478,7 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet): class Meta: model = IPRange - fields = ('id', 'mark_utilized', 'size', 'description') + fields = ('id', 'mark_reserved', 'mark_utilized', 'size', 'description') def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/ipam/forms/bulk_edit.py b/netbox/ipam/forms/bulk_edit.py index f1aa6d845..d45c8bd19 100644 --- a/netbox/ipam/forms/bulk_edit.py +++ b/netbox/ipam/forms/bulk_edit.py @@ -296,6 +296,11 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm): queryset=Role.objects.all(), required=False ) + mark_reserved = forms.NullBooleanField( + required=False, + widget=BulkEditNullBooleanSelect(), + label=_('Treat as reserved') + ) mark_utilized = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect(), diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index 85583ca18..9fb4c19f3 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -268,8 +268,8 @@ class IPRangeImportForm(NetBoxModelImportForm): class Meta: model = IPRange fields = ( - 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'mark_utilized', 'description', - 'comments', 'tags', + 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'mark_reserved', 'mark_utilized', + 'description', 'comments', 'tags', ) diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index f60003c56..fac0bb839 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -266,7 +266,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = IPRange fieldsets = ( FieldSet('q', 'filter_id', 'tag'), - FieldSet('family', 'vrf_id', 'status', 'role_id', 'mark_utilized', name=_('Attributes')), + FieldSet('family', 'vrf_id', 'status', 'role_id', 'mark_reserved', 'mark_utilized', name=_('Attributes')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), ) family = forms.ChoiceField( @@ -291,6 +291,13 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): null_option='None', label=_('Role') ) + mark_reserved = forms.NullBooleanField( + required=False, + label=_('Treat as reserved'), + widget=forms.Select( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) mark_utilized = forms.NullBooleanField( required=False, label=_('Treat as fully utilized'), diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 22f98f6f0..f2cea5971 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -257,8 +257,8 @@ class IPRangeForm(TenancyForm, NetBoxModelForm): fieldsets = ( FieldSet( - 'vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags', - name=_('IP Range') + 'vrf', 'start_address', 'end_address', 'role', 'status', 'mark_reserved', 'mark_utilized', 'description', + 'tags', name=_('IP Range') ), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), ) @@ -266,8 +266,8 @@ class IPRangeForm(TenancyForm, NetBoxModelForm): class Meta: model = IPRange fields = [ - 'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'mark_utilized', - 'description', 'comments', 'tags', + 'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'mark_reserved', + 'mark_utilized', 'description', 'comments', 'tags', ] diff --git a/netbox/ipam/migrations/0078_iprange_mark_utilized.py b/netbox/ipam/migrations/0078_iprange_mark_utilized.py new file mode 100644 index 000000000..3ae9c0c03 --- /dev/null +++ b/netbox/ipam/migrations/0078_iprange_mark_utilized.py @@ -0,0 +1,16 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0077_vlangroup_tenant'), + ] + + operations = [ + migrations.AddField( + model_name='iprange', + name='mark_reserved', + field=models.BooleanField(default=False), + ), + ] diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index e1a8d91e3..97c154ed6 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -519,6 +519,11 @@ class IPRange(ContactsMixin, PrimaryModel): null=True, help_text=_('The primary function of this range') ) + mark_reserved = models.BooleanField( + verbose_name=_('mark reserved'), + default=False, + help_text=_("Prevent the creation of IP addresses within this range") + ) mark_utilized = models.BooleanField( verbose_name=_('mark utilized'), default=False, @@ -526,7 +531,7 @@ class IPRange(ContactsMixin, PrimaryModel): ) clone_fields = ( - 'vrf', 'tenant', 'status', 'role', 'description', + 'vrf', 'tenant', 'status', 'role', 'description', 'mark_reserved', 'mark_utilized', ) class Meta: diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 1eefa6b3a..928d8d99a 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -268,6 +268,10 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable): verbose_name=_('Role'), linkify=True ) + mark_reserved = columns.BooleanColumn( + verbose_name=_('Marked Reserved'), + false_mark=None + ) mark_utilized = columns.BooleanColumn( verbose_name=_('Marked Utilized'), false_mark=None @@ -288,7 +292,8 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable): model = IPRange fields = ( 'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group', - 'mark_utilized', 'utilization', 'description', 'comments', 'tags', 'created', 'last_updated', + 'mark_reserved', 'mark_utilized', 'utilization', 'description', 'comments', 'tags', 'created', + 'last_updated', ) default_columns = ( 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', diff --git a/netbox/templates/ipam/iprange.html b/netbox/templates/ipam/iprange.html index 19fbd5171..38d8b1e30 100644 --- a/netbox/templates/ipam/iprange.html +++ b/netbox/templates/ipam/iprange.html @@ -25,6 +25,10 @@ {% trans "Size" %} {{ object.size }} + + {% trans "Reserved" %} + {% checkmark object.mark_reserved %} + {% trans "Utilization" %}