Add mark_reserved boolean field to IPRange

This commit is contained in:
Jeremy Stretch 2025-04-01 15:21:19 -04:00
parent 8d7889e2c0
commit 67615368b2
11 changed files with 58 additions and 11 deletions

View File

@ -29,6 +29,10 @@ The IP range's operational status. Note that the status of a range does _not_ ha
!!! tip !!! tip
Additional statuses may be defined by setting `IPRange.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter. 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 ### 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. 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.

View File

@ -147,7 +147,8 @@ class IPRangeSerializer(NetBoxModelSerializer):
fields = [ fields = [
'id', 'url', 'display_url', 'display', 'family', 'start_address', 'end_address', 'size', 'vrf', 'tenant', 'id', 'url', 'display_url', 'display', 'family', 'start_address', 'end_address', 'size', 'vrf', 'tenant',
'status', 'role', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', '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') brief_fields = ('id', 'url', 'display', 'family', 'start_address', 'end_address', 'description')

View File

@ -478,7 +478,7 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
class Meta: class Meta:
model = IPRange model = IPRange
fields = ('id', 'mark_utilized', 'size', 'description') fields = ('id', 'mark_reserved', 'mark_utilized', 'size', 'description')
def search(self, queryset, name, value): def search(self, queryset, name, value):
if not value.strip(): if not value.strip():

View File

@ -296,6 +296,11 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
queryset=Role.objects.all(), queryset=Role.objects.all(),
required=False required=False
) )
mark_reserved = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect(),
label=_('Treat as reserved')
)
mark_utilized = forms.NullBooleanField( mark_utilized = forms.NullBooleanField(
required=False, required=False,
widget=BulkEditNullBooleanSelect(), widget=BulkEditNullBooleanSelect(),

View File

@ -268,8 +268,8 @@ class IPRangeImportForm(NetBoxModelImportForm):
class Meta: class Meta:
model = IPRange model = IPRange
fields = ( fields = (
'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'mark_utilized', 'description', 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'mark_reserved', 'mark_utilized',
'comments', 'tags', 'description', 'comments', 'tags',
) )

View File

@ -266,7 +266,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = IPRange model = IPRange
fieldsets = ( fieldsets = (
FieldSet('q', 'filter_id', 'tag'), 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')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
) )
family = forms.ChoiceField( family = forms.ChoiceField(
@ -291,6 +291,13 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
null_option='None', null_option='None',
label=_('Role') label=_('Role')
) )
mark_reserved = forms.NullBooleanField(
required=False,
label=_('Treat as reserved'),
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
mark_utilized = forms.NullBooleanField( mark_utilized = forms.NullBooleanField(
required=False, required=False,
label=_('Treat as fully utilized'), label=_('Treat as fully utilized'),

View File

@ -257,8 +257,8 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
fieldsets = ( fieldsets = (
FieldSet( FieldSet(
'vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags', 'vrf', 'start_address', 'end_address', 'role', 'status', 'mark_reserved', 'mark_utilized', 'description',
name=_('IP Range') 'tags', name=_('IP Range')
), ),
FieldSet('tenant_group', 'tenant', name=_('Tenancy')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
) )
@ -266,8 +266,8 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
class Meta: class Meta:
model = IPRange model = IPRange
fields = [ fields = [
'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'mark_utilized', 'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'mark_reserved',
'description', 'comments', 'tags', 'mark_utilized', 'description', 'comments', 'tags',
] ]

View File

@ -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),
),
]

View File

@ -519,6 +519,11 @@ class IPRange(ContactsMixin, PrimaryModel):
null=True, null=True,
help_text=_('The primary function of this range') 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( mark_utilized = models.BooleanField(
verbose_name=_('mark utilized'), verbose_name=_('mark utilized'),
default=False, default=False,
@ -526,7 +531,7 @@ class IPRange(ContactsMixin, PrimaryModel):
) )
clone_fields = ( clone_fields = (
'vrf', 'tenant', 'status', 'role', 'description', 'vrf', 'tenant', 'status', 'role', 'description', 'mark_reserved', 'mark_utilized',
) )
class Meta: class Meta:

View File

@ -268,6 +268,10 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
verbose_name=_('Role'), verbose_name=_('Role'),
linkify=True linkify=True
) )
mark_reserved = columns.BooleanColumn(
verbose_name=_('Marked Reserved'),
false_mark=None
)
mark_utilized = columns.BooleanColumn( mark_utilized = columns.BooleanColumn(
verbose_name=_('Marked Utilized'), verbose_name=_('Marked Utilized'),
false_mark=None false_mark=None
@ -288,7 +292,8 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
model = IPRange model = IPRange
fields = ( fields = (
'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group', '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 = ( default_columns = (
'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',

View File

@ -25,6 +25,10 @@
<th scope="row">{% trans "Size" %}</th> <th scope="row">{% trans "Size" %}</th>
<td>{{ object.size }}</td> <td>{{ object.size }}</td>
</tr> </tr>
<tr>
<th scope="row">{% trans "Reserved" %}</th>
<td>{% checkmark object.mark_reserved %}</td>
</tr>
<tr> <tr>
<th scope="row">{% trans "Utilization" %}</th> <th scope="row">{% trans "Utilization" %}</th>
<td> <td>