From d680a3f4477056c69629b0421db4bb254b8c69d0 Mon Sep 17 00:00:00 2001
From: Claude Paroz <claude@2xlibre.net>
Date: Sat, 15 Dec 2012 00:26:08 +0100
Subject: [PATCH] Added support for serializing BinaryField

---
 django/db/models/fields/__init__.py | 13 ++++++++++++-
 django/utils/encoding.py            |  2 ++
 tests/serializers_regress/models.py |  3 +++
 tests/serializers_regress/tests.py  | 24 +++++++++++++++++-------
 4 files changed, 34 insertions(+), 8 deletions(-)

diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 8302aaceaa..f8ec205611 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -6,6 +6,7 @@ import datetime
 import decimal
 import math
 import warnings
+from base64 import b64decode, b64encode
 from itertools import tee
 
 from django.db import connection
@@ -19,7 +20,7 @@ from django.utils.functional import curry, total_ordering
 from django.utils.text import capfirst
 from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
-from django.utils.encoding import smart_text, force_text
+from django.utils.encoding import smart_text, force_text, force_bytes
 from django.utils.ipv6 import clean_ipv6_address
 from django.utils import six
 
@@ -1318,3 +1319,13 @@ class BinaryField(Field):
         if value is not None:
             return connection.Database.Binary(value)
         return value
+
+    def value_to_string(self, obj):
+        """Binary data is serialized as base64"""
+        return b64encode(force_bytes(self._get_val_from_obj(obj))).decode('ascii')
+
+    def to_python(self, value):
+        # If it's a string, it should be base64-encoded data
+        if isinstance(value, six.text_type):
+            return six.memoryview(b64decode(force_bytes(value)))
+        return value
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index 68929cc0e5..607b48277a 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -142,6 +142,8 @@ def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
 
     If strings_only is True, don't convert (some) non-string-like objects.
     """
+    if isinstance(s, six.memoryview):
+        s = bytes(s)
     if isinstance(s, bytes):
         if encoding == 'utf-8':
             return s
diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py
index af261f9cbc..21fe073122 100644
--- a/tests/serializers_regress/models.py
+++ b/tests/serializers_regress/models.py
@@ -12,6 +12,9 @@ from django.contrib.contenttypes.models import ContentType
 # The following classes are for testing basic data
 # marshalling, including NULL values, where allowed.
 
+class BinaryData(models.Model):
+    data = models.BinaryField(null=True)
+
 class BooleanData(models.Model):
     data = models.BooleanField()
 
diff --git a/tests/serializers_regress/tests.py b/tests/serializers_regress/tests.py
index e586d76e7f..72e3825d91 100644
--- a/tests/serializers_regress/tests.py
+++ b/tests/serializers_regress/tests.py
@@ -24,10 +24,11 @@ from django.db import connection, models
 from django.http import HttpResponse
 from django.test import TestCase
 from django.utils import six
+from django.utils.encoding import force_text
 from django.utils.functional import curry
 from django.utils.unittest import skipUnless
 
-from .models import (BooleanData, CharData, DateData, DateTimeData, EmailData,
+from .models import (BinaryData, BooleanData, CharData, DateData, DateTimeData, EmailData,
     FileData, FilePathData, DecimalData, FloatData, IntegerData, IPAddressData,
     GenericIPAddressData, NullBooleanData, PositiveIntegerData,
     PositiveSmallIntegerData, SlugData, SmallData, TextData, TimeData,
@@ -116,10 +117,17 @@ def inherited_create(pk, klass, data):
 # test data objects of various kinds
 def data_compare(testcase, pk, klass, data):
     instance = klass.objects.get(id=pk)
-    testcase.assertEqual(data, instance.data,
-         "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % (
-            pk, data, type(data), instance.data, type(instance.data))
-    )
+    if klass == BinaryData and data is not None:
+        testcase.assertEqual(bytes(data), bytes(instance.data),
+             "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % (
+                pk, repr(bytes(data)), type(data), repr(bytes(instance.data)),
+                type(instance.data))
+        )
+    else:
+        testcase.assertEqual(data, instance.data,
+             "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % (
+                pk, data, type(data), instance, type(instance.data))
+        )
 
 def generic_compare(testcase, pk, klass, data):
     instance = klass.objects.get(id=pk)
@@ -175,8 +183,10 @@ inherited_obj = (inherited_create, inherited_compare)
 
 test_data = [
     # Format: (data type, PK value, Model Class, data)
-    (data_obj, 1, BooleanData, True),
-    (data_obj, 2, BooleanData, False),
+    (data_obj, 1, BinaryData, six.memoryview(b"\x05\xFD\x00")),
+    (data_obj, 2, BinaryData, None),
+    (data_obj, 5, BooleanData, True),
+    (data_obj, 6, BooleanData, False),
     (data_obj, 10, CharData, "Test Char Data"),
     (data_obj, 11, CharData, ""),
     (data_obj, 12, CharData, "None"),