Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(révision 13294)
+++ django/db/models/fields/__init__.py	(copie de travail)
@@ -906,6 +906,22 @@
         defaults.update(kwargs)
         return super(IPAddressField, self).formfield(**defaults)
 
+class IPv6AddressField(Field):
+    empty_strings_allowed = False
+    description = _("IPv6/IPv4 address")
+    def __init__(self, *args, **kwargs):
+        kwargs['max_length'] = 39
+        Field.__init__(self, *args, **kwargs)
+
+    def get_internal_type(self):
+        return "IPv6AddressField"
+
+    def formfield(self, **kwargs):
+        defaults = {'form_class': forms.IPv6AddressField}
+        defaults.update(kwargs)
+        return super(IPv6AddressField, self).formfield(**defaults)
+
+
 class NullBooleanField(Field):
     empty_strings_allowed = False
     default_error_messages = {
Index: django/db/backends/postgresql/introspection.py
===================================================================
--- django/db/backends/postgresql/introspection.py	(révision 13294)
+++ django/db/backends/postgresql/introspection.py	(copie de travail)
@@ -10,7 +10,7 @@
         25: 'TextField',
         700: 'FloatField',
         701: 'FloatField',
-        869: 'IPAddressField',
+        869: 'IPv6AddressField',
         1043: 'CharField',
         1082: 'DateField',
         1083: 'TimeField',
Index: django/db/backends/postgresql/creation.py
===================================================================
--- django/db/backends/postgresql/creation.py	(révision 13294)
+++ django/db/backends/postgresql/creation.py	(copie de travail)
@@ -19,6 +19,7 @@
         'IntegerField':      'integer',
         'BigIntegerField':   'bigint',
         'IPAddressField':    'inet',
+        'IPv6AddressField':  'inet',
         'NullBooleanField':  'boolean',
         'OneToOneField':     'integer',
         'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
Index: django/db/backends/sqlite3/creation.py
===================================================================
--- django/db/backends/sqlite3/creation.py	(révision 13294)
+++ django/db/backends/sqlite3/creation.py	(copie de travail)
@@ -20,6 +20,7 @@
         'IntegerField':                 'integer',
         'BigIntegerField':              'bigint',
         'IPAddressField':               'char(15)',
+        'IPv6AddressField':             'char(39)',
         'NullBooleanField':             'bool',
         'OneToOneField':                'integer',
         'PositiveIntegerField':         'integer unsigned',
Index: django/db/backends/mysql/creation.py
===================================================================
--- django/db/backends/mysql/creation.py	(révision 13294)
+++ django/db/backends/mysql/creation.py	(copie de travail)
@@ -19,6 +19,7 @@
         'IntegerField':      'integer',
         'BigIntegerField':   'bigint',
         'IPAddressField':    'char(15)',
+        'IPv6AddressField':  'char(39)',
         'NullBooleanField':  'bool',
         'OneToOneField':     'integer',
         'PositiveIntegerField': 'integer UNSIGNED',
Index: django/db/backends/oracle/creation.py
===================================================================
--- django/db/backends/oracle/creation.py	(révision 13294)
+++ django/db/backends/oracle/creation.py	(copie de travail)
@@ -28,6 +28,7 @@
         'IntegerField':                 'NUMBER(11)',
         'BigIntegerField':              'NUMBER(19)',
         'IPAddressField':               'VARCHAR2(15)',
+        'IPv6AddressField':             'VARCHAR2(39)',
         'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
         'OneToOneField':                'NUMBER(11)',
         'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
Index: django/forms/fields.py
===================================================================
--- django/forms/fields.py	(révision 13294)
+++ django/forms/fields.py	(copie de travail)
@@ -39,7 +39,7 @@
     'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
     'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
     'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
-    'TypedChoiceField'
+    'TypedChoiceField', 'IPv6AddressField'
 )
 
 def en_format(name):
@@ -868,6 +868,15 @@
     default_validators = [validators.validate_ipv4_address]
 
 
+ipv6_re = re.compile(r'^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$')
+
+class IPv6AddressField(CharField):
+    default_error_messages = {
+        'invalid': _(u'Enter a valid IP address.'),
+    }
+    default_validators = [validators.validate_ipv6_address]
+
+
 class SlugField(CharField):
     default_error_messages = {
         'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers,"
Index: django/core/validators.py
===================================================================
--- django/core/validators.py	(révision 13294)
+++ django/core/validators.py	(copie de travail)
@@ -38,6 +38,32 @@
         if not self.regex.search(smart_unicode(value)):
             raise ValidationError(self.message, code=self.code)
 
+ipv6_re = re.compile(r'^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$')
+
+class IPv6Validator(object):
+    message = _(u'Enter a valid IP address.')
+    code = 'invalid'
+
+    def __init__(self, message=None, code=None):
+        if message is not None:
+            self.message = message
+        if code is not None:
+            self.code = code
+
+    def __call__(self, value):
+        """
+        Validates that the input matches an IPv4/IPv6 address
+        """
+        ip = smart_unicode(value)
+        if not ipv4_re.search(ip):
+            from django.utils.http import ipv6_normalize
+            try:
+                ipv6 = ipv6_normalize(ip)
+                if not ipv6_re.search(ipv6):
+                    raise ValidationError(self.message, code=self.code)
+            except ValueError:
+                    raise ValidationError(self.message, code=self.code)
+
 class URLValidator(RegexValidator):
     regex = re.compile(
         r'^https?://' # http:// or https://
@@ -124,6 +150,7 @@
 
 ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
 validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
+validate_ipv6_address = IPv6Validator(_(u'Enter a valid IPv4 address.'), 'invalid')
 
 comma_separated_int_list_re = re.compile('^[\d,]+$')
 validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
Index: django/utils/http.py
===================================================================
--- django/utils/http.py	(révision 13294)
+++ django/utils/http.py	(copie de travail)
@@ -117,3 +117,53 @@
     """
     return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"')
 
+def ipv6_normalize(addr):
+    """
+    Normalize an IPv6 address to allow easy regexp validation
+    mostly checks the length, and gets ride of tricky things
+    like IPv4 mapped addresses and :: shortcuts
+    
+    Outputs a string
+    """
+    # Some basic error checking
+    if addr.count('::') > 2 or ':::' in addr:
+        raise ValueError
+
+    ip = addr.split(':')
+    nbfull = len([elem for elem in ip if elem != ''])
+    nb = len(ip)
+
+    if nbfull >= 1 and '.' in ip[-1]:
+        # Convert IPv4 mapped addresses to full hexa
+        ipv4 = ip[-1].split('.')
+        hex1 = (int(ipv4[0]) << 8) + int(ipv4[1])
+        hex2 = (int(ipv4[2]) << 8) + int(ipv4[3])
+        ip[-1:] = [hex(hex1)[2:], hex(hex2)[2:]]
+        nbfull = nbfull + 1
+        nb = nb + 1
+
+    if nbfull == 8 or nbfull == nb:
+        # No need to bother
+        return addr
+    elif nbfull > 8:
+        # Has to be invalid anyway
+        raise ValueError
+
+    # Begin normalization
+    start, end, index = (None, None, 0)
+    for elem in ip:
+        if elem == '':
+            if start is None:
+                start = index
+                end = index
+            else:
+                end = index
+        index += 1
+    pad = 8 - nbfull
+    if end != start:
+        ip[start:end-start+1] = ['0'] * pad
+    else:
+        ip[start] = '0'
+        if pad > 1:
+            ip[start:1] = ['0'] * (pad - 1)
+    return ':'.join([item for item in ip if len(item) > 0])
Index: tests/regressiontests/forms/extra.py
===================================================================
--- tests/regressiontests/forms/extra.py	(révision 13294)
+++ tests/regressiontests/forms/extra.py	(copie de travail)
@@ -580,6 +580,85 @@
 ...
 ValidationError: [u'Enter a valid IPv4 address.']
 
+# IPv6AddressField ##################################################################
+
+>>> f = IPv6AddressField()
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('127.0.0.1')
+u'127.0.0.1'
+>>> f.clean('2a01:05d8:25ae:9451:020d:39bc:21e6:8cab')
+u'2a01:05d8:25ae:9451:020d:39bc:21e6:8cab'
+>>> f.clean('2a01:5d8:25ae:9451:20d:39bc:21e6:8cab')
+u'2a01:5d8:25ae:9451:20d:39bc:21e6:8cab'
+>>> f.clean('::1')
+u'::1'
+>>> f.clean('::ffff:88.191.32.1')
+u'::ffff:88.191.32.1'
+>>> f.clean('foo')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('127.0.0.')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('::1:')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('1.2.3.4.5')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('2a01:5d8:25ae:9451:20d:39bc:21e6:8cab:fb2c')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('2a01:5d8:25ae:9451:20d:39bc:1e6:cab:b2c')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('256.125.1.5')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('::12345')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+
+>>> f = IPv6AddressField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
+>>> f.clean('127.0.0.1')
+u'127.0.0.1'
+>>> f.clean('foo')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('127.0.0.')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('1.2.3.4.5')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+>>> f.clean('256.125.1.5')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IP address.']
+
+
 #################################
 # Tests of underlying functions #
 #################################
Index: docs/topics/forms/modelforms.txt
===================================================================
--- docs/topics/forms/modelforms.txt	(révision 13294)
+++ docs/topics/forms/modelforms.txt	(copie de travail)
@@ -79,6 +79,8 @@
 
     ``IPAddressField``               ``IPAddressField``
 
+    ``IPv6AddressField``              ``IPv6AddressField``
+
     ``ManyToManyField``              ``ModelMultipleChoiceField`` (see
                                      below)
 
Index: docs/ref/models/fields.txt
===================================================================
--- docs/ref/models/fields.txt	(révision 13294)
+++ docs/ref/models/fields.txt	(copie de travail)
@@ -715,9 +715,18 @@
 
 .. class:: IPAddressField([**options])
 
-An IP address, in string format (e.g. "192.0.2.30"). The admin represents this
+An IPv4 address, in string format (e.g. "192.0.2.30"). The admin represents this
 as an ``<input type="text">`` (a single-line input).
 
+``IPv6AddressField``
+------------------
+
+.. class:: IPv6AddressField([**options])
+
+An IPv4 or IPv6 address, in string format (e.g. "192.0.2.30" or 
+"2a01:5d8:25ae:9451:20d:39bc:1e6:cab:b2c"). The admin represents this as an 
+``<input type="text">`` (a single-line input).
+
 ``NullBooleanField``
 --------------------
 
Index: docs/ref/forms/fields.txt
===================================================================
--- docs/ref/forms/fields.txt	(révision 13294)
+++ docs/ref/forms/fields.txt	(copie de travail)
@@ -623,6 +623,19 @@
       expression.
     * Error message keys: ``required``, ``invalid``
 
+``IPv6AddressField``
+~~~~~~~~~~~~~~~~~~
+
+.. class:: IPv6AddressField(**kwargs)
+
+    * Default widget: ``TextInput``
+    * Empty value: ``''`` (an empty string)
+    * Normalizes to: A Unicode object.
+    * Validates that the given value is a valid IPv4 or IPv6 address, using 
+      a regular expression.
+    * Error message keys: ``required``, ``invalid``
+
+
 ``MultipleChoiceField``
 ~~~~~~~~~~~~~~~~~~~~~~~
 
