From 536b46158a8bbdcce033c27b85e43d0aa000def9 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 28 Feb 2023 10:09:09 -0500 Subject: [PATCH] Add mark_utilized to IPRange --- docs/models/ipam/iprange.md | 4 ++++ docs/release-notes/version-3.5.md | 1 + netbox/ipam/api/serializers.py | 2 +- netbox/ipam/filtersets.py | 2 +- netbox/ipam/forms/bulk_edit.py | 7 ++++++- netbox/ipam/forms/bulk_import.py | 3 ++- netbox/ipam/forms/filtersets.py | 9 ++++++++- netbox/ipam/forms/model_forms.py | 6 +++--- .../migrations/0065_iprange_mark_utilized.py | 18 ++++++++++++++++++ netbox/ipam/models/ip.py | 7 +++++++ netbox/ipam/tables/ip.py | 5 ++++- netbox/templates/ipam/iprange.html | 7 ++++++- 12 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 netbox/ipam/migrations/0065_iprange_mark_utilized.py diff --git a/docs/models/ipam/iprange.md b/docs/models/ipam/iprange.md index 53abf10b8..71f0884d9 100644 --- a/docs/models/ipam/iprange.md +++ b/docs/models/ipam/iprange.md @@ -28,3 +28,7 @@ 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 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/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 02045186f..1d407846d 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -26,6 +26,7 @@ A new ASN range model has been introduced to facilitate the provisioning of new ### Enhancements +* [#7947](https://github.com/netbox-community/netbox/issues/7947) - Enable marking IP ranges as fully utilized * [#9073](https://github.com/netbox-community/netbox/issues/9073) - Enable syncing config context data from remote sources * [#9653](https://github.com/netbox-community/netbox/issues/9653) - Enable setting a default platform for device types * [#10374](https://github.com/netbox-community/netbox/issues/10374) - Require unique tenant names & slugs per group (not globally) diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index f62ed0d83..dc0817b8a 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -377,7 +377,7 @@ class IPRangeSerializer(NetBoxModelSerializer): model = IPRange fields = [ 'id', 'url', 'display', 'family', 'start_address', 'end_address', 'size', 'vrf', 'tenant', 'status', 'role', - 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'children', + 'mark_utilized', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'children', ] read_only_fields = ['family'] diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index 1a70eaa9e..a412c50da 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -468,7 +468,7 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet): class Meta: model = IPRange - fields = ['id', 'description'] + fields = ['id', 'mark_utilized', '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 b28a9f959..fe4f770a8 100644 --- a/netbox/ipam/forms/bulk_edit.py +++ b/netbox/ipam/forms/bulk_edit.py @@ -282,6 +282,11 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm): queryset=Role.objects.all(), required=False ) + mark_utilized = forms.NullBooleanField( + required=False, + widget=BulkEditNullBooleanSelect(), + label=_('Treat as 100% utilized') + ) description = forms.CharField( max_length=200, required=False @@ -293,7 +298,7 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm): model = IPRange fieldsets = ( - (None, ('status', 'role', 'vrf', 'tenant', 'description')), + (None, ('status', 'role', 'vrf', 'tenant', 'mark_utilized', 'description')), ) nullable_fields = ( 'vrf', 'tenant', 'role', 'description', 'comments', diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index 9aa592388..e2eb33569 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -223,7 +223,8 @@ class IPRangeImportForm(NetBoxModelImportForm): class Meta: model = IPRange fields = ( - 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description', 'comments', 'tags', + 'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'mark_utilized', 'description', + 'comments', 'tags', ) diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index e86ed32f6..83fe84cd2 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -253,7 +253,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = IPRange fieldsets = ( (None, ('q', 'filter_id', 'tag')), - ('Attriubtes', ('family', 'vrf_id', 'status', 'role_id')), + ('Attriubtes', ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) family = forms.ChoiceField( @@ -277,6 +277,13 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): null_option='None', label=_('Role') ) + mark_utilized = forms.NullBooleanField( + required=False, + label=_('Marked as 100% utilized'), + widget=forms.Select( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) tag = TagFilterField(model) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 4c14b6bd4..5844df8c4 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -288,15 +288,15 @@ class IPRangeForm(TenancyForm, NetBoxModelForm): comments = CommentField() fieldsets = ( - ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')), + ('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags')), ('Tenancy', ('tenant_group', 'tenant')), ) class Meta: model = IPRange fields = [ - 'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'description', - 'comments', 'tags', + 'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'mark_utilized', + 'description', 'comments', 'tags', ] diff --git a/netbox/ipam/migrations/0065_iprange_mark_utilized.py b/netbox/ipam/migrations/0065_iprange_mark_utilized.py new file mode 100644 index 000000000..5c2cb5858 --- /dev/null +++ b/netbox/ipam/migrations/0065_iprange_mark_utilized.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-02-28 14:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0064_asnrange'), + ] + + operations = [ + migrations.AddField( + model_name='iprange', + name='mark_utilized', + field=models.BooleanField(default=False), + ), + ] diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 3463b9486..1002145e6 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -511,6 +511,10 @@ class IPRange(PrimaryModel): null=True, help_text=_('The primary function of this range') ) + mark_utilized = models.BooleanField( + default=False, + help_text=_("Treat as 100% utilized") + ) clone_fields = ( 'vrf', 'tenant', 'status', 'role', 'description', @@ -652,6 +656,9 @@ class IPRange(PrimaryModel): """ Determine the utilization of the range and return it as a percentage. """ + if self.mark_utilized: + return 100 + # Compile an IPSet to avoid counting duplicate IPs child_count = netaddr.IPSet([ ip.address.ip for ip in self.get_child_ips() diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 37aff148d..86d1a3775 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -275,6 +275,9 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable): role = tables.Column( linkify=True ) + mark_utilized = columns.BooleanColumn( + verbose_name='Marked Utilized' + ) utilization = columns.UtilizationColumn( accessor='utilization', orderable=False @@ -288,7 +291,7 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable): model = IPRange fields = ( 'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group', - 'utilization', 'description', 'comments', 'tags', 'created', 'last_updated', + '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 6ba9e4bea..93c3c2889 100644 --- a/netbox/templates/ipam/iprange.html +++ b/netbox/templates/ipam/iprange.html @@ -30,7 +30,12 @@ Utilization - {% utilization_graph object.utilization %} + {% if object.mark_utilized %} + {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %} + (Marked fully utilized) + {% else %} + {% utilization_graph object.utilization %} + {% endif %}