Code

Ticket #811: genericipaddressfield.2.diff

File genericipaddressfield.2.diff, 54.2 KB (added by erikr, 3 years ago)

GenericIPAddressField patch

Line 
1Index: docs/topics/forms/modelforms.txt
2===================================================================
3--- docs/topics/forms/modelforms.txt    (revision 16357)
4+++ docs/topics/forms/modelforms.txt    (working copy)
5@@ -83,6 +83,8 @@
6 
7     ``IPAddressField``               ``IPAddressField``
8 
9+    ``GenericIPAddressField``            ``GenericIPAddressField``
10+
11     ``ManyToManyField``              ``ModelMultipleChoiceField`` (see
12                                      below)
13 
14Index: docs/ref/models/fields.txt
15===================================================================
16--- docs/ref/models/fields.txt  (revision 16357)
17+++ docs/ref/models/fields.txt  (working copy)
18@@ -760,6 +760,36 @@
19 An IP address, in string format (e.g. "192.0.2.30"). The admin represents this
20 as an ``<input type="text">`` (a single-line input).
21 
22+``GenericIPAddressField``
23+-------------------------
24+
25+.. class:: GenericIPAddressField([protocols=both, unpack_ipv4_mapped=False, **options])
26+
27+.. versionadded:: 1.4
28+
29+An IPv4 or IPv6 address, in string format (e.g. "192.0.2.30" or "2a02:42fe::4")
30+The admin represents this as an ``<input type="text">`` (a single-line input).
31+
32+The IPv6 address normalization follows `RFC4291 section 2.2`_, including using
33+the IPv4 format suggested in paragraph 3 of that section, like ``::ffff:192.0.2.0``.
34+For example, ``2001:0::0:01`` would be normalized to ``2001::1``, and ``::ffff:0a0a:0a0a``
35+to ``::ffff:10.10.10.10``. All characters are converted to lowercase.
36+
37+.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
38+
39+.. attribute:: GenericIPAddressField.protocol
40+
41+    Limits valid inputs to the specified protocol.
42+    Accepted values are ``both`` (default), ``IPv4``
43+    or ``IPv6``. Matching is case insensitive.
44+
45+.. attribute:: GenericIPAddressField.unpack_ipv4_mapped
46+
47+    Unpack IPv4 mapped addresses, like ``::ffff::192.0.2.1``.
48+    If this option is enabled, that address would be unpacked to
49+    ``192.0.2.1``. Default is disabled. Can only be used
50+    when ``protocol`` is set to ``both``.
51+
52 ``NullBooleanField``
53 --------------------
54 
55Index: docs/ref/forms/fields.txt
56===================================================================
57--- docs/ref/forms/fields.txt   (revision 16357)
58+++ docs/ref/forms/fields.txt   (working copy)
59@@ -622,6 +622,45 @@
60       expression.
61     * Error message keys: ``required``, ``invalid``
62 
63+``GenericIPAddressField``
64+~~~~~~~~~~~~~~~~~~~~~~~~~
65+
66+.. class:: GenericIPAddressField(**kwargs)
67+
68+.. versionadded:: 1.4
69+
70+A field containing either an IPv4 or an IPv6 address.
71+
72+    * Default widget: ``TextInput``
73+    * Empty value: ``''`` (an empty string)
74+    * Normalizes to: A Unicode object. IPv6 addresses are
75+      normalized as described below.
76+    * Validates that the given value is a valid IP address, using regular
77+      expressions.
78+    * Error message keys: ``required``, ``invalid``
79+
80+The IPv6 address normalization follows `RFC4291 section 2.2`_, including using
81+the IPv4 format suggested in paragraph 3 of that section, like ``::ffff:192.0.2.0``.
82+For example, ``2001:0::0:01`` would be normalized to ``2001::1``, and ``::ffff:0a0a:0a0a``
83+to ``::ffff:10.10.10.10``. All characters are converted to lowercase.
84+
85+.. _RFC4291 section 2.2: http://tools.ietf.org/html/rfc4291#section-2.2
86+
87+Takes two optional arguments:
88+
89+.. attribute:: GenericIPAddressField.protocol
90+
91+    Limits valid inputs to the specified protocol.
92+    Accepted values are ``both`` (default), ``IPv4``
93+    or ``IPv6``. Matching is case insensitive.
94+
95+.. attribute:: GenericIPAddressField.unpack_ipv4_mapped
96+
97+    Unpack IPv4 mapped addresses, like ``::ffff::192.0.2.1``.
98+    If this option is enabled, that address would be unpacked to
99+    ``192.0.2.1``. Default is disabled. Can only be used
100+    when ``protocol`` is set to ``both``.
101+
102 ``MultipleChoiceField``
103 ~~~~~~~~~~~~~~~~~~~~~~~
104 
105Index: docs/ref/validators.txt
106===================================================================
107--- docs/ref/validators.txt     (revision 16357)
108+++ docs/ref/validators.txt     (working copy)
109@@ -130,6 +130,23 @@
110     A :class:`RegexValidator` instance that ensures a value looks like an IPv4
111     address.
112 
113+``validate_ipv6_address``
114+-------------------------
115+.. versionadded:: 1.4
116+
117+.. data:: validate_ipv6_address
118+
119+    Uses :mod:`django.utils.ipv6` to check the validity of an IPv6 address.
120+
121+``validate_ipv46_address``
122+--------------------------
123+.. versionadded:: 1.4
124+
125+.. data:: validate_ipv46_address
126+
127+    Uses both ``validate_ipv4_address`` and ``validate_ipv6_address`` to ensure
128+    a value is either a valid IPv4 or IPv6 address.
129+
130 ``validate_comma_separated_integer_list``
131 -----------------------------------------
132 .. data:: validate_comma_separated_integer_list
133Index: django/db/models/fields/__init__.py
134===================================================================
135--- django/db/models/fields/__init__.py (revision 16357)
136+++ django/db/models/fields/__init__.py (working copy)
137@@ -17,6 +17,8 @@
138 from django.utils.translation import ugettext_lazy as _
139 from django.utils.encoding import smart_unicode, force_unicode, smart_str
140 from django.utils import datetime_safe
141+from django.utils.ipv6 import (clean_ipv6_address, is_valid_ipv6_address,
142+    validate_configure_ipaddressfield_settings)
143 
144 class NOT_PROVIDED:
145     pass
146@@ -920,7 +922,7 @@
147 
148 class IPAddressField(Field):
149     empty_strings_allowed = False
150-    description = _("IP address")
151+    description = _("IPv4 address")
152     def __init__(self, *args, **kwargs):
153         kwargs['max_length'] = 15
154         Field.__init__(self, *args, **kwargs)
155@@ -933,6 +935,44 @@
156         defaults.update(kwargs)
157         return super(IPAddressField, self).formfield(**defaults)
158 
159+class GenericIPAddressField(Field):
160+    empty_strings_allowed = True
161+    description = _("IP address")
162+    default_error_messages = {}
163+   
164+    def __init__(self, protocol='both', unpack_ipv4_mapped=False, *args, **kwargs):
165+        self.unpack_ipv4_mapped = unpack_ipv4_mapped
166+
167+        self.default_validators = validate_configure_ipaddressfield_settings(
168+                protocol, unpack_ipv4_mapped, self.default_error_messages)
169+
170+        kwargs['max_length'] = 39
171+        Field.__init__(self, *args, **kwargs)
172+
173+    def get_internal_type(self):
174+        return "GenericIPAddressField"
175+
176+    def to_python(self, value):
177+        if value and ':' in value:
178+            return clean_ipv6_address(value, self.unpack_ipv4_mapped, self.default_error_messages['invalid'])
179+               
180+        return value
181+
182+    def get_prep_value(self, value):       
183+        if value and ':' in value:
184+            try:
185+                return clean_ipv6_address(value, self.unpack_ipv4_mapped)
186+            except ValidationError:
187+                pass
188+               
189+        return value
190+
191+    def formfield(self, **kwargs):
192+        defaults = {'form_class': forms.GenericIPAddressField}
193+        defaults.update(kwargs)
194+        return super(GenericIPAddressField, self).formfield(**defaults)
195+       
196+
197 class NullBooleanField(Field):
198     empty_strings_allowed = False
199     default_error_messages = {
200Index: django/db/backends/sqlite3/creation.py
201===================================================================
202--- django/db/backends/sqlite3/creation.py      (revision 16357)
203+++ django/db/backends/sqlite3/creation.py      (working copy)
204@@ -20,6 +20,7 @@
205         'IntegerField':                 'integer',
206         'BigIntegerField':              'bigint',
207         'IPAddressField':               'char(15)',
208+        'GenericIPAddressField':        'char(39)',
209         'NullBooleanField':             'bool',
210         'OneToOneField':                'integer',
211         'PositiveIntegerField':         'integer unsigned',
212Index: django/db/backends/mysql/creation.py
213===================================================================
214--- django/db/backends/mysql/creation.py        (revision 16357)
215+++ django/db/backends/mysql/creation.py        (working copy)
216@@ -19,6 +19,7 @@
217         'IntegerField':      'integer',
218         'BigIntegerField':   'bigint',
219         'IPAddressField':    'char(15)',
220+        'GenericIPAddressField': 'char(39)',
221         'NullBooleanField':  'bool',
222         'OneToOneField':     'integer',
223         'PositiveIntegerField': 'integer UNSIGNED',
224Index: django/db/backends/oracle/creation.py
225===================================================================
226--- django/db/backends/oracle/creation.py       (revision 16357)
227+++ django/db/backends/oracle/creation.py       (working copy)
228@@ -27,6 +27,7 @@
229         'IntegerField':                 'NUMBER(11)',
230         'BigIntegerField':              'NUMBER(19)',
231         'IPAddressField':               'VARCHAR2(15)',
232+        'GenericIPAddressField':        'VARCHAR2(39)',
233         'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
234         'OneToOneField':                'NUMBER(11)',
235         'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
236Index: django/db/backends/postgresql_psycopg2/introspection.py
237===================================================================
238--- django/db/backends/postgresql_psycopg2/introspection.py     (revision 16357)
239+++ django/db/backends/postgresql_psycopg2/introspection.py     (working copy)
240@@ -12,6 +12,7 @@
241         700: 'FloatField',
242         701: 'FloatField',
243         869: 'IPAddressField',
244+        869: 'GenericIPAddressField',
245         1043: 'CharField',
246         1082: 'DateField',
247         1083: 'TimeField',
248Index: django/db/backends/postgresql_psycopg2/creation.py
249===================================================================
250--- django/db/backends/postgresql_psycopg2/creation.py  (revision 16357)
251+++ django/db/backends/postgresql_psycopg2/creation.py  (working copy)
252@@ -21,6 +21,7 @@
253         'IntegerField':      'integer',
254         'BigIntegerField':   'bigint',
255         'IPAddressField':    'inet',
256+        'GenericIPAddressField': 'inet',
257         'NullBooleanField':  'boolean',
258         'OneToOneField':     'integer',
259         'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
260Index: django/forms/fields.py
261===================================================================
262--- django/forms/fields.py      (revision 16357)
263+++ django/forms/fields.py      (working copy)
264@@ -9,6 +9,7 @@
265 import time
266 import urlparse
267 import warnings
268+import socket
269 from decimal import Decimal, DecimalException
270 try:
271     from cStringIO import StringIO
272@@ -21,6 +22,7 @@
273 from django.utils.translation import ugettext_lazy as _
274 from django.utils.encoding import smart_unicode, smart_str, force_unicode
275 from django.utils.functional import lazy
276+from django.utils.ipv6 import clean_ipv6_address, validate_configure_ipaddressfield_settings
277 
278 # Provide this import for backwards compatibility.
279 from django.core.validators import EMPTY_VALUES
280@@ -37,8 +39,8 @@
281     'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
282     'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
283     'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
284-    'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
285-    'TypedChoiceField', 'TypedMultipleChoiceField'
286+    'SplitDateTimeField', 'IPAddressField', 'GenericIPAddressField', 'FilePathField',
287+    'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField'
288 )
289 
290 
291@@ -955,6 +957,27 @@
292     default_validators = [validators.validate_ipv4_address]
293 
294 
295+class GenericIPAddressField(CharField):
296+    default_error_messages = {}
297+   
298+    def __init__(self, protocol='both', unpack_ipv4_mapped=False, *args, **kwargs):
299+        self.unpack_ipv4_mapped = unpack_ipv4_mapped
300+
301+        self.default_validators = validate_configure_ipaddressfield_settings(
302+                protocol, unpack_ipv4_mapped, self.default_error_messages)
303+       
304+        super(GenericIPAddressField, self).__init__(*args, **kwargs)
305+       
306+    def to_python(self, value):
307+        if not value:
308+            return ''
309+       
310+        if value and ':' in value:
311+                return clean_ipv6_address(value, self.unpack_ipv4_mapped, self.default_error_messages['invalid'])
312+
313+        return value
314+               
315+
316 class SlugField(CharField):
317     default_error_messages = {
318         'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers,"
319Index: django/core/validators.py
320===================================================================
321--- django/core/validators.py   (revision 16357)
322+++ django/core/validators.py   (working copy)
323@@ -5,6 +5,7 @@
324 from django.core.exceptions import ValidationError
325 from django.utils.translation import ugettext_lazy as _
326 from django.utils.encoding import smart_unicode
327+from django.utils.ipv6 import is_valid_ipv6_address
328 
329 # These values, if given to validate(), will trigger the self.required check.
330 EMPTY_VALUES = (None, '', [], (), {})
331@@ -145,6 +146,19 @@
332 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}$')
333 validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
334 
335+def validate_ipv6_address(value):
336+    if not is_valid_ipv6_address(value):
337+        raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid')
338+
339+def validate_ipv46_address(value):
340+    try:
341+        validate_ipv4_address(value)
342+    except ValidationError:
343+        try:
344+            validate_ipv6_address(value)
345+        except ValidationError:
346+            raise ValidationError(_(u'Enter a valid IPv4 or IPv6 address.'), code='invalid')
347+           
348 comma_separated_int_list_re = re.compile('^[\d,]+$')
349 validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
350 
351Index: django/utils/ipv6.py
352===================================================================
353--- django/utils/ipv6.py        (revision 0)
354+++ django/utils/ipv6.py        (revision 0)
355@@ -0,0 +1,590 @@
356+# This code was mostly based on ipaddr-py
357+# Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/
358+# Licensed under the Apache License, Version 2.0 (the "License").
359+
360+from django.core.exceptions import ValidationError
361+from django.utils.translation import ugettext_lazy as _
362+
363+def clean_ipv6_address(ip_str, unpack_ipv4_mapped=False, error_message="This is not a valid IPv6 address"):
364+    """Cleans a IPv6 address string.
365+   
366+    Validity is checked by calling is_valid_ipv6_address() - if an
367+    invalid address is passed, ValidationError is raised.
368+   
369+    Replaces the longest continious zero-sequence with "::" and
370+    removes leading zeroes and makes sure all hextets are lowercase.
371+
372+    Args:
373+        ip_str: A valid IPv6 address.
374+        unpack_ipv4_mapped: if an IPv4-mapped address is found,
375+        return the plain IPv4 address (default=False).
376+        error_message: A error message for in the ValidationError.
377+
378+    Returns:
379+        A compressed IPv6 address, or the same value
380+
381+    """
382+    best_doublecolon_start = -1
383+    best_doublecolon_len = 0
384+    doublecolon_start = -1
385+    doublecolon_len = 0
386+   
387+    if not is_valid_ipv6_address(ip_str):
388+        raise ValidationError(error_message)
389+       
390+    # This algorithm can only handle fully exploded
391+    # IP strings
392+    ip_str = _explode_shorthand_ip_string(ip_str)
393+   
394+    ip_str = _sanitize_ipv4_mapping(ip_str)
395+
396+    # If needed, unpack the IPv4 and return straight away
397+    # - no need in running the rest of the algorithm
398+    if unpack_ipv4_mapped:
399+        ipv4_unpacked = _unpack_ipv4(ip_str)
400+       
401+        if ipv4_unpacked:
402+            return ipv4_unpacked
403+   
404+    hextets = ip_str.split(":")
405+   
406+    for index in range(len(hextets)):
407+        # Remove leading zeroes
408+        hextets[index] = hextets[index].lstrip('0')
409+        if not hextets[index]:
410+            hextets[index] = '0'
411+
412+        # Determine best hextet to compress
413+        if hextets[index] == '0':
414+            doublecolon_len += 1
415+            if doublecolon_start == -1:
416+                # Start of a sequence of zeros.
417+                doublecolon_start = index
418+            if doublecolon_len > best_doublecolon_len:
419+                # This is the longest sequence of zeros so far.
420+                best_doublecolon_len = doublecolon_len
421+                best_doublecolon_start = doublecolon_start
422+        else:
423+            doublecolon_len = 0
424+            doublecolon_start = -1
425+
426+    # Compress the most suitable hextet
427+    if best_doublecolon_len > 1:
428+        best_doublecolon_end = (best_doublecolon_start +
429+                                best_doublecolon_len)
430+        # For zeros at the end of the address.
431+        if best_doublecolon_end == len(hextets):
432+            hextets += ['']
433+        hextets[best_doublecolon_start:best_doublecolon_end] = ['']
434+        # For zeros at the beginning of the address.
435+        if best_doublecolon_start == 0:
436+            hextets = [''] + hextets
437+   
438+    result = ":".join(hextets)
439+
440+    return result.lower()
441+
442+
443+def _sanitize_ipv4_mapping(ip_str):
444+    """Sanitize IPv4 mapping in a expanded IPv6 address.
445+   
446+    This converts ::ffff:0a0a:0a0a to ::ffff:10.10.10.10.
447+    If there is nothing to sanitize, returns an unchanged
448+    string.
449+   
450+    Args:
451+        ip_str: A string, the expanded IPv6 address.
452+
453+    Returns:
454+        The sanitized output string, if applicable.
455+    """
456+    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
457+        # not an ipv4 mapping
458+        return ip_str
459+   
460+    hextets = ip_str.split(':')
461+   
462+    if '.' in hextets[-1]:
463+        # already sanitized
464+        return ip_str
465+
466+    ipv4_address = "%d.%d.%d.%d" % (
467+        int(hextets[6][0:2], 16),
468+        int(hextets[6][2:4], 16),
469+        int(hextets[7][0:2], 16),
470+        int(hextets[7][2:4], 16),
471+    )
472+   
473+    result = ':'.join(hextets[0:6])
474+    result += ':' + ipv4_address
475+   
476+    return result
477+
478+def _unpack_ipv4(ip_str):
479+    """Unpack an IPv4 address that was mapped in a compressed IPv6 address.
480+   
481+    This converts 0000:0000:0000:0000:0000:ffff:10.10.10.10 to 10.10.10.10.
482+    If there is nothing to sanitize, returns None.
483+   
484+    Args:
485+        ip_str: A string, the expanded IPv6 address.
486+
487+    Returns:
488+        The unpacked IPv4 address, or None if there was nothing to unpack.
489+    """
490+    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
491+        return None
492+   
493+    hextets = ip_str.split(':')
494+    return hextets[-1]
495+   
496+def is_valid_ipv6_address(ip_str):
497+    """Ensure we have a valid IPv6 address.
498+
499+    Args:
500+        ip_str: A string, the IPv6 address.
501+
502+    Returns:
503+        A boolean, True if this is a valid IPv6 address.
504+
505+    """
506+    from django.core.validators import validate_ipv4_address
507+   
508+    # We need to have at least one ':'.
509+    if ':' not in ip_str:
510+        return False
511+
512+    # We can only have one '::' shortener.
513+    if ip_str.count('::') > 1:
514+        return False
515+       
516+    # '::' should be encompassed by start, digits or end.
517+    if ':::' in ip_str:
518+        return False
519+
520+    # A single colon can neither start nor end an address.
521+    if ((ip_str.startswith(':') and not ip_str.startswith('::')) or
522+            (ip_str.endswith(':') and not ip_str.endswith('::'))):
523+        return False
524+
525+    # We can never have more than 7 ':' (1::2:3:4:5:6:7:8 is invalid)
526+    if ip_str.count(':') > 7:
527+        return False
528+
529+    # If we have no concatenation, we need to have 8 fields with 7 ':'.
530+    if '::' not in ip_str and ip_str.count(':') != 7:
531+        # We might have an IPv4 mapped address.
532+        if ip_str.count('.') != 3:
533+            return False
534+
535+    ip_str = _explode_shorthand_ip_string(ip_str)
536+
537+    # Now that we have that all squared away, let's check that each of the
538+    # hextets are between 0x0 and 0xFFFF.
539+    for hextet in ip_str.split(':'):
540+        if hextet.count('.') == 3:
541+            # If we have an IPv4 mapped address, the IPv4 portion has to
542+            # be at the end of the IPv6 portion.
543+            if not ip_str.split(':')[-1] == hextet:
544+                return False
545+            try:
546+                validate_ipv4_address(hextet)
547+            except ValidationError:
548+                return False
549+        else:
550+            try:
551+                # a value error here means that we got a bad hextet,
552+                # something like 0xzzzz
553+                if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF:
554+                    return False
555+            except ValueError:
556+                return False
557+    return True
558+   
559+   
560+def _explode_shorthand_ip_string(ip_str):
561+    """Expand a shortened IPv6 address.
562+
563+    Args:
564+        ip_str: A string, the IPv6 address.
565+
566+    Returns:
567+        A string, the expanded IPv6 address.
568+
569+    """
570+    if not _is_shorthand_ip(ip_str):
571+        # We've already got a longhand ip_str.
572+        return ip_str
573+       
574+    new_ip = []
575+    hextet = ip_str.split('::')
576+
577+    # If there is a ::, we need to expand it with zeroes
578+    # to get to 8 hextets - unless there is a dot in the last hextet,
579+    # meaning we're doing v4-mapping
580+    if '.' in ip_str.split(':')[-1]:
581+        fill_to = 7
582+    else:
583+        fill_to = 8
584+   
585+    if len(hextet) > 1:
586+        sep = len(hextet[0].split(':')) + len(hextet[1].split(':'))
587+        new_ip = hextet[0].split(':')
588+
589+        for _ in xrange(fill_to - sep):
590+            new_ip.append('0000')
591+        new_ip += hextet[1].split(':')
592+
593+    else:
594+        new_ip = ip_str.split(':')
595+       
596+    # Now need to make sure every hextet is 4 lower case characters.
597+    # If a hextet is < 4 characters, we've got missing leading 0's.
598+    ret_ip = []
599+    for hextet in new_ip:
600+        ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower())
601+    return ':'.join(ret_ip)
602+
603+
604+def _is_shorthand_ip(ip_str):
605+    """Determine if the address is shortened.
606+
607+    Args:
608+        ip_str: A string, the IPv6 address.
609+
610+    Returns:
611+        A boolean, True if the address is shortened.
612+
613+    """
614+    if ip_str.count('::') == 1:
615+        return True
616+    if filter(lambda x: len(x) < 4, ip_str.split(':')):
617+        return True
618+    return False
619+
620+
621+def validate_configure_ipaddressfield_settings(protocol, unpack_ipv4_mapped, default_error_messages):
622+    """Set up an GenericIPAddressField instance and validate it's inputs.
623+   
624+    This code is here, because it is exactly the same for the model and the form field.
625+    """
626+    from django.core import validators
627+   
628+    if protocol != 'both' and unpack_ipv4_mapped:
629+        raise ValueError(
630+            "You can only use `unpack_ipv4_mapped` if `protocol` is set to 'both'")
631+   
632+    if protocol.lower() == "both":
633+        default_validators = [validators.validate_ipv46_address]
634+        default_error_messages['invalid'] = _(u'Enter a valid IPv4 or IPv6 address.')
635+
636+    elif protocol.lower() == "ipv4":
637+        default_validators = [validators.validate_ipv4_address]
638+        default_error_messages['invalid'] = _(u'Enter a valid IPv4 address.')
639+
640+    elif protocol.lower() == "ipv6":
641+        default_validators = [validators.validate_ipv6_address]
642+        default_error_messages['invalid'] = _(u'Enter a valid IPv6 address.')
643+       
644+    else:
645+        raise ValueError(
646+            "The protocol '%s' is not a known protocol for GenericIPAddressField"
647+            % protocol)
648+           
649+    return default_validators
650+           
651+# This code was mostly based on ipaddr-py
652+# Copyright 2007 Google Inc. http://code.google.com/p/ipaddr-py/
653+# Licensed under the Apache License, Version 2.0 (the "License").
654+
655+from django.core.exceptions import ValidationError
656+from django.utils.translation import ugettext_lazy as _
657+
658+def clean_ipv6_address(ip_str, unpack_ipv4_mapped=False, error_message="This is not a valid IPv6 address"):
659+    """Cleans a IPv6 address string.
660+   
661+    Validity is checked by calling is_valid_ipv6_address() - if an
662+    invalid address is passed, ValidationError is raised.
663+   
664+    Replaces the longest continious zero-sequence with "::" and
665+    removes leading zeroes and makes sure all hextets are lowercase.
666+
667+    Args:
668+        ip_str: A valid IPv6 address.
669+        unpack_ipv4_mapped: if an IPv4-mapped address is found,
670+        return the plain IPv4 address (default=False).
671+        error_message: A error message for in the ValidationError.
672+
673+    Returns:
674+        A compressed IPv6 address, or the same value
675+
676+    """
677+    best_doublecolon_start = -1
678+    best_doublecolon_len = 0
679+    doublecolon_start = -1
680+    doublecolon_len = 0
681+   
682+    if not is_valid_ipv6_address(ip_str):
683+        raise ValidationError(error_message)
684+       
685+    # This algorithm can only handle fully exploded
686+    # IP strings
687+    ip_str = _explode_shorthand_ip_string(ip_str)
688+   
689+    ip_str = _sanitize_ipv4_mapping(ip_str)
690+
691+    # If needed, unpack the IPv4 and return straight away
692+    # - no need in running the rest of the algorithm
693+    if unpack_ipv4_mapped:
694+        ipv4_unpacked = _unpack_ipv4(ip_str)
695+       
696+        if ipv4_unpacked:
697+            return ipv4_unpacked
698+   
699+    hextets = ip_str.split(":")
700+   
701+    for index in range(len(hextets)):
702+        # Remove leading zeroes
703+        hextets[index] = hextets[index].lstrip('0')
704+        if not hextets[index]:
705+            hextets[index] = '0'
706+
707+        # Determine best hextet to compress
708+        if hextets[index] == '0':
709+            doublecolon_len += 1
710+            if doublecolon_start == -1:
711+                # Start of a sequence of zeros.
712+                doublecolon_start = index
713+            if doublecolon_len > best_doublecolon_len:
714+                # This is the longest sequence of zeros so far.
715+                best_doublecolon_len = doublecolon_len
716+                best_doublecolon_start = doublecolon_start
717+        else:
718+            doublecolon_len = 0
719+            doublecolon_start = -1
720+
721+    # Compress the most suitable hextet
722+    if best_doublecolon_len > 1:
723+        best_doublecolon_end = (best_doublecolon_start +
724+                                best_doublecolon_len)
725+        # For zeros at the end of the address.
726+        if best_doublecolon_end == len(hextets):
727+            hextets += ['']
728+        hextets[best_doublecolon_start:best_doublecolon_end] = ['']
729+        # For zeros at the beginning of the address.
730+        if best_doublecolon_start == 0:
731+            hextets = [''] + hextets
732+   
733+    result = ":".join(hextets)
734+
735+    return result.lower()
736+
737+
738+def _sanitize_ipv4_mapping(ip_str):
739+    """Sanitize IPv4 mapping in a expanded IPv6 address.
740+   
741+    This converts ::ffff:0a0a:0a0a to ::ffff:10.10.10.10.
742+    If there is nothing to sanitize, returns an unchanged
743+    string.
744+   
745+    Args:
746+        ip_str: A string, the expanded IPv6 address.
747+
748+    Returns:
749+        The sanitized output string, if applicable.
750+    """
751+    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
752+        # not an ipv4 mapping
753+        return ip_str
754+   
755+    hextets = ip_str.split(':')
756+   
757+    if '.' in hextets[-1]:
758+        # already sanitized
759+        return ip_str
760+
761+    ipv4_address = "%d.%d.%d.%d" % (
762+        int(hextets[6][0:2], 16),
763+        int(hextets[6][2:4], 16),
764+        int(hextets[7][0:2], 16),
765+        int(hextets[7][2:4], 16),
766+    )
767+   
768+    result = ':'.join(hextets[0:6])
769+    result += ':' + ipv4_address
770+   
771+    return result
772+
773+def _unpack_ipv4(ip_str):
774+    """Unpack an IPv4 address that was mapped in a compressed IPv6 address.
775+   
776+    This converts 0000:0000:0000:0000:0000:ffff:10.10.10.10 to 10.10.10.10.
777+    If there is nothing to sanitize, returns None.
778+   
779+    Args:
780+        ip_str: A string, the expanded IPv6 address.
781+
782+    Returns:
783+        The unpacked IPv4 address, or None if there was nothing to unpack.
784+    """
785+    if not ip_str.lower().startswith('0000:0000:0000:0000:0000:ffff:'):
786+        return None
787+   
788+    hextets = ip_str.split(':')
789+    return hextets[-1]
790+   
791+def is_valid_ipv6_address(ip_str):
792+    """Ensure we have a valid IPv6 address.
793+
794+    Args:
795+        ip_str: A string, the IPv6 address.
796+
797+    Returns:
798+        A boolean, True if this is a valid IPv6 address.
799+
800+    """
801+    from django.core.validators import validate_ipv4_address
802+   
803+    # We need to have at least one ':'.
804+    if ':' not in ip_str:
805+        return False
806+
807+    # We can only have one '::' shortener.
808+    if ip_str.count('::') > 1:
809+        return False
810+       
811+    # '::' should be encompassed by start, digits or end.
812+    if ':::' in ip_str:
813+        return False
814+
815+    # A single colon can neither start nor end an address.
816+    if ((ip_str.startswith(':') and not ip_str.startswith('::')) or
817+            (ip_str.endswith(':') and not ip_str.endswith('::'))):
818+        return False
819+
820+    # We can never have more than 7 ':' (1::2:3:4:5:6:7:8 is invalid)
821+    if ip_str.count(':') > 7:
822+        return False
823+
824+    # If we have no concatenation, we need to have 8 fields with 7 ':'.
825+    if '::' not in ip_str and ip_str.count(':') != 7:
826+        # We might have an IPv4 mapped address.
827+        if ip_str.count('.') != 3:
828+            return False
829+
830+    ip_str = _explode_shorthand_ip_string(ip_str)
831+
832+    # Now that we have that all squared away, let's check that each of the
833+    # hextets are between 0x0 and 0xFFFF.
834+    for hextet in ip_str.split(':'):
835+        if hextet.count('.') == 3:
836+            # If we have an IPv4 mapped address, the IPv4 portion has to
837+            # be at the end of the IPv6 portion.
838+            if not ip_str.split(':')[-1] == hextet:
839+                return False
840+            try:
841+                validate_ipv4_address(hextet)
842+            except ValidationError:
843+                return False
844+        else:
845+            try:
846+                # a value error here means that we got a bad hextet,
847+                # something like 0xzzzz
848+                if int(hextet, 16) < 0x0 or int(hextet, 16) > 0xFFFF:
849+                    return False
850+            except ValueError:
851+                return False
852+    return True
853+   
854+   
855+def _explode_shorthand_ip_string(ip_str):
856+    """Expand a shortened IPv6 address.
857+
858+    Args:
859+        ip_str: A string, the IPv6 address.
860+
861+    Returns:
862+        A string, the expanded IPv6 address.
863+
864+    """
865+    if not _is_shorthand_ip(ip_str):
866+        # We've already got a longhand ip_str.
867+        return ip_str
868+       
869+    new_ip = []
870+    hextet = ip_str.split('::')
871+
872+    # If there is a ::, we need to expand it with zeroes
873+    # to get to 8 hextets - unless there is a dot in the last hextet,
874+    # meaning we're doing v4-mapping
875+    if '.' in ip_str.split(':')[-1]:
876+        fill_to = 7
877+    else:
878+        fill_to = 8
879+   
880+    if len(hextet) > 1:
881+        sep = len(hextet[0].split(':')) + len(hextet[1].split(':'))
882+        new_ip = hextet[0].split(':')
883+
884+        for _ in xrange(fill_to - sep):
885+            new_ip.append('0000')
886+        new_ip += hextet[1].split(':')
887+
888+    else:
889+        new_ip = ip_str.split(':')
890+       
891+    # Now need to make sure every hextet is 4 lower case characters.
892+    # If a hextet is < 4 characters, we've got missing leading 0's.
893+    ret_ip = []
894+    for hextet in new_ip:
895+        ret_ip.append(('0' * (4 - len(hextet)) + hextet).lower())
896+    return ':'.join(ret_ip)
897+
898+
899+def _is_shorthand_ip(ip_str):
900+    """Determine if the address is shortened.
901+
902+    Args:
903+        ip_str: A string, the IPv6 address.
904+
905+    Returns:
906+        A boolean, True if the address is shortened.
907+
908+    """
909+    if ip_str.count('::') == 1:
910+        return True
911+    if filter(lambda x: len(x) < 4, ip_str.split(':')):
912+        return True
913+    return False
914+
915+
916+def validate_configure_ipaddressfield_settings(protocol, unpack_ipv4_mapped, default_error_messages):
917+    """Set up an GenericIPAddressField instance and validate it's inputs.
918+   
919+    This code is here, because it is exactly the same for the model and the form field.
920+    """
921+    from django.core import validators
922+   
923+    if protocol != 'both' and unpack_ipv4_mapped:
924+        raise ValueError(
925+            "You can only use `unpack_ipv4_mapped` if `protocol` is set to 'both'")
926+   
927+    if protocol.lower() == "both":
928+        default_validators = [validators.validate_ipv46_address]
929+        default_error_messages['invalid'] = _(u'Enter a valid IPv4 or IPv6 address.')
930+
931+    elif protocol.lower() == "ipv4":
932+        default_validators = [validators.validate_ipv4_address]
933+        default_error_messages['invalid'] = _(u'Enter a valid IPv4 address.')
934+
935+    elif protocol.lower() == "ipv6":
936+        default_validators = [validators.validate_ipv6_address]
937+        default_error_messages['invalid'] = _(u'Enter a valid IPv6 address.')
938+       
939+    else:
940+        raise ValueError(
941+            "The protocol '%s' is not a known protocol for GenericIPAddressField"
942+            % protocol)
943+           
944+    return default_validators
945+           
946Index: tests/modeltests/validators/tests.py
947===================================================================
948--- tests/modeltests/validators/tests.py        (revision 16357)
949+++ tests/modeltests/validators/tests.py        (working copy)
950@@ -52,6 +52,31 @@
951     (validate_ipv4_address, '25,1,1,1', ValidationError),
952     (validate_ipv4_address, '25.1 .1.1', ValidationError),
953 
954+    # validate_ipv6_address uses django.utils.ipv6, which
955+    # is tested in much greater detail in it's own testcase
956+    (validate_ipv6_address, 'fe80::1', None),
957+    (validate_ipv6_address, '::1', None),
958+    (validate_ipv6_address, '1:2:3:4:5:6:7:8', None),
959+
960+    (validate_ipv6_address, '1:2', ValidationError),
961+    (validate_ipv6_address, '::zzz', ValidationError),
962+    (validate_ipv6_address, '12345::', ValidationError),
963+
964+    (validate_ipv46_address, '1.1.1.1', None),
965+    (validate_ipv46_address, '255.0.0.0', None),
966+    (validate_ipv46_address, '0.0.0.0', None),
967+    (validate_ipv46_address, 'fe80::1', None),
968+    (validate_ipv46_address, '::1', None),
969+    (validate_ipv46_address, '1:2:3:4:5:6:7:8', None),
970+
971+    (validate_ipv46_address, '256.1.1.1', ValidationError),
972+    (validate_ipv46_address, '25.1.1.', ValidationError),
973+    (validate_ipv46_address, '25,1,1,1', ValidationError),
974+    (validate_ipv46_address, '25.1 .1.1', ValidationError),
975+    (validate_ipv46_address, '1:2', ValidationError),
976+    (validate_ipv46_address, '::zzz', ValidationError),
977+    (validate_ipv46_address, '12345::', ValidationError),
978+
979     (validate_comma_separated_integer_list, '1', None),
980     (validate_comma_separated_integer_list, '1,2,3', None),
981     (validate_comma_separated_integer_list, '1,2,3,', None),
982Index: tests/modeltests/validation/tests.py
983===================================================================
984--- tests/modeltests/validation/tests.py        (revision 16357)
985+++ tests/modeltests/validation/tests.py        (working copy)
986@@ -2,7 +2,8 @@
987 from django.test import TestCase
988 from django.core.exceptions import NON_FIELD_ERRORS
989 from modeltests.validation import ValidationTestCase
990-from modeltests.validation.models import Author, Article, ModelToValidate
991+from modeltests.validation.models import (Author, Article, ModelToValidate,
992+    GenericIPAddressTestModel, GenericIPAddressWithUnpackUniqueTestModel)
993 
994 # Import other tests for this package.
995 from modeltests.validation.validators import TestModelsWithValidators
996@@ -77,6 +78,7 @@
997         mtv = ModelToValidate(number=10, name='Some Name'*100)
998         self.assertFailsValidation(mtv.full_clean, ['name',])
999 
1000+
1001 class ArticleForm(forms.ModelForm):
1002     class Meta:
1003         model = Article
1004@@ -124,3 +126,59 @@
1005         article = Article(author_id=self.author.id)
1006         form = ArticleForm(data, instance=article)
1007         self.assertEqual(form.errors.keys(), ['pub_date'])
1008+
1009+
1010+class GenericIPAddressFieldTests(ValidationTestCase):
1011+   
1012+    def test_correct_generic_ip_passes(self):
1013+        giptm = GenericIPAddressTestModel(generic_ip="1.2.3.4")
1014+        self.assertEqual(None, giptm.full_clean())
1015+        giptm = GenericIPAddressTestModel(generic_ip="2001::2")
1016+        self.assertEqual(None, giptm.full_clean())
1017+   
1018+    def test_invalid_generic_ip_raises_error(self):
1019+        giptm = GenericIPAddressTestModel(generic_ip="294.4.2.1")
1020+        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
1021+        giptm = GenericIPAddressTestModel(generic_ip="1:2")
1022+        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
1023+   
1024+    def test_correct_v4_ip_passes(self):
1025+        giptm = GenericIPAddressTestModel(v4_ip="1.2.3.4")
1026+        self.assertEqual(None, giptm.full_clean())
1027+   
1028+    def test_invalid_v4_ip_raises_error(self):
1029+        giptm = GenericIPAddressTestModel(v4_ip="294.4.2.1")
1030+        self.assertFailsValidation(giptm.full_clean, ['v4_ip',])
1031+        giptm = GenericIPAddressTestModel(v4_ip="2001::2")
1032+        self.assertFailsValidation(giptm.full_clean, ['v4_ip',])
1033+   
1034+    def test_correct_v6_ip_passes(self):
1035+        giptm = GenericIPAddressTestModel(v6_ip="2001::2")
1036+        self.assertEqual(None, giptm.full_clean())
1037+   
1038+    def test_invalid_v6_ip_raises_error(self):
1039+        giptm = GenericIPAddressTestModel(v6_ip="1.2.3.4")
1040+        self.assertFailsValidation(giptm.full_clean, ['v6_ip',])
1041+        giptm = GenericIPAddressTestModel(v6_ip="1:2")
1042+        self.assertFailsValidation(giptm.full_clean, ['v6_ip',])
1043+   
1044+    def test_v6_uniqueness_detection(self):
1045+        # These two addresses are the same with different syntax
1046+        giptm = GenericIPAddressTestModel(generic_ip="2001::1:0:0:0:0:2")
1047+        giptm.save()
1048+        giptm = GenericIPAddressTestModel(generic_ip="2001:0:1:2")
1049+        self.assertFailsValidation(giptm.full_clean, ['generic_ip',])
1050+   
1051+    def test_v4_unpack_uniqueness_detection(self):
1052+        # These two are different, because we are not doing IPv4 unpacking
1053+        giptm = GenericIPAddressTestModel(generic_ip="::ffff:10.10.10.10")
1054+        giptm.save()
1055+        giptm = GenericIPAddressTestModel(generic_ip="10.10.10.10")
1056+        self.assertEqual(None, giptm.full_clean())
1057+   
1058+        # These two are the same, because we are doing IPv4 unpacking
1059+        giptm = GenericIPAddressWithUnpackUniqueTestModel(generic_v4unpack_ip="::ffff:18.52.18.52")
1060+        giptm.save()
1061+        giptm = GenericIPAddressWithUnpackUniqueTestModel(generic_v4unpack_ip="18.52.18.52")
1062+        self.assertFailsValidation(giptm.full_clean, ['generic_v4unpack_ip',])
1063+       
1064\ No newline at end of file
1065Index: tests/modeltests/validation/models.py
1066===================================================================
1067--- tests/modeltests/validation/models.py       (revision 16357)
1068+++ tests/modeltests/validation/models.py       (working copy)
1069@@ -81,4 +81,13 @@
1070 
1071 class UniqueErrorsModel(models.Model):
1072     name = models.CharField(max_length=100, unique=True, error_messages={'unique': u'Custom unique name message.'})
1073-    number = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'})
1074\ No newline at end of file
1075+    number = models.IntegerField(unique=True, error_messages={'unique': u'Custom unique number message.'})
1076+   
1077+class GenericIPAddressTestModel(models.Model):
1078+    generic_ip = models.GenericIPAddressField(blank=True, unique=True)
1079+    v4_ip = models.GenericIPAddressField(blank=True, protocol="ipv4")
1080+    v6_ip = models.GenericIPAddressField(blank=True, protocol="ipv6")
1081+
1082+class GenericIPAddressWithUnpackUniqueTestModel(models.Model):
1083+    generic_v4unpack_ip = models.GenericIPAddressField(blank=True, unique=True, unpack_ipv4_mapped=True)
1084+   
1085\ No newline at end of file
1086Index: tests/regressiontests/forms/tests/error_messages.py
1087===================================================================
1088--- tests/regressiontests/forms/tests/error_messages.py (revision 16357)
1089+++ tests/regressiontests/forms/tests/error_messages.py (working copy)
1090@@ -196,6 +196,15 @@
1091         self.assertFormErrors([u'REQUIRED'], f.clean, '')
1092         self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
1093 
1094+    def test_generic_ipaddressfield(self):
1095+        e = {
1096+            'required': 'REQUIRED',
1097+            'invalid': 'INVALID IP ADDRESS',
1098+        }
1099+        f = GenericIPAddressField(error_messages=e)
1100+        self.assertFormErrors([u'REQUIRED'], f.clean, '')
1101+        self.assertFormErrors([u'INVALID IP ADDRESS'], f.clean, '127.0.0')
1102+
1103     def test_subclassing_errorlist(self):
1104         class TestForm(Form):
1105             first_name = CharField()
1106Index: tests/regressiontests/forms/tests/extra.py
1107===================================================================
1108--- tests/regressiontests/forms/tests/extra.py  (revision 16357)
1109+++ tests/regressiontests/forms/tests/extra.py  (working copy)
1110@@ -460,6 +460,89 @@
1111         self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
1112         self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
1113 
1114+
1115+    def test_generic_ipaddress_invalid_arguments(self):
1116+        self.assertRaises(ValueError, GenericIPAddressField, protocol="hamster")
1117+        self.assertRaises(ValueError, GenericIPAddressField, protocol="ipv4", unpack_ipv4_mapped=True)
1118+
1119+    def test_generic_ipaddress_as_generic(self):
1120+        # The edges of the IPv6 validation code are not deeply tested here,
1121+        # they are covered in the tests for django.utils.ipv6
1122+       
1123+        f = GenericIPAddressField()
1124+        self.assertFormErrors([u'This field is required.'], f.clean, '')
1125+        self.assertFormErrors([u'This field is required.'], f.clean, None)
1126+        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
1127+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo')
1128+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.')
1129+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5')
1130+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5')
1131+        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
1132+        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
1133+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4')
1134+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4')
1135+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
1136+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
1137+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2')
1138+
1139+    def test_generic_ipaddress_as_ipv4_only(self):
1140+        f = GenericIPAddressField(protocol="IPv4")
1141+        self.assertFormErrors([u'This field is required.'], f.clean, '')
1142+        self.assertFormErrors([u'This field is required.'], f.clean, None)
1143+        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
1144+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'foo')
1145+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '127.0.0.')
1146+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '1.2.3.4.5')
1147+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '256.125.1.5')
1148+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, 'fe80::223:6cff:fe8a:2e8a')
1149+        self.assertFormErrors([u'Enter a valid IPv4 address.'], f.clean, '2a02::223:6cff:fe8a:2e8a')
1150+
1151+    def test_generic_ipaddress_as_ipv4_only(self):               
1152+        f = GenericIPAddressField(protocol="IPv6")
1153+        self.assertFormErrors([u'This field is required.'], f.clean, '')
1154+        self.assertFormErrors([u'This field is required.'], f.clean, None)
1155+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.1')
1156+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo')
1157+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '127.0.0.')
1158+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1.2.3.4.5')
1159+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '256.125.1.5')
1160+        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
1161+        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
1162+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '12345:2:3:4')
1163+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3::4')
1164+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
1165+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
1166+        self.assertFormErrors([u'Enter a valid IPv6 address.'], f.clean, '1:2')
1167+
1168+    def test_generic_ipaddress_as_generic_not_required(self):
1169+        f = GenericIPAddressField(required=False)
1170+        self.assertEqual(f.clean(''), u'')
1171+        self.assertEqual(f.clean(None), u'')
1172+        self.assertEqual(f.clean('127.0.0.1'), u'127.0.0.1')
1173+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo')
1174+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '127.0.0.')
1175+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1.2.3.4.5')
1176+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '256.125.1.5')
1177+        self.assertEqual(f.clean('fe80::223:6cff:fe8a:2e8a'), u'fe80::223:6cff:fe8a:2e8a')
1178+        self.assertEqual(f.clean('2a02::223:6cff:fe8a:2e8a'), u'2a02::223:6cff:fe8a:2e8a')
1179+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '12345:2:3:4')
1180+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3::4')
1181+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, 'foo::223:6cff:fe8a:2e8a')
1182+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1::2:3:4:5:6:7:8')
1183+        self.assertFormErrors([u'Enter a valid IPv4 or IPv6 address.'], f.clean, '1:2')
1184+
1185+    def test_generic_ipaddress_normalisation(self):
1186+        # Test the normalising code
1187+        f = GenericIPAddressField()
1188+        self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
1189+        self.assertEqual(f.clean('::ffff:10.10.10.10'), u'::ffff:10.10.10.10')
1190+        self.assertEqual(f.clean('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
1191+        self.assertEqual(f.clean('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
1192+
1193+        f = GenericIPAddressField(unpack_ipv4_mapped=True)
1194+        self.assertEqual(f.clean('::ffff:0a0a:0a0a'), u'10.10.10.10')
1195+
1196+
1197     def test_smart_unicode(self):
1198         class Test:
1199             def __str__(self):
1200Index: tests/regressiontests/serializers_regress/tests.py
1201===================================================================
1202--- tests/regressiontests/serializers_regress/tests.py  (revision 16357)
1203+++ tests/regressiontests/serializers_regress/tests.py  (working copy)
1204@@ -196,6 +196,8 @@
1205     #(XX, ImageData
1206     (data_obj, 90, IPAddressData, "127.0.0.1"),
1207     (data_obj, 91, IPAddressData, None),
1208+    (data_obj, 95, GenericIPAddressData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
1209+    (data_obj, 96, GenericIPAddressData, None),
1210     (data_obj, 100, NullBooleanData, True),
1211     (data_obj, 101, NullBooleanData, False),
1212     (data_obj, 102, NullBooleanData, None),
1213@@ -298,6 +300,7 @@
1214     (pk_obj, 682, IntegerPKData, 0),
1215 #     (XX, ImagePKData
1216     (pk_obj, 690, IPAddressPKData, "127.0.0.1"),
1217+    (pk_obj, 695, GenericIPAddressPKData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
1218     # (pk_obj, 700, NullBooleanPKData, True),
1219     # (pk_obj, 701, NullBooleanPKData, False),
1220     (pk_obj, 710, PhonePKData, "212-634-5789"),
1221Index: tests/regressiontests/serializers_regress/models.py
1222===================================================================
1223--- tests/regressiontests/serializers_regress/models.py (revision 16357)
1224+++ tests/regressiontests/serializers_regress/models.py (working copy)
1225@@ -52,6 +52,9 @@
1226 class IPAddressData(models.Model):
1227     data = models.IPAddressField(null=True)
1228 
1229+class GenericIPAddressData(models.Model):
1230+    data = models.GenericIPAddressField(null=True)
1231+
1232 class NullBooleanData(models.Model):
1233     data = models.NullBooleanField(null=True)
1234 
1235@@ -187,6 +190,9 @@
1236 class IPAddressPKData(models.Model):
1237     data = models.IPAddressField(primary_key=True)
1238 
1239+class GenericIPAddressPKData(models.Model):
1240+    data = models.GenericIPAddressField(primary_key=True)
1241+
1242 # This is just a Boolean field with null=True, and we can't test a PK value of NULL.
1243 # class NullBooleanPKData(models.Model):
1244 #     data = models.NullBooleanField(primary_key=True)
1245Index: tests/regressiontests/utils/tests.py
1246===================================================================
1247--- tests/regressiontests/utils/tests.py        (revision 16357)
1248+++ tests/regressiontests/utils/tests.py        (working copy)
1249@@ -19,3 +19,4 @@
1250 from datetime_safe import *
1251 from baseconv import *
1252 from jslex import *
1253+from ipv6 import *
1254Index: tests/regressiontests/utils/ipv6.py
1255===================================================================
1256--- tests/regressiontests/utils/ipv6.py (revision 0)
1257+++ tests/regressiontests/utils/ipv6.py (revision 0)
1258@@ -0,0 +1,102 @@
1259+from django.utils.ipv6 import is_valid_ipv6_address, clean_ipv6_address
1260+from django.utils import unittest
1261+
1262+class TestUtilsIPv6(unittest.TestCase):
1263+
1264+    def test_validates_correct_plain_address(self):
1265+        self.assertTrue(is_valid_ipv6_address('fe80::223:6cff:fe8a:2e8a'))
1266+        self.assertTrue(is_valid_ipv6_address('2a02::223:6cff:fe8a:2e8a'))
1267+        self.assertTrue(is_valid_ipv6_address('1::2:3:4:5:6:7'))
1268+        self.assertTrue(is_valid_ipv6_address('::'))
1269+        self.assertTrue(is_valid_ipv6_address('::a'))
1270+        self.assertTrue(is_valid_ipv6_address('2::'))
1271+
1272+    def test_validates_correct_with_v4mapping(self):
1273+        self.assertTrue(is_valid_ipv6_address('::ffff:254.42.16.14'))
1274+        self.assertTrue(is_valid_ipv6_address('::ffff:0a0a:0a0a'))
1275+
1276+    def test_validates_incorrect_plain_address(self):
1277+        self.assertFalse(is_valid_ipv6_address('foo'))
1278+        self.assertFalse(is_valid_ipv6_address('127.0.0.1'))
1279+        self.assertFalse(is_valid_ipv6_address('12345::'))
1280+        self.assertFalse(is_valid_ipv6_address('1::2:3::4'))
1281+        self.assertFalse(is_valid_ipv6_address('1::zzz'))
1282+        self.assertFalse(is_valid_ipv6_address('1::2:3:4:5:6:7:8'))
1283+        self.assertFalse(is_valid_ipv6_address('1:2'))
1284+        self.assertFalse(is_valid_ipv6_address('1:::2'))
1285+
1286+    def test_validates_incorrect_with_v4mapping(self):
1287+        self.assertFalse(is_valid_ipv6_address('::ffff:999.42.16.14'))
1288+        self.assertFalse(is_valid_ipv6_address('::ffff:zzzz:0a0a'))
1289+        # The ::1.2.3.4 format used to be valid but was deprecated
1290+        # in rfc4291 section 2.5.5.1
1291+        self.assertTrue(is_valid_ipv6_address('::254.42.16.14'))
1292+        self.assertTrue(is_valid_ipv6_address('::0a0a:0a0a'))
1293+        self.assertFalse(is_valid_ipv6_address('::999.42.16.14'))
1294+        self.assertFalse(is_valid_ipv6_address('::zzzz:0a0a'))
1295+
1296+    def test_cleanes_plain_address(self):
1297+        self.assertEqual(clean_ipv6_address('DEAD::0:BEEF'), u'dead::beef')
1298+        self.assertEqual(clean_ipv6_address('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
1299+        self.assertEqual(clean_ipv6_address('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
1300+
1301+    def test_cleanes_with_v4_mapping(self):
1302+        self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
1303+        self.assertEqual(clean_ipv6_address('::ffff:1234:1234'), u'::ffff:18.52.18.52')
1304+        self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52'), u'::ffff:18.52.18.52')
1305+
1306+    def test_unpacks_ipv4(self):
1307+        self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a', unpack_ipv4_mapped=True), u'10.10.10.10')
1308+        self.assertEqual(clean_ipv6_address('::ffff:1234:1234', unpack_ipv4_mapped=True), u'18.52.18.52')
1309+        self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52', unpack_ipv4_mapped=True), u'18.52.18.52')
1310+from django.utils.ipv6 import is_valid_ipv6_address, clean_ipv6_address
1311+from django.utils import unittest
1312+
1313+class TestUtilsIPv6(unittest.TestCase):
1314+
1315+    def test_validates_correct_plain_address(self):
1316+        self.assertTrue(is_valid_ipv6_address('fe80::223:6cff:fe8a:2e8a'))
1317+        self.assertTrue(is_valid_ipv6_address('2a02::223:6cff:fe8a:2e8a'))
1318+        self.assertTrue(is_valid_ipv6_address('1::2:3:4:5:6:7'))
1319+        self.assertTrue(is_valid_ipv6_address('::'))
1320+        self.assertTrue(is_valid_ipv6_address('::a'))
1321+        self.assertTrue(is_valid_ipv6_address('2::'))
1322+
1323+    def test_validates_correct_with_v4mapping(self):
1324+        self.assertTrue(is_valid_ipv6_address('::ffff:254.42.16.14'))
1325+        self.assertTrue(is_valid_ipv6_address('::ffff:0a0a:0a0a'))
1326+
1327+    def test_validates_incorrect_plain_address(self):
1328+        self.assertFalse(is_valid_ipv6_address('foo'))
1329+        self.assertFalse(is_valid_ipv6_address('127.0.0.1'))
1330+        self.assertFalse(is_valid_ipv6_address('12345::'))
1331+        self.assertFalse(is_valid_ipv6_address('1::2:3::4'))
1332+        self.assertFalse(is_valid_ipv6_address('1::zzz'))
1333+        self.assertFalse(is_valid_ipv6_address('1::2:3:4:5:6:7:8'))
1334+        self.assertFalse(is_valid_ipv6_address('1:2'))
1335+        self.assertFalse(is_valid_ipv6_address('1:::2'))
1336+
1337+    def test_validates_incorrect_with_v4mapping(self):
1338+        self.assertFalse(is_valid_ipv6_address('::ffff:999.42.16.14'))
1339+        self.assertFalse(is_valid_ipv6_address('::ffff:zzzz:0a0a'))
1340+        # The ::1.2.3.4 format used to be valid but was deprecated
1341+        # in rfc4291 section 2.5.5.1
1342+        self.assertTrue(is_valid_ipv6_address('::254.42.16.14'))
1343+        self.assertTrue(is_valid_ipv6_address('::0a0a:0a0a'))
1344+        self.assertFalse(is_valid_ipv6_address('::999.42.16.14'))
1345+        self.assertFalse(is_valid_ipv6_address('::zzzz:0a0a'))
1346+
1347+    def test_cleanes_plain_address(self):
1348+        self.assertEqual(clean_ipv6_address('DEAD::0:BEEF'), u'dead::beef')
1349+        self.assertEqual(clean_ipv6_address('2001:000:a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
1350+        self.assertEqual(clean_ipv6_address('2001::a:0000:0:fe:fe:beef'), u'2001:0:a::fe:fe:beef')
1351+
1352+    def test_cleanes_with_v4_mapping(self):
1353+        self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a'), u'::ffff:10.10.10.10')
1354+        self.assertEqual(clean_ipv6_address('::ffff:1234:1234'), u'::ffff:18.52.18.52')
1355+        self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52'), u'::ffff:18.52.18.52')
1356+
1357+    def test_unpacks_ipv4(self):
1358+        self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a', unpack_ipv4_mapped=True), u'10.10.10.10')
1359+        self.assertEqual(clean_ipv6_address('::ffff:1234:1234', unpack_ipv4_mapped=True), u'18.52.18.52')
1360+        self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52', unpack_ipv4_mapped=True), u'18.52.18.52')