Ticket #811: genericipaddressfield.2.diff

File genericipaddressfield.2.diff, 54.2 KB (added by Sasha Romijn, 13 years ago)

GenericIPAddressField patch

  • docs/topics/forms/modelforms.txt

     
    8383
    8484    ``IPAddressField``               ``IPAddressField``
    8585
     86    ``GenericIPAddressField``            ``GenericIPAddressField``
     87
    8688    ``ManyToManyField``              ``ModelMultipleChoiceField`` (see
    8789                                     below)
    8890
  • docs/ref/models/fields.txt

     
    760760An IP address, in string format (e.g. "192.0.2.30"). The admin represents this
    761761as an ``<input type="text">`` (a single-line input).
    762762
     763``GenericIPAddressField``
     764-------------------------
     765
     766.. class:: GenericIPAddressField([protocols=both, unpack_ipv4_mapped=False, **options])
     767
     768.. versionadded:: 1.4
     769
     770An IPv4 or IPv6 address, in string format (e.g. "192.0.2.30" or "2a02:42fe::4")
     771The admin represents this as an ``<input type="text">`` (a single-line input).
     772
     773The IPv6 address normalization follows `RFC4291 section 2.2`_, including using
     774the IPv4 format suggested in paragraph 3 of that section, like ``::ffff:192.0.2.0``.
     775For example, ``2001:0::0:01`` would be normalized to ``2001::1``, and ``::ffff:0a0a:0a0a``
     776to ``::ffff:10.10.10.10``. All characters are converted to lowercase.
     777
     778.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
     779
     780.. attribute:: GenericIPAddressField.protocol
     781
     782    Limits valid inputs to the specified protocol.
     783    Accepted values are ``both`` (default), ``IPv4``
     784    or ``IPv6``. Matching is case insensitive.
     785
     786.. attribute:: GenericIPAddressField.unpack_ipv4_mapped
     787
     788    Unpack IPv4 mapped addresses, like ``::ffff::192.0.2.1``.
     789    If this option is enabled, that address would be unpacked to
     790    ``192.0.2.1``. Default is disabled. Can only be used
     791    when ``protocol`` is set to ``both``.
     792
    763793``NullBooleanField``
    764794--------------------
    765795
  • docs/ref/forms/fields.txt

     
    622622      expression.
    623623    * Error message keys: ``required``, ``invalid``
    624624
     625``GenericIPAddressField``
     626~~~~~~~~~~~~~~~~~~~~~~~~~
     627
     628.. class:: GenericIPAddressField(**kwargs)
     629
     630.. versionadded:: 1.4
     631
     632A field containing either an IPv4 or an IPv6 address.
     633
     634    * Default widget: ``TextInput``
     635    * Empty value: ``''`` (an empty string)
     636    * Normalizes to: A Unicode object. IPv6 addresses are
     637      normalized as described below.
     638    * Validates that the given value is a valid IP address, using regular
     639      expressions.
     640    * Error message keys: ``required``, ``invalid``
     641
     642The IPv6 address normalization follows `RFC4291 section 2.2`_, including using
     643the IPv4 format suggested in paragraph 3 of that section, like ``::ffff:192.0.2.0``.
     644For example, ``2001:0::0:01`` would be normalized to ``2001::1``, and ``::ffff:0a0a:0a0a``
     645to ``::ffff:10.10.10.10``. All characters are converted to lowercase.
     646
     647.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
     648
     649Takes two optional arguments:
     650
     651.. attribute:: GenericIPAddressField.protocol
     652
     653    Limits valid inputs to the specified protocol.
     654    Accepted values are ``both`` (default), ``IPv4``
     655    or ``IPv6``. Matching is case insensitive.
     656
     657.. attribute:: GenericIPAddressField.unpack_ipv4_mapped
     658
     659    Unpack IPv4 mapped addresses, like ``::ffff::192.0.2.1``.
     660    If this option is enabled, that address would be unpacked to
     661    ``192.0.2.1``. Default is disabled. Can only be used
     662    when ``protocol`` is set to ``both``.
     663
    625664``MultipleChoiceField``
    626665~~~~~~~~~~~~~~~~~~~~~~~
    627666
  • docs/ref/validators.txt

     
    130130    A :class:`RegexValidator` instance that ensures a value looks like an IPv4
    131131    address.
    132132
     133``validate_ipv6_address``
     134-------------------------
     135.. versionadded:: 1.4
     136
     137.. data:: validate_ipv6_address
     138
     139    Uses :mod:`django.utils.ipv6` to check the validity of an IPv6 address.
     140
     141``validate_ipv46_address``
     142--------------------------
     143.. versionadded:: 1.4
     144
     145.. data:: validate_ipv46_address
     146
     147    Uses both ``validate_ipv4_address`` and ``validate_ipv6_address`` to ensure
     148    a value is either a valid IPv4 or IPv6 address.
     149
    133150``validate_comma_separated_integer_list``
    134151-----------------------------------------
    135152.. data:: validate_comma_separated_integer_list
  • django/db/models/fields/__init__.py

     
    1717from django.utils.translation import ugettext_lazy as _
    1818from django.utils.encoding import smart_unicode, force_unicode, smart_str
    1919from django.utils import datetime_safe
     20from django.utils.ipv6 import (clean_ipv6_address, is_valid_ipv6_address,
     21    validate_configure_ipaddressfield_settings)
    2022
    2123class NOT_PROVIDED:
    2224    pass
     
    920922
    921923class IPAddressField(Field):
    922924    empty_strings_allowed = False
    923     description = _("IP address")
     925    description = _("IPv4 address")
    924926    def __init__(self, *args, **kwargs):
    925927        kwargs['max_length'] = 15
    926928        Field.__init__(self, *args, **kwargs)
     
    933935        defaults.update(kwargs)
    934936        return super(IPAddressField, self).formfield(**defaults)
    935937
     938class GenericIPAddressField(Field):
     939    empty_strings_allowed = True
     940    description = _("IP address")
     941    default_error_messages = {}
     942   
     943    def __init__(self, protocol='both', unpack_ipv4_mapped=False, *args, **kwargs):
     944        self.unpack_ipv4_mapped = unpack_ipv4_mapped
     945
     946        self.default_validators = validate_configure_ipaddressfield_settings(
     947                protocol, unpack_ipv4_mapped, self.default_error_messages)
     948
     949        kwargs['max_length'] = 39
     950        Field.__init__(self, *args, **kwargs)
     951
     952    def get_internal_type(self):
     953        return "GenericIPAddressField"
     954
     955    def to_python(self, value):
     956        if value and ':' in value:
     957            return clean_ipv6_address(value, self.unpack_ipv4_mapped, self.default_error_messages['invalid'])
     958               
     959        return value
     960
     961    def get_prep_value(self, value):       
     962        if value and ':' in value:
     963            try:
     964                return clean_ipv6_address(value, self.unpack_ipv4_mapped)
     965            except ValidationError:
     966                pass
     967               
     968        return value
     969
     970    def formfield(self, **kwargs):
     971        defaults = {'form_class': forms.GenericIPAddressField}
     972        defaults.update(kwargs)
     973        return super(GenericIPAddressField, self).formfield(**defaults)
     974       
     975
    936976class NullBooleanField(Field):
    937977    empty_strings_allowed = False
    938978    default_error_messages = {
  • django/db/backends/sqlite3/creation.py

     
    2020        'IntegerField':                 'integer',
    2121        'BigIntegerField':              'bigint',
    2222        'IPAddressField':               'char(15)',
     23        'GenericIPAddressField':        'char(39)',
    2324        'NullBooleanField':             'bool',
    2425        'OneToOneField':                'integer',
    2526        'PositiveIntegerField':         'integer unsigned',
  • django/db/backends/mysql/creation.py

     
    1919        'IntegerField':      'integer',
    2020        'BigIntegerField':   'bigint',
    2121        'IPAddressField':    'char(15)',
     22        'GenericIPAddressField': 'char(39)',
    2223        'NullBooleanField':  'bool',
    2324        'OneToOneField':     'integer',
    2425        'PositiveIntegerField': 'integer UNSIGNED',
  • django/db/backends/oracle/creation.py

     
    2727        'IntegerField':                 'NUMBER(11)',
    2828        'BigIntegerField':              'NUMBER(19)',
    2929        'IPAddressField':               'VARCHAR2(15)',
     30        'GenericIPAddressField':        'VARCHAR2(39)',
    3031        'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
    3132        'OneToOneField':                'NUMBER(11)',
    3233        'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
  • django/db/backends/postgresql_psycopg2/introspection.py

     
    1212        700: 'FloatField',
    1313        701: 'FloatField',
    1414        869: 'IPAddressField',
     15        869: 'GenericIPAddressField',
    1516        1043: 'CharField',
    1617        1082: 'DateField',
    1718        1083: 'TimeField',
  • django/db/backends/postgresql_psycopg2/creation.py

     
    2121        'IntegerField':      'integer',
    2222        'BigIntegerField':   'bigint',
    2323        'IPAddressField':    'inet',
     24        'GenericIPAddressField': 'inet',
    2425        'NullBooleanField':  'boolean',
    2526        'OneToOneField':     'integer',
    2627        'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
  • django/forms/fields.py

     
    99import time
    1010import urlparse
    1111import warnings
     12import socket
    1213from decimal import Decimal, DecimalException
    1314try:
    1415    from cStringIO import StringIO
     
    2122from django.utils.translation import ugettext_lazy as _
    2223from django.utils.encoding import smart_unicode, smart_str, force_unicode
    2324from django.utils.functional import lazy
     25from django.utils.ipv6 import clean_ipv6_address, validate_configure_ipaddressfield_settings
    2426
    2527# Provide this import for backwards compatibility.
    2628from django.core.validators import EMPTY_VALUES
     
    3739    'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
    3840    'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
    3941    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    40     'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
    41     'TypedChoiceField', 'TypedMultipleChoiceField'
     42    'SplitDateTimeField', 'IPAddressField', 'GenericIPAddressField', 'FilePathField',
     43    'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField'
    4244)
    4345
    4446
     
    955957    default_validators = [validators.validate_ipv4_address]
    956958
    957959
     960class GenericIPAddressField(CharField):
     961    default_error_messages = {}
     962   
     963    def __init__(self, protocol='both', unpack_ipv4_mapped=False, *args, **kwargs):
     964        self.unpack_ipv4_mapped = unpack_ipv4_mapped
     965
     966        self.default_validators = validate_configure_ipaddressfield_settings(
     967                protocol, unpack_ipv4_mapped, self.default_error_messages)
     968       
     969        super(GenericIPAddressField, self).__init__(*args, **kwargs)
     970       
     971    def to_python(self, value):
     972        if not value:
     973            return ''
     974       
     975        if value and ':' in value:
     976                return clean_ipv6_address(value, self.unpack_ipv4_mapped, self.default_error_messages['invalid'])
     977
     978        return value
     979               
     980
    958981class SlugField(CharField):
    959982    default_error_messages = {
    960983        'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers,"
  • django/core/validators.py

     
    55from django.core.exceptions import ValidationError
    66from django.utils.translation import ugettext_lazy as _
    77from django.utils.encoding import smart_unicode
     8from django.utils.ipv6 import is_valid_ipv6_address
    89
    910# These values, if given to validate(), will trigger the self.required check.
    1011EMPTY_VALUES = (None, '', [], (), {})
     
    145146ipv4_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}$')
    146147validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
    147148
     149def validate_ipv6_address(value):
     150    if not is_valid_ipv6_address(value):
     151        raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid')
     152
     153def validate_ipv46_address(value):
     154    try:
     155        validate_ipv4_address(value)
     156    except ValidationError:
     157        try:
     158            validate_ipv6_address(value)
     159        except ValidationError:
     160            raise ValidationError(_(u'Enter a valid IPv4 or IPv6 address.'), code='invalid')
     161           
    148162comma_separated_int_list_re = re.compile('^[\d,]+$')
    149163validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
    150164
  • django/utils/ipv6.py

     
     1# This code was mostly based on ipaddr-py
     2# Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/
     3# Licensed under the Apache License, Version 2.0 (the "License").
     4
     5from django.core.exceptions import ValidationError
     6from django.utils.translation import ugettext_lazy as _
     7
     8def clean_ipv6_address(ip_str, unpack_ipv4_mapped=False, error_message="This is not a valid IPv6 address"):
     9    """Cleans a IPv6 address string.
     10   
     11    Validity is checked by calling is_valid_ipv6_address() - if an
     12    invalid address is passed, ValidationError is raised.
     13   
     14    Replaces the longest continious zero-sequence with "::" and
     15    removes leading zeroes and makes sure all hextets are lowercase.
     16
     17    Args:
     18        ip_str: A valid IPv6 address.
     19        unpack_ipv4_mapped: if an IPv4-mapped address is found,
     20        return the plain IPv4 address (default=False).
     21        error_message: A error message for in the ValidationError.
     22
     23    Returns:
     24        A compressed IPv6 address, or the same value
     25
     26    """
     27    best_doublecolon_start = -1
     28    best_doublecolon_len = 0
     29    doublecolon_start = -1
     30    doublecolon_len = 0
     31   
     32    if not is_valid_ipv6_address(ip_str):
     33        raise ValidationError(error_message)
     34       
     35    # This algorithm can only handle fully exploded
     36    # IP strings
     37    ip_str = _explode_shorthand_ip_string(ip_str)
     38   
     39    ip_str = _sanitize_ipv4_mapping(ip_str)
     40
     41    # If needed, unpack the IPv4 and return straight away
     42    # - no need in running the rest of the algorithm
     43    if unpack_ipv4_mapped:
     44        ipv4_unpacked = _unpack_ipv4(ip_str)
     45       
     46        if ipv4_unpacked:
     47            return ipv4_unpacked
     48   
     49    hextets = ip_str.split(":")
     50   
     51    for index in range(len(hextets)):
     52        # Remove leading zeroes
     53        hextets[index] = hextets[index].lstrip('0')
     54        if not hextets[index]:
     55            hextets[index] = '0'
     56
     57        # Determine best hextet to compress
     58        if hextets[index] == '0':
     59            doublecolon_len += 1
     60            if doublecolon_start == -1:
     61                # Start of a sequence of zeros.
     62                doublecolon_start = index
     63            if doublecolon_len > best_doublecolon_len:
     64                # This is the longest sequence of zeros so far.
     65                best_doublecolon_len = doublecolon_len
     66                best_doublecolon_start = doublecolon_start
     67        else:
     68            doublecolon_len = 0
     69            doublecolon_start = -1
     70
     71    # Compress the most suitable hextet
     72    if best_doublecolon_len > 1:
     73        best_doublecolon_end = (best_doublecolon_start +
     74                                best_doublecolon_len)
     75        # For zeros at the end of the address.
     76        if best_doublecolon_end == len(hextets):
     77            hextets += ['']
     78        hextets[best_doublecolon_start:best_doublecolon_end] = ['']
     79        # For zeros at the beginning of the address.
     80        if best_doublecolon_start == 0:
     81            hextets = [''] + hextets
     82   
     83    result = ":".join(hextets)
     84
     85    return result.lower()
     86
     87
     88def _sanitize_ipv4_mapping(ip_str):
     89    """Sanitize IPv4 mapping in a expanded IPv6 address.
     90   
     91    This converts ::ffff:0a0a:0a0a to ::ffff:10.10.10.10.
     92    If there is nothing to sanitize, returns an unchanged
     93    string.
     94   
     95    Args:
     96        ip_str: A string, the expanded IPv6 address.
     97
     98    Returns:
     99        The sanitized output string, if applicable.
     100    """
     101    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
     102        # not an ipv4 mapping
     103        return ip_str
     104   
     105    hextets = ip_str.split(':')
     106   
     107    if '.' in hextets[-1]:
     108        # already sanitized
     109        return ip_str
     110
     111    ipv4_address = "%d.%d.%d.%d" % (
     112        int(hextets[6][0:2], 16),
     113        int(hextets[6][2:4], 16),
     114        int(hextets[7][0:2], 16),
     115        int(hextets[7][2:4], 16),
     116    )
     117   
     118    result = ':'.join(hextets[0:6])
     119    result += ':' + ipv4_address
     120   
     121    return result
     122
     123def _unpack_ipv4(ip_str):
     124    """Unpack an IPv4 address that was mapped in a compressed IPv6 address.
     125   
     126    This converts 0000:0000:0000:0000:0000:ffff:10.10.10.10 to 10.10.10.10.
     127    If there is nothing to sanitize, returns None.
     128   
     129    Args:
     130        ip_str: A string, the expanded IPv6 address.
     131
     132    Returns:
     133        The unpacked IPv4 address, or None if there was nothing to unpack.
     134    """
     135    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
     136        return None
     137   
     138    hextets = ip_str.split(':')
     139    return hextets[-1]
     140   
     141def is_valid_ipv6_address(ip_str):
     142    """Ensure we have a valid IPv6 address.
     143
     144    Args:
     145        ip_str: A string, the IPv6 address.
     146
     147    Returns:
     148        A boolean, True if this is a valid IPv6 address.
     149
     150    """
     151    from django.core.validators import validate_ipv4_address
     152   
     153    # We need to have at least one ':'.
     154    if ':' not in ip_str:
     155        return False
     156
     157    # We can only have one '::' shortener.
     158    if ip_str.count('::') > 1:
     159        return False
     160       
     161    # '::' should be encompassed by start, digits or end.
     162    if ':::' in ip_str:
     163        return False
     164
     165    # A single colon can neither start nor end an address.
     166    if ((ip_str.startswith(':') and not ip_str.startswith('::')) or
     167            (ip_str.endswith(':') and not ip_str.endswith('::'))):
     168        return False
     169
     170    # We can never have more than 7 ':' (1::2:3:4:5:6:7:8 is invalid)
     171    if ip_str.count(':') > 7:
     172        return False
     173
     174    # If we have no concatenation, we need to have 8 fields with 7 ':'.
     175    if '::' not in ip_str and ip_str.count(':') != 7:
     176        # We might have an IPv4 mapped address.
     177        if ip_str.count('.') != 3:
     178            return False
     179
     180    ip_str = _explode_shorthand_ip_string(ip_str)
     181
     182    # Now that we have that all squared away, let's check that each of the
     183    # hextets are between 0x0 and 0xFFFF.
     184    for hextet in ip_str.split(':'):
     185        if hextet.count('.') == 3:
     186            # If we have an IPv4 mapped address, the IPv4 portion has to
     187            # be at the end of the IPv6 portion.
     188            if not ip_str.split(':')[-1] == hextet:
     189                return False
     190            try:
     191                validate_ipv4_address(hextet)
     192            except ValidationError:
     193                return False
     194        else:
     195            try:
     196                # a value error here means that we got a bad hextet,
     197                # something like 0xzzzz
     198                if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF:
     199                    return False
     200            except ValueError:
     201                return False
     202    return True
     203   
     204   
     205def _explode_shorthand_ip_string(ip_str):
     206    """Expand a shortened IPv6 address.
     207
     208    Args:
     209        ip_str: A string, the IPv6 address.
     210
     211    Returns:
     212        A string, the expanded IPv6 address.
     213
     214    """
     215    if not _is_shorthand_ip(ip_str):
     216        # We've already got a longhand ip_str.
     217        return ip_str
     218       
     219    new_ip = []
     220    hextet = ip_str.split('::')
     221
     222    # If there is a ::, we need to expand it with zeroes
     223    # to get to 8 hextets - unless there is a dot in the last hextet,
     224    # meaning we're doing v4-mapping
     225    if '.' in ip_str.split(':')[-1]:
     226        fill_to = 7
     227    else:
     228        fill_to = 8
     229   
     230    if len(hextet) > 1:
     231        sep = len(hextet[0].split(':')) + len(hextet[1].split(':'))
     232        new_ip = hextet[0].split(':')
     233
     234        for _ in xrange(fill_to - sep):
     235            new_ip.append('0000')
     236        new_ip += hextet[1].split(':')
     237
     238    else:
     239        new_ip = ip_str.split(':')
     240       
     241    # Now need to make sure every hextet is 4 lower case characters.
     242    # If a hextet is < 4 characters, we've got missing leading 0's.
     243    ret_ip = []
     244    for hextet in new_ip:
     245        ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower())
     246    return ':'.join(ret_ip)
     247
     248
     249def _is_shorthand_ip(ip_str):
     250    """Determine if the address is shortened.
     251
     252    Args:
     253        ip_str: A string, the IPv6 address.
     254
     255    Returns:
     256        A boolean, True if the address is shortened.
     257
     258    """
     259    if ip_str.count('::') == 1:
     260        return True
     261    if filter(lambda x: len(x) < 4, ip_str.split(':')):
     262        return True
     263    return False
     264
     265
     266def validate_configure_ipaddressfield_settings(protocol, unpack_ipv4_mapped, default_error_messages):
     267    """Set up an GenericIPAddressField instance and validate it's inputs.
     268   
     269    This code is here, because it is exactly the same for the model and the form field.
     270    """
     271    from django.core import validators
     272   
     273    if protocol != 'both' and unpack_ipv4_mapped:
     274        raise ValueError(
     275            "You can only use `unpack_ipv4_mapped` if `protocol` is set to 'both'")
     276   
     277    if protocol.lower() == "both":
     278        default_validators = [validators.validate_ipv46_address]
     279        default_error_messages['invalid'] = _(u'Enter a valid IPv4 or IPv6 address.')
     280
     281    elif protocol.lower() == "ipv4":
     282        default_validators = [validators.validate_ipv4_address]
     283        default_error_messages['invalid'] = _(u'Enter a valid IPv4 address.')
     284
     285    elif protocol.lower() == "ipv6":
     286        default_validators = [validators.validate_ipv6_address]
     287        default_error_messages['invalid'] = _(u'Enter a valid IPv6 address.')
     288       
     289    else:
     290        raise ValueError(
     291            "The protocol '%s' is not a known protocol for GenericIPAddressField"
     292            % protocol)
     293           
     294    return default_validators
     295           
     296# This code was mostly based on ipaddr-py
     297# Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/
     298# Licensed under the Apache License, Version 2.0 (the "License").
     299
     300from django.core.exceptions import ValidationError
     301from django.utils.translation import ugettext_lazy as _
     302
     303def clean_ipv6_address(ip_str, unpack_ipv4_mapped=False, error_message="This is not a valid IPv6 address"):
     304    """Cleans a IPv6 address string.
     305   
     306    Validity is checked by calling is_valid_ipv6_address() - if an
     307    invalid address is passed, ValidationError is raised.
     308   
     309    Replaces the longest continious zero-sequence with "::" and
     310    removes leading zeroes and makes sure all hextets are lowercase.
     311
     312    Args:
     313        ip_str: A valid IPv6 address.
     314        unpack_ipv4_mapped: if an IPv4-mapped address is found,
     315        return the plain IPv4 address (default=False).
     316        error_message: A error message for in the ValidationError.
     317
     318    Returns:
     319        A compressed IPv6 address, or the same value
     320
     321    """
     322    best_doublecolon_start = -1
     323    best_doublecolon_len = 0
     324    doublecolon_start = -1
     325    doublecolon_len = 0
     326   
     327    if not is_valid_ipv6_address(ip_str):
     328        raise ValidationError(error_message)
     329       
     330    # This algorithm can only handle fully exploded
     331    # IP strings
     332    ip_str = _explode_shorthand_ip_string(ip_str)
     333   
     334    ip_str = _sanitize_ipv4_mapping(ip_str)
     335
     336    # If needed, unpack the IPv4 and return straight away
     337    # - no need in running the rest of the algorithm
     338    if unpack_ipv4_mapped:
     339        ipv4_unpacked = _unpack_ipv4(ip_str)
     340       
     341        if ipv4_unpacked:
     342            return ipv4_unpacked
     343   
     344    hextets = ip_str.split(":")
     345   
     346    for index in range(len(hextets)):
     347        # Remove leading zeroes
     348        hextets[index] = hextets[index].lstrip('0')
     349        if not hextets[index]:
     350            hextets[index] = '0'
     351
     352        # Determine best hextet to compress
     353        if hextets[index] == '0':
     354            doublecolon_len += 1
     355            if doublecolon_start == -1:
     356                # Start of a sequence of zeros.
     357                doublecolon_start = index
     358            if doublecolon_len > best_doublecolon_len:
     359                # This is the longest sequence of zeros so far.
     360                best_doublecolon_len = doublecolon_len
     361                best_doublecolon_start = doublecolon_start
     362        else:
     363            doublecolon_len = 0
     364            doublecolon_start = -1
     365
     366    # Compress the most suitable hextet
     367    if best_doublecolon_len > 1:
     368        best_doublecolon_end = (best_doublecolon_start +
     369                                best_doublecolon_len)
     370        # For zeros at the end of the address.
     371        if best_doublecolon_end == len(hextets):
     372            hextets += ['']
     373        hextets[best_doublecolon_start:best_doublecolon_end] = ['']
     374        # For zeros at the beginning of the address.
     375        if best_doublecolon_start == 0:
     376            hextets = [''] + hextets
     377   
     378    result = ":".join(hextets)
     379
     380    return result.lower()
     381
     382
     383def _sanitize_ipv4_mapping(ip_str):
     384    """Sanitize IPv4 mapping in a expanded IPv6 address.
     385   
     386    This converts ::ffff:0a0a:0a0a to ::ffff:10.10.10.10.
     387    If there is nothing to sanitize, returns an unchanged
     388    string.
     389   
     390    Args:
     391        ip_str: A string, the expanded IPv6 address.
     392
     393    Returns:
     394        The sanitized output string, if applicable.
     395    """
     396    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
     397        # not an ipv4 mapping
     398        return ip_str
     399   
     400    hextets = ip_str.split(':')
     401   
     402    if '.' in hextets[-1]:
     403        # already sanitized
     404        return ip_str
     405
     406    ipv4_address = "%d.%d.%d.%d" % (
     407        int(hextets[6][0:2], 16),
     408        int(hextets[6][2:4], 16),
     409        int(hextets[7][0:2], 16),
     410        int(hextets[7][2:4], 16),
     411    )
     412   
     413    result = ':'.join(hextets[0:6])
     414    result += ':' + ipv4_address
     415   
     416    return result
     417
     418def _unpack_ipv4(ip_str):
     419    """Unpack an IPv4 address that was mapped in a compressed IPv6 address.
     420   
     421    This converts 0000:0000:0000:0000:0000:ffff:10.10.10.10 to 10.10.10.10.
     422    If there is nothing to sanitize, returns None.
     423   
     424    Args:
     425        ip_str: A string, the expanded IPv6 address.
     426
     427    Returns:
     428        The unpacked IPv4 address, or None if there was nothing to unpack.
     429    """
     430    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
     431        return None
     432   
     433    hextets = ip_str.split(':')
     434    return hextets[-1]
     435   
     436def is_valid_ipv6_address(ip_str):
     437    """Ensure we have a valid IPv6 address.
     438
     439    Args:
     440        ip_str: A string, the IPv6 address.
     441
     442    Returns:
     443        A boolean, True if this is a valid IPv6 address.
     444
     445    """
     446    from django.core.validators import validate_ipv4_address
     447   
     448    # We need to have at least one ':'.
     449    if ':' not in ip_str:
     450        return False
     451
     452    # We can only have one '::' shortener.
     453    if ip_str.count('::') > 1:
     454        return False
     455       
     456    # '::' should be encompassed by start, digits or end.
     457    if ':::' in ip_str:
     458        return False
     459
     460    # A single colon can neither start nor end an address.
     461    if ((ip_str.startswith(':') and not ip_str.startswith('::')) or
     462            (ip_str.endswith(':') and not ip_str.endswith('::'))):
     463        return False
     464
     465    # We can never have more than 7 ':' (1::2:3:4:5:6:7:8 is invalid)
     466    if ip_str.count(':') > 7:
     467        return False
     468
     469    # If we have no concatenation, we need to have 8 fields with 7 ':'.
     470    if '::' not in ip_str and ip_str.count(':') != 7:
     471        # We might have an IPv4 mapped address.
     472        if ip_str.count('.') != 3:
     473            return False
     474
     475    ip_str = _explode_shorthand_ip_string(ip_str)
     476
     477    # Now that we have that all squared away, let's check that each of the
     478    # hextets are between 0x0 and 0xFFFF.
     479    for hextet in ip_str.split(':'):
     480        if hextet.count('.') == 3:
     481            # If we have an IPv4 mapped address, the IPv4 portion has to
     482            # be at the end of the IPv6 portion.
     483            if not ip_str.split(':')[-1] == hextet:
     484                return False
     485            try:
     486                validate_ipv4_address(hextet)
     487            except ValidationError:
     488                return False
     489        else:
     490            try:
     491                # a value error here means that we got a bad hextet,
     492                # something like 0xzzzz
     493                if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF:
     494                    return False
     495            except ValueError:
     496                return False
     497    return True
     498   
     499   
     500def _explode_shorthand_ip_string(ip_str):
     501    """Expand a shortened IPv6 address.
     502
     503    Args:
     504        ip_str: A string, the IPv6 address.
     505
     506    Returns:
     507        A string, the expanded IPv6 address.
     508
     509    """
     510    if not _is_shorthand_ip(ip_str):
     511        # We've already got a longhand ip_str.
     512        return ip_str
     513       
     514    new_ip = []
     515    hextet = ip_str.split('::')
     516
     517    # If there is a ::, we need to expand it with zeroes
     518    # to get to 8 hextets - unless there is a dot in the last hextet,
     519    # meaning we're doing v4-mapping
     520    if '.' in ip_str.split(':')[-1]:
     521        fill_to = 7
     522    else:
     523        fill_to = 8
     524   
     525    if len(hextet) > 1:
     526        sep = len(hextet[0].split(':')) + len(hextet[1].split(':'))
     527        new_ip = hextet[0].split(':')
     528
     529        for _ in xrange(fill_to - sep):
     530            new_ip.append('0000')
     531        new_ip += hextet[1].split(':')
     532
     533    else:
     534        new_ip = ip_str.split(':')
     535       
     536    # Now need to make sure every hextet is 4 lower case characters.
     537    # If a hextet is < 4 characters, we've got missing leading 0's.
     538    ret_ip = []
     539    for hextet in new_ip:
     540        ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower())
     541    return ':'.join(ret_ip)
     542
     543
     544def _is_shorthand_ip(ip_str):
     545    """Determine if the address is shortened.
     546
     547    Args:
     548        ip_str: A string, the IPv6 address.
     549
     550    Returns:
     551        A boolean, True if the address is shortened.
     552
     553    """
     554    if ip_str.count('::') == 1:
     555        return True
     556    if filter(lambda x: len(x) < 4, ip_str.split(':')):
     557        return True
     558    return False
     559
     560
     561def validate_configure_ipaddressfield_settings(protocol, unpack_ipv4_mapped, default_error_messages):
     562    """Set up an GenericIPAddressField instance and validate it's inputs.
     563   
     564    This code is here, because it is exactly the same for the model and the form field.
     565    """
     566    from django.core import validators
     567   
     568    if protocol != 'both' and unpack_ipv4_mapped:
     569        raise ValueError(
     570            "You can only use `unpack_ipv4_mapped` if `protocol` is set to 'both'")
     571   
     572    if protocol.lower() == "both":
     573        default_validators = [validators.validate_ipv46_address]
     574        default_error_messages['invalid'] = _(u'Enter a valid IPv4 or IPv6 address.')
     575
     576    elif protocol.lower() == "ipv4":
     577        default_validators = [validators.validate_ipv4_address]
     578        default_error_messages['invalid'] = _(u'Enter a valid IPv4 address.')
     579
     580    elif protocol.lower() == "ipv6":
     581        default_validators = [validators.validate_ipv6_address]
     582        default_error_messages['invalid'] = _(u'Enter a valid IPv6 address.')
     583       
     584    else:
     585        raise ValueError(
     586            "The protocol '%s' is not a known protocol for GenericIPAddressField"
     587            % protocol)
     588           
     589    return default_validators
     590           
  • tests/modeltests/validators/tests.py

     
    5252    (validate_ipv4_address, '25,1,1,1', ValidationError),
    5353    (validate_ipv4_address, '25.1 .1.1', ValidationError),
    5454
     55    # validate_ipv6_address uses django.utils.ipv6, which
     56    # is tested in much greater detail in it's own testcase
     57    (validate_ipv6_address, 'fe80::1', None),
     58    (validate_ipv6_address, '::1', None),
     59    (validate_ipv6_address, '1:2:3:4:5:6:7:8', None),
     60
     61    (validate_ipv6_address, '1:2', ValidationError),
     62    (validate_ipv6_address, '::zzz', ValidationError),
     63    (validate_ipv6_address, '12345::', ValidationError),
     64
     65    (validate_ipv46_address, '1.1.1.1', None),
     66    (validate_ipv46_address, '255.0.0.0', None),
     67    (validate_ipv46_address, '0.0.0.0', None),
     68    (validate_ipv46_address, 'fe80::1', None),
     69    (validate_ipv46_address, '::1', None),
     70    (validate_ipv46_address, '1:2:3:4:5:6:7:8', None),
     71
     72    (validate_ipv46_address, '256.1.1.1', ValidationError),
     73    (validate_ipv46_address, '25.1.1.', ValidationError),
     74    (validate_ipv46_address, '25,1,1,1', ValidationError),
     75    (validate_ipv46_address, '25.1 .1.1', ValidationError),
     76    (validate_ipv46_address, '1:2', ValidationError),
     77    (validate_ipv46_address, '::zzz', ValidationError),
     78    (validate_ipv46_address, '12345::', ValidationError),
     79
    5580    (validate_comma_separated_integer_list, '1', None),
    5681    (validate_comma_separated_integer_list, '1,2,3', None),
    5782    (validate_comma_separated_integer_list, '1,2,3,', None),
  • tests/modeltests/validation/tests.py

     
    22from django.test import TestCase
    33from django.core.exceptions import NON_FIELD_ERRORS
    44from modeltests.validation import ValidationTestCase
    5 from modeltests.validation.models import Author, Article, ModelToValidate
     5from modeltests.validation.models import (Author, Article, ModelToValidate,
     6    GenericIPAddressTestModel, GenericIPAddressWithUnpackUniqueTestModel)
    67
    78# Import other tests for this package.
    89from modeltests.validation.validators import TestModelsWithValidators
     
    7778        mtv = ModelToValidate(number=10, name='Some Name'*100)
    7879        self.assertFailsValidation(mtv.full_clean, ['name',])
    7980
     81
    8082class ArticleForm(forms.ModelForm):
    8183    class Meta:
    8284        model = Article
     
    124126        article = Article(author_id=self.author.id)
    125127        form = ArticleForm(data, instance=article)
    126128        self.assertEqual(form.errors.keys(), ['pub_date'])
     129
     130
     131class GenericIPAddressFieldTests(ValidationTestCase):
     132   
     133    def test_correct_generic_ip_passes(self):
     134        giptm = GenericIPAddressTestModel(generic_ip="1.2.3.4")
     135        self.assertEqual(None, giptm.full_clean())
     136        giptm = GenericIPAddressTestModel(generic_ip="2001::2")
     137        self.assertEqual(None, giptm.full_clean())
     138   
     139    def test_invalid_generic_ip_raises_error(self):
     140        giptm = GenericIPAddressTestModel(generic_ip="294.4.2.1")
     141        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
     142        giptm = GenericIPAddressTestModel(generic_ip="1:2")
     143        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
     144   
     145    def test_correct_v4_ip_passes(self):
     146        giptm = GenericIPAddressTestModel(v4_ip="1.2.3.4")
     147        self.assertEqual(None, giptm.full_clean())
     148   
     149    def test_invalid_v4_ip_raises_error(self):
     150        giptm = GenericIPAddressTestModel(v4_ip="294.4.2.1")
     151        self.assertFailsValidation(giptm.full_clean, ['v4_ip',])
     152        giptm = GenericIPAddressTestModel(v4_ip="2001::2")
     153        self.assertFailsValidation(giptm.full_clean, ['v4_ip',])
     154   
     155    def test_correct_v6_ip_passes(self):
     156        giptm = GenericIPAddressTestModel(v6_ip="2001::2")
     157        self.assertEqual(None, giptm.full_clean())
     158   
     159    def test_invalid_v6_ip_raises_error(self):
     160        giptm = GenericIPAddressTestModel(v6_ip="1.2.3.4")
     161        self.assertFailsValidation(giptm.full_clean, ['v6_ip',])
     162        giptm = GenericIPAddressTestModel(v6_ip="1:2")
     163        self.assertFailsValidation(giptm.full_clean, ['v6_ip',])
     164   
     165    def test_v6_uniqueness_detection(self):
     166        # These two addresses are the same with different syntax
     167        giptm = GenericIPAddressTestModel(generic_ip="2001::1:0:0:0:0:2")
     168        giptm.save()
     169        giptm = GenericIPAddressTestModel(generic_ip="2001:0:1:2")
     170        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
     171   
     172    def test_v4_unpack_uniqueness_detection(self):
     173        # These two are different, because we are not doing IPv4 unpacking
     174        giptm = GenericIPAddressTestModel(generic_ip="::ffff:10.10.10.10")
     175        giptm.save()
     176        giptm = GenericIPAddressTestModel(generic_ip="10.10.10.10")
     177        self.assertEqual(None, giptm.full_clean())
     178   
     179        # These two are the same, because we are doing IPv4 unpacking
     180        giptm = GenericIPAddressWithUnpackUniqueTestModel(generic_v4unpack_ip="::ffff:18.52.18.52")
     181        giptm.save()
     182        giptm = GenericIPAddressWithUnpackUniqueTestModel(generic_v4unpack_ip="18.52.18.52")
     183        self.assertFailsValidation(giptm.full_clean, ['generic_v4unpack_ip',])
     184       
     185 No newline at end of file
  • tests/modeltests/validation/models.py

     
    8181
    8282class UniqueErrorsModel(models.Model):
    8383    name = models.CharField(max_length=100, unique=True, error_messages={'unique': u'Custom unique name message.'})
    84     number = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'})
    85  No newline at end of file
     84    number = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'})
     85   
     86class GenericIPAddressTestModel(models.Model):
     87    generic_ip = models.GenericIPAddressField(blank=True, unique=True)
     88    v4_ip = models.GenericIPAddressField(blank=True, protocol="ipv4")
     89    v6_ip = models.GenericIPAddressField(blank=True, protocol="ipv6")
     90
     91class GenericIPAddressWithUnpackUniqueTestModel(models.Model):
     92    generic_v4unpack_ip = models.GenericIPAddressField(blank=True, unique=True, unpack_ipv4_mapped=True)
     93   
     94 No newline at end of file
  • tests/regressiontests/forms/tests/error_messages.py

     
    196196        self.assertFormErrors([u'REQUIRED'], f.clean, '')
    197197        self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
    198198
     199    def test_generic_ipaddressfield(self):
     200        e = {
     201            'required': 'REQUIRED',
     202            'invalid': 'INVALID IP ADDRESS',
     203        }
     204        f = GenericIPAddressField(error_messages=e)
     205        self.assertFormErrors([u'REQUIRED'], f.clean, '')
     206        self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
     207
    199208    def test_subclassing_errorlist(self):
    200209        class TestForm(Form):
    201210            first_name = CharField()
  • tests/regressiontests/forms/tests/extra.py

     
    460460        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
    461461        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
    462462
     463
     464    def test_generic_ipaddress_invalid_arguments(self):
     465        self.assertRaises(ValueError, GenericIPAddressField, protocol="hamster")
     466        self.assertRaises(ValueError, GenericIPAddressField, protocol="ipv4", unpack_ipv4_mapped=True)
     467
     468    def test_generic_ipaddress_as_generic(self):
     469        # The edges of the IPv6 validation code are not deeply tested here,
     470        # they are covered in the tests for django.utils.ipv6
     471       
     472        f = GenericIPAddressField()
     473        self.assertFormErrors([u'This field is required.'], f.clean, '')
     474        self.assertFormErrors([u'This field is required.'], f.clean, None)
     475        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
     476        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo')
     477        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.')
     478        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5')
     479        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5')
     480        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
     481        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
     482        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4')
     483        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4')
     484        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
     485        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
     486        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2')
     487
     488    def test_generic_ipaddress_as_ipv4_only(self):
     489        f = GenericIPAddressField(protocol="IPv4")
     490        self.assertFormErrors([u'This field is required.'], f.clean, '')
     491        self.assertFormErrors([u'This field is required.'], f.clean, None)
     492        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
     493        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
     494        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
     495        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
     496        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
     497        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'fe80::223:6cff:fe8a:2e8a')
     498        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '2a02::223:6cff:fe8a:2e8a')
     499
     500    def test_generic_ipaddress_as_ipv4_only(self):               
     501        f = GenericIPAddressField(protocol="IPv6")
     502        self.assertFormErrors([u'This field is required.'], f.clean, '')
     503        self.assertFormErrors([u'This field is required.'], f.clean, None)
     504        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.1')
     505        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo')
     506        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.')
     507        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5')
     508        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '256.125.1.5')
     509        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
     510        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
     511        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '12345:2:3:4')
     512        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3::4')
     513        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
     514        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
     515        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2')
     516
     517    def test_generic_ipaddress_as_generic_not_required(self):
     518        f = GenericIPAddressField(required=False)
     519        self.assertEqual(f.clean(''), u'')
     520        self.assertEqual(f.clean(None), u'')
     521        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
     522        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo')
     523        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.')
     524        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5')
     525        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5')
     526        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
     527        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
     528        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4')
     529        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4')
     530        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
     531        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
     532        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2')
     533
     534    def test_generic_ipaddress_normalisation(self):
     535        # Test the normalising code
     536        f = GenericIPAddressField()
     537        self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
     538        self.assertEqual(f.clean('::ffff:10.10.10.10'), u'::ffff:10.10.10.10')
     539        self.assertEqual(f.clean('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
     540        self.assertEqual(f.clean('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
     541
     542        f = GenericIPAddressField(unpack_ipv4_mapped=True)
     543        self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'10.10.10.10')
     544
     545
    463546    def test_smart_unicode(self):
    464547        class Test:
    465548            def __str__(self):
  • tests/regressiontests/serializers_regress/tests.py

     
    196196    #(XX, ImageData
    197197    (data_obj, 90, IPAddressData, "127.0.0.1"),
    198198    (data_obj, 91, IPAddressData, None),
     199    (data_obj, 95, GenericIPAddressData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
     200    (data_obj, 96, GenericIPAddressData, None),
    199201    (data_obj, 100, NullBooleanData, True),
    200202    (data_obj, 101, NullBooleanData, False),
    201203    (data_obj, 102, NullBooleanData, None),
     
    298300    (pk_obj, 682, IntegerPKData, 0),
    299301#     (XX, ImagePKData
    300302    (pk_obj, 690, IPAddressPKData, "127.0.0.1"),
     303    (pk_obj, 695, GenericIPAddressPKData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
    301304    # (pk_obj, 700, NullBooleanPKData, True),
    302305    # (pk_obj, 701, NullBooleanPKData, False),
    303306    (pk_obj, 710, PhonePKData, "212-634-5789"),
  • tests/regressiontests/serializers_regress/models.py

     
    5252class IPAddressData(models.Model):
    5353    data = models.IPAddressField(null=True)
    5454
     55class GenericIPAddressData(models.Model):
     56    data = models.GenericIPAddressField(null=True)
     57
    5558class NullBooleanData(models.Model):
    5659    data = models.NullBooleanField(null=True)
    5760
     
    187190class IPAddressPKData(models.Model):
    188191    data = models.IPAddressField(primary_key=True)
    189192
     193class GenericIPAddressPKData(models.Model):
     194    data = models.GenericIPAddressField(primary_key=True)
     195
    190196# This is just a Boolean field with null=True, and we can't test a PK value of NULL.
    191197# class NullBooleanPKData(models.Model):
    192198#     data = models.NullBooleanField(primary_key=True)
  • tests/regressiontests/utils/tests.py

     
    1919from datetime_safe import *
    2020from baseconv import *
    2121from jslex import *
     22from ipv6 import *
  • tests/regressiontests/utils/ipv6.py

     
     1from django.utils.ipv6 import is_valid_ipv6_address, clean_ipv6_address
     2from django.utils import unittest
     3
     4class TestUtilsIPv6(unittest.TestCase):
     5
     6    def test_validates_correct_plain_address(self):
     7        self.assertTrue(is_valid_ipv6_address('fe80::223:6cff:fe8a:2e8a'))
     8        self.assertTrue(is_valid_ipv6_address('2a02::223:6cff:fe8a:2e8a'))
     9        self.assertTrue(is_valid_ipv6_address('1::2:3:4:5:6:7'))
     10        self.assertTrue(is_valid_ipv6_address('::'))
     11        self.assertTrue(is_valid_ipv6_address('::a'))
     12        self.assertTrue(is_valid_ipv6_address('2::'))
     13
     14    def test_validates_correct_with_v4mapping(self):
     15        self.assertTrue(is_valid_ipv6_address('::ffff:254.42.16.14'))
     16        self.assertTrue(is_valid_ipv6_address('::ffff:0a0a:0a0a'))
     17
     18    def test_validates_incorrect_plain_address(self):
     19        self.assertFalse(is_valid_ipv6_address('foo'))
     20        self.assertFalse(is_valid_ipv6_address('127.0.0.1'))
     21        self.assertFalse(is_valid_ipv6_address('12345::'))
     22        self.assertFalse(is_valid_ipv6_address('1::2:3::4'))
     23        self.assertFalse(is_valid_ipv6_address('1::zzz'))
     24        self.assertFalse(is_valid_ipv6_address('1::2:3:4:5:6:7:8'))
     25        self.assertFalse(is_valid_ipv6_address('1:2'))
     26        self.assertFalse(is_valid_ipv6_address('1:::2'))
     27
     28    def test_validates_incorrect_with_v4mapping(self):
     29        self.assertFalse(is_valid_ipv6_address('::ffff:999.42.16.14'))
     30        self.assertFalse(is_valid_ipv6_address('::ffff:zzzz:0a0a'))
     31        # The ::1.2.3.4 format used to be valid but was deprecated
     32        # in rfc4291 section 2.5.5.1
     33        self.assertTrue(is_valid_ipv6_address('::254.42.16.14'))
     34        self.assertTrue(is_valid_ipv6_address('::0a0a:0a0a'))
     35        self.assertFalse(is_valid_ipv6_address('::999.42.16.14'))
     36        self.assertFalse(is_valid_ipv6_address('::zzzz:0a0a'))
     37
     38    def test_cleanes_plain_address(self):
     39        self.assertEqual(clean_ipv6_address('DEAD::0:BEEF'), u'dead::beef')
     40        self.assertEqual(clean_ipv6_address('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
     41        self.assertEqual(clean_ipv6_address('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
     42
     43    def test_cleanes_with_v4_mapping(self):
     44        self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
     45        self.assertEqual(clean_ipv6_address('::ffff:1234:1234'), u'::ffff:18.52.18.52')
     46        self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52'), u'::ffff:18.52.18.52')
     47
     48    def test_unpacks_ipv4(self):
     49        self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a', unpack_ipv4_mapped=True), u'10.10.10.10')
     50        self.assertEqual(clean_ipv6_address('::ffff:1234:1234', unpack_ipv4_mapped=True), u'18.52.18.52')
     51        self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52', unpack_ipv4_mapped=True), u'18.52.18.52')
     52from django.utils.ipv6 import is_valid_ipv6_address, clean_ipv6_address
     53from django.utils import unittest
     54
     55class TestUtilsIPv6(unittest.TestCase):
     56
     57    def test_validates_correct_plain_address(self):
     58        self.assertTrue(is_valid_ipv6_address('fe80::223:6cff:fe8a:2e8a'))
     59        self.assertTrue(is_valid_ipv6_address('2a02::223:6cff:fe8a:2e8a'))
     60        self.assertTrue(is_valid_ipv6_address('1::2:3:4:5:6:7'))
     61        self.assertTrue(is_valid_ipv6_address('::'))
     62        self.assertTrue(is_valid_ipv6_address('::a'))
     63        self.assertTrue(is_valid_ipv6_address('2::'))
     64
     65    def test_validates_correct_with_v4mapping(self):
     66        self.assertTrue(is_valid_ipv6_address('::ffff:254.42.16.14'))
     67        self.assertTrue(is_valid_ipv6_address('::ffff:0a0a:0a0a'))
     68
     69    def test_validates_incorrect_plain_address(self):
     70        self.assertFalse(is_valid_ipv6_address('foo'))
     71        self.assertFalse(is_valid_ipv6_address('127.0.0.1'))
     72        self.assertFalse(is_valid_ipv6_address('12345::'))
     73        self.assertFalse(is_valid_ipv6_address('1::2:3::4'))
     74        self.assertFalse(is_valid_ipv6_address('1::zzz'))
     75        self.assertFalse(is_valid_ipv6_address('1::2:3:4:5:6:7:8'))
     76        self.assertFalse(is_valid_ipv6_address('1:2'))
     77        self.assertFalse(is_valid_ipv6_address('1:::2'))
     78
     79    def test_validates_incorrect_with_v4mapping(self):
     80        self.assertFalse(is_valid_ipv6_address('::ffff:999.42.16.14'))
     81        self.assertFalse(is_valid_ipv6_address('::ffff:zzzz:0a0a'))
     82        # The ::1.2.3.4 format used to be valid but was deprecated
     83        # in rfc4291 section 2.5.5.1
     84        self.assertTrue(is_valid_ipv6_address('::254.42.16.14'))
     85        self.assertTrue(is_valid_ipv6_address('::0a0a:0a0a'))
     86        self.assertFalse(is_valid_ipv6_address('::999.42.16.14'))
     87        self.assertFalse(is_valid_ipv6_address('::zzzz:0a0a'))
     88
     89    def test_cleanes_plain_address(self):
     90        self.assertEqual(clean_ipv6_address('DEAD::0:BEEF'), u'dead::beef')
     91        self.assertEqual(clean_ipv6_address('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
     92        self.assertEqual(clean_ipv6_address('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
     93
     94    def test_cleanes_with_v4_mapping(self):
     95        self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
     96        self.assertEqual(clean_ipv6_address('::ffff:1234:1234'), u'::ffff:18.52.18.52')
     97        self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52'), u'::ffff:18.52.18.52')
     98
     99    def test_unpacks_ipv4(self):
     100        self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a', unpack_ipv4_mapped=True), u'10.10.10.10')
     101        self.assertEqual(clean_ipv6_address('::ffff:1234:1234', unpack_ipv4_mapped=True), u'18.52.18.52')
     102        self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52', unpack_ipv4_mapped=True), u'18.52.18.52')
Back to Top