Merge pull request #3008 from linovia/feature/ipaddress-fix

`IPAddressField` improvements
This commit is contained in:
Tom Christie 2015-06-04 14:54:50 +01:00
commit a3d6601e09
4 changed files with 97 additions and 1 deletions

View File

@ -192,6 +192,17 @@ A field that ensures the input is a valid UUID string. The `to_internal_value` m
- `'urn'` - RFC 4122 URN representation of the UUID: `"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"` - `'urn'` - RFC 4122 URN representation of the UUID: `"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"`
Changing the `format` parameters only affects representation values. All formats are accepted by `to_internal_value` Changing the `format` parameters only affects representation values. All formats are accepted by `to_internal_value`
## IPAddressField
A field that ensures the input is a valid IPv4 or IPv6 string.
Corresponds to `django.forms.fields.IPAddressField` and `django.forms.fields.GenericIPAddressField`.
**Signature**: `IPAddressField(protocol='both', unpack_ipv4=False, **options)`
- `protocol` Limits valid inputs to the specified protocol. Accepted values are 'both' (default), 'IPv4' or 'IPv6'. Matching is case insensitive.
- `unpack_ipv4` Unpacks IPv4 mapped addresses like ::ffff:192.0.2.1. If this option is enabled that address would be unpacked to 192.0.2.1. Default is disabled. Can only be used when protocol is set to 'both'.
--- ---
# Numeric fields # Numeric fields

View File

@ -2,12 +2,13 @@ from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.validators import RegexValidator from django.core.validators import RegexValidator, ip_address_validators
from django.forms import ImageField as DjangoImageField from django.forms import ImageField as DjangoImageField
from django.utils import six, timezone from django.utils import six, timezone
from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.encoding import is_protected_type, smart_text from django.utils.encoding import is_protected_type, smart_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.ipv6 import clean_ipv6_address
from rest_framework import ISO_8601 from rest_framework import ISO_8601
from rest_framework.compat import ( from rest_framework.compat import (
EmailValidator, MinValueValidator, MaxValueValidator, EmailValidator, MinValueValidator, MaxValueValidator,
@ -672,6 +673,31 @@ class UUIDField(Field):
return getattr(value, self.uuid_format) return getattr(value, self.uuid_format)
class IPAddressField(CharField):
"""Support both IPAddressField and GenericIPAddressField"""
default_error_messages = {
'invalid': _('Enter a valid IPv4 or IPv6 address.'),
}
def __init__(self, protocol='both', **kwargs):
self.protocol = protocol.lower()
self.unpack_ipv4 = (self.protocol == 'both')
super(IPAddressField, self).__init__(**kwargs)
validators, error_message = ip_address_validators(protocol, self.unpack_ipv4)
self.validators.extend(validators)
def to_internal_value(self, data):
if data and ':' in data:
try:
if self.protocol in ('both', 'ipv6'):
return clean_ipv6_address(data, self.unpack_ipv4)
except DjangoValidationError:
self.fail('invalid', value=data)
return super(IPAddressField, self).to_internal_value(data)
# Number types... # Number types...
class IntegerField(Field): class IntegerField(Field):

View File

@ -734,6 +734,7 @@ class ModelSerializer(Serializer):
models.TextField: CharField, models.TextField: CharField,
models.TimeField: TimeField, models.TimeField: TimeField,
models.URLField: URLField, models.URLField: URLField,
models.GenericIPAddressField: IPAddressField,
} }
if ModelDurationField is not None: if ModelDurationField is not None:
serializer_field_mapping[ModelDurationField] = DurationField serializer_field_mapping[ModelDurationField] = DurationField
@ -1360,6 +1361,10 @@ class ModelSerializer(Serializer):
if hasattr(models, 'UUIDField'): if hasattr(models, 'UUIDField'):
ModelSerializer.serializer_field_mapping[models.UUIDField] = UUIDField ModelSerializer.serializer_field_mapping[models.UUIDField] = UUIDField
# IPAddressField is deprecated in Django
if hasattr(models, 'IPAddressField'):
ModelSerializer.serializer_field_mapping[models.IPAddressField] = IPAddressField
if postgres_fields: if postgres_fields:
class CharMappingField(DictField): class CharMappingField(DictField):
child = CharField() child = CharField()

View File

@ -549,6 +549,60 @@ class TestUUIDField(FieldValues):
self._test_format('hex', '0' * 32) self._test_format('hex', '0' * 32)
class TestIPAddressField(FieldValues):
"""
Valid and invalid values for `IPAddressField`
"""
valid_inputs = {
'127.0.0.1': '127.0.0.1',
'192.168.33.255': '192.168.33.255',
'2001:0db8:85a3:0042:1000:8a2e:0370:7334': '2001:db8:85a3:42:1000:8a2e:370:7334',
'2001:cdba:0:0:0:0:3257:9652': '2001:cdba::3257:9652',
'2001:cdba::3257:9652': '2001:cdba::3257:9652'
}
invalid_inputs = {
'127001': ['Enter a valid IPv4 or IPv6 address.'],
'127.122.111.2231': ['Enter a valid IPv4 or IPv6 address.'],
'2001:::9652': ['Enter a valid IPv4 or IPv6 address.'],
'2001:0db8:85a3:0042:1000:8a2e:0370:73341': ['Enter a valid IPv4 or IPv6 address.'],
}
outputs = {}
field = serializers.IPAddressField()
class TestIPv4AddressField(FieldValues):
"""
Valid and invalid values for `IPAddressField`
"""
valid_inputs = {
'127.0.0.1': '127.0.0.1',
'192.168.33.255': '192.168.33.255',
}
invalid_inputs = {
'127001': ['Enter a valid IPv4 address.'],
'127.122.111.2231': ['Enter a valid IPv4 address.'],
}
outputs = {}
field = serializers.IPAddressField(protocol='IPv4')
class TestIPv6AddressField(FieldValues):
"""
Valid and invalid values for `IPAddressField`
"""
valid_inputs = {
'2001:0db8:85a3:0042:1000:8a2e:0370:7334': '2001:db8:85a3:42:1000:8a2e:370:7334',
'2001:cdba:0:0:0:0:3257:9652': '2001:cdba::3257:9652',
'2001:cdba::3257:9652': '2001:cdba::3257:9652'
}
invalid_inputs = {
'2001:::9652': ['Enter a valid IPv4 or IPv6 address.'],
'2001:0db8:85a3:0042:1000:8a2e:0370:73341': ['Enter a valid IPv4 or IPv6 address.'],
}
outputs = {}
field = serializers.IPAddressField(protocol='IPv6')
# Number types... # Number types...
class TestIntegerField(FieldValues): class TestIntegerField(FieldValues):