jeremystretch 3 лет назад
Родитель
Сommit
536b46158a

+ 4 - 0
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.

+ 1 - 0
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)

+ 1 - 1
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']
 

+ 1 - 1
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():

+ 6 - 1
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',

+ 2 - 1
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',
         )
 
 

+ 8 - 1
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)
 
 

+ 3 - 3
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',
         ]
 
 

+ 18 - 0
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),
+        ),
+    ]

+ 7 - 0
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()

+ 4 - 1
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',

+ 6 - 1
netbox/templates/ipam/iprange.html

@@ -30,7 +30,12 @@
                 <tr>
                     <th scope="row">Utilization</th>
                     <td>
-                      {% utilization_graph object.utilization %}
+                      {% if object.mark_utilized %}
+                        {% utilization_graph 100 warning_threshold=0 danger_threshold=0 %}
+                        <small>(Marked fully utilized)</small>
+                      {% else %}
+                        {% utilization_graph object.utilization %}
+                      {% endif %}
                     </td>
                 </tr>
                 <tr>