Ticket #811: 811.1.diff

File 811.1.diff, 14.3 KB (added by Jannis Leidel, 14 years ago)
  • django/core/validators.py

    From cbe78fc6b3b8e579a3b6e0c02001ed9ab2739893 Mon Sep 17 00:00:00 2001
    From: Jannis Leidel <jannis@leidel.info>
    Date: Fri, 6 May 2011 19:28:46 +0200
    Subject: [PATCH] Fixed #811 -- Added IP6AddressField.
    
    ---
     django/core/validators.py                          |   24 ++++++++-
     django/db/backends/mysql/creation.py               |    1 +
     django/db/backends/oracle/creation.py              |    1 +
     django/db/backends/postgresql_psycopg2/creation.py |    1 +
     django/db/backends/sqlite3/creation.py             |    1 +
     django/db/models/fields/__init__.py                |   18 ++++++-
     django/forms/fields.py                             |    8 +++-
     django/utils/ip.py                                 |   54 ++++++++++++++++++++
     tests/regressiontests/forms/tests/extra.py         |   22 ++++++++
     .../regressiontests/serializers_regress/models.py  |    6 ++
     tests/regressiontests/serializers_regress/tests.py |    2 +
     11 files changed, 135 insertions(+), 3 deletions(-)
     create mode 100644 django/utils/ip.py
    
    diff --git a/django/core/validators.py b/django/core/validators.py
    index a40af0c..33517b6 100644
    a b import urlparse  
    55from django.core.exceptions import ValidationError
    66from django.utils.translation import ugettext_lazy as _
    77from django.utils.encoding import smart_unicode
     8from django.utils.ip import ipv6_normalize
    89
    910# These values, if given to validate(), will trigger the self.required check.
    1011EMPTY_VALUES = (None, '', [], (), {})
    class RegexValidator(object):  
    2021    regex = ''
    2122    message = _(u'Enter a valid value.')
    2223    code = 'invalid'
     24    normalizer = None
    2325
    24     def __init__(self, regex=None, message=None, code=None):
     26    def __init__(self, regex=None, message=None, code=None, normalizer=None):
    2527        if regex is not None:
    2628            self.regex = regex
    2729        if message is not None:
    2830            self.message = message
    2931        if code is not None:
    3032            self.code = code
     33        if normalizer is not None:
     34            self.normalizer = normalizer
    3135
    3236        if isinstance(self.regex, basestring):
    3337            self.regex = re.compile(regex)
    class RegexValidator(object):  
    3640        """
    3741        Validates that the input matches the regular expression.
    3842        """
     43        if self.normalizer:
     44            try:
     45                value = self.normalizer(value)
     46            except ValueError:
     47                raise ValidationError(self.message, code=self.code)
     48
    3949        if not self.regex.search(smart_unicode(value)):
    4050            raise ValidationError(self.message, code=self.code)
    4151
    validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of l  
    143153ipv4_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}$')
    144154validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
    145155
     156ipv6_re = re.compile(r'^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$')
     157validate_ipv6_address = RegexValidator(ipv6_re, _(u'Enter a valid IPv6 address.'), 'invalid', ipv6_normalize)
     158
     159def validate_ip_address(value):
     160    try:
     161        validate_ipv4_address(value)
     162    except ValidationError:
     163        try:
     164            validate_ipv6_address(value)
     165        except ValidationError:
     166            raise ValidationError(_(u'Enter a valid IP address.'), code='invalid')
     167
    146168comma_separated_int_list_re = re.compile('^[\d,]+$')
    147169validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
    148170
  • django/db/backends/mysql/creation.py

    diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py
    index 8b026a9..5fa71be 100644
    a b class DatabaseCreation(BaseDatabaseCreation):  
    1919        'IntegerField':      'integer',
    2020        'BigIntegerField':   'bigint',
    2121        'IPAddressField':    'char(15)',
     22        'IP6AddressField':  'char(39)',
    2223        'NullBooleanField':  'bool',
    2324        'OneToOneField':     'integer',
    2425        'PositiveIntegerField': 'integer UNSIGNED',
  • django/db/backends/oracle/creation.py

    diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
    index 29293db..2ff4c94 100644
    a b class DatabaseCreation(BaseDatabaseCreation):  
    2727        'IntegerField':                 'NUMBER(11)',
    2828        'BigIntegerField':              'NUMBER(19)',
    2929        'IPAddressField':               'VARCHAR2(15)',
     30        'IP6AddressField':             '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/creation.py

    diff --git a/django/db/backends/postgresql_psycopg2/creation.py b/django/db/backends/postgresql_psycopg2/creation.py
    index 5d4d50b..388f2d9 100644
    a b class DatabaseCreation(BaseDatabaseCreation):  
    2121        'IntegerField':      'integer',
    2222        'BigIntegerField':   'bigint',
    2323        'IPAddressField':    'inet',
     24        'IP6AddressField':  'inet',
    2425        'NullBooleanField':  'boolean',
    2526        'OneToOneField':     'integer',
    2627        'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
  • django/db/backends/sqlite3/creation.py

    diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py
    index f32bd0a..8fa42c7 100644
    a b class DatabaseCreation(BaseDatabaseCreation):  
    2020        'IntegerField':                 'integer',
    2121        'BigIntegerField':              'bigint',
    2222        'IPAddressField':               'char(15)',
     23        'IP6AddressField':             'char(39)',
    2324        'NullBooleanField':             'bool',
    2425        'OneToOneField':                'integer',
    2526        'PositiveIntegerField':         'integer unsigned',
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index 9037265..c7f020d 100644
    a b class BigIntegerField(IntegerField):  
    919919
    920920class IPAddressField(Field):
    921921    empty_strings_allowed = False
    922     description = _("IP address")
     922    description = _("IPv4 address")
    923923    def __init__(self, *args, **kwargs):
    924924        kwargs['max_length'] = 15
    925925        Field.__init__(self, *args, **kwargs)
    class IPAddressField(Field):  
    932932        defaults.update(kwargs)
    933933        return super(IPAddressField, self).formfield(**defaults)
    934934
     935class IP6AddressField(Field):
     936    empty_strings_allowed = False
     937    description = _("IPv6 address")
     938    def __init__(self, *args, **kwargs):
     939        kwargs['max_length'] = 39
     940        Field.__init__(self, *args, **kwargs)
     941
     942    def get_internal_type(self):
     943        return "IP6AddressField"
     944
     945    def formfield(self, **kwargs):
     946        defaults = {'form_class': forms.IP6AddressField}
     947        defaults.update(kwargs)
     948        return super(IP6AddressField, self).formfield(**defaults)
     949
     950
    935951class NullBooleanField(Field):
    936952    empty_strings_allowed = False
    937953    default_error_messages = {
  • django/forms/fields.py

    diff --git a/django/forms/fields.py b/django/forms/fields.py
    index a5ea81d..11fd416 100644
    a b __all__ = (  
    3838    'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
    3939    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    4040    'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
    41     'TypedChoiceField', 'TypedMultipleChoiceField'
     41    'TypedChoiceField', 'TypedMultipleChoiceField', 'IP6AddressField',
    4242)
    4343
    4444
    class IPAddressField(CharField):  
    946946    }
    947947    default_validators = [validators.validate_ipv4_address]
    948948
     949class IP6AddressField(CharField):
     950    default_error_messages = {
     951        'invalid': _(u'Enter a valid IPv6 address.'),
     952    }
     953    default_validators = [validators.validate_ipv6_address]
     954
    949955
    950956class SlugField(CharField):
    951957    default_error_messages = {
  • new file django/utils/ip.py

    diff --git a/django/utils/ip.py b/django/utils/ip.py
    new file mode 100644
    index 0000000..5929689
    - +  
     1def ipv6_normalize(addr):
     2    """
     3    Normalize an IPv6 address to allow easy regexp validation.
     4    Mostly checks the length, and gets rid of tricky things
     5    like IPv4 mapped addresses and :: shortcuts
     6   
     7    Outputs a string
     8    """
     9    # Some basic error checking
     10    if addr.count('::') > 2 or ':::' in addr:
     11        raise ValueError
     12
     13    ip = addr.split(':')
     14    nbfull = len([elem for elem in ip if elem != ''])
     15    nb = len(ip)
     16   
     17    if nb < 3:
     18        # The minimal IPv6 address is :: so at least 3 parts after split
     19        raise ValueError
     20
     21    if nbfull >= 1 and '.' in ip[-1]:
     22        # Convert IPv4 mapped addresses to full hexadecimal notation
     23        ipv4 = ip[-1].split('.')
     24        hex1 = (int(ipv4[0]) << 8) + int(ipv4[1])
     25        hex2 = (int(ipv4[2]) << 8) + int(ipv4[3])
     26        ip[-1:] = [hex(hex1)[2:], hex(hex2)[2:]]
     27        nbfull = nbfull + 1
     28        nb = nb + 1
     29
     30    if nbfull == 8 or nbfull == nb:
     31        # No need to bother
     32        return addr
     33    elif nbfull > 8:
     34        # Has to be invalid anyway
     35        raise ValueError
     36
     37    # Begin normalization
     38    start, end, index = (None, None, 0)
     39    for elem in ip:
     40        if elem == '':
     41            if start is None:
     42                start = index
     43                end = index
     44            else:
     45                end = index
     46        index += 1
     47    pad = 8 - nbfull
     48    if end != start:
     49        ip[start:end-start+1] = ['0'] * pad
     50    else:
     51        ip[start] = '0'
     52        if pad > 1:
     53            ip[start:1] = ['0'] * (pad - 1)
     54    return ':'.join([item for item in ip if len(item) > 0])
  • tests/regressiontests/forms/tests/extra.py

    diff --git a/tests/regressiontests/forms/tests/extra.py b/tests/regressiontests/forms/tests/extra.py
    index 927362a..6b68114 100644
    a b class FormsExtraTestCase(unittest.TestCase, AssertFormErrorsMixin):  
    451451        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
    452452        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
    453453
     454        f = IP6AddressField()
     455        self.assertEqual(f.clean('::1'), u'::1')
     456        self.assertFormErrors([u'This field is required.'], f.clean, '')
     457        self.assertFormErrors([u'This field is required.'], f.clean, None)
     458        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.1')
     459        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo')
     460        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.')
     461        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5')
     462        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '256.125.1.5')
     463        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2:3:4:5:6:7')
     464        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2:3:4:5:6:7:8:9')
     465
    454466        f = IPAddressField(required=False)
    455467        self.assertEqual(f.clean(''), u'')
    456468        self.assertEqual(f.clean(None), u'')
    class FormsExtraTestCase(unittest.TestCase, AssertFormErrorsMixin):  
    460472        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
    461473        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
    462474
     475        f = IP6AddressField(required=False)
     476        self.assertEqual(f.clean('::1'), u'::1')
     477        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.1')
     478        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo')
     479        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.')
     480        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5')
     481        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '256.125.1.5')
     482        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2:3:4:5:6:7')
     483        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2:3:4:5:6:7:8:9')
     484
    463485    def test_smart_unicode(self):
    464486        class Test:
    465487            def __str__(self):
  • tests/regressiontests/serializers_regress/models.py

    diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
    index 3a2c81a..4670185 100644
    a b class BigIntegerData(models.Model):  
    5252class IPAddressData(models.Model):
    5353    data = models.IPAddressField(null=True)
    5454
     55class IP6AddressData(models.Model):
     56    data = models.IP6AddressField(null=True)
     57
    5558class NullBooleanData(models.Model):
    5659    data = models.NullBooleanField(null=True)
    5760
    class IntegerPKData(models.Model):  
    187190class IPAddressPKData(models.Model):
    188191    data = models.IPAddressField(primary_key=True)
    189192
     193class IP6AddressPKData(models.Model):
     194    data = models.IP6AddressField(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/serializers_regress/tests.py

    diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
    index 97b2a79..5380c0f 100644
    a b test_data = [  
    196196    #(XX, ImageData
    197197    (data_obj, 90, IPAddressData, "127.0.0.1"),
    198198    (data_obj, 91, IPAddressData, None),
     199    (data_obj, 92, IP6AddressData, "::1"),
    199200    (data_obj, 100, NullBooleanData, True),
    200201    (data_obj, 101, NullBooleanData, False),
    201202    (data_obj, 102, NullBooleanData, None),
    The end."""),  
    298299    (pk_obj, 682, IntegerPKData, 0),
    299300#     (XX, ImagePKData
    300301    (pk_obj, 690, IPAddressPKData, "127.0.0.1"),
     302    (pk_obj, 691, IP6AddressPKData, "::1"),
    301303    # (pk_obj, 700, NullBooleanPKData, True),
    302304    # (pk_obj, 701, NullBooleanPKData, False),
    303305    (pk_obj, 710, PhonePKData, "212-634-5789"),
Back to Top