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"),