From 91e546c8001aba28c2e303393b36fa8fd8c280e2 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 18 Apr 2023 08:55:09 -0700 Subject: [PATCH 1/8] #12278 add serializer for ipaddressfield to remove spectacular warnings --- netbox/ipam/api/serializers.py | 16 ++++++++++++++++ netbox/ipam/validators.py | 11 +++++++++++ 2 files changed, 27 insertions(+) diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 10c27b2522..220bafa8ed 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -1,4 +1,5 @@ from django.contrib.contenttypes.models import ContentType +from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema_field from rest_framework import serializers @@ -6,6 +7,8 @@ from ipam.choices import * from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS, VLANGROUP_SCOPE_TYPES from ipam.models import * +from ipam.validators import validate_ipaddress_with_mask +from netaddr import AddrFormatError, IPNetwork from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField from netbox.api.serializers import NetBoxModelSerializer from netbox.constants import NESTED_SERIALIZER_PREFIX @@ -387,10 +390,23 @@ class Meta: # # IP addresses # +class IPAddressField(serializers.CharField): + """IPAddressField with mask""" + + default_error_messages = { + 'invalid': _('Enter a valid IPv4 or IPv6 address with optional mask.'), + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + validator = validate_ipaddress_with_mask + self.validators.append(validator) + class IPAddressSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) + address = IPAddressField() vrf = NestedVRFSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) status = ChoiceField(choices=IPAddressStatusChoices, required=False) diff --git a/netbox/ipam/validators.py b/netbox/ipam/validators.py index 50faea8b88..b8e36c5d27 100644 --- a/netbox/ipam/validators.py +++ b/netbox/ipam/validators.py @@ -1,5 +1,6 @@ from django.core.exceptions import ValidationError from django.core.validators import BaseValidator, RegexValidator +from netaddr import AddrFormatError, IPNetwork def prefix_validator(prefix): @@ -28,3 +29,13 @@ def compare(self, a, b): message='Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names', code='invalid' ) + + +def validate_ipaddress_with_mask(address): + if address: + try: + IPNetwork(address) + except AddrFormatError: + raise ValidationError("Invalid IP address format: {}".format(address)) + except (TypeError, ValueError) as e: + raise ValidationError(e) From fb1827cac1c49c0d328b59d2e443d6bb35094f36 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 18 Apr 2023 09:59:46 -0700 Subject: [PATCH 2/8] #12278 add ipaddressfieldserializer to nested serializers --- netbox/ipam/api/nested_serializers.py | 23 +++++++++++++++++++++++ netbox/ipam/api/serializers.py | 16 ++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index 57bb58b5ed..0865e45014 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -1,11 +1,14 @@ +from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema_serializer from rest_framework import serializers from ipam import models from ipam.models.l2vpn import L2VPNTermination, L2VPN +from ipam.validators import validate_ipaddress_with_mask from netbox.api.serializers import WritableNestedSerializer __all__ = [ + 'IPAddressField', 'NestedAggregateSerializer', 'NestedASNSerializer', 'NestedASNRangeSerializer', @@ -27,6 +30,23 @@ ] +# +# IP address field +# + +class IPAddressField(serializers.CharField): + """IPAddressField with mask""" + + default_error_messages = { + 'invalid': _('Enter a valid IPv4 or IPv6 address with optional mask.'), + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + validator = validate_ipaddress_with_mask + self.validators.append(validator) + + # # ASN ranges # @@ -182,6 +202,8 @@ class Meta: class NestedIPRangeSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:iprange-detail') family = serializers.IntegerField(read_only=True) + start_address = IPAddressField() + end_address = IPAddressField() class Meta: model = models.IPRange @@ -195,6 +217,7 @@ class Meta: class NestedIPAddressSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') family = serializers.IntegerField(read_only=True) + address = IPAddressField() class Meta: model = models.IPAddress diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 220bafa8ed..4aa0a75de9 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -1,5 +1,4 @@ from django.contrib.contenttypes.models import ContentType -from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema_field from rest_framework import serializers @@ -7,7 +6,6 @@ from ipam.choices import * from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS, VLANGROUP_SCOPE_TYPES from ipam.models import * -from ipam.validators import validate_ipaddress_with_mask from netaddr import AddrFormatError, IPNetwork from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField from netbox.api.serializers import NetBoxModelSerializer @@ -372,6 +370,8 @@ def to_representation(self, instance): class IPRangeSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:iprange-detail') family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) + start_address = IPAddressField() + end_address = IPAddressField() vrf = NestedVRFSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) status = ChoiceField(choices=IPRangeStatusChoices, required=False) @@ -390,18 +390,6 @@ class Meta: # # IP addresses # -class IPAddressField(serializers.CharField): - """IPAddressField with mask""" - - default_error_messages = { - 'invalid': _('Enter a valid IPv4 or IPv6 address with optional mask.'), - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - validator = validate_ipaddress_with_mask - self.validators.append(validator) - class IPAddressSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail') From 95e37e47cc725108c119e248565d906ec23f561c Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 18 Apr 2023 10:42:03 -0700 Subject: [PATCH 3/8] #12278 fix to_internal_value to_representation in serializer --- netbox/ipam/api/nested_serializers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index 0865e45014..b3ac4eac05 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -6,6 +6,7 @@ from ipam.models.l2vpn import L2VPNTermination, L2VPN from ipam.validators import validate_ipaddress_with_mask from netbox.api.serializers import WritableNestedSerializer +from netaddr import IPNetwork __all__ = [ 'IPAddressField', @@ -46,6 +47,12 @@ def __init__(self, **kwargs): validator = validate_ipaddress_with_mask self.validators.append(validator) + def to_internal_value(self, data): + return IPNetwork(data) + + def to_representation(self, value): + return str(value) + # # ASN ranges From 38f1b919884d92a019a881b2a2e4830b0e570a4b Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 18 Apr 2023 11:27:12 -0700 Subject: [PATCH 4/8] #12278 to_internal_value is called before validation! need to raise validation error if incorrect format --- netbox/ipam/api/nested_serializers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index b3ac4eac05..0589998e4c 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -6,7 +6,7 @@ from ipam.models.l2vpn import L2VPNTermination, L2VPN from ipam.validators import validate_ipaddress_with_mask from netbox.api.serializers import WritableNestedSerializer -from netaddr import IPNetwork +from netaddr import AddrFormatError, IPNetwork __all__ = [ 'IPAddressField', @@ -48,7 +48,12 @@ def __init__(self, **kwargs): self.validators.append(validator) def to_internal_value(self, data): - return IPNetwork(data) + try: + IPNetwork(data) + except AddrFormatError: + raise serializers.ValidationError("Invalid IP address format: {}".format(data)) + except (TypeError, ValueError) as e: + raise serializers.ValidationError(e) def to_representation(self, value): return str(value) From b96792179d539720d0d14ed434865ea7816ace63 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 18 Apr 2023 12:08:03 -0700 Subject: [PATCH 5/8] #12278 to_internal_value needs to return value doh --- netbox/ipam/api/nested_serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index 0589998e4c..1f88272a46 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -49,7 +49,7 @@ def __init__(self, **kwargs): def to_internal_value(self, data): try: - IPNetwork(data) + return IPNetwork(data) except AddrFormatError: raise serializers.ValidationError("Invalid IP address format: {}".format(data)) except (TypeError, ValueError) as e: From dc888e0ac2264cacd1ea569244ea5a2d05d47982 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 20 Apr 2023 11:17:37 -0700 Subject: [PATCH 6/8] #12278 move IPAddressField to field_serializers --- netbox/ipam/api/field_serializers.py | 38 +++++++++++++++++++++++++++ netbox/ipam/api/nested_serializers.py | 33 +---------------------- netbox/ipam/api/serializers.py | 1 + 3 files changed, 40 insertions(+), 32 deletions(-) create mode 100644 netbox/ipam/api/field_serializers.py diff --git a/netbox/ipam/api/field_serializers.py b/netbox/ipam/api/field_serializers.py new file mode 100644 index 0000000000..63033d41f8 --- /dev/null +++ b/netbox/ipam/api/field_serializers.py @@ -0,0 +1,38 @@ +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from ipam import models +from ipam.validators import validate_ipaddress_with_mask +from netaddr import AddrFormatError, IPNetwork + +__all__ = [ + 'IPAddressField', +] + + +# +# IP address field +# + +class IPAddressField(serializers.CharField): + """IPAddressField with mask""" + + default_error_messages = { + 'invalid': _('Enter a valid IPv4 or IPv6 address with optional mask.'), + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + validator = validate_ipaddress_with_mask + self.validators.append(validator) + + def to_internal_value(self, data): + try: + return IPNetwork(data) + except AddrFormatError: + raise serializers.ValidationError("Invalid IP address format: {}".format(data)) + except (TypeError, ValueError) as e: + raise serializers.ValidationError(e) + + def to_representation(self, value): + return str(value) diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index 1f88272a46..9e150e2cb0 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -1,15 +1,12 @@ -from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema_serializer from rest_framework import serializers from ipam import models from ipam.models.l2vpn import L2VPNTermination, L2VPN -from ipam.validators import validate_ipaddress_with_mask from netbox.api.serializers import WritableNestedSerializer -from netaddr import AddrFormatError, IPNetwork +from .field_serializers import IPAddressField __all__ = [ - 'IPAddressField', 'NestedAggregateSerializer', 'NestedASNSerializer', 'NestedASNRangeSerializer', @@ -31,34 +28,6 @@ ] -# -# IP address field -# - -class IPAddressField(serializers.CharField): - """IPAddressField with mask""" - - default_error_messages = { - 'invalid': _('Enter a valid IPv4 or IPv6 address with optional mask.'), - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - validator = validate_ipaddress_with_mask - self.validators.append(validator) - - def to_internal_value(self, data): - try: - return IPNetwork(data) - except AddrFormatError: - raise serializers.ValidationError("Invalid IP address format: {}".format(data)) - except (TypeError, ValueError) as e: - raise serializers.ValidationError(e) - - def to_representation(self, value): - return str(value) - - # # ASN ranges # diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 4aa0a75de9..dea7cb157d 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -14,6 +14,7 @@ from utilities.api import get_serializer_for_model from virtualization.api.nested_serializers import NestedVirtualMachineSerializer from .nested_serializers import * +from .field_serializers import IPAddressField # From 1ea55d6d56f1c373db8e89e91e8f194a69f15b91 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 20 Apr 2023 11:18:47 -0700 Subject: [PATCH 7/8] #12278 remove old import --- netbox/ipam/api/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index dea7cb157d..a7b5bb8609 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -6,7 +6,6 @@ from ipam.choices import * from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS, VLANGROUP_SCOPE_TYPES from ipam.models import * -from netaddr import AddrFormatError, IPNetwork from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField from netbox.api.serializers import NetBoxModelSerializer from netbox.constants import NESTED_SERIALIZER_PREFIX From 762d9bdbf6565d5218f3cb91e1f27a3e262223f6 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 21 Apr 2023 08:43:28 -0700 Subject: [PATCH 8/8] 12278 remove validator --- netbox/ipam/api/field_serializers.py | 6 ------ netbox/ipam/validators.py | 11 ----------- 2 files changed, 17 deletions(-) diff --git a/netbox/ipam/api/field_serializers.py b/netbox/ipam/api/field_serializers.py index 63033d41f8..d44d8b7d46 100644 --- a/netbox/ipam/api/field_serializers.py +++ b/netbox/ipam/api/field_serializers.py @@ -2,7 +2,6 @@ from rest_framework import serializers from ipam import models -from ipam.validators import validate_ipaddress_with_mask from netaddr import AddrFormatError, IPNetwork __all__ = [ @@ -21,11 +20,6 @@ class IPAddressField(serializers.CharField): 'invalid': _('Enter a valid IPv4 or IPv6 address with optional mask.'), } - def __init__(self, **kwargs): - super().__init__(**kwargs) - validator = validate_ipaddress_with_mask - self.validators.append(validator) - def to_internal_value(self, data): try: return IPNetwork(data) diff --git a/netbox/ipam/validators.py b/netbox/ipam/validators.py index b8e36c5d27..50faea8b88 100644 --- a/netbox/ipam/validators.py +++ b/netbox/ipam/validators.py @@ -1,6 +1,5 @@ from django.core.exceptions import ValidationError from django.core.validators import BaseValidator, RegexValidator -from netaddr import AddrFormatError, IPNetwork def prefix_validator(prefix): @@ -29,13 +28,3 @@ def compare(self, a, b): message='Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names', code='invalid' ) - - -def validate_ipaddress_with_mask(address): - if address: - try: - IPNetwork(address) - except AddrFormatError: - raise ValidationError("Invalid IP address format: {}".format(address)) - except (TypeError, ValueError) as e: - raise ValidationError(e)