Index: docs/topics/forms/modelforms.txt
===================================================================
--- docs/topics/forms/modelforms.txt	(revision 16357)
+++ docs/topics/forms/modelforms.txt	(working copy)
@@ -83,6 +83,8 @@
 
     ``IPAddressField``               ``IPAddressField``
 
+    ``GenericIPAddressField``            ``GenericIPAddressField``
+
     ``ManyToManyField``              ``ModelMultipleChoiceField`` (see
                                      below)
 
Index: docs/ref/models/fields.txt
===================================================================
--- docs/ref/models/fields.txt	(revision 16357)
+++ docs/ref/models/fields.txt	(working copy)
@@ -760,6 +760,36 @@
 An IP address, in string format (e.g. "192.0.2.30"). The admin represents this
 as an ``<input type="text">`` (a single-line input).
 
+``GenericIPAddressField``
+-------------------------
+
+.. class:: GenericIPAddressField([protocols=both, unpack_ipv4_mapped=False, **options])
+
+.. versionadded:: 1.4
+
+An IPv4 or IPv6 address, in string format (e.g. "192.0.2.30" or "2a02:42fe::4") 
+The admin represents this as an ``<input type="text">`` (a single-line input).
+
+The IPv6 address normalization follows `RFC4291 section 2.2`_, including using
+the IPv4 format suggested in paragraph 3 of that section, like ``::ffff:192.0.2.0``.
+For example, ``2001:0::0:01`` would be normalized to ``2001::1``, and ``::ffff:0a0a:0a0a``
+to ``::ffff:10.10.10.10``. All characters are converted to lowercase.
+
+.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
+
+.. attribute:: GenericIPAddressField.protocol
+
+    Limits valid inputs to the specified protocol.
+    Accepted values are ``both`` (default), ``IPv4``
+    or ``IPv6``. Matching is case insensitive.
+
+.. attribute:: GenericIPAddressField.unpack_ipv4_mapped
+
+    Unpack IPv4 mapped addresses, like ``::ffff::192.0.2.1``.
+    If this option is enabled, that address would be unpacked to
+    ``192.0.2.1``. Default is disabled. Can only be used
+    when ``protocol`` is set to ``both``.
+
 ``NullBooleanField``
 --------------------
 
Index: docs/ref/validators.txt
===================================================================
--- docs/ref/validators.txt	(revision 16357)
+++ docs/ref/validators.txt	(working copy)
@@ -130,6 +130,23 @@
     A :class:`RegexValidator` instance that ensures a value looks like an IPv4
     address.
 
+``validate_ipv6_address``
+-------------------------
+.. versionadded:: 1.4
+
+.. data:: validate_ipv6_address
+
+    Uses :mod:`django.utils.ipv6` to check the validity of an IPv6 address.
+
+``validate_ipv46_address``
+--------------------------
+.. versionadded:: 1.4
+
+.. data:: validate_ipv46_address
+
+    Uses both ``validate_ipv4_address`` and ``validate_ipv6_address`` to ensure
+    a value is either a valid IPv4 or IPv6 address.
+
 ``validate_comma_separated_integer_list``
 -----------------------------------------
 .. data:: validate_comma_separated_integer_list
Index: docs/ref/forms/fields.txt
===================================================================
--- docs/ref/forms/fields.txt	(revision 16357)
+++ docs/ref/forms/fields.txt	(working copy)
@@ -622,6 +622,45 @@
       expression.
     * Error message keys: ``required``, ``invalid``
 
+``GenericIPAddressField``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. class:: GenericIPAddressField(**kwargs)
+
+.. versionadded:: 1.4
+
+A field containing either an IPv4 or an IPv6 address.
+
+    * Default widget: ``TextInput``
+    * Empty value: ``''`` (an empty string)
+    * Normalizes to: A Unicode object. IPv6 addresses are
+      normalized as described below.
+    * Validates that the given value is a valid IP address, using regular
+      expressions.
+    * Error message keys: ``required``, ``invalid``
+
+The IPv6 address normalization follows `RFC4291 section 2.2`_, including using
+the IPv4 format suggested in paragraph 3 of that section, like ``::ffff:192.0.2.0``.
+For example, ``2001:0::0:01`` would be normalized to ``2001::1``, and ``::ffff:0a0a:0a0a``
+to ``::ffff:10.10.10.10``. All characters are converted to lowercase.
+
+.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
+
+Takes two optional arguments:
+
+.. attribute:: GenericIPAddressField.protocol
+
+    Limits valid inputs to the specified protocol.
+    Accepted values are ``both`` (default), ``IPv4``
+    or ``IPv6``. Matching is case insensitive.
+
+.. attribute:: GenericIPAddressField.unpack_ipv4_mapped
+
+    Unpack IPv4 mapped addresses, like ``::ffff::192.0.2.1``.
+    If this option is enabled, that address would be unpacked to
+    ``192.0.2.1``. Default is disabled. Can only be used
+    when ``protocol`` is set to ``both``.
+
 ``MultipleChoiceField``
 ~~~~~~~~~~~~~~~~~~~~~~~
 
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 16357)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -17,6 +17,8 @@
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode, force_unicode, smart_str
 from django.utils import datetime_safe
+from django.utils.ipv6 import (clean_ipv6_address, is_valid_ipv6_address,
+    validate_configure_ipaddressfield_settings)
 
 class NOT_PROVIDED:
     pass
@@ -920,7 +922,7 @@
 
 class IPAddressField(Field):
     empty_strings_allowed = False
-    description = _("IP address")
+    description = _("IPv4 address")
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = 15
         Field.__init__(self, *args, **kwargs)
@@ -933,6 +935,44 @@
         defaults.update(kwargs)
         return super(IPAddressField, self).formfield(**defaults)
 
+class GenericIPAddressField(Field):
+    empty_strings_allowed = True
+    description = _("IP address")
+    default_error_messages = {}
+    
+    def __init__(self, protocol='both', unpack_ipv4_mapped=False, *args, **kwargs):
+        self.unpack_ipv4_mapped = unpack_ipv4_mapped
+
+        self.default_validators = validate_configure_ipaddressfield_settings(
+                protocol, unpack_ipv4_mapped, self.default_error_messages)
+
+        kwargs['max_length'] = 39
+        Field.__init__(self, *args, **kwargs)
+
+    def get_internal_type(self):
+        return "GenericIPAddressField"
+
+    def to_python(self, value):
+        if value and ':' in value:
+            return clean_ipv6_address(value, self.unpack_ipv4_mapped, self.default_error_messages['invalid'])
+                
+        return value
+
+    def get_prep_value(self, value):        
+        if value and ':' in value:
+            try:
+                return clean_ipv6_address(value, self.unpack_ipv4_mapped)
+            except ValidationError:
+                pass
+                
+        return value
+
+    def formfield(self, **kwargs):
+        defaults = {'form_class': forms.GenericIPAddressField}
+        defaults.update(kwargs)
+        return super(GenericIPAddressField, self).formfield(**defaults)
+        
+
 class NullBooleanField(Field):
     empty_strings_allowed = False
     default_error_messages = {
Index: django/db/backends/sqlite3/creation.py
===================================================================
--- django/db/backends/sqlite3/creation.py	(revision 16357)
+++ django/db/backends/sqlite3/creation.py	(working copy)
@@ -20,6 +20,7 @@
         'IntegerField':                 'integer',
         'BigIntegerField':              'bigint',
         'IPAddressField':               'char(15)',
+        'GenericIPAddressField':        'char(39)',
         'NullBooleanField':             'bool',
         'OneToOneField':                'integer',
         'PositiveIntegerField':         'integer unsigned',
Index: django/db/backends/mysql/creation.py
===================================================================
--- django/db/backends/mysql/creation.py	(revision 16357)
+++ django/db/backends/mysql/creation.py	(working copy)
@@ -19,6 +19,7 @@
         'IntegerField':      'integer',
         'BigIntegerField':   'bigint',
         'IPAddressField':    'char(15)',
+        'GenericIPAddressField': 'char(39)',
         'NullBooleanField':  'bool',
         'OneToOneField':     'integer',
         'PositiveIntegerField': 'integer UNSIGNED',
Index: django/db/backends/oracle/creation.py
===================================================================
--- django/db/backends/oracle/creation.py	(revision 16357)
+++ django/db/backends/oracle/creation.py	(working copy)
@@ -27,6 +27,7 @@
         'IntegerField':                 'NUMBER(11)',
         'BigIntegerField':              'NUMBER(19)',
         'IPAddressField':               'VARCHAR2(15)',
+        'GenericIPAddressField':        'VARCHAR2(39)',
         'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
         'OneToOneField':                'NUMBER(11)',
         'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
Index: django/db/backends/postgresql_psycopg2/introspection.py
===================================================================
--- django/db/backends/postgresql_psycopg2/introspection.py	(revision 16357)
+++ django/db/backends/postgresql_psycopg2/introspection.py	(working copy)
@@ -12,6 +12,7 @@
         700: 'FloatField',
         701: 'FloatField',
         869: 'IPAddressField',
+        869: 'GenericIPAddressField',
         1043: 'CharField',
         1082: 'DateField',
         1083: 'TimeField',
Index: django/db/backends/postgresql_psycopg2/creation.py
===================================================================
--- django/db/backends/postgresql_psycopg2/creation.py	(revision 16357)
+++ django/db/backends/postgresql_psycopg2/creation.py	(working copy)
@@ -21,6 +21,7 @@
         'IntegerField':      'integer',
         'BigIntegerField':   'bigint',
         'IPAddressField':    'inet',
+        'GenericIPAddressField': 'inet',
         'NullBooleanField':  'boolean',
         'OneToOneField':     'integer',
         'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
Index: django/forms/fields.py
===================================================================
--- django/forms/fields.py	(revision 16357)
+++ django/forms/fields.py	(working copy)
@@ -9,6 +9,7 @@
 import time
 import urlparse
 import warnings
+import socket
 from decimal import Decimal, DecimalException
 try:
     from cStringIO import StringIO
@@ -21,6 +22,7 @@
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode, smart_str, force_unicode
 from django.utils.functional import lazy
+from django.utils.ipv6 import clean_ipv6_address, validate_configure_ipaddressfield_settings
 
 # Provide this import for backwards compatibility.
 from django.core.validators import EMPTY_VALUES
@@ -37,8 +39,8 @@
     'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
     'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
     'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
-    'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
-    'TypedChoiceField', 'TypedMultipleChoiceField'
+    'SplitDateTimeField', 'IPAddressField', 'GenericIPAddressField', 'FilePathField',
+    'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField'
 )
 
 
@@ -955,6 +957,27 @@
     default_validators = [validators.validate_ipv4_address]
 
 
+class GenericIPAddressField(CharField):
+    default_error_messages = {}
+    
+    def __init__(self, protocol='both', unpack_ipv4_mapped=False, *args, **kwargs):
+        self.unpack_ipv4_mapped = unpack_ipv4_mapped
+
+        self.default_validators = validate_configure_ipaddressfield_settings(
+                protocol, unpack_ipv4_mapped, self.default_error_messages)
+        
+        super(GenericIPAddressField, self).__init__(*args, **kwargs)
+        
+    def to_python(self, value):
+        if not value:
+            return ''
+        
+        if value and ':' in value:
+                return clean_ipv6_address(value, self.unpack_ipv4_mapped, self.default_error_messages['invalid'])
+
+        return value
+                
+
 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	(revision 16357)
+++ django/core/validators.py	(working copy)
@@ -5,6 +5,7 @@
 from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode
+from django.utils.ipv6 import is_valid_ipv6_address
 
 # These values, if given to validate(), will trigger the self.required check.
 EMPTY_VALUES = (None, '', [], (), {})
@@ -145,6 +146,19 @@
 ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
 validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
 
+def validate_ipv6_address(value):
+    if not is_valid_ipv6_address(value):
+        raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid')
+
+def validate_ipv46_address(value): 
+    try: 
+        validate_ipv4_address(value) 
+    except ValidationError: 
+        try: 
+            validate_ipv6_address(value) 
+        except ValidationError: 
+            raise ValidationError(_(u'Enter a valid IPv4 or IPv6 address.'), code='invalid')
+            
 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: tests/modeltests/validators/tests.py
===================================================================
--- tests/modeltests/validators/tests.py	(revision 16357)
+++ tests/modeltests/validators/tests.py	(working copy)
@@ -52,6 +52,31 @@
     (validate_ipv4_address, '25,1,1,1', ValidationError),
     (validate_ipv4_address, '25.1 .1.1', ValidationError),
 
+    # validate_ipv6_address uses django.utils.ipv6, which
+    # is tested in much greater detail in it's own testcase
+    (validate_ipv6_address, 'fe80::1', None),
+    (validate_ipv6_address, '::1', None),
+    (validate_ipv6_address, '1:2:3:4:5:6:7:8', None),
+
+    (validate_ipv6_address, '1:2', ValidationError),
+    (validate_ipv6_address, '::zzz', ValidationError),
+    (validate_ipv6_address, '12345::', ValidationError),
+
+    (validate_ipv46_address, '1.1.1.1', None),
+    (validate_ipv46_address, '255.0.0.0', None),
+    (validate_ipv46_address, '0.0.0.0', None),
+    (validate_ipv46_address, 'fe80::1', None),
+    (validate_ipv46_address, '::1', None),
+    (validate_ipv46_address, '1:2:3:4:5:6:7:8', None),
+
+    (validate_ipv46_address, '256.1.1.1', ValidationError),
+    (validate_ipv46_address, '25.1.1.', ValidationError),
+    (validate_ipv46_address, '25,1,1,1', ValidationError),
+    (validate_ipv46_address, '25.1 .1.1', ValidationError),
+    (validate_ipv46_address, '1:2', ValidationError),
+    (validate_ipv46_address, '::zzz', ValidationError),
+    (validate_ipv46_address, '12345::', ValidationError),
+
     (validate_comma_separated_integer_list, '1', None),
     (validate_comma_separated_integer_list, '1,2,3', None),
     (validate_comma_separated_integer_list, '1,2,3,', None),
Index: tests/modeltests/validation/tests.py
===================================================================
--- tests/modeltests/validation/tests.py	(revision 16357)
+++ tests/modeltests/validation/tests.py	(working copy)
@@ -2,7 +2,8 @@
 from django.test import TestCase
 from django.core.exceptions import NON_FIELD_ERRORS
 from modeltests.validation import ValidationTestCase
-from modeltests.validation.models import Author, Article, ModelToValidate
+from modeltests.validation.models import (Author, Article, ModelToValidate, 
+    GenericIPAddressTestModel, GenericIPAddressWithUnpackUniqueTestModel)
 
 # Import other tests for this package.
 from modeltests.validation.validators import TestModelsWithValidators
@@ -77,6 +78,7 @@
         mtv = ModelToValidate(number=10, name='Some Name'*100)
         self.assertFailsValidation(mtv.full_clean, ['name',])
 
+
 class ArticleForm(forms.ModelForm):
     class Meta:
         model = Article
@@ -124,3 +126,59 @@
         article = Article(author_id=self.author.id)
         form = ArticleForm(data, instance=article)
         self.assertEqual(form.errors.keys(), ['pub_date'])
+
+
+class GenericIPAddressFieldTests(ValidationTestCase):
+    
+    def test_correct_generic_ip_passes(self):
+        giptm = GenericIPAddressTestModel(generic_ip="1.2.3.4")
+        self.assertEqual(None, giptm.full_clean())
+        giptm = GenericIPAddressTestModel(generic_ip="2001::2")
+        self.assertEqual(None, giptm.full_clean())
+    
+    def test_invalid_generic_ip_raises_error(self):
+        giptm = GenericIPAddressTestModel(generic_ip="294.4.2.1")
+        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
+        giptm = GenericIPAddressTestModel(generic_ip="1:2")
+        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
+    
+    def test_correct_v4_ip_passes(self):
+        giptm = GenericIPAddressTestModel(v4_ip="1.2.3.4")
+        self.assertEqual(None, giptm.full_clean())
+    
+    def test_invalid_v4_ip_raises_error(self):
+        giptm = GenericIPAddressTestModel(v4_ip="294.4.2.1")
+        self.assertFailsValidation(giptm.full_clean, ['v4_ip',])
+        giptm = GenericIPAddressTestModel(v4_ip="2001::2")
+        self.assertFailsValidation(giptm.full_clean, ['v4_ip',])
+    
+    def test_correct_v6_ip_passes(self):
+        giptm = GenericIPAddressTestModel(v6_ip="2001::2")
+        self.assertEqual(None, giptm.full_clean())
+    
+    def test_invalid_v6_ip_raises_error(self):
+        giptm = GenericIPAddressTestModel(v6_ip="1.2.3.4")
+        self.assertFailsValidation(giptm.full_clean, ['v6_ip',])
+        giptm = GenericIPAddressTestModel(v6_ip="1:2")
+        self.assertFailsValidation(giptm.full_clean, ['v6_ip',])
+    
+    def test_v6_uniqueness_detection(self):
+        # These two addresses are the same with different syntax
+        giptm = GenericIPAddressTestModel(generic_ip="2001::1:0:0:0:0:2")
+        giptm.save()
+        giptm = GenericIPAddressTestModel(generic_ip="2001:0:1:2")
+        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
+    
+    def test_v4_unpack_uniqueness_detection(self):
+        # These two are different, because we are not doing IPv4 unpacking
+        giptm = GenericIPAddressTestModel(generic_ip="::ffff:10.10.10.10")
+        giptm.save()
+        giptm = GenericIPAddressTestModel(generic_ip="10.10.10.10")
+        self.assertEqual(None, giptm.full_clean())
+    
+        # These two are the same, because we are doing IPv4 unpacking
+        giptm = GenericIPAddressWithUnpackUniqueTestModel(generic_v4unpack_ip="::ffff:18.52.18.52")
+        giptm.save()
+        giptm = GenericIPAddressWithUnpackUniqueTestModel(generic_v4unpack_ip="18.52.18.52")
+        self.assertFailsValidation(giptm.full_clean, ['generic_v4unpack_ip',])
+        
\ No newline at end of file
Index: tests/modeltests/validation/models.py
===================================================================
--- tests/modeltests/validation/models.py	(revision 16357)
+++ tests/modeltests/validation/models.py	(working copy)
@@ -81,4 +81,13 @@
 
 class UniqueErrorsModel(models.Model):
     name = models.CharField(max_length=100, unique=True, error_messages={'unique': u'Custom unique name message.'})
-    number = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'})
\ No newline at end of file
+    number = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'})
+    
+class GenericIPAddressTestModel(models.Model):
+    generic_ip = models.GenericIPAddressField(blank=True, unique=True)
+    v4_ip = models.GenericIPAddressField(blank=True, protocol="ipv4")
+    v6_ip = models.GenericIPAddressField(blank=True, protocol="ipv6")
+
+class GenericIPAddressWithUnpackUniqueTestModel(models.Model):
+    generic_v4unpack_ip = models.GenericIPAddressField(blank=True, unique=True, unpack_ipv4_mapped=True)
+    
\ No newline at end of file
Index: tests/regressiontests/forms/tests/error_messages.py
===================================================================
--- tests/regressiontests/forms/tests/error_messages.py	(revision 16357)
+++ tests/regressiontests/forms/tests/error_messages.py	(working copy)
@@ -196,6 +196,15 @@
         self.assertFormErrors([u'REQUIRED'], f.clean, '')
         self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
 
+    def test_generic_ipaddressfield(self):
+        e = {
+            'required': 'REQUIRED',
+            'invalid': 'INVALID IP ADDRESS',
+        }
+        f = GenericIPAddressField(error_messages=e)
+        self.assertFormErrors([u'REQUIRED'], f.clean, '')
+        self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
+
     def test_subclassing_errorlist(self):
         class TestForm(Form):
             first_name = CharField()
Index: tests/regressiontests/forms/tests/extra.py
===================================================================
--- tests/regressiontests/forms/tests/extra.py	(revision 16357)
+++ tests/regressiontests/forms/tests/extra.py	(working copy)
@@ -460,6 +460,89 @@
         self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
         self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
 
+
+    def test_generic_ipaddress_invalid_arguments(self):
+        self.assertRaises(ValueError, GenericIPAddressField, protocol="hamster")
+        self.assertRaises(ValueError, GenericIPAddressField, protocol="ipv4", unpack_ipv4_mapped=True)
+
+    def test_generic_ipaddress_as_generic(self):
+        # The edges of the IPv6 validation code are not deeply tested here,
+        # they are covered in the tests for django.utils.ipv6
+        
+        f = GenericIPAddressField()
+        self.assertFormErrors([u'This field is required.'], f.clean, '')
+        self.assertFormErrors([u'This field is required.'], f.clean, None)
+        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5')
+        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
+        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2')
+
+    def test_generic_ipaddress_as_ipv4_only(self):
+        f = GenericIPAddressField(protocol="IPv4")
+        self.assertFormErrors([u'This field is required.'], f.clean, '')
+        self.assertFormErrors([u'This field is required.'], f.clean, None)
+        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'fe80::223:6cff:fe8a:2e8a')
+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '2a02::223:6cff:fe8a:2e8a')
+
+    def test_generic_ipaddress_as_ipv4_only(self):                
+        f = GenericIPAddressField(protocol="IPv6")
+        self.assertFormErrors([u'This field is required.'], f.clean, '')
+        self.assertFormErrors([u'This field is required.'], f.clean, None)
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.1')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '256.125.1.5')
+        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
+        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '12345:2:3:4')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3::4')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2')
+
+    def test_generic_ipaddress_as_generic_not_required(self):
+        f = GenericIPAddressField(required=False)
+        self.assertEqual(f.clean(''), u'')
+        self.assertEqual(f.clean(None), u'')
+        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5')
+        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
+        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2')
+
+    def test_generic_ipaddress_normalisation(self):
+        # Test the normalising code
+        f = GenericIPAddressField()
+        self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
+        self.assertEqual(f.clean('::ffff:10.10.10.10'), u'::ffff:10.10.10.10')
+        self.assertEqual(f.clean('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
+        self.assertEqual(f.clean('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
+
+        f = GenericIPAddressField(unpack_ipv4_mapped=True)
+        self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'10.10.10.10')
+
+
     def test_smart_unicode(self):
         class Test:
             def __str__(self):
Index: tests/regressiontests/serializers_regress/tests.py
===================================================================
--- tests/regressiontests/serializers_regress/tests.py	(revision 16357)
+++ tests/regressiontests/serializers_regress/tests.py	(working copy)
@@ -196,6 +196,8 @@
     #(XX, ImageData
     (data_obj, 90, IPAddressData, "127.0.0.1"),
     (data_obj, 91, IPAddressData, None),
+    (data_obj, 95, GenericIPAddressData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
+    (data_obj, 96, GenericIPAddressData, None),
     (data_obj, 100, NullBooleanData, True),
     (data_obj, 101, NullBooleanData, False),
     (data_obj, 102, NullBooleanData, None),
@@ -298,6 +300,7 @@
     (pk_obj, 682, IntegerPKData, 0),
 #     (XX, ImagePKData
     (pk_obj, 690, IPAddressPKData, "127.0.0.1"),
+    (pk_obj, 695, GenericIPAddressPKData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
     # (pk_obj, 700, NullBooleanPKData, True),
     # (pk_obj, 701, NullBooleanPKData, False),
     (pk_obj, 710, PhonePKData, "212-634-5789"),
Index: tests/regressiontests/serializers_regress/models.py
===================================================================
--- tests/regressiontests/serializers_regress/models.py	(revision 16357)
+++ tests/regressiontests/serializers_regress/models.py	(working copy)
@@ -52,6 +52,9 @@
 class IPAddressData(models.Model):
     data = models.IPAddressField(null=True)
 
+class GenericIPAddressData(models.Model):
+    data = models.GenericIPAddressField(null=True)
+
 class NullBooleanData(models.Model):
     data = models.NullBooleanField(null=True)
 
@@ -187,6 +190,9 @@
 class IPAddressPKData(models.Model):
     data = models.IPAddressField(primary_key=True)
 
+class GenericIPAddressPKData(models.Model):
+    data = models.GenericIPAddressField(primary_key=True)
+
 # This is just a Boolean field with null=True, and we can't test a PK value of NULL.
 # class NullBooleanPKData(models.Model):
 #     data = models.NullBooleanField(primary_key=True)
Index: tests/regressiontests/utils/tests.py
===================================================================
--- tests/regressiontests/utils/tests.py	(revision 16357)
+++ tests/regressiontests/utils/tests.py	(working copy)
@@ -19,3 +19,4 @@
 from datetime_safe import *
 from baseconv import *
 from jslex import *
+from ipv6 import *
