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
This commit is contained in:
parent
87571cdb37
commit
ce3c281090
|
@ -5,6 +5,7 @@ import urlparse
|
|||
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 @@ validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of l
|
|||
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.text import capfirst
|
|||
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 @@ class BigIntegerField(IntegerField):
|
|||
|
||||
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 @@ class IPAddressField(Field):
|
|||
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.core import validators
|
|||
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 @@ __all__ = (
|
|||
'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,"
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
# This code was mostly based on ipaddr-py
|
||||
# Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/
|
||||
# Licensed under the Apache License, Version 2.0 (the "License").
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
def clean_ipv6_address(ip_str, unpack_ipv4=False,
|
||||
error_message="This is not a valid IPv6 address"):
|
||||
"""
|
||||
Cleans a IPv6 address string.
|
||||
|
||||
Validity is checked by calling is_valid_ipv6_address() - if an
|
||||
invalid address is passed, ValidationError is raised.
|
||||
|
||||
Replaces the longest continious zero-sequence with "::" and
|
||||
removes leading zeroes and makes sure all hextets are lowercase.
|
||||
|
||||
Args:
|
||||
ip_str: A valid IPv6 address.
|
||||
unpack_ipv4: if an IPv4-mapped address is found,
|
||||
return the plain IPv4 address (default=False).
|
||||
error_message: A error message for in the ValidationError.
|
||||
|
||||
Returns:
|
||||
A compressed IPv6 address, or the same value
|
||||
|
||||
"""
|
||||
best_doublecolon_start = -1
|
||||
best_doublecolon_len = 0
|
||||
doublecolon_start = -1
|
||||
doublecolon_len = 0
|
||||
|
||||
if not is_valid_ipv6_address(ip_str):
|
||||
raise ValidationError(error_message)
|
||||
|
||||
# This algorithm can only handle fully exploded
|
||||
# IP strings
|
||||
ip_str = _explode_shorthand_ip_string(ip_str)
|
||||
|
||||
ip_str = _sanitize_ipv4_mapping(ip_str)
|
||||
|
||||
# If needed, unpack the IPv4 and return straight away
|
||||
# - no need in running the rest of the algorithm
|
||||
if unpack_ipv4:
|
||||
ipv4_unpacked = _unpack_ipv4(ip_str)
|
||||
|
||||
if ipv4_unpacked:
|
||||
return ipv4_unpacked
|
||||
|
||||
hextets = ip_str.split(":")
|
||||
|
||||
for index in range(len(hextets)):
|
||||
# Remove leading zeroes
|
||||
hextets[index] = hextets[index].lstrip('0')
|
||||
if not hextets[index]:
|
||||
hextets[index] = '0'
|
||||
|
||||
# Determine best hextet to compress
|
||||
if hextets[index] == '0':
|
||||
doublecolon_len += 1
|
||||
if doublecolon_start == -1:
|
||||
# Start of a sequence of zeros.
|
||||
doublecolon_start = index
|
||||
if doublecolon_len > best_doublecolon_len:
|
||||
# This is the longest sequence of zeros so far.
|
||||
best_doublecolon_len = doublecolon_len
|
||||
best_doublecolon_start = doublecolon_start
|
||||
else:
|
||||
doublecolon_len = 0
|
||||
doublecolon_start = -1
|
||||
|
||||
# Compress the most suitable hextet
|
||||
if best_doublecolon_len > 1:
|
||||
best_doublecolon_end = (best_doublecolon_start +
|
||||
best_doublecolon_len)
|
||||
# For zeros at the end of the address.
|
||||
if best_doublecolon_end == len(hextets):
|
||||
hextets += ['']
|
||||
hextets[best_doublecolon_start:best_doublecolon_end] = ['']
|
||||
# For zeros at the beginning of the address.
|
||||
if best_doublecolon_start == 0:
|
||||
hextets = [''] + hextets
|
||||
|
||||
result = ":".join(hextets)
|
||||
|
||||
return result.lower()
|
||||
|
||||
|
||||
def _sanitize_ipv4_mapping(ip_str):
|
||||
"""
|
||||
Sanitize IPv4 mapping in a expanded IPv6 address.
|
||||
|
||||
This converts ::ffff:0a0a:0a0a to ::ffff:10.10.10.10.
|
||||
If there is nothing to sanitize, returns an unchanged
|
||||
string.
|
||||
|
||||
Args:
|
||||
ip_str: A string, the expanded IPv6 address.
|
||||
|
||||
Returns:
|
||||
The sanitized output string, if applicable.
|
||||
"""
|
||||
if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
|
||||
# not an ipv4 mapping
|
||||
return ip_str
|
||||
|
||||
hextets = ip_str.split(':')
|
||||
|
||||
if '.' in hextets[-1]:
|
||||
# already sanitized
|
||||
return ip_str
|
||||
|
||||
ipv4_address = "%d.%d.%d.%d" % (
|
||||
int(hextets[6][0:2], 16),
|
||||
int(hextets[6][2:4], 16),
|
||||
int(hextets[7][0:2], 16),
|
||||
int(hextets[7][2:4], 16),
|
||||
)
|
||||
|
||||
result = ':'.join(hextets[0:6])
|
||||
result += ':' + ipv4_address
|
||||
|
||||
return result
|
||||
|
||||
def _unpack_ipv4(ip_str):
|
||||
"""
|
||||
Unpack an IPv4 address that was mapped in a compressed IPv6 address.
|
||||
|
||||
This converts 0000:0000:0000:0000:0000:ffff:10.10.10.10 to 10.10.10.10.
|
||||
If there is nothing to sanitize, returns None.
|
||||
|
||||
Args:
|
||||
ip_str: A string, the expanded IPv6 address.
|
||||
|
||||
Returns:
|
||||
The unpacked IPv4 address, or None if there was nothing to unpack.
|
||||
"""
|
||||
if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
|
||||
return None
|
||||
|
||||
hextets = ip_str.split(':')
|
||||
return hextets[-1]
|
||||
|
||||
def is_valid_ipv6_address(ip_str):
|
||||
"""
|
||||
Ensure we have a valid IPv6 address.
|
||||
|
||||
Args:
|
||||
ip_str: A string, the IPv6 address.
|
||||
|
||||
Returns:
|
||||
A boolean, True if this is a valid IPv6 address.
|
||||
|
||||
"""
|
||||
from django.core.validators import validate_ipv4_address
|
||||
|
||||
# We need to have at least one ':'.
|
||||
if ':' not in ip_str:
|
||||
return False
|
||||
|
||||
# We can only have one '::' shortener.
|
||||
if ip_str.count('::') > 1:
|
||||
return False
|
||||
|
||||
# '::' should be encompassed by start, digits or end.
|
||||
if ':::' in ip_str:
|
||||
return False
|
||||
|
||||
# A single colon can neither start nor end an address.
|
||||
if ((ip_str.startswith(':') and not ip_str.startswith('::')) or
|
||||
(ip_str.endswith(':') and not ip_str.endswith('::'))):
|
||||
return False
|
||||
|
||||
# We can never have more than 7 ':' (1::2:3:4:5:6:7:8 is invalid)
|
||||
if ip_str.count(':') > 7:
|
||||
return False
|
||||
|
||||
# If we have no concatenation, we need to have 8 fields with 7 ':'.
|
||||
if '::' not in ip_str and ip_str.count(':') != 7:
|
||||
# We might have an IPv4 mapped address.
|
||||
if ip_str.count('.') != 3:
|
||||
return False
|
||||
|
||||
ip_str = _explode_shorthand_ip_string(ip_str)
|
||||
|
||||
# Now that we have that all squared away, let's check that each of the
|
||||
# hextets are between 0x0 and 0xFFFF.
|
||||
for hextet in ip_str.split(':'):
|
||||
if hextet.count('.') == 3:
|
||||
# If we have an IPv4 mapped address, the IPv4 portion has to
|
||||
# be at the end of the IPv6 portion.
|
||||
if not ip_str.split(':')[-1] == hextet:
|
||||
return False
|
||||
try:
|
||||
validate_ipv4_address(hextet)
|
||||
except ValidationError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
# a value error here means that we got a bad hextet,
|
||||
# something like 0xzzzz
|
||||
if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF:
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _explode_shorthand_ip_string(ip_str):
|
||||
"""
|
||||
Expand a shortened IPv6 address.
|
||||
|
||||
Args:
|
||||
ip_str: A string, the IPv6 address.
|
||||
|
||||
Returns:
|
||||
A string, the expanded IPv6 address.
|
||||
|
||||
"""
|
||||
if not _is_shorthand_ip(ip_str):
|
||||
# We've already got a longhand ip_str.
|
||||
return ip_str
|
||||
|
||||
new_ip = []
|
||||
hextet = ip_str.split('::')
|
||||
|
||||
# If there is a ::, we need to expand it with zeroes
|
||||
# to get to 8 hextets - unless there is a dot in the last hextet,
|
||||
# meaning we're doing v4-mapping
|
||||
if '.' in ip_str.split(':')[-1]:
|
||||
fill_to = 7
|
||||
else:
|
||||
fill_to = 8
|
||||
|
||||
if len(hextet) > 1:
|
||||
sep = len(hextet[0].split(':')) + len(hextet[1].split(':'))
|
||||
new_ip = hextet[0].split(':')
|
||||
|
||||
for _ in xrange(fill_to - sep):
|
||||
new_ip.append('0000')
|
||||
new_ip += hextet[1].split(':')
|
||||
|
||||
else:
|
||||
new_ip = ip_str.split(':')
|
||||
|
||||
# Now need to make sure every hextet is 4 lower case characters.
|
||||
# If a hextet is < 4 characters, we've got missing leading 0's.
|
||||
ret_ip = []
|
||||
for hextet in new_ip:
|
||||
ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower())
|
||||
return ':'.join(ret_ip)
|
||||
|
||||
|
||||
def _is_shorthand_ip(ip_str):
|
||||
"""Determine if the address is shortened.
|
||||
|
||||
Args:
|
||||
ip_str: A string, the IPv6 address.
|
||||
|
||||
Returns:
|
||||
A boolean, True if the address is shortened.
|
||||
|
||||
"""
|
||||
if ip_str.count('::') == 1:
|
||||
return True
|
||||
if filter(lambda x: len(x) < 4, ip_str.split(':')):
|
||||
return True
|
||||
return False
|
|
@ -622,6 +622,45 @@ Takes two optional arguments for validation:
|
|||
expression.
|
||||
* Error message keys: ``required``, ``invalid``
|
||||
|
||||
``GenericIPAddressField``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: GenericIPAddressField(**kwargs)
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
A field containing either an IPv4 or an IPv6 address.
|
||||
|
||||
* Default widget: ``TextInput``
|
||||
* Empty value: ``''`` (an empty string)
|
||||
* Normalizes to: A Unicode object. IPv6 addresses are
|
||||
normalized as described below.
|
||||
* Validates that the given value is a valid IP address.
|
||||
* Error message keys: ``required``, ``invalid``
|
||||
|
||||
The IPv6 address normalization follows `RFC4291 section 2.2`_, including using
|
||||
the IPv4 format suggested in paragraph 3 of that section, like
|
||||
``::ffff:192.0.2.0``. For example, ``2001:0::0:01`` would be normalized to
|
||||
``2001::1``, and ``::ffff:0a0a:0a0a`` to ``::ffff:10.10.10.10``. All
|
||||
characters are converted to lowercase.
|
||||
|
||||
.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
|
||||
|
||||
Takes two optional arguments:
|
||||
|
||||
.. attribute:: GenericIPAddressField.protocol
|
||||
|
||||
Limits valid inputs to the specified protocol.
|
||||
Accepted values are ``both`` (default), ``IPv4``
|
||||
or ``IPv6``. Matching is case insensitive.
|
||||
|
||||
.. attribute:: GenericIPAddressField.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'``.
|
||||
|
||||
``MultipleChoiceField``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -760,6 +760,38 @@ single-line input).
|
|||
An IP address, in string format (e.g. "192.0.2.30"). The admin represents this
|
||||
as an ``<input type="text">`` (a single-line input).
|
||||
|
||||
``GenericIPAddressField``
|
||||
-------------------------
|
||||
|
||||
.. class:: GenericIPAddressField([protocols=both, unpack_ipv4=False, **options])
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
An IPv4 or IPv6 address, in string format (e.g. ``192.0.2.30`` or
|
||||
``2a02:42fe::4``). The admin represents this as an ``<input type="text">``
|
||||
(a single-line input).
|
||||
|
||||
The IPv6 address normalization follows `RFC4291 section 2.2`_, including using
|
||||
the IPv4 format suggested in paragraph 3 of that section, like
|
||||
``::ffff:192.0.2.0``. For example, ``2001:0::0:01`` would be normalized to
|
||||
``2001::1``, and ``::ffff:0a0a:0a0a`` to ``::ffff:10.10.10.10``. All
|
||||
characters are converted to lowercase.
|
||||
|
||||
.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
|
||||
|
||||
.. attribute:: GenericIPAddressField.protocol
|
||||
|
||||
Limits valid inputs to the specified protocol.
|
||||
Accepted values are ``'both'`` (default), ``'IPv4'``
|
||||
or ``'IPv6'``. Matching is case insensitive.
|
||||
|
||||
.. attribute:: GenericIPAddressField.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'``.
|
||||
|
||||
``NullBooleanField``
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -130,6 +130,23 @@ to, or in lieu of custom ``field.clean()`` methods.
|
|||
A :class:`RegexValidator` instance that ensures a value looks like an IPv4
|
||||
address.
|
||||
|
||||
``validate_ipv6_address``
|
||||
-------------------------
|
||||
.. versionadded:: 1.4
|
||||
|
||||
.. data:: validate_ipv6_address
|
||||
|
||||
Uses :mod:`django.utils.ipv6` to check the validity of an IPv6 address.
|
||||
|
||||
``validate_ipv46_address``
|
||||
--------------------------
|
||||
.. versionadded:: 1.4
|
||||
|
||||
.. data:: validate_ipv46_address
|
||||
|
||||
Uses both ``validate_ipv4_address`` and ``validate_ipv6_address`` to
|
||||
ensure a value is either a valid IPv4 or IPv6 address.
|
||||
|
||||
``validate_comma_separated_integer_list``
|
||||
-----------------------------------------
|
||||
.. data:: validate_comma_separated_integer_list
|
||||
|
|
|
@ -155,6 +155,15 @@ You may override or customize the default filtering by writing a
|
|||
:ref:`custom filter<custom-error-reports>`. Learn more on
|
||||
:ref:`Filtering error reports<filtering-error-reports>`.
|
||||
|
||||
Extended IPv6 support
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The previously added support for IPv6 addresses when using the runserver
|
||||
management command in Django 1.3 has now been further extended by adding
|
||||
a :class:`~django.db.models.fields.GenericIPAddressField` model field,
|
||||
a :class:`~django.forms.fields.GenericIPAddressField` form field and
|
||||
the validators :data:`~django.core.validators.validate_ipv46_address` and
|
||||
:data:`~django.core.validators.validate_ipv6_address`
|
||||
|
||||
Minor features
|
||||
~~~~~~~~~~~~~~
|
||||
|
|
|
@ -83,6 +83,8 @@ the full list of conversions:
|
|||
|
||||
``IPAddressField`` ``IPAddressField``
|
||||
|
||||
``GenericIPAddressField`` ``GenericIPAddressField``
|
||||
|
||||
``ManyToManyField`` ``ModelMultipleChoiceField`` (see
|
||||
below)
|
||||
|
||||
|
|
|
@ -81,4 +81,12 @@ class FlexibleDatePost(models.Model):
|
|||
|
||||
class UniqueErrorsModel(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True, error_messages={'unique': u'Custom unique name message.'})
|
||||
number = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'})
|
||||
number = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'})
|
||||
|
||||
class GenericIPAddressTestModel(models.Model):
|
||||
generic_ip = models.GenericIPAddressField(blank=True, unique=True)
|
||||
v4_ip = models.GenericIPAddressField(blank=True, protocol="ipv4")
|
||||
v6_ip = models.GenericIPAddressField(blank=True, protocol="ipv6")
|
||||
|
||||
class GenericIPAddressWithUnpackUniqueTestModel(models.Model):
|
||||
generic_v4unpack_ip = models.GenericIPAddressField(blank=True, unique=True, unpack_ipv4=True)
|
||||
|
|
|
@ -2,7 +2,8 @@ from django import forms
|
|||
from django.test import TestCase
|
||||
from django.core.exceptions import NON_FIELD_ERRORS
|
||||
from modeltests.validation import ValidationTestCase
|
||||
from modeltests.validation.models import Author, Article, ModelToValidate
|
||||
from modeltests.validation.models import (Author, Article, ModelToValidate,
|
||||
GenericIPAddressTestModel, GenericIPAddressWithUnpackUniqueTestModel)
|
||||
|
||||
# Import other tests for this package.
|
||||
from modeltests.validation.validators import TestModelsWithValidators
|
||||
|
@ -77,6 +78,7 @@ class BaseModelValidationTests(ValidationTestCase):
|
|||
mtv = ModelToValidate(number=10, name='Some Name'*100)
|
||||
self.assertFailsValidation(mtv.full_clean, ['name',])
|
||||
|
||||
|
||||
class ArticleForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Article
|
||||
|
@ -124,3 +126,58 @@ class ModelFormsTests(TestCase):
|
|||
article = Article(author_id=self.author.id)
|
||||
form = ArticleForm(data, instance=article)
|
||||
self.assertEqual(form.errors.keys(), ['pub_date'])
|
||||
|
||||
|
||||
class GenericIPAddressFieldTests(ValidationTestCase):
|
||||
|
||||
def test_correct_generic_ip_passes(self):
|
||||
giptm = GenericIPAddressTestModel(generic_ip="1.2.3.4")
|
||||
self.assertEqual(None, giptm.full_clean())
|
||||
giptm = GenericIPAddressTestModel(generic_ip="2001::2")
|
||||
self.assertEqual(None, giptm.full_clean())
|
||||
|
||||
def test_invalid_generic_ip_raises_error(self):
|
||||
giptm = GenericIPAddressTestModel(generic_ip="294.4.2.1")
|
||||
self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
|
||||
giptm = GenericIPAddressTestModel(generic_ip="1:2")
|
||||
self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
|
||||
|
||||
def test_correct_v4_ip_passes(self):
|
||||
giptm = GenericIPAddressTestModel(v4_ip="1.2.3.4")
|
||||
self.assertEqual(None, giptm.full_clean())
|
||||
|
||||
def test_invalid_v4_ip_raises_error(self):
|
||||
giptm = GenericIPAddressTestModel(v4_ip="294.4.2.1")
|
||||
self.assertFailsValidation(giptm.full_clean, ['v4_ip',])
|
||||
giptm = GenericIPAddressTestModel(v4_ip="2001::2")
|
||||
self.assertFailsValidation(giptm.full_clean, ['v4_ip',])
|
||||
|
||||
def test_correct_v6_ip_passes(self):
|
||||
giptm = GenericIPAddressTestModel(v6_ip="2001::2")
|
||||
self.assertEqual(None, giptm.full_clean())
|
||||
|
||||
def test_invalid_v6_ip_raises_error(self):
|
||||
giptm = GenericIPAddressTestModel(v6_ip="1.2.3.4")
|
||||
self.assertFailsValidation(giptm.full_clean, ['v6_ip',])
|
||||
giptm = GenericIPAddressTestModel(v6_ip="1:2")
|
||||
self.assertFailsValidation(giptm.full_clean, ['v6_ip',])
|
||||
|
||||
def test_v6_uniqueness_detection(self):
|
||||
# These two addresses are the same with different syntax
|
||||
giptm = GenericIPAddressTestModel(generic_ip="2001::1:0:0:0:0:2")
|
||||
giptm.save()
|
||||
giptm = GenericIPAddressTestModel(generic_ip="2001:0:1:2")
|
||||
self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
|
||||
|
||||
def test_v4_unpack_uniqueness_detection(self):
|
||||
# These two are different, because we are not doing IPv4 unpacking
|
||||
giptm = GenericIPAddressTestModel(generic_ip="::ffff:10.10.10.10")
|
||||
giptm.save()
|
||||
giptm = GenericIPAddressTestModel(generic_ip="10.10.10.10")
|
||||
self.assertEqual(None, giptm.full_clean())
|
||||
|
||||
# These two are the same, because we are doing IPv4 unpacking
|
||||
giptm = GenericIPAddressWithUnpackUniqueTestModel(generic_v4unpack_ip="::ffff:18.52.18.52")
|
||||
giptm.save()
|
||||
giptm = GenericIPAddressWithUnpackUniqueTestModel(generic_v4unpack_ip="18.52.18.52")
|
||||
self.assertFailsValidation(giptm.full_clean, ['generic_v4unpack_ip',])
|
||||
|
|
|
@ -52,6 +52,31 @@ TEST_DATA = (
|
|||
(validate_ipv4_address, '25,1,1,1', ValidationError),
|
||||
(validate_ipv4_address, '25.1 .1.1', ValidationError),
|
||||
|
||||
# validate_ipv6_address uses django.utils.ipv6, which
|
||||
# is tested in much greater detail in it's own testcase
|
||||
(validate_ipv6_address, 'fe80::1', None),
|
||||
(validate_ipv6_address, '::1', None),
|
||||
(validate_ipv6_address, '1:2:3:4:5:6:7:8', None),
|
||||
|
||||
(validate_ipv6_address, '1:2', ValidationError),
|
||||
(validate_ipv6_address, '::zzz', ValidationError),
|
||||
(validate_ipv6_address, '12345::', ValidationError),
|
||||
|
||||
(validate_ipv46_address, '1.1.1.1', None),
|
||||
(validate_ipv46_address, '255.0.0.0', None),
|
||||
(validate_ipv46_address, '0.0.0.0', None),
|
||||
(validate_ipv46_address, 'fe80::1', None),
|
||||
(validate_ipv46_address, '::1', None),
|
||||
(validate_ipv46_address, '1:2:3:4:5:6:7:8', None),
|
||||
|
||||
(validate_ipv46_address, '256.1.1.1', ValidationError),
|
||||
(validate_ipv46_address, '25.1.1.', ValidationError),
|
||||
(validate_ipv46_address, '25,1,1,1', ValidationError),
|
||||
(validate_ipv46_address, '25.1 .1.1', ValidationError),
|
||||
(validate_ipv46_address, '1:2', ValidationError),
|
||||
(validate_ipv46_address, '::zzz', ValidationError),
|
||||
(validate_ipv46_address, '12345::', ValidationError),
|
||||
|
||||
(validate_comma_separated_integer_list, '1', None),
|
||||
(validate_comma_separated_integer_list, '1,2,3', None),
|
||||
(validate_comma_separated_integer_list, '1,2,3,', None),
|
||||
|
|
|
@ -196,6 +196,15 @@ class FormsErrorMessagesTestCase(unittest.TestCase, AssertFormErrorsMixin):
|
|||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
|
||||
|
||||
def test_generic_ipaddressfield(self):
|
||||
e = {
|
||||
'required': 'REQUIRED',
|
||||
'invalid': 'INVALID IP ADDRESS',
|
||||
}
|
||||
f = GenericIPAddressField(error_messages=e)
|
||||
self.assertFormErrors([u'REQUIRED'], f.clean, '')
|
||||
self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
|
||||
|
||||
def test_subclassing_errorlist(self):
|
||||
class TestForm(Form):
|
||||
first_name = CharField()
|
||||
|
|
|
@ -460,6 +460,86 @@ class FormsExtraTestCase(unittest.TestCase, AssertFormErrorsMixin):
|
|||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
|
||||
|
||||
def test_generic_ipaddress_invalid_arguments(self):
|
||||
self.assertRaises(ValueError, GenericIPAddressField, protocol="hamster")
|
||||
self.assertRaises(ValueError, GenericIPAddressField, protocol="ipv4", unpack_ipv4=True)
|
||||
|
||||
def test_generic_ipaddress_as_generic(self):
|
||||
# The edge cases of the IPv6 validation code are not deeply tested
|
||||
# here, they are covered in the tests for django.utils.ipv6
|
||||
f = GenericIPAddressField()
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, '')
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, None)
|
||||
self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5')
|
||||
self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
|
||||
self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2')
|
||||
|
||||
def test_generic_ipaddress_as_ipv4_only(self):
|
||||
f = GenericIPAddressField(protocol="IPv4")
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, '')
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, None)
|
||||
self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'fe80::223:6cff:fe8a:2e8a')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '2a02::223:6cff:fe8a:2e8a')
|
||||
|
||||
def test_generic_ipaddress_as_ipv4_only(self):
|
||||
f = GenericIPAddressField(protocol="IPv6")
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, '')
|
||||
self.assertFormErrors([u'This field is required.'], f.clean, None)
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.1')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '256.125.1.5')
|
||||
self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
|
||||
self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '12345:2:3:4')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3::4')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
|
||||
self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2')
|
||||
|
||||
def test_generic_ipaddress_as_generic_not_required(self):
|
||||
f = GenericIPAddressField(required=False)
|
||||
self.assertEqual(f.clean(''), u'')
|
||||
self.assertEqual(f.clean(None), u'')
|
||||
self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5')
|
||||
self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
|
||||
self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
|
||||
self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2')
|
||||
|
||||
def test_generic_ipaddress_normalization(self):
|
||||
# Test the normalising code
|
||||
f = GenericIPAddressField()
|
||||
self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
|
||||
self.assertEqual(f.clean('::ffff:10.10.10.10'), u'::ffff:10.10.10.10')
|
||||
self.assertEqual(f.clean('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
|
||||
self.assertEqual(f.clean('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
|
||||
|
||||
f = GenericIPAddressField(unpack_ipv4=True)
|
||||
self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'10.10.10.10')
|
||||
|
||||
def test_smart_unicode(self):
|
||||
class Test:
|
||||
def __str__(self):
|
||||
|
|
|
@ -52,6 +52,9 @@ class BigIntegerData(models.Model):
|
|||
class IPAddressData(models.Model):
|
||||
data = models.IPAddressField(null=True)
|
||||
|
||||
class GenericIPAddressData(models.Model):
|
||||
data = models.GenericIPAddressField(null=True)
|
||||
|
||||
class NullBooleanData(models.Model):
|
||||
data = models.NullBooleanField(null=True)
|
||||
|
||||
|
@ -187,6 +190,9 @@ class IntegerPKData(models.Model):
|
|||
class IPAddressPKData(models.Model):
|
||||
data = models.IPAddressField(primary_key=True)
|
||||
|
||||
class GenericIPAddressPKData(models.Model):
|
||||
data = models.GenericIPAddressField(primary_key=True)
|
||||
|
||||
# This is just a Boolean field with null=True, and we can't test a PK value of NULL.
|
||||
# class NullBooleanPKData(models.Model):
|
||||
# data = models.NullBooleanField(primary_key=True)
|
||||
|
|
|
@ -196,6 +196,8 @@ test_data = [
|
|||
#(XX, ImageData
|
||||
(data_obj, 90, IPAddressData, "127.0.0.1"),
|
||||
(data_obj, 91, IPAddressData, None),
|
||||
(data_obj, 95, GenericIPAddressData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
|
||||
(data_obj, 96, GenericIPAddressData, None),
|
||||
(data_obj, 100, NullBooleanData, True),
|
||||
(data_obj, 101, NullBooleanData, False),
|
||||
(data_obj, 102, NullBooleanData, None),
|
||||
|
@ -298,6 +300,7 @@ The end."""),
|
|||
(pk_obj, 682, IntegerPKData, 0),
|
||||
# (XX, ImagePKData
|
||||
(pk_obj, 690, IPAddressPKData, "127.0.0.1"),
|
||||
(pk_obj, 695, GenericIPAddressPKData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
|
||||
# (pk_obj, 700, NullBooleanPKData, True),
|
||||
# (pk_obj, 701, NullBooleanPKData, False),
|
||||
(pk_obj, 710, PhonePKData, "212-634-5789"),
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
from django.utils import unittest
|
||||
from django.utils.ipv6 import is_valid_ipv6_address, clean_ipv6_address
|
||||
|
||||
class TestUtilsIPv6(unittest.TestCase):
|
||||
|
||||
def test_validates_correct_plain_address(self):
|
||||
self.assertTrue(is_valid_ipv6_address('fe80::223:6cff:fe8a:2e8a'))
|
||||
self.assertTrue(is_valid_ipv6_address('2a02::223:6cff:fe8a:2e8a'))
|
||||
self.assertTrue(is_valid_ipv6_address('1::2:3:4:5:6:7'))
|
||||
self.assertTrue(is_valid_ipv6_address('::'))
|
||||
self.assertTrue(is_valid_ipv6_address('::a'))
|
||||
self.assertTrue(is_valid_ipv6_address('2::'))
|
||||
|
||||
def test_validates_correct_with_v4mapping(self):
|
||||
self.assertTrue(is_valid_ipv6_address('::ffff:254.42.16.14'))
|
||||
self.assertTrue(is_valid_ipv6_address('::ffff:0a0a:0a0a'))
|
||||
|
||||
def test_validates_incorrect_plain_address(self):
|
||||
self.assertFalse(is_valid_ipv6_address('foo'))
|
||||
self.assertFalse(is_valid_ipv6_address('127.0.0.1'))
|
||||
self.assertFalse(is_valid_ipv6_address('12345::'))
|
||||
self.assertFalse(is_valid_ipv6_address('1::2:3::4'))
|
||||
self.assertFalse(is_valid_ipv6_address('1::zzz'))
|
||||
self.assertFalse(is_valid_ipv6_address('1::2:3:4:5:6:7:8'))
|
||||
self.assertFalse(is_valid_ipv6_address('1:2'))
|
||||
self.assertFalse(is_valid_ipv6_address('1:::2'))
|
||||
|
||||
def test_validates_incorrect_with_v4mapping(self):
|
||||
self.assertFalse(is_valid_ipv6_address('::ffff:999.42.16.14'))
|
||||
self.assertFalse(is_valid_ipv6_address('::ffff:zzzz:0a0a'))
|
||||
# The ::1.2.3.4 format used to be valid but was deprecated
|
||||
# in rfc4291 section 2.5.5.1
|
||||
self.assertTrue(is_valid_ipv6_address('::254.42.16.14'))
|
||||
self.assertTrue(is_valid_ipv6_address('::0a0a:0a0a'))
|
||||
self.assertFalse(is_valid_ipv6_address('::999.42.16.14'))
|
||||
self.assertFalse(is_valid_ipv6_address('::zzzz:0a0a'))
|
||||
|
||||
def test_cleanes_plain_address(self):
|
||||
self.assertEqual(clean_ipv6_address('DEAD::0:BEEF'), u'dead::beef')
|
||||
self.assertEqual(clean_ipv6_address('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
|
||||
self.assertEqual(clean_ipv6_address('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
|
||||
|
||||
def test_cleanes_with_v4_mapping(self):
|
||||
self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
|
||||
self.assertEqual(clean_ipv6_address('::ffff:1234:1234'), u'::ffff:18.52.18.52')
|
||||
self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52'), u'::ffff:18.52.18.52')
|
||||
|
||||
def test_unpacks_ipv4(self):
|
||||
self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a', unpack_ipv4=True), u'10.10.10.10')
|
||||
self.assertEqual(clean_ipv6_address('::ffff:1234:1234', unpack_ipv4=True), u'18.52.18.52')
|
||||
self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52', unpack_ipv4=True), u'18.52.18.52')
|
|
@ -19,3 +19,4 @@ from tzinfo import *
|
|||
from datetime_safe import *
|
||||
from baseconv import *
|
||||
from jslex import *
|
||||
from ipv6 import *
|
||||
|
|
Loading…
Reference in New Issue