From 4d5eee04a0a4e126d19f31ffc7325685331c0f71 Mon Sep 17 00:00:00 2001
From: Aider Ibragimov <aider.ibragimov@gmail.com>
Date: Sat, 28 Feb 2015 10:11:38 +0300
Subject: [PATCH] add IPAddressField, update docs

---
 docs/api-guide/fields.md | 13 ++++++++++++-
 rest_framework/fields.py | 31 ++++++++++++++++++++++++++++++-
 tests/test_fields.py     | 21 +++++++++++++++++++++
 3 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 4d7d9eee8..9c60b3d35 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -188,6 +188,17 @@ A field that ensures the input is a valid UUID string. The `to_internal_value` m
 
     "de305d54-75b4-431b-adb2-eb6b9e546013"
 
+## IPAddressField
+
+A field that ensures the input is a valid IPv4 or IPv6 string.
+
+Corresponds to `django.forms.fields.IPAddressField` and `django.forms.fields.GenericIPAddressField`.
+
+**Signature**: `IPAddressField(protocol='both', unpack_ipv4=False, **options)`
+
+- `protocol` Limits valid inputs to the specified protocol. Accepted values are 'both' (default), 'IPv4' or 'IPv6'. Matching is case insensitive.
+- `unpack_ipv4` Unpacks IPv4 mapped addresses like ::ffff:192.0.2.1. If this option is enabled that address would be unpacked to 192.0.2.1. Default is disabled. Can only be used when protocol is set to 'both'.
+
 ---
 
 # Numeric fields
@@ -524,7 +535,7 @@ As an example, let's create a field that can be used represent the class name of
             # We pass the object instance onto `to_representation`,
             # not just the field attribute.
             return obj
- 
+
         def to_representation(self, obj):
             """
             Serialize the object's class name.
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 71a9f1938..860fd3070 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -2,12 +2,13 @@ from __future__ import unicode_literals
 from django.conf import settings
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ValidationError as DjangoValidationError
-from django.core.validators import RegexValidator
+from django.core.validators import RegexValidator, ip_address_validators
 from django.forms import ImageField as DjangoImageField
 from django.utils import six, timezone
 from django.utils.dateparse import parse_date, parse_datetime, parse_time
 from django.utils.encoding import is_protected_type, smart_text
 from django.utils.translation import ugettext_lazy as _
+from django.utils.ipv6 import clean_ipv6_address
 from rest_framework import ISO_8601
 from rest_framework.compat import (
     EmailValidator, MinValueValidator, MaxValueValidator,
@@ -650,6 +651,34 @@ class UUIDField(Field):
         return str(value)
 
 
+class IPAddressField(CharField):
+    """Support both IPAddressField and GenericIPAddressField"""
+
+    default_error_messages = {
+        'invalid': _('Enter a valid IPv4 or IPv6 address.'),
+    }
+
+    def __init__(self, protocol='both', unpack_ipv4=False, **kwargs):
+        self.protocol = protocol
+        self.unpack_ipv4 = unpack_ipv4
+        super(IPAddressField, self).__init__(**kwargs)
+        validators, error_message = ip_address_validators(protocol, unpack_ipv4)
+        self.validators.extend(validators)
+
+    def to_internal_value(self, data):
+        if data == '' and self.allow_blank:
+            return ''
+        data = data.strip()
+
+        if data and ':' in data:
+            try:
+                return clean_ipv6_address(data, self.unpack_ipv4)
+            except DjangoValidationError:
+                self.fail('invalid', value=data)
+
+        return data
+
+
 # Number types...
 
 class IntegerField(Field):
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 6744cf645..f0451d5ea 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -485,6 +485,27 @@ class TestUUIDField(FieldValues):
     field = serializers.UUIDField()
 
 
+class TestIPAddressField(FieldValues):
+    """
+    Valid and invalid values for `IPAddressField`
+    """
+    valid_inputs = {
+        '127.0.0.1': '127.0.0.1',
+        '192.168.33.255': '192.168.33.255',
+        '2001:0db8:85a3:0042:1000:8a2e:0370:7334': '2001:db8:85a3:42:1000:8a2e:370:7334',
+        '2001:cdba:0:0:0:0:3257:9652': '2001:cdba::3257:9652',
+        '2001:cdba::3257:9652': '2001:cdba::3257:9652'
+    }
+    invalid_inputs = {
+        '127001': ['Enter a valid IPv4 or IPv6 address.'],
+        '127.122.111.2231': ['Enter a valid IPv4 or IPv6 address.'],
+        '2001:::9652': ['Enter a valid IPv4 or IPv6 address.'],
+        '2001:0db8:85a3:0042:1000:8a2e:0370:73341': ['Enter a valid IPv4 or IPv6 address.'],
+    }
+    outputs = {}
+    field = serializers.IPAddressField()
+
+
 # Number types...
 
 class TestIntegerField(FieldValues):