Ticket #811: ipv46address.diff

File ipv46address.diff, 20.9 KB (added by erikr, 4 years ago)

Patch for my IPv46AddressField

  • docs/topics/forms/modelforms.txt

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

     
    755755An IP address, in string format (e.g. "192.0.2.30"). The admin represents this
    756756as an ``<input type="text">`` (a single-line input).
    757757
     758``IPv46AddressField``
     759---------------------
     760
     761.. class:: IPv46AddressField([**options])
     762
     763.. versionadded:: 1.4
     764
     765An IPv4 or IPv6 address, in string format (e.g. "192.0.2.30" or "2a02:42fe::4")
     766The admin represents this as an ``<input type="text">`` (a single-line input).
     767
    758768``NullBooleanField``
    759769--------------------
    760770
  • docs/ref/forms/fields.txt

     
    622622      expression.
    623623    * Error message keys: ``required``, ``invalid``
    624624
     625``IPv46AddressField``
     626~~~~~~~~~~~~~~~~~~~~~
     627
     628.. class:: IPv46AddressField(**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. By default, 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 normalisation follows `RFC4291 section 2.2`_, including using
     643the IPv4 format suggested in paragraph 3 of that section. For example,
     644``2001:0::0:01`` would be compressed to ``2001::1`` and ``::0a0a:0a0a``
     645to ``::10.10.10.10``. This behaviour can be disabled by setting the
     646``skip_ipv6_normalisation`` attribute.
     647
     648.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
     649
     650Takes three optional arguments:
     651
     652.. attribute:: IPv46AddressField.skip_ipv6_normalisation
     653
     654    Disables the IPv6 address normalisation described above.
     655
     656.. attribute:: IPv46AddressField.only_ipv4
     657
     658    Limits valid inputs to only IPv4 addresses
     659
     660.. attribute:: IPv46AddressField.only_ipv6
     661
     662    Limits valid inputs to only IPv6 addresses
     663
     664Setting both ``only_ipv4`` and ``only_ipv6`` is invalid.
     665
    625666``MultipleChoiceField``
    626667~~~~~~~~~~~~~~~~~~~~~~~
    627668
  • django/db/models/fields/__init__.py

     
    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)
     
    932932        defaults.update(kwargs)
    933933        return super(IPAddressField, self).formfield(**defaults)
    934934
     935class IPv46AddressField(Field):
     936    empty_strings_allowed = False
     937    description = _("IP 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 "IPv46AddressField"
     944
     945    def formfield(self, **kwargs):
     946        defaults = {'form_class': forms.IPv46AddressField}
     947        defaults.update(kwargs)
     948        return super(IPv46AddressField, self).formfield(**defaults)
     949
    935950class NullBooleanField(Field):
    936951    empty_strings_allowed = False
    937952    default_error_messages = {
  • django/db/backends/sqlite3/creation.py

     
    2020        'IntegerField':                 'integer',
    2121        'BigIntegerField':              'bigint',
    2222        'IPAddressField':               'char(15)',
     23        'IPv46AddressField':            '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        'IPv46AddressField': '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        'IPv46AddressField':            '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: 'IPv46AddressField',
    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        'IPv6AddressField':  '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
     
    3738    'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
    3839    'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
    3940    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    40     'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
    41     'TypedChoiceField', 'TypedMultipleChoiceField'
     41    'SplitDateTimeField', 'IPAddressField', 'IPv46AddressField', 'FilePathField',
     42    'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField'
    4243)
    4344
    4445
     
    947948    default_validators = [validators.validate_ipv4_address]
    948949
    949950
     951class IPv46AddressField(CharField):
     952    def __init__(self, only_ipv4=False, only_ipv6=False, skip_ipv6_normalisation=False, *args, **kwargs):
     953        self.skip_ipv6_normalisation = skip_ipv6_normalisation
     954
     955        if only_ipv4 and only_ipv6:
     956            raise ValueError(
     957                "You can not simultaniously use both only_ipv4 and only_ipv6 on an IPv46AddressField - "
     958                "it would generate a field that could never validate")
     959
     960        self.default_validators = [validators.validate_ipv46_address]
     961        self.default_error_messages['invalid'] = _(u'Enter a valid IP address.')
     962
     963        if only_ipv4:
     964            self.default_validators = [validators.validate_ipv4_address]
     965            self.default_error_messages['invalid'] = _(u'Enter a valid IPv4 address.')
     966
     967        if only_ipv6:
     968            self.default_validators = [validators.validate_ipv6_address]
     969            self.default_error_messages['invalid'] = _(u'Enter a valid IPv6 address.')
     970           
     971        super(IPv46AddressField, self).__init__(*args, **kwargs)
     972       
     973    def to_python(self, value):
     974        "Try to normalize the address using getaddrinfo"
     975        if not value:
     976            return ''
     977       
     978        if self.skip_ipv6_normalisation:
     979            return value
     980           
     981        try:
     982            new_value = socket.getaddrinfo(value, None, 0, 0, 0, socket.AI_NUMERICHOST)[0][4][0]
     983            return new_value
     984        except socket.gaierror:
     985            return value
     986       
     987    default_error_messages = {}
     988   
     989
    950990class SlugField(CharField):
    951991    default_error_messages = {
    952992        'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers,"
  • django/core/validators.py

     
    143143ipv4_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}$')
    144144validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
    145145
     146ipv6_re = re.compile(r'^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$')
     147validate_ipv6_address = RegexValidator(ipv6_re, _(u'Enter a valid IPv6 address.'), 'invalid')
     148
     149def validate_ipv46_address(value):
     150    try:
     151        validate_ipv4_address(value)
     152    except ValidationError:
     153        try:
     154            validate_ipv6_address(value)
     155        except ValidationError:
     156            raise ValidationError(_(u'Enter a valid IP address.'), code='invalid')
     157           
    146158comma_separated_int_list_re = re.compile('^[\d,]+$')
    147159validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
    148160
  • 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_ipv46addressfield(self):
     200        e = {
     201            'required': 'REQUIRED',
     202            'invalid': 'INVALID IP ADDRESS',
     203        }
     204        f = IPv46AddressField(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_ipv46address(self):
     465        f = IPv46AddressField()
     466        self.assertFormErrors([u'This field is required.'], f.clean, '')
     467        self.assertFormErrors([u'This field is required.'], f.clean, None)
     468        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
     469        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, 'foo')
     470        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '127.0.0.')
     471        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '1.2.3.4.5')
     472        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '256.125.1.5')
     473        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
     474        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
     475        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, 'foo')
     476        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '12345:2:3:4')
     477        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '1::2:3::4')
     478        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
     479        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '1::2:3:4:5:6:7:8')
     480        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '1:2')
     481       
     482        f = IPv46AddressField(only_ipv4=True)
     483        self.assertFormErrors([u'This field is required.'], f.clean, '')
     484        self.assertFormErrors([u'This field is required.'], f.clean, None)
     485        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
     486        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
     487        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
     488        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
     489        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
     490        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'fe80::223:6cff:fe8a:2e8a')
     491        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '2a02::223:6cff:fe8a:2e8a')
     492        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
     493        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '12345:2:3:4')
     494        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1::2:3::4')
     495        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
     496        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1::2:3:4:5:6:7:8')
     497        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1:2')
     498               
     499        f = IPv46AddressField(only_ipv6=True)
     500        self.assertFormErrors([u'This field is required.'], f.clean, '')
     501        self.assertFormErrors([u'This field is required.'], f.clean, None)
     502        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.1')
     503        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo')
     504        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.')
     505        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5')
     506        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '256.125.1.5')
     507        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
     508        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
     509        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo')
     510        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '12345:2:3:4')
     511        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3::4')
     512        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
     513        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
     514        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2')
     515
     516        f = IPv46AddressField(required=False)
     517        self.assertEqual(f.clean(''), u'')
     518        self.assertEqual(f.clean(None), u'')
     519        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
     520        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, 'foo')
     521        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '127.0.0.')
     522        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '1.2.3.4.5')
     523        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '256.125.1.5')
     524        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
     525        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
     526        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, 'foo')
     527        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '12345:2:3:4')
     528        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '1::2:3::4')
     529        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
     530        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '1::2:3:4:5:6:7:8')
     531        self.assertFormErrors([u'Enter a valid IP address.'], f.clean, '1:2')
     532
     533        self.assertRaises(ValueError, IPv46AddressField, only_ipv4=True, only_ipv6=True)
     534
     535        # Test the normalising code
     536        f = IPv46AddressField(only_ipv6=True)
     537        self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
     538        self.assertEqual(f.clean('::0a0a:0a0a'), u'::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        # Test the normalising code
     543        f = IPv46AddressField(only_ipv6=True, skip_ipv6_normalisation=True)
     544        self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'::ffff:0a0a:0a0a')
     545        self.assertEqual(f.clean('::0a0a:0a0a'), u'::0a0a:0a0a')
     546        self.assertEqual(f.clean('2001:000:a:0000:0:fe:fe:beef'), u'2001:000:a:0000:0:fe:fe:beef')
     547        self.assertEqual(f.clean('2001::a:0000:0:fe:fe:beef'), u'2001::a:0000:0:fe:fe:beef')
     548       
     549
    463550    def test_smart_unicode(self):
    464551        class Test:
    465552            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, IPv46AddressData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
     200    (data_obj, 96, IPv46AddressData, 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, IPv46AddressPKData, "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 IPv46AddressData(models.Model):
     56    data = models.IPv46AddressField(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 IPv46AddressPKData(models.Model):
     194    data = models.IPv46AddressField(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)
Back to Top