feat(dcim): Change Interface speed field to BigInteger

Converts the Interface speed field from PositiveIntegerField to
PositiveBigIntegerField to support higher speed values. Updates
filters, GraphQL types, and test fixtures accordingly.

Fixes #21542
This commit is contained in:
Martin Hauser
2026-04-03 15:47:01 +02:00
parent f058ee3d60
commit e83e2f85fc
10 changed files with 69 additions and 9 deletions

View File

@@ -26,6 +26,7 @@ from tenancy.models import *
from users.filterset_mixins import OwnerFilterMixin from users.filterset_mixins import OwnerFilterMixin
from users.models import User from users.models import User
from utilities.filters import ( from utilities.filters import (
MultiValueBigNumberFilter,
MultiValueCharFilter, MultiValueCharFilter,
MultiValueContentTypeFilter, MultiValueContentTypeFilter,
MultiValueMACAddressFilter, MultiValueMACAddressFilter,
@@ -2175,7 +2176,7 @@ class InterfaceFilterSet(
distinct=False, distinct=False,
label=_('LAG interface (ID)'), label=_('LAG interface (ID)'),
) )
speed = MultiValueNumberFilter() speed = MultiValueBigNumberFilter(min_value=0)
duplex = django_filters.MultipleChoiceFilter( duplex = django_filters.MultipleChoiceFilter(
choices=InterfaceDuplexChoices, choices=InterfaceDuplexChoices,
distinct=False, distinct=False,

View File

@@ -20,7 +20,13 @@ from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
from tenancy.models import Tenant from tenancy.models import Tenant
from users.models import User from users.models import User
from utilities.forms import BulkEditForm, add_blank_choice, form_from_model from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
from utilities.forms.fields import ColorField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField from utilities.forms.fields import (
ColorField,
DynamicModelChoiceField,
DynamicModelMultipleChoiceField,
JSONField,
PositiveBigIntegerField,
)
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
from virtualization.models import Cluster from virtualization.models import Cluster
@@ -1420,7 +1426,7 @@ class InterfaceBulkEditForm(
'device_id': '$device', 'device_id': '$device',
} }
) )
speed = forms.IntegerField( speed = PositiveBigIntegerField(
label=_('Speed'), label=_('Speed'),
required=False, required=False,
widget=NumberWithOptions( widget=NumberWithOptions(

View File

@@ -19,7 +19,7 @@ from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
from tenancy.models import Tenant from tenancy.models import Tenant
from users.models import User from users.models import User
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, PositiveBigIntegerField, TagFilterField
from utilities.forms.rendering import FieldSet from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import NumberWithOptions from utilities.forms.widgets import NumberWithOptions
from virtualization.models import Cluster, ClusterGroup, VirtualMachine from virtualization.models import Cluster, ClusterGroup, VirtualMachine
@@ -1603,7 +1603,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
choices=InterfaceTypeChoices, choices=InterfaceTypeChoices,
required=False required=False
) )
speed = forms.IntegerField( speed = PositiveBigIntegerField(
label=_('Speed'), label=_('Speed'),
required=False, required=False,
widget=NumberWithOptions( widget=NumberWithOptions(

View File

@@ -47,7 +47,13 @@ if TYPE_CHECKING:
VRFFilter, VRFFilter,
) )
from netbox.graphql.enums import ColorEnum from netbox.graphql.enums import ColorEnum
from netbox.graphql.filter_lookups import FloatLookup, IntegerArrayLookup, IntegerLookup, TreeNodeFilter from netbox.graphql.filter_lookups import (
BigIntegerLookup,
FloatLookup,
IntegerArrayLookup,
IntegerLookup,
TreeNodeFilter,
)
from users.graphql.filters import UserFilter from users.graphql.filters import UserFilter
from virtualization.graphql.filters import ClusterFilter from virtualization.graphql.filters import ClusterFilter
from vpn.graphql.filters import L2VPNFilter, TunnelTerminationFilter from vpn.graphql.filters import L2VPNFilter, TunnelTerminationFilter
@@ -519,7 +525,7 @@ class InterfaceFilter(
strawberry_django.filter_field() strawberry_django.filter_field()
) )
mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field()
speed: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = ( speed: Annotated['BigIntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
strawberry_django.filter_field() strawberry_django.filter_field()
) )
duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = ( duplex: BaseFilterLookup[Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')]] | None = (

View File

@@ -433,6 +433,7 @@ class MACAddressType(PrimaryObjectType):
) )
class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin): class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin):
_name: str _name: str
speed: BigInt | None
wwn: str | None wwn: str | None
parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
bridge: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None bridge: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None

View File

@@ -0,0 +1,15 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0226_modulebay_rebuild_tree'),
]
operations = [
migrations.AlterField(
model_name='interface',
name='speed',
field=models.PositiveBigIntegerField(blank=True, null=True),
),
]

View File

@@ -806,7 +806,7 @@ class Interface(
verbose_name=_('management only'), verbose_name=_('management only'),
help_text=_('This interface is used only for out-of-band management') help_text=_('This interface is used only for out-of-band management')
) )
speed = models.PositiveIntegerField( speed = models.PositiveBigIntegerField(
blank=True, blank=True,
null=True, null=True,
verbose_name=_('speed (Kbps)') verbose_name=_('speed (Kbps)')

View File

@@ -1932,7 +1932,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
'name': 'Interface 4', 'name': 'Interface 4',
'type': '1000base-t', 'type': '1000base-t',
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'speed': 1000000, 'speed': 16000000000,
'duplex': 'full', 'duplex': 'full',
'vrf': vrfs[0].pk, 'vrf': vrfs[0].pk,
'poe_mode': InterfacePoEModeChoices.MODE_PD, 'poe_mode': InterfacePoEModeChoices.MODE_PD,

View File

@@ -7,9 +7,12 @@ from django_filters.constants import EMPTY_VALUES
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from .forms.fields import BigIntegerField
__all__ = ( __all__ = (
'ContentTypeFilter', 'ContentTypeFilter',
'MultiValueArrayFilter', 'MultiValueArrayFilter',
'MultiValueBigNumberFilter',
'MultiValueCharFilter', 'MultiValueCharFilter',
'MultiValueContentTypeFilter', 'MultiValueContentTypeFilter',
'MultiValueDateFilter', 'MultiValueDateFilter',
@@ -77,6 +80,11 @@ class MultiValueNumberFilter(django_filters.MultipleChoiceFilter):
field_class = multivalue_field_factory(forms.IntegerField) field_class = multivalue_field_factory(forms.IntegerField)
@extend_schema_field(OpenApiTypes.INT64)
class MultiValueBigNumberFilter(MultiValueNumberFilter):
field_class = multivalue_field_factory(BigIntegerField)
@extend_schema_field(OpenApiTypes.DECIMAL) @extend_schema_field(OpenApiTypes.DECIMAL)
class MultiValueDecimalFilter(django_filters.MultipleChoiceFilter): class MultiValueDecimalFilter(django_filters.MultipleChoiceFilter):
field_class = multivalue_field_factory(forms.DecimalField) field_class = multivalue_field_factory(forms.DecimalField)

View File

@@ -2,6 +2,7 @@ import json
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.db.models import BigIntegerField as BigIntegerModelField
from django.db.models import Count from django.db.models import Count
from django.forms.fields import InvalidJSONInput from django.forms.fields import InvalidJSONInput
from django.forms.fields import JSONField as _JSONField from django.forms.fields import JSONField as _JSONField
@@ -13,17 +14,39 @@ from utilities.forms import widgets
from utilities.validators import EnhancedURLValidator from utilities.validators import EnhancedURLValidator
__all__ = ( __all__ = (
'BigIntegerField',
'ColorField', 'ColorField',
'CommentField', 'CommentField',
'JSONField', 'JSONField',
'LaxURLField', 'LaxURLField',
'MACAddressField', 'MACAddressField',
'PositiveBigIntegerField',
'QueryField', 'QueryField',
'SlugField', 'SlugField',
'TagFilterField', 'TagFilterField',
) )
class BigIntegerField(forms.IntegerField):
"""
An IntegerField constrained to the range of a signed 64-bit integer.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('min_value', -BigIntegerModelField.MAX_BIGINT - 1)
kwargs.setdefault('max_value', BigIntegerModelField.MAX_BIGINT)
super().__init__(*args, **kwargs)
class PositiveBigIntegerField(BigIntegerField):
"""
An IntegerField constrained to the range supported by Django's
PositiveBigIntegerField model field.
"""
def __init__(self, *args, **kwargs):
kwargs.setdefault('min_value', 0)
super().__init__(*args, **kwargs)
class QueryField(forms.CharField): class QueryField(forms.CharField):
""" """
A CharField subclass used for global search/query fields in filter forms. A CharField subclass used for global search/query fields in filter forms.