Skip to content

Commit

Permalink
Fixed #811 -- Added support for IPv6 to forms and model fields. Many …
Browse files Browse the repository at this point in the history
…thanks to Erik Romijn.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16366 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
jezdez committed Jun 11, 2011
1 parent 404d8af commit b8d9c00
Show file tree
Hide file tree
Showing 23 changed files with 708 additions and 5 deletions.
36 changes: 36 additions & 0 deletions django/core/validators.py
Expand Up @@ -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, '', [], (), {})
Expand Down Expand Up @@ -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')

Expand Down
1 change: 1 addition & 0 deletions django/db/backends/mysql/creation.py
Expand Up @@ -19,6 +19,7 @@ class DatabaseCreation(BaseDatabaseCreation):
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
Expand Down
1 change: 1 addition & 0 deletions django/db/backends/oracle/creation.py
Expand Up @@ -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)',
Expand Down
1 change: 1 addition & 0 deletions django/db/backends/postgresql_psycopg2/creation.py
Expand Up @@ -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)',
Expand Down
1 change: 1 addition & 0 deletions django/db/backends/postgresql_psycopg2/introspection.py
Expand Up @@ -12,6 +12,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
700: 'FloatField',
701: 'FloatField',
869: 'IPAddressField',
869: 'GenericIPAddressField',
1043: 'CharField',
1082: 'DateField',
1083: 'TimeField',
Expand Down
1 change: 1 addition & 0 deletions django/db/backends/sqlite3/creation.py
Expand Up @@ -20,6 +20,7 @@ class DatabaseCreation(BaseDatabaseCreation):
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer unsigned',
Expand Down
38 changes: 37 additions & 1 deletion django/db/models/fields/__init__.py
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 = {
Expand Down
24 changes: 22 additions & 2 deletions django/forms/fields.py
Expand Up @@ -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
Expand All @@ -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'
)


Expand Down Expand Up @@ -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,"
Expand Down

0 comments on commit b8d9c00

Please sign in to comment.