Permalink
Browse files

Fixed #811 -- Added support for IPv6 to forms and model fields. Many …

…thanks to Erik Romijn.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16366 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 87571cd commit ce3c281090320172d22e8a6057250d103c93438e @jezdez jezdez committed Jun 11, 2011
@@ -5,6 +5,7 @@
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
+from django.utils.ipv6 import is_valid_ipv6_address
# These values, if given to validate(), will trigger the self.required check.
EMPTY_VALUES = (None, '', [], (), {})
@@ -145,6 +146,41 @@ def __call__(self, value):
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
+def validate_ipv6_address(value):
+ if not is_valid_ipv6_address(value):
+ raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid')
+
+def validate_ipv46_address(value):
+ try:
+ validate_ipv4_address(value)
+ except ValidationError:
+ try:
+ validate_ipv6_address(value)
+ except ValidationError:
+ raise ValidationError(_(u'Enter a valid IPv4 or IPv6 address.'), code='invalid')
+
+ip_address_validator_map = {
+ 'both': ([validate_ipv46_address], _('Enter a valid IPv4 or IPv6 address.')),
+ 'ipv4': ([validate_ipv4_address], _('Enter a valid IPv4 address.')),
+ 'ipv6': ([validate_ipv6_address], _('Enter a valid IPv6 address.')),
+}
+
+def ip_address_validators(protocol, unpack_ipv4):
+ """
+ Depending on the given parameters returns the appropriate validators for
+ the GenericIPAddressField.
+
+ This code is here, because it is exactly the same for the model and the form field.
+ """
+ if protocol != 'both' and unpack_ipv4:
+ raise ValueError(
+ "You can only use `unpack_ipv4` if `protocol` is set to 'both'")
+ try:
+ return ip_address_validator_map[protocol.lower()]
+ except KeyError:
+ raise ValueError("The protocol '%s' is unknown. Supported: %s"
+ % (protocol, ip_address_validator_map.keys()))
+
comma_separated_int_list_re = re.compile('^[\d,]+$')
validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
@@ -19,6 +19,7 @@ class DatabaseCreation(BaseDatabaseCreation):
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
+ 'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
@@ -27,6 +27,7 @@ class DatabaseCreation(BaseDatabaseCreation):
'IntegerField': 'NUMBER(11)',
'BigIntegerField': 'NUMBER(19)',
'IPAddressField': 'VARCHAR2(15)',
+ 'GenericIPAddressField': 'VARCHAR2(39)',
'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
'OneToOneField': 'NUMBER(11)',
'PositiveIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)',
@@ -21,6 +21,7 @@ class DatabaseCreation(BaseDatabaseCreation):
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'inet',
+ 'GenericIPAddressField': 'inet',
'NullBooleanField': 'boolean',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
@@ -12,6 +12,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
700: 'FloatField',
701: 'FloatField',
869: 'IPAddressField',
+ 869: 'GenericIPAddressField',
1043: 'CharField',
1082: 'DateField',
1083: 'TimeField',
@@ -20,6 +20,7 @@ class DatabaseCreation(BaseDatabaseCreation):
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
+ 'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer unsigned',
@@ -17,6 +17,7 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode, force_unicode, smart_str
from django.utils import datetime_safe
+from django.utils.ipv6 import clean_ipv6_address, is_valid_ipv6_address
class NOT_PROVIDED:
pass
@@ -920,7 +921,7 @@ def formfield(self, **kwargs):
class IPAddressField(Field):
empty_strings_allowed = False
- description = _("IP address")
+ description = _("IPv4 address")
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 15
Field.__init__(self, *args, **kwargs)
@@ -933,6 +934,41 @@ def formfield(self, **kwargs):
defaults.update(kwargs)
return super(IPAddressField, self).formfield(**defaults)
+class GenericIPAddressField(Field):
+ empty_strings_allowed = True
+ description = _("IP address")
+
+ def __init__(self, protocol='both', unpack_ipv4=False, *args, **kwargs):
+ self.unpack_ipv4 = unpack_ipv4
+ self.default_validators, invalid_error_message = \
+ validators.ip_address_validators(protocol, unpack_ipv4)
+ self.default_error_messages['invalid'] = invalid_error_message
+ kwargs['max_length'] = 39
+ Field.__init__(self, *args, **kwargs)
+
+ def get_internal_type(self):
+ return "GenericIPAddressField"
+
+ def to_python(self, value):
+ if value and ':' in value:
+ return clean_ipv6_address(value,
+ self.unpack_ipv4, self.error_messages['invalid'])
+ return value
+
+ def get_prep_value(self, value):
+ if value and ':' in value:
+ try:
+ return clean_ipv6_address(value, self.unpack_ipv4)
+ except ValidationError:
+ pass
+ return value
+
+ def formfield(self, **kwargs):
+ defaults = {'form_class': forms.GenericIPAddressField}
+ defaults.update(kwargs)
+ return super(GenericIPAddressField, self).formfield(**defaults)
+
+
class NullBooleanField(Field):
empty_strings_allowed = False
default_error_messages = {
@@ -18,6 +18,7 @@
from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode, smart_str, force_unicode
+from django.utils.ipv6 import clean_ipv6_address
# Provide this import for backwards compatibility.
from django.core.validators import EMPTY_VALUES
@@ -34,8 +35,8 @@
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
- 'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
- 'TypedChoiceField', 'TypedMultipleChoiceField'
+ 'SplitDateTimeField', 'IPAddressField', 'GenericIPAddressField', 'FilePathField',
+ 'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField'
)
@@ -953,6 +954,25 @@ class IPAddressField(CharField):
default_validators = [validators.validate_ipv4_address]
+class GenericIPAddressField(CharField):
+ default_error_messages = {}
+
+ def __init__(self, protocol='both', unpack_ipv4=False, *args, **kwargs):
+ self.unpack_ipv4 = unpack_ipv4
+ self.default_validators, invalid_error_message = \
+ validators.ip_address_validators(protocol, unpack_ipv4)
+ self.default_error_messages['invalid'] = invalid_error_message
+ super(GenericIPAddressField, self).__init__(*args, **kwargs)
+
+ def to_python(self, value):
+ if not value:
+ return ''
+ if value and ':' in value:
+ return clean_ipv6_address(value,
+ self.unpack_ipv4, self.error_messages['invalid'])
+ return value
+
+
class SlugField(CharField):
default_error_messages = {
'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers,"
Oops, something went wrong. Retry.

0 comments on commit ce3c281

Please sign in to comment.