Code

Ticket #811: genericipaddressfield.3.diff

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

removed unneeded import of socket in fields.py

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