From 72ed36fd5b4e9b2dc95832086dabc364c99d7815 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 24 Jun 2024 11:40:10 -0700 Subject: [PATCH] 9627 change to NumericArrayField --- netbox/ipam/forms/bulk_edit.py | 8 +++-- netbox/ipam/forms/bulk_import.py | 9 ++++-- netbox/ipam/forms/model_forms.py | 13 +++++--- netbox/ipam/graphql/types.py | 2 +- ...nges.py => 0070_vlangroup_allowed_vids.py} | 13 +++----- netbox/ipam/models/vlans.py | 20 +++++------- netbox/ipam/querysets.py | 5 +-- netbox/utilities/forms/fields/array.py | 31 ------------------- 8 files changed, 34 insertions(+), 67 deletions(-) rename netbox/ipam/migrations/{0070_vlangroup_vlan_id_ranges.py => 0070_vlangroup_allowed_vids.py} (77%) diff --git a/netbox/ipam/forms/bulk_edit.py b/netbox/ipam/forms/bulk_edit.py index e543beea4..7445724a7 100644 --- a/netbox/ipam/forms/bulk_edit.py +++ b/netbox/ipam/forms/bulk_edit.py @@ -12,7 +12,6 @@ from tenancy.models import Tenant from utilities.forms import add_blank_choice from utilities.forms.fields import ( CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, - NumericRangeArrayField, ) from utilities.forms.rendering import FieldSet from utilities.forms.widgets import BulkEditNullBooleanSelect @@ -472,13 +471,16 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm): 'group_id': '$clustergroup', } ) - vlan_id_ranges = NumericRangeArrayField( + allowed_vids = NumericArrayField( required=False, + label=_('min/max VLAN IDs'), + base_field=forms.IntegerField(), + help_text=_('Comma-separated list of numeric VLAN IDs. A range may be specified using a hyphen.'), ) model = VLANGroup fieldsets = ( - FieldSet('site', 'vlan_id_ranges', 'description'), + FieldSet('site', 'allowed_vids', 'description'), FieldSet( 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster', name=_('Scope') ), diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index c50f194e8..849c1e46c 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -10,7 +10,7 @@ from netbox.forms import NetBoxModelImportForm from tenancy.models import Tenant from utilities.forms.fields import ( CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField, - NumericRangeArrayField, + NumericArrayField, ) from virtualization.models import VirtualMachine, VMInterface @@ -412,13 +412,16 @@ class VLANGroupImportForm(NetBoxModelImportForm): required=False, label=_('Scope type (app & model)') ) - vlan_id_ranges = NumericRangeArrayField( + allowed_vids = NumericArrayField( required=False, + label=_('min/max VLAN IDs'), + base_field=forms.IntegerField(), + help_text=_('Comma-separated list of numeric VLAN IDs. A range may be specified using a hyphen.'), ) class Meta: model = VLANGroup - fields = ('name', 'slug', 'scope_type', 'scope_id', 'vlan_id_ranges', 'description', 'tags') + fields = ('name', 'slug', 'scope_type', 'scope_id', 'allowed_vids', 'description', 'tags') labels = { 'scope_id': 'Scope ID', } diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index f78c08201..4b3018e3d 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -15,7 +15,7 @@ from utilities.exceptions import PermissionsViolation from utilities.forms import add_blank_choice from utilities.forms.fields import ( CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, - SlugField, NumericRangeArrayField + SlugField ) from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups from utilities.forms.widgets import DatePicker @@ -633,13 +633,16 @@ class VLANGroupForm(NetBoxModelForm): } ) slug = SlugField() - vlan_id_ranges = NumericRangeArrayField( - required=False + allowed_vids = NumericArrayField( + required=False, + label=_('min/max VLAN IDs'), + base_field=forms.IntegerField(), + help_text=_('Comma-separated list of numeric VLAN IDs. A range may be specified using a hyphen.'), ) fieldsets = ( FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')), - FieldSet('vlan_id_ranges', name=_('Child VLANs')), + FieldSet('allowed_vids', name=_('Child VLANs')), FieldSet( 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster', name=_('Scope') @@ -650,7 +653,7 @@ class VLANGroupForm(NetBoxModelForm): model = VLANGroup fields = [ 'name', 'slug', 'description', 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', - 'clustergroup', 'cluster', 'vlan_id_ranges', 'tags', + 'clustergroup', 'cluster', 'allowed_vids', 'tags', ] def __init__(self, *args, **kwargs): diff --git a/netbox/ipam/graphql/types.py b/netbox/ipam/graphql/types.py index 2adaa31f2..6d2333225 100644 --- a/netbox/ipam/graphql/types.py +++ b/netbox/ipam/graphql/types.py @@ -251,7 +251,7 @@ class VLANType(NetBoxObjectType): class VLANGroupType(OrganizationalObjectType): vlans: List[VLANType] - vlan_id_ranges: List[str] + allowed_vids: str @strawberry_django.field def scope(self) -> Annotated[Union[ diff --git a/netbox/ipam/migrations/0070_vlangroup_vlan_id_ranges.py b/netbox/ipam/migrations/0070_vlangroup_allowed_vids.py similarity index 77% rename from netbox/ipam/migrations/0070_vlangroup_vlan_id_ranges.py rename to netbox/ipam/migrations/0070_vlangroup_allowed_vids.py index b83fac3e5..d61240dd2 100644 --- a/netbox/ipam/migrations/0070_vlangroup_vlan_id_ranges.py +++ b/netbox/ipam/migrations/0070_vlangroup_allowed_vids.py @@ -30,18 +30,13 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='vlangroup', - name='vlan_id_ranges', + name='allowed_vids', field=django.contrib.postgres.fields.ArrayField( - base_field=django.contrib.postgres.fields.ranges.BigIntegerRangeField(), - blank=True, null=True, size=None, - default=ipam.models.vlans.get_default_vlan_ids, + base_field=models.PositiveSmallIntegerField(), + default=ipam.models.vlans.get_default_allowed_vids, + size=None, ), ), - migrations.AddField( - model_name='vlangroup', - name='_total_vlan_ids', - field=models.PositiveBigIntegerField(default=4094), - ), migrations.RunPython( code=move_min_max, reverse_code=migrations.RunPython.noop diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 45de17059..6c745d167 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -1,9 +1,8 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation -from django.contrib.postgres.fields import ArrayField, BigIntegerRangeField +from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from django.db.backends.postgresql.psycopg_any import NumericRange from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -21,8 +20,8 @@ __all__ = ( ) -def get_default_vlan_ids(): - return [NumericRange(VLAN_VID_MIN, VLAN_VID_MAX)] +def get_default_allowed_vids(): + return list(range(VLAN_VID_MIN, VLAN_VID_MAX + 1)) class VLANGroup(OrganizationalModel): @@ -52,16 +51,11 @@ class VLANGroup(OrganizationalModel): ct_field='scope_type', fk_field='scope_id' ) - vlan_id_ranges = ArrayField( - BigIntegerRangeField(), + allowed_vids = ArrayField( verbose_name=_('min/max VLAN IDs'), - default=get_default_vlan_ids, - help_text=_('Ranges of Minimum, maximum VLAN IDs'), - blank=True, - null=True - ) - _total_vlan_ids = models.PositiveBigIntegerField( - default=VLAN_VID_MAX - VLAN_VID_MIN + 1 + base_field=models.PositiveSmallIntegerField(), + default=get_default_allowed_vids, + help_text=_('Ranges of Minimum-maximum child VLAN VID'), ) objects = VLANGroupQuerySet.as_manager() diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index 076d05445..b214fa4a3 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -1,5 +1,5 @@ from django.contrib.contenttypes.models import ContentType -from django.db.models import Count, F, OuterRef, Q, Subquery, Value +from django.db.models import Count, F, Func, OuterRef, Q, Subquery, Value from django.db.models.expressions import RawSQL from django.db.models.functions import Round @@ -63,7 +63,8 @@ class VLANGroupQuerySet(RestrictedQuerySet): return self.annotate( vlan_count=count_related(VLAN, 'group'), - utilization=Round(F('vlan_count') * 100 / F('_total_vlan_ids'), 2) + total_allowed_vids=Func(F('allowed_vids'), function='CARDINALITY'), + utilization=Round(F('vlan_count') * 100 / F('total_allowed_vids'), 2) ) diff --git a/netbox/utilities/forms/fields/array.py b/netbox/utilities/forms/fields/array.py index d7a0fece2..24260e043 100644 --- a/netbox/utilities/forms/fields/array.py +++ b/netbox/utilities/forms/fields/array.py @@ -7,7 +7,6 @@ from ..utils import parse_numeric_range __all__ = ( 'NumericArrayField', - 'NumericRangeArrayField', ) @@ -26,33 +25,3 @@ class NumericArrayField(SimpleArrayField): if isinstance(value, str): value = ','.join([str(n) for n in parse_numeric_range(value)]) return super().to_python(value) - - -class NumericRangeArrayField(forms.CharField): - """ - A field which allows for array of numeric ranges: - Example: 1-5,7-20,30-50 - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if not self.help_text: - self.help_text = _( - "Specify one or more numeric ranges separated by commas " - "Example: 1-5,20-30" - ) - - def clean(self, value): - if value and not self.to_python(value): - raise forms.ValidationError( - _("Invalid ranges ({value}). Must be range of number '100-200' and ranges must be in ascending order.").format(value=value) - ) - return super().clean(value) - - def prepare_value(self, value): - if isinstance(value, str): - return value - return ranges_to_string(value) - - def to_python(self, value): - return string_to_range_array(value)